Skip to content

Commit b48c472

Browse files
committed
First working concept
1 parent 5900056 commit b48c472

File tree

7 files changed

+722
-16
lines changed

7 files changed

+722
-16
lines changed

canopen/nmt.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,18 @@ def state(self) -> str:
9393
- 'RESET'
9494
- 'RESET COMMUNICATION'
9595
"""
96+
logger.warning("Accessing NmtBase.state attribute is deprecated")
9697
if self._state in NMT_STATES:
9798
return NMT_STATES[self._state]
9899
else:
99100
return self._state
100101

101102
@state.setter
102103
def state(self, new_state: str):
104+
logger.warning("Accessing NmtBase.state setter is deprecated")
105+
self.set_state(new_state)
106+
107+
def set_state(self, new_state: str):
103108
if new_state in NMT_COMMANDS:
104109
code = NMT_COMMANDS[new_state]
105110
else:

canopen/pdo/base.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from collections import Mapping
99
import logging
1010
import binascii
11+
import asyncio
1112

1213
if TYPE_CHECKING:
1314
from ..network import Network
@@ -59,6 +60,11 @@ def read(self):
5960
for pdo_map in self.map.values():
6061
pdo_map.read()
6162

63+
async def aread(self):
64+
"""Read PDO configuration from node using SDO."""
65+
for pdo_map in self.map.values():
66+
await pdo_map.aread()
67+
6268
def save(self):
6369
"""Save PDO configuration to node using SDO."""
6470
for pdo_map in self.map.values():
@@ -195,7 +201,8 @@ def __init__(self, pdo_node: PdoBase, com_record, map_array):
195201
#: Set explicitly or using the :meth:`start()` method.
196202
self.period: Optional[float] = None
197203
self.callbacks = []
198-
self.receive_condition = threading.Condition()
204+
#self.receive_condition = threading.Condition() # FIXME
205+
self.receive_condition = asyncio.Condition()
199206
self.is_received: bool = False
200207
self._task = None
201208

@@ -296,6 +303,22 @@ def is_periodic(self) -> bool:
296303
# Unknown transmission type, assume non-periodic
297304
return False
298305

306+
async def aon_message(self, can_id, data, timestamp):
307+
is_transmitting = self._task is not None
308+
if can_id == self.cob_id and not is_transmitting:
309+
async with self.receive_condition:
310+
self.is_received = True
311+
self.data = data
312+
if self.timestamp is not None:
313+
self.period = timestamp - self.timestamp
314+
self.timestamp = timestamp
315+
self.receive_condition.notify_all()
316+
for callback in self.callbacks:
317+
callback(self)
318+
319+
def on_message_async(self, can_id, data, timestamp):
320+
asyncio.create_task(self.aon_message(can_id, data, timestamp))
321+
299322
def on_message(self, can_id, data, timestamp):
300323
is_transmitting = self._task is not None
301324
if can_id == self.cob_id and not is_transmitting:
@@ -367,6 +390,55 @@ def read(self) -> None:
367390

368391
self.subscribe()
369392

393+
async def aread(self) -> None:
394+
"""Read PDO configuration for this map using SDO."""
395+
cob_id = await self.com_record[1].aget_raw()
396+
self.cob_id = cob_id & 0x1FFFFFFF
397+
logger.info("COB-ID is 0x%X", self.cob_id)
398+
self.enabled = cob_id & PDO_NOT_VALID == 0
399+
logger.info("PDO is %s", "enabled" if self.enabled else "disabled")
400+
self.rtr_allowed = cob_id & RTR_NOT_ALLOWED == 0
401+
logger.info("RTR is %s", "allowed" if self.rtr_allowed else "not allowed")
402+
self.trans_type = await self.com_record[2].aget_raw()
403+
logger.info("Transmission type is %d", self.trans_type)
404+
if self.trans_type >= 254:
405+
try:
406+
self.inhibit_time = await self.com_record[3].aget_raw()
407+
except (KeyError, SdoAbortedError) as e:
408+
logger.info("Could not read inhibit time (%s)", e)
409+
else:
410+
logger.info("Inhibit time is set to %d ms", self.inhibit_time)
411+
412+
try:
413+
self.event_timer = await self.com_record[5].aget_raw()
414+
except (KeyError, SdoAbortedError) as e:
415+
logger.info("Could not read event timer (%s)", e)
416+
else:
417+
logger.info("Event timer is set to %d ms", self.event_timer)
418+
419+
try:
420+
self.sync_start_value = await self.com_record[6].aget_raw()
421+
except (KeyError, SdoAbortedError) as e:
422+
logger.info("Could not read SYNC start value (%s)", e)
423+
else:
424+
logger.info("SYNC start value is set to %d ms", self.sync_start_value)
425+
426+
self.clear()
427+
nof_entries = await self.map_array[0].aget_raw()
428+
for subindex in range(1, nof_entries + 1):
429+
value = await self.map_array[subindex].aget_raw()
430+
index = value >> 16
431+
subindex = (value >> 8) & 0xFF
432+
size = value & 0xFF
433+
if hasattr(self.pdo_node.node, "curtis_hack") and self.pdo_node.node.curtis_hack: # Curtis HACK: mixed up field order
434+
index = value & 0xFFFF
435+
subindex = (value >> 16) & 0xFF
436+
size = (value >> 24) & 0xFF
437+
if index and size:
438+
self.add_variable(index, subindex, size)
439+
440+
self.subscribe()
441+
370442
def save(self) -> None:
371443
"""Save PDO configuration for this map using SDO."""
372444
logger.info("Setting COB-ID 0x%X and temporarily disabling PDO",
@@ -433,7 +505,8 @@ def subscribe(self) -> None:
433505
"""
434506
if self.enabled:
435507
logger.info("Subscribing to enabled PDO 0x%X on the network", self.cob_id)
436-
self.pdo_node.network.subscribe(self.cob_id, self.on_message)
508+
#self.pdo_node.network.subscribe(self.cob_id, self.on_message) # FIXME
509+
self.pdo_node.network.subscribe(self.cob_id, self.on_message_async)
437510

438511
def clear(self) -> None:
439512
"""Clear all variables from this map."""
@@ -532,6 +605,17 @@ def wait_for_reception(self, timeout: float = 10) -> float:
532605
self.receive_condition.wait(timeout)
533606
return self.timestamp if self.is_received else None
534607

608+
async def await_for_reception(self, timeout: float = 10) -> float:
609+
"""Wait for the next transmit PDO.
610+
611+
:param float timeout: Max time to wait in seconds.
612+
:return: Timestamp of message received or None if timeout.
613+
"""
614+
async with self.receive_condition:
615+
self.is_received = False
616+
await self.receive_condition.wait()
617+
return self.timestamp if self.is_received else None
618+
535619

536620
class Variable(variable.Variable):
537621
"""One object dictionary variable mapped to a PDO."""
@@ -571,6 +655,11 @@ def get_data(self) -> bytes:
571655

572656
return data
573657

658+
async def aget_data(self) -> bytes:
659+
# As long as get_data() is not making any IO, it can be called
660+
# directly with no special async variant
661+
return self.get_data()
662+
574663
def set_data(self, data: bytes):
575664
"""Set for the given variable the PDO data.
576665
@@ -603,3 +692,8 @@ def set_data(self, data: bytes):
603692
self.pdo_parent.data[byte_offset:byte_offset + len(data)] = data
604693

605694
self.pdo_parent.update()
695+
696+
async def aset_data(self, data: bytes):
697+
# As long as get_data() is not making any IO, it can be called
698+
# directly with no special async variant
699+
return self.set_data(data)

canopen/profiles/p402.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@ def op_mode(self):
391391
:raises TypeError: When setting a mode not advertised as supported by the node.
392392
:raises RuntimeError: If the switch is not confirmed within the configured timeout.
393393
"""
394+
logger.warning("Accessing BaseNode402.op_mode property is deprecated")
394395
try:
395396
pdo = self.tpdo_pointers[0x6061].pdo_parent
396397
if pdo.is_periodic:
@@ -406,6 +407,7 @@ def op_mode(self):
406407

407408
@op_mode.setter
408409
def op_mode(self, mode):
410+
logger.warning("Accessing BaseNode402.op_mode setter is deprecated")
409411
try:
410412
if not self.is_op_mode_supported(mode):
411413
raise TypeError(
@@ -470,6 +472,7 @@ def statusword(self):
470472
If the object 0x6041 is not configured in any TPDO it will fall back to the SDO
471473
mechanism and try to get the value.
472474
"""
475+
logger.warning("Accessing BaseNode402.statusword property is deprecated")
473476
try:
474477
return self.tpdo_values[0x6041]
475478
except KeyError:
@@ -507,6 +510,7 @@ def controlword(self):
507510

508511
@controlword.setter
509512
def controlword(self, value):
513+
logger.warning("Accessing BaseNode402.controlword setter is deprecated")
510514
if 0x6040 in self.rpdo_pointers:
511515
self.rpdo_pointers[0x6040].raw = value
512516
pdo = self.rpdo_pointers[0x6040].pdo_parent
@@ -536,6 +540,7 @@ def state(self):
536540
:raises RuntimeError: If the switch is not confirmed within the configured timeout.
537541
:raises ValueError: Trying to execute a illegal transition in the state machine.
538542
"""
543+
logger.warning("Accessing BaseNode402.state property is deprecated")
539544
for state, mask_val_pair in State402.SW_MASK.items():
540545
bitmask, bits = mask_val_pair
541546
if self.statusword & bitmask == bits:
@@ -544,6 +549,7 @@ def state(self):
544549

545550
@state.setter
546551
def state(self, target_state):
552+
logger.warning("Accessing BaseNode402.state setter is deprecated")
547553
timeout = time.monotonic() + self.TIMEOUT_SWITCH_STATE_FINAL
548554
while self.state != target_state:
549555
next_state = self._next_state(target_state)

canopen/sdo/base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ def __contains__(self, key: Union[int, str]) -> bool:
7373
def upload(self, index: int, subindex: int) -> bytes:
7474
raise NotImplementedError()
7575

76+
async def aupload(self, index: int, subindex: int) -> bytes:
77+
raise NotImplementedError()
78+
7679
def download(
7780
self,
7881
index: int,
@@ -82,6 +85,15 @@ def download(
8285
) -> None:
8386
raise NotImplementedError()
8487

88+
async def adownload(
89+
self,
90+
index: int,
91+
subindex: int,
92+
data: bytes,
93+
force_segment: bool = False,
94+
) -> None:
95+
raise NotImplementedError()
96+
8597

8698
class Record(Mapping):
8799

@@ -131,10 +143,17 @@ def __init__(self, sdo_node: SdoBase, od: objectdictionary.ObjectDictionary):
131143
def get_data(self) -> bytes:
132144
return self.sdo_node.upload(self.od.index, self.od.subindex)
133145

146+
async def aget_data(self) -> bytes:
147+
return await self.sdo_node.aupload(self.od.index, self.od.subindex)
148+
134149
def set_data(self, data: bytes):
135150
force_segment = self.od.data_type == objectdictionary.DOMAIN
136151
self.sdo_node.download(self.od.index, self.od.subindex, data, force_segment)
137152

153+
async def aset_data(self, data: bytes):
154+
force_segment = self.od.data_type == objectdictionary.DOMAIN
155+
await self.sdo_node.adownload(self.od.index, self.od.subindex, data, force_segment)
156+
138157
def open(self, mode="rb", encoding="ascii", buffering=1024, size=None,
139158
block_transfer=False, request_crc_support=True):
140159
"""Open the data stream as a file like object.

0 commit comments

Comments
 (0)