diff --git a/README.rst b/README.rst index bdf66701..48a26ab0 100644 --- a/README.rst +++ b/README.rst @@ -115,11 +115,11 @@ The :code:`n` is the PDO index (normally 1 to 4). The second form of access is f # network.connect(bustype='nican', channel='CAN0', bitrate=250000) # Read a variable using SDO - device_name = node.sdo['Manufacturer device name'].raw - vendor_id = node.sdo[0x1018][1].raw + device_name = node.sdo['Manufacturer device name'].get_raw() + vendor_id = node.sdo[0x1018][1].get_raw() # Write a variable using SDO - node.sdo['Producer heartbeat time'].raw = 1000 + node.sdo['Producer heartbeat time'].set_raw(1000) # Read PDO configuration from node node.tpdo.read() @@ -139,12 +139,12 @@ The :code:`n` is the PDO index (normally 1 to 4). The second form of access is f network.sync.start(0.1) # Change state to operational (NMT start) - node.nmt.state = 'OPERATIONAL' + node.nmt.set_state('OPERATIONAL') # Read a value from TPDO[1] node.tpdo[1].wait_for_reception() - speed = node.tpdo[1]['Velocity actual value'].phys - val = node.tpdo['Some group.Some subindex'].raw + speed = node.tpdo[1]['Velocity actual value'].get_phys() + val = node.tpdo['Some group.Some subindex'].get_raw() # Disconnect from CAN bus network.sync.stop() diff --git a/canopen/nmt.py b/canopen/nmt.py index 98d8ea25..dc51d356 100644 --- a/canopen/nmt.py +++ b/canopen/nmt.py @@ -73,6 +73,9 @@ def send_command(self, code: int): @property def state(self) -> str: + return self.get_state() + + def get_state(self) -> str: """Attribute to get or set node's state as a string. Can be one of: @@ -93,6 +96,10 @@ def state(self) -> str: @state.setter def state(self, new_state: str): + logger.warning("Accessing NmtBase.state setter is deprecated, use set_state()") + self.set_state(new_state) + + def set_state(self, new_state: str): if new_state in NMT_COMMANDS: code = NMT_COMMANDS[new_state] else: @@ -148,7 +155,7 @@ def wait_for_heartbeat(self, timeout: float = 10): self.state_update.wait(timeout) if self._state_received is None: raise NmtError("No boot-up or heartbeat received") - return self.state + return self.get_state() def wait_for_bootup(self, timeout: float = 10) -> None: """Wait until a boot-up message is received.""" @@ -218,7 +225,7 @@ def send_command(self, code: int) -> None: # The heartbeat service should start on the transition # between INITIALIZING and PRE-OPERATIONAL state if old_state == 0 and self._state == 127: - heartbeat_time_ms = self._local_node.sdo[0x1017].raw + heartbeat_time_ms = self._local_node.sdo[0x1017].get_raw() self.start_heartbeat(heartbeat_time_ms) else: self.update_heartbeat() diff --git a/canopen/node/remote.py b/canopen/node/remote.py index 8e4025d7..6bf52d3c 100644 --- a/canopen/node/remote.py +++ b/canopen/node/remote.py @@ -127,9 +127,9 @@ def __load_configuration_helper(self, index, subindex, name, value): subindex=subindex, name=name, value=value))) - self.sdo[index][subindex].raw = value + self.sdo[index][subindex].set_raw(value) else: - self.sdo[index].raw = value + self.sdo[index].set_raw(value) logger.info(str('SDO [{index:#06x}]: {name}: {value:#06x}'.format( index=index, name=name, diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index 086a0774..f8b3ec3b 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -315,41 +315,41 @@ def add_callback(self, callback: Callable[["Map"], None]) -> None: def read(self) -> None: """Read PDO configuration for this map using SDO.""" - cob_id = self.com_record[1].raw + cob_id = self.com_record[1].get_raw() self.cob_id = cob_id & 0x1FFFFFFF logger.info("COB-ID is 0x%X", self.cob_id) self.enabled = cob_id & PDO_NOT_VALID == 0 logger.info("PDO is %s", "enabled" if self.enabled else "disabled") self.rtr_allowed = cob_id & RTR_NOT_ALLOWED == 0 logger.info("RTR is %s", "allowed" if self.rtr_allowed else "not allowed") - self.trans_type = self.com_record[2].raw + self.trans_type = self.com_record[2].get_raw() logger.info("Transmission type is %d", self.trans_type) if self.trans_type >= 254: try: - self.inhibit_time = self.com_record[3].raw + self.inhibit_time = self.com_record[3].get_raw() except (KeyError, SdoAbortedError) as e: logger.info("Could not read inhibit time (%s)", e) else: logger.info("Inhibit time is set to %d ms", self.inhibit_time) try: - self.event_timer = self.com_record[5].raw + self.event_timer = self.com_record[5].get_raw() except (KeyError, SdoAbortedError) as e: logger.info("Could not read event timer (%s)", e) else: logger.info("Event timer is set to %d ms", self.event_timer) try: - self.sync_start_value = self.com_record[6].raw + self.sync_start_value = self.com_record[6].get_raw() except (KeyError, SdoAbortedError) as e: logger.info("Could not read SYNC start value (%s)", e) else: logger.info("SYNC start value is set to %d ms", self.sync_start_value) self.clear() - nof_entries = self.map_array[0].raw + nof_entries = self.map_array[0].get_raw() for subindex in range(1, nof_entries + 1): - value = self.map_array[subindex].raw + value = self.map_array[subindex].get_raw() index = value >> 16 subindex = (value >> 8) & 0xFF size = value & 0xFF @@ -366,44 +366,44 @@ def save(self) -> None: """Save PDO configuration for this map using SDO.""" logger.info("Setting COB-ID 0x%X and temporarily disabling PDO", self.cob_id) - self.com_record[1].raw = self.cob_id | PDO_NOT_VALID | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0) + self.com_record[1].set_raw(self.cob_id | PDO_NOT_VALID | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0)) if self.trans_type is not None: logger.info("Setting transmission type to %d", self.trans_type) - self.com_record[2].raw = self.trans_type + self.com_record[2].set_raw(self.trans_type) if self.inhibit_time is not None: logger.info("Setting inhibit time to %d us", (self.inhibit_time * 100)) - self.com_record[3].raw = self.inhibit_time + self.com_record[3].set_raw(self.inhibit_time) if self.event_timer is not None: logger.info("Setting event timer to %d ms", self.event_timer) - self.com_record[5].raw = self.event_timer + self.com_record[5].set_raw(self.event_timer) if self.sync_start_value is not None: logger.info("Setting SYNC start value to %d", self.sync_start_value) - self.com_record[6].raw = self.sync_start_value + self.com_record[6].set_raw(self.sync_start_value) if self.map is not None: try: - self.map_array[0].raw = 0 + self.map_array[0].set_raw(0) except SdoAbortedError: # WORKAROUND for broken implementations: If the array has a # fixed number of entries (count not writable), generate dummy # mappings for an invalid object 0x0000:00 to overwrite any # excess entries with all-zeros. - self._fill_map(self.map_array[0].raw) + self._fill_map(self.map_array[0].get_raw()) subindex = 1 for var in self.map: logger.info("Writing %s (0x%X:%d, %d bits) to PDO map", var.name, var.index, var.subindex, var.length) if hasattr(self.pdo_node.node, "curtis_hack") and self.pdo_node.node.curtis_hack: # Curtis HACK: mixed up field order - self.map_array[subindex].raw = (var.index | - var.subindex << 16 | - var.length << 24) + self.map_array[subindex].set_raw(var.index | + var.subindex << 16 | + var.length << 24) else: - self.map_array[subindex].raw = (var.index << 16 | - var.subindex << 8 | - var.length) + self.map_array[subindex].set_raw(var.index << 16 | + var.subindex << 8 | + var.length) subindex += 1 try: - self.map_array[0].raw = len(self.map) + self.map_array[0].set_raw(len(self.map)) except SdoAbortedError as e: # WORKAROUND for broken implementations: If the array # number-of-entries parameter is not writable, we have already @@ -415,7 +415,7 @@ def save(self) -> None: self._update_data_size() if self.enabled: - self.com_record[1].raw = self.cob_id | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0) + self.com_record[1].set_raw(self.cob_id | (RTR_NOT_ALLOWED if not self.rtr_allowed else 0x0)) self.subscribe() def subscribe(self) -> None: diff --git a/canopen/profiles/p402.py b/canopen/profiles/p402.py index 2e5fb133..4894123c 100644 --- a/canopen/profiles/p402.py +++ b/canopen/profiles/p402.py @@ -237,7 +237,7 @@ def setup_pdos(self, upload=True): When the node's NMT state disallows SDOs for reading the PDO configuration. """ if upload: - assert self.nmt.state in 'PRE-OPERATIONAL', 'OPERATIONAL' + assert self.nmt.get_state() in 'PRE-OPERATIONAL', 'OPERATIONAL' self.pdo.read() # TPDO and RPDO configurations else: self.pdo.subscribe() # Get notified on reception, usually a side-effect of read() @@ -288,9 +288,9 @@ def _check_op_mode_configured(self): def reset_from_fault(self): """Reset node from fault and set it to Operation Enable state.""" - if self.state == 'FAULT': + if self.get_state() == 'FAULT': # Resets the Fault Reset bit (rising edge 0 -> 1) - self.controlword = State402.CW_DISABLE_VOLTAGE + self.set_controlword(State402.CW_DISABLE_VOLTAGE) # FIXME! The rising edge happens with the transitions toward OPERATION # ENABLED below, but until then the loop will always reach the timeout! timeout = time.monotonic() + self.TIMEOUT_RESET_FAULT @@ -298,11 +298,11 @@ def reset_from_fault(self): if time.monotonic() > timeout: break self.check_statusword() - self.state = 'OPERATION ENABLED' + self.set_state('OPERATION ENABLED') def is_faulted(self): bitmask, bits = State402.SW_MASK['FAULT'] - return self.statusword & bitmask == bits + return self.get_statusword() & bitmask == bits def _homing_status(self): """Interpret the current Statusword bits as homing state string.""" @@ -311,7 +311,7 @@ def _homing_status(self): status = None for key, value in Homing.STATES.items(): bitmask, bits = value - if self.statusword & bitmask == bits: + if self.get_statusword() & bitmask == bits: status = key return status @@ -322,13 +322,13 @@ def is_homed(self, restore_op_mode=False): :return: If the status indicates successful homing. :rtype: bool """ - previous_op_mode = self.op_mode + previous_op_mode = self.get_op_mode() if previous_op_mode != 'HOMING': logger.info('Switch to HOMING from %s', previous_op_mode) - self.op_mode = 'HOMING' # blocks until confirmed + self.set_op_mode('HOMING') # blocks until confirmed homingstatus = self._homing_status() if restore_op_mode: - self.op_mode = previous_op_mode + self.set_op_mode(previous_op_mode) return homingstatus in ('TARGET REACHED', 'ATTAINED') def homing(self, timeout=None, restore_op_mode=False): @@ -343,12 +343,12 @@ def homing(self, timeout=None, restore_op_mode=False): if timeout is None: timeout = self.TIMEOUT_HOMING_DEFAULT if restore_op_mode: - previous_op_mode = self.op_mode - self.op_mode = 'HOMING' + previous_op_mode = self.get_op_mode() + self.set_op_mode('HOMING') # The homing process will initialize at operation enabled - self.state = 'OPERATION ENABLED' + self.set_state('OPERATION ENABLED') homingstatus = 'UNKNOWN' - self.controlword = State402.CW_OPERATION_ENABLED | Homing.CW_START # does not block + self.set_controlword(State402.CW_OPERATION_ENABLED | Homing.CW_START) # does not block # Wait for one extra cycle, to make sure the controlword was received self.check_statusword() t = time.monotonic() + timeout @@ -366,11 +366,15 @@ def homing(self, timeout=None, restore_op_mode=False): logger.info(str(e)) finally: if restore_op_mode: - self.op_mode = previous_op_mode + self.set_op_mode(previous_op_mode) return False @property def op_mode(self): + logger.warning("Accessing BaseNode402.op_mode property is deprecated, use get_op_mode()") + return self.get_op_mode() + + def get_op_mode(self): """The node's Operation Mode stored in the object 0x6061. Uses SDO or PDO to access the current value. The modes are passed as one of the @@ -402,25 +406,29 @@ def op_mode(self): code = self.tpdo_values[0x6061] except KeyError: logger.warning('The object 0x6061 is not a configured TPDO, fallback to SDO') - code = self.sdo[0x6061].raw + code = self.sdo[0x6061].get_raw() return OperationMode.CODE2NAME[code] @op_mode.setter def op_mode(self, mode): + logger.warning("Accessing BaseNode402.op_mode setter is deprecated, use set_op_mode()") + self.set_op_mode(mode) + + def set_op_mode(self, mode): try: if not self.is_op_mode_supported(mode): raise TypeError( 'Operation mode {m} not suppported on node {n}.'.format(n=self.id, m=mode)) # Update operation mode in RPDO if possible, fall back to SDO if 0x6060 in self.rpdo_pointers: - self.rpdo_pointers[0x6060].raw = OperationMode.NAME2CODE[mode] + self.rpdo_pointers[0x6060].set_raw(OperationMode.NAME2CODE[mode]) pdo = self.rpdo_pointers[0x6060].pdo_parent if not pdo.is_periodic: pdo.transmit() else: - self.sdo[0x6060].raw = OperationMode.NAME2CODE[mode] + self.sdo[0x6060].set_raw(OperationMode.NAME2CODE[mode]) timeout = time.monotonic() + self.TIMEOUT_SWITCH_OP_MODE - while self.op_mode != mode: + while self.get_op_mode() != mode: if time.monotonic() > timeout: raise RuntimeError( "Timeout setting node {0}'s new mode of operation to {1}.".format( @@ -435,7 +443,7 @@ def _clear_target_values(self): # [target velocity, target position, target torque] for target_index in [0x60FF, 0x607A, 0x6071]: if target_index in self.sdo.keys(): - self.sdo[target_index].raw = 0 + self.sdo[target_index].set_raw(0) def is_op_mode_supported(self, mode): """Check if the operation mode is supported by the node. @@ -449,7 +457,7 @@ def is_op_mode_supported(self, mode): """ if not hasattr(self, '_op_mode_support'): # Cache value only on first lookup, this object should never change. - self._op_mode_support = self.sdo[0x6502].raw + self._op_mode_support = self.sdo[0x6502].get_raw() logger.info('Caching node {n} supported operation modes 0x{m:04X}'.format( n=self.id, m=self._op_mode_support)) bits = OperationMode.SUPPORTED[mode] @@ -462,10 +470,14 @@ def on_TPDOs_update_callback(self, mapobject): :type mapobject: canopen.pdo.Map """ for obj in mapobject: - self.tpdo_values[obj.index] = obj.raw + self.tpdo_values[obj.index] = obj.get_raw() @property def statusword(self): + logger.warning("Accessing BaseNode402.statusword property is deprecated, use get_statusword()") + return self.get_statusword() + + def get_statusword(self): """Return the last read value of the Statusword (0x6041) from the device. If the object 0x6041 is not configured in any TPDO it will fall back to the SDO @@ -475,7 +487,7 @@ def statusword(self): return self.tpdo_values[0x6041] except KeyError: logger.warning('The object 0x6041 is not a configured TPDO, fallback to SDO') - return self.sdo[0x6041].raw + return self.sdo[0x6041].get_raw() def check_statusword(self, timeout=None): """Report an up-to-date reading of the Statusword (0x6041) from the device. @@ -496,8 +508,8 @@ def check_statusword(self, timeout=None): if timestamp is None: raise RuntimeError('Timeout waiting for updated statusword') else: - return self.sdo[0x6041].raw - return self.statusword + return self.sdo[0x6041].get_raw() + return self.get_statusword() @property def controlword(self): @@ -510,16 +522,24 @@ def controlword(self): @controlword.setter def controlword(self, value): + logger.warning("Accessing BaseNode402.controlword setter is deprecated, use set_controlword()") + self.set_controlword(value) + + def set_controlword(self, value): if 0x6040 in self.rpdo_pointers: - self.rpdo_pointers[0x6040].raw = value + self.rpdo_pointers[0x6040].set_raw(value) pdo = self.rpdo_pointers[0x6040].pdo_parent if not pdo.is_periodic: pdo.transmit() else: - self.sdo[0x6040].raw = value + self.sdo[0x6040].set_raw(value) @property def state(self): + logger.warning("Accessing BaseNode402.state property is deprecated, use get_state()") + return self.get_state() + + def get_state(self): """Manipulate current state of the DS402 State Machine on the node. Uses the last received Statusword value for read access, and manipulates the @@ -541,14 +561,18 @@ def state(self): """ for state, mask_val_pair in State402.SW_MASK.items(): bitmask, bits = mask_val_pair - if self.statusword & bitmask == bits: + if self.get_statusword() & bitmask == bits: return state return 'UNKNOWN' @state.setter def state(self, target_state): + logger.warning("Accessing BaseNode402.state setter is deprecated, use set_state()") + self.set_state(target_state) + + def set_state(self, target_state): timeout = time.monotonic() + self.TIMEOUT_SWITCH_STATE_FINAL - while self.state != target_state: + while self.get_state() != target_state: next_state = self._next_state(target_state) if self._change_state(next_state): continue @@ -562,7 +586,7 @@ def _next_state(self, target_state): 'FAULT'): raise ValueError( 'Target state {} cannot be entered programmatically'.format(target_state)) - from_state = self.state + from_state = self.get_state() if (from_state, target_state) in State402.TRANSITIONTABLE: return target_state else: @@ -570,12 +594,12 @@ def _next_state(self, target_state): def _change_state(self, target_state): try: - self.controlword = State402.TRANSITIONTABLE[(self.state, target_state)] + self.set_controlword(State402.TRANSITIONTABLE[(self.get_state(), target_state)]) except KeyError: raise ValueError( - 'Illegal state transition from {f} to {t}'.format(f=self.state, t=target_state)) + 'Illegal state transition from {f} to {t}'.format(f=self.get_state(), t=target_state)) timeout = time.monotonic() + self.TIMEOUT_SWITCH_STATE_SINGLE - while self.state != target_state: + while self.get_state() != target_state: if time.monotonic() > timeout: return False self.check_statusword() diff --git a/canopen/sdo/base.py b/canopen/sdo/base.py index 25d3d60c..fe5137a6 100644 --- a/canopen/sdo/base.py +++ b/canopen/sdo/base.py @@ -112,7 +112,7 @@ def __iter__(self) -> Iterable[int]: return iter(range(1, len(self) + 1)) def __len__(self) -> int: - return self[0].raw + return self[0].get_raw() def __contains__(self, subindex: int) -> bool: return 0 <= subindex <= len(self) diff --git a/canopen/variable.py b/canopen/variable.py index c7924e51..7b255aa8 100644 --- a/canopen/variable.py +++ b/canopen/variable.py @@ -26,6 +26,7 @@ def __init__(self, od: objectdictionary.Variable): self.subindex = od.subindex def get_data(self) -> bytes: + """Byte representation of the object as :class:`bytes`.""" raise NotImplementedError("Variable is not readable") def set_data(self, data: bytes): @@ -33,15 +34,20 @@ def set_data(self, data: bytes): @property def data(self) -> bytes: - """Byte representation of the object as :class:`bytes`.""" + logger.warning("Accessing Variable.data property is deprecated, use get_data()") return self.get_data() @data.setter def data(self, data: bytes): + logger.warning("Accessing Variable.data setter is deprecated, use set_data()") self.set_data(data) @property def raw(self) -> Union[int, bool, float, str, bytes]: + logger.warning("Accessing Variable.raw property is deprecated, use get_raw()") + return self.get_raw() + + def get_raw(self) -> Union[int, bool, float, str, bytes]: """Raw representation of the object. This table lists the translations between object dictionary data types @@ -72,7 +78,7 @@ def raw(self) -> Union[int, bool, float, str, bytes]: Data types that this library does not handle yet must be read and written as :class:`bytes`. """ - value = self.od.decode_raw(self.data) + value = self.od.decode_raw(self.get_data()) text = "Value of %s (0x%X:%d) is %r" % ( self.name, self.index, self.subindex, value) @@ -83,41 +89,65 @@ def raw(self) -> Union[int, bool, float, str, bytes]: @raw.setter def raw(self, value: Union[int, bool, float, str, bytes]): + logger.warning("Accessing Variable.raw setter is deprecated, use set_raw()") + self.set_raw(value) + + def set_raw(self, value: Union[int, bool, float, str, bytes]): logger.debug("Writing %s (0x%X:%d) = %r", self.name, self.index, self.subindex, value) - self.data = self.od.encode_raw(value) + self.set_data(self.od.encode_raw(value)) @property def phys(self) -> Union[int, bool, float, str, bytes]: + logger.warning("Accessing Variable.phys attribute is deprecated, use get_phys()") + return self.get_phys() + + def get_phys(self) -> Union[int, bool, float, str, bytes]: """Physical value scaled with some factor (defaults to 1). On object dictionaries that support specifying a factor, this can be either a :class:`float` or an :class:`int`. Non integers will be passed as is. """ - value = self.od.decode_phys(self.raw) + value = self.od.decode_phys(self.get_raw()) if self.od.unit: logger.debug("Physical value is %s %s", value, self.od.unit) return value @phys.setter def phys(self, value: Union[int, bool, float, str, bytes]): - self.raw = self.od.encode_phys(value) + logger.warning("Accessing Variable.phys setter is deprecated, use set_phys()") + self.set_phys(value) + + def set_phys(self, value: Union[int, bool, float, str, bytes]): + self.set_raw(self.od.encode_phys(value)) @property def desc(self) -> str: + logger.warning("Accessing Variable.desc attribute is deprecated, use get_desc()") + return self.get_desc() + + def get_desc(self) -> str: """Converts to and from a description of the value as a string.""" - value = self.od.decode_desc(self.raw) + value = self.od.decode_desc(self.get_raw()) logger.debug("Description is '%s'", value) return value @desc.setter def desc(self, desc: str): - self.raw = self.od.encode_desc(desc) + logger.warning("Accessing Variable.desc setter is deprecated, use set_desc()") + self.set_desc(desc) + + def set_desc(self, desc: str): + self.set_raw(self.od.encode_desc(desc)) @property def bits(self) -> "Bits": + logger.warning("Accessing Variable.bits attribute is deprecated, use get_bits()") + return self.get_bits() + + def get_bits(self) -> "Bits": """Access bits using integers, slices, or bit descriptions.""" return Bits(self) @@ -136,11 +166,11 @@ def read(self, fmt: str = "raw") -> Union[int, bool, float, str, bytes]: The value of the variable. """ if fmt == "raw": - return self.raw + return self.get_raw() elif fmt == "phys": - return self.phys + return self.get_phys() elif fmt == "desc": - return self.desc + return self.get_desc() def write( self, value: Union[int, bool, float, str, bytes], fmt: str = "raw" @@ -156,11 +186,11 @@ def write( - 'desc' """ if fmt == "raw": - self.raw = value + self.set_raw(value) elif fmt == "phys": - self.phys = value + self.set_phys(value) elif fmt == "desc": - self.desc = value + self.set_desc(value) class Bits(Mapping): @@ -194,7 +224,7 @@ def __len__(self): return len(self.variable.od.bit_definitions) def read(self): - self.raw = self.variable.raw + self.raw = self.variable.get_raw() def write(self): - self.variable.raw = self.raw + self.variable.set_raw(self.raw) diff --git a/doc/nmt.rst b/doc/nmt.rst index 3a9706f3..901a689e 100644 --- a/doc/nmt.rst +++ b/doc/nmt.rst @@ -27,25 +27,24 @@ Examples -------- Access the NMT functionality using the :attr:`canopen.Node.nmt` attribute. -Changing state can be done using the :attr:`~canopen.nmt.NmtMaster.state` -attribute:: +Changing state can be done using the :meth:`~canopen.nmt.NmtMaster.set_state()` +method:: - node.nmt.state = 'OPERATIONAL' + node.nmt.set_state('OPERATIONAL') # Same as sending NMT start node.nmt.send_command(0x1) You can also change state of all nodes simulaneously as a broadcast message:: - network.nmt.state = 'OPERATIONAL' + network.nmt.set_state('OPERATIONAL') -If the node transmits heartbeat messages, the -:attr:`~canopen.nmt.NmtMaster.state` attribute gets automatically updated with -current state:: +If the node transmits heartbeat messages, the state can be read with +:meth:`~canopen.nmt.NmtMaster.get_state()`:: # Send NMT start to all nodes network.send_message(0x0, [0x1, 0]) node.nmt.wait_for_heartbeat() - assert node.nmt.state == 'OPERATIONAL' + assert node.nmt.get_state() == 'OPERATIONAL' API diff --git a/doc/pdo.rst b/doc/pdo.rst index 9a7c027b..2fba53af 100644 --- a/doc/pdo.rst +++ b/doc/pdo.rst @@ -51,20 +51,20 @@ starts at 1, not 0):: node.rpdo[4].enabled = True # Save new configuration (node must be in pre-operational) - node.nmt.state = 'PRE-OPERATIONAL' + node.nmt.set_state('PRE-OPERATIONAL') node.tpdo.save() node.rpdo.save() # Start RPDO4 with an interval of 100 ms - node.rpdo[4]['Application Commands.Command Speed'].phys = 1000 + node.rpdo[4]['Application Commands.Command Speed'].set_phys(1000) node.rpdo[4].start(0.1) - node.nmt.state = 'OPERATIONAL' + node.nmt.set_state('OPERATIONAL') # Read 50 values of speed and save to a file with open('output.txt', 'w') as f: for i in range(50): node.tpdo[4].wait_for_reception() - speed = node.tpdo['Application Status.Actual Speed'].phys + speed = node.tpdo['Application Status.Actual Speed'].get_phys() f.write('%s\n' % speed) # Using a callback to asynchronously receive values @@ -72,7 +72,7 @@ starts at 1, not 0):: def print_speed(message): print('%s received' % message.name) for var in message: - print('%s = %d' % (var.name, var.raw)) + print('%s = %d' % (var.name, var.get_raw())) node.tpdo[4].add_callback(print_speed) time.sleep(5) diff --git a/doc/profiles.rst b/doc/profiles.rst index 9fdc1d29..71b09b3e 100644 --- a/doc/profiles.rst +++ b/doc/profiles.rst @@ -48,25 +48,25 @@ That works only in the 'OPERATIONAL' or 'PRE-OPERATIONAL' states of the Write Controlword and read Statusword:: # command to go to 'READY TO SWITCH ON' from 'NOT READY TO SWITCH ON' or 'SWITCHED ON' - some_node.sdo[0x6040].raw = 0x06 + some_node.sdo[0x6040].set_raw(0x06) # Read the state of the Statusword - some_node.sdo[0x6041].raw + some_node.sdo[0x6041].get_raw() During operation the state can change to states which cannot be commanded by the Controlword, for example a 'FAULT' state. Therefore the :class:`BaseNode402` class (in similarity to :class:`NmtMaster`) automatically monitors state changes of the Statusword which is sent by TPDO. The available callback on that TPDO will then extract the information and mirror the state change in the -:attr:`BaseNode402.state` attribute. +:meth:`BaseNode402.get_state()` method. Similar to the :class:`NmtMaster` class, the states of the :class:`BaseNode402` -class :attr:`.state` attribute can be read and set (command) by a string:: +class can be read and set (command) by a string:: # command a state (an SDO message will be called) - some_node.state = 'SWITCHED ON' + some_node.set_state('SWITCHED ON') # read the current state - some_node.state + some_node.get_state() Available states: diff --git a/doc/sdo.rst b/doc/sdo.rst index c0db4ca5..f156521b 100644 --- a/doc/sdo.rst +++ b/doc/sdo.rst @@ -44,23 +44,24 @@ The code below only creates objects, no messages are sent or received yet:: # Arrays error_log = node.sdo[0x1003] -To actually read or write the variables, use the ``.raw``, ``.phys``, ``.desc``, -or ``.bits`` attributes:: +To actually read or write the variables, use the +``.get_raw()``, ``.set_raw()``, ``.get_phys()``, ``.set_phys()``, +``.get_desc()``, ``.set_desc()``, ``.get_bits()`` methods:: - print("The device type is 0x%X" % device_type.raw) + print("The device type is 0x%X" % device_type.get_raw()) # Using value descriptions instead of integers (if supported by OD) - control_mode.desc = 'Speed Mode' + control_mode.set_desc('Speed Mode') # Set individual bit - command_all.bits[3] = 1 + command_all.get_bits()[3] = 1 # Read and write physical values scaled by a factor (if supported by OD) - print("The actual speed is %f rpm" % actual_speed.phys) + print("The actual speed is %f rpm" % actual_speed.get_phys()) # Iterate over arrays or records for error in error_log.values(): - print("Error 0x%X was found in the log" % error.raw) + print("Error 0x%X was found in the log" % error.get_raw()) It is also possible to read and write to variables that are not in the Object Dictionary, but only using raw bytes:: diff --git a/examples/simple_ds402_node.py b/examples/simple_ds402_node.py index c99831a0..d1856102 100644 --- a/examples/simple_ds402_node.py +++ b/examples/simple_ds402_node.py @@ -22,48 +22,48 @@ # node = network[34] # Reset network - node.nmt.state = 'RESET COMMUNICATION' - #node.nmt.state = 'RESET' + node.nmt.set_state('RESET COMMUNICATION') + #node.nmt.set_state('RESET') node.nmt.wait_for_bootup(15) - print('node state 1) = {0}'.format(node.nmt.state)) + print('node state 1) = {0}'.format(node.nmt.get_state())) # Iterate over arrays or records error_log = node.sdo[0x1003] for error in error_log.values(): - print("Error {0} was found in the log".format(error.raw)) + print("Error {0} was found in the log".format(error.get_raw())) for node_id in network: print(network[node_id]) - print('node state 2) = {0}'.format(node.nmt.state)) + print('node state 2) = {0}'.format(node.nmt.get_state())) # Read a variable using SDO - node.sdo[0x1006].raw = 1 - node.sdo[0x100c].raw = 100 - node.sdo[0x100d].raw = 3 - node.sdo[0x1014].raw = 163 - node.sdo[0x1003][0].raw = 0 + node.sdo[0x1006].set_raw(1) + node.sdo[0x100c].set_raw(100) + node.sdo[0x100d].set_raw(3) + node.sdo[0x1014].set_raw(163) + node.sdo[0x1003][0].set_raw(0) # Transmit SYNC every 100 ms network.sync.start(0.1) node.load_configuration() - print('node state 3) = {0}'.format(node.nmt.state)) + print('node state 3) = {0}'.format(node.nmt.get_state())) node.setup_402_state_machine() - device_name = node.sdo[0x1008].raw - vendor_id = node.sdo[0x1018][1].raw + device_name = node.sdo[0x1008].get_raw() + vendor_id = node.sdo[0x1018][1].get_raw() print(device_name) print(vendor_id) - node.state = 'SWITCH ON DISABLED' + node.set_state('SWITCH ON DISABLED') - print('node state 4) = {0}'.format(node.nmt.state)) + print('node state 4) = {0}'.format(node.nmt.get_state())) # Read PDO configuration from node node.tpdo.read() @@ -80,13 +80,13 @@ # publish the a value to the control word (in this case reset the fault at the motors) node.rpdo.read() - node.rpdo[1]['Controlword'].raw = 0x80 + node.rpdo[1]['Controlword'].set_raw(0x80) node.rpdo[1].transmit() - node.rpdo[1]['Controlword'].raw = 0x81 + node.rpdo[1]['Controlword'].set_raw(0x81) node.rpdo[1].transmit() - node.state = 'READY TO SWITCH ON' - node.state = 'SWITCHED ON' + node.set_state('READY TO SWITCH ON') + node.set_state('SWITCHED ON') node.rpdo.export('database.dbc') @@ -95,27 +95,27 @@ print('Node booted up') timeout = time.time() + 15 - node.state = 'READY TO SWITCH ON' - while node.state != 'READY TO SWITCH ON': + node.set_state('READY TO SWITCH ON') + while node.get_state() != 'READY TO SWITCH ON': if time.time() > timeout: raise Exception('Timeout when trying to change state') time.sleep(0.001) timeout = time.time() + 15 - node.state = 'SWITCHED ON' - while node.state != 'SWITCHED ON': + node.set_state('SWITCHED ON') + while node.get_state() != 'SWITCHED ON': if time.time() > timeout: raise Exception('Timeout when trying to change state') time.sleep(0.001) timeout = time.time() + 15 - node.state = 'OPERATION ENABLED' - while node.state != 'OPERATION ENABLED': + node.set_state('OPERATION ENABLED') + while node.get_state() != 'OPERATION ENABLED': if time.time() > timeout: raise Exception('Timeout when trying to change state') time.sleep(0.001) - print('Node Status {0}'.format(node.powerstate_402.state)) + print('Node Status {0}'.format(node.powerstate_402.get_state())) # ----------------------------------------------------------------------------------------- node.nmt.start_node_guarding(0.01) @@ -127,10 +127,10 @@ # Read a value from TxPDO1 node.tpdo[1].wait_for_reception() - speed = node.tpdo[1]['Velocity actual value'].phys + speed = node.tpdo[1]['Velocity actual value'].get_phys() # Read the state of the Statusword - statusword = node.sdo[0x6041].raw + statusword = node.sdo[0x6041].get_raw() print('statusword: {0}'.format(statusword)) print('VEL: {0}'.format(speed)) @@ -151,7 +151,7 @@ for node_id in network: node = network[node_id] - node.nmt.state = 'PRE-OPERATIONAL' + node.nmt.set_state('PRE-OPERATIONAL') node.nmt.stop_node_guarding() network.sync.stop() network.disconnect() diff --git a/test/test_local.py b/test/test_local.py index 493cef1b..07ca3c7e 100644 --- a/test/test_local.py +++ b/test/test_local.py @@ -34,8 +34,8 @@ def tearDownClass(cls): cls.network2.disconnect() def test_expedited_upload(self): - self.local_node.sdo[0x1400][1].raw = 0x99 - vendor_id = self.remote_node.sdo[0x1400][1].raw + self.local_node.sdo[0x1400][1].set_raw(0x99) + vendor_id = self.remote_node.sdo[0x1400][1].get_raw() self.assertEqual(vendor_id, 0x99) def test_block_upload_switch_to_expedite_upload(self): @@ -56,21 +56,21 @@ def test_block_download_not_supported(self): self.assertEqual(context.exception.code, 0x05040001) def test_expedited_upload_default_value_visible_string(self): - device_name = self.remote_node.sdo["Manufacturer device name"].raw + device_name = self.remote_node.sdo["Manufacturer device name"].get_raw() self.assertEqual(device_name, "TEST DEVICE") def test_expedited_upload_default_value_real(self): - sampling_rate = self.remote_node.sdo["Sensor Sampling Rate (Hz)"].raw + sampling_rate = self.remote_node.sdo["Sensor Sampling Rate (Hz)"].get_raw() self.assertAlmostEqual(sampling_rate, 5.2, places=2) def test_segmented_upload(self): - self.local_node.sdo["Manufacturer device name"].raw = "Some cool device" - device_name = self.remote_node.sdo["Manufacturer device name"].data + self.local_node.sdo["Manufacturer device name"].set_raw("Some cool device") + device_name = self.remote_node.sdo["Manufacturer device name"].get_data() self.assertEqual(device_name, b"Some cool device") def test_expedited_download(self): - self.remote_node.sdo[0x2004].raw = 0xfeff - value = self.local_node.sdo[0x2004].raw + self.remote_node.sdo[0x2004].set_raw(0xfeff) + value = self.local_node.sdo[0x2004].get_raw() self.assertEqual(value, 0xfeff) def test_expedited_download_wrong_datatype(self): @@ -84,14 +84,14 @@ def test_expedited_download_wrong_datatype(self): self.assertEqual(value, bytes([10, 10])) def test_segmented_download(self): - self.remote_node.sdo[0x2000].raw = "Another cool device" - value = self.local_node.sdo[0x2000].data + self.remote_node.sdo[0x2000].set_raw("Another cool device") + value = self.local_node.sdo[0x2000].get_data() self.assertEqual(value, b"Another cool device") def test_slave_send_heartbeat(self): # Setting the heartbeat time should trigger hearbeating # to start - self.remote_node.sdo["Producer heartbeat time"].raw = 1000 + self.remote_node.sdo["Producer heartbeat time"].set_raw(1000) state = self.remote_node.nmt.wait_for_heartbeat() self.local_node.nmt.stop_heartbeat() # The NMT master will change the state INITIALISING (0) @@ -100,11 +100,11 @@ def test_slave_send_heartbeat(self): def test_nmt_state_initializing_to_preoper(self): # Initialize the heartbeat timer - self.local_node.sdo["Producer heartbeat time"].raw = 1000 + self.local_node.sdo["Producer heartbeat time"].set_raw(1000) self.local_node.nmt.stop_heartbeat() # This transition shall start the heartbeating - self.local_node.nmt.state = 'INITIALISING' - self.local_node.nmt.state = 'PRE-OPERATIONAL' + self.local_node.nmt.set_state('INITIALISING') + self.local_node.nmt.set_state('PRE-OPERATIONAL') state = self.remote_node.nmt.wait_for_heartbeat() self.local_node.nmt.stop_heartbeat() self.assertEqual(state, 'PRE-OPERATIONAL') @@ -117,20 +117,20 @@ def test_receive_abort_request(self): self.assertEqual(self.local_node.sdo.last_received_error, 0x05040003) def test_start_remote_node(self): - self.remote_node.nmt.state = 'OPERATIONAL' + self.remote_node.nmt.set_state('OPERATIONAL') # Line below is just so that we are sure the client have received the command # before we do the check time.sleep(0.1) - slave_state = self.local_node.nmt.state + slave_state = self.local_node.nmt.get_state() self.assertEqual(slave_state, 'OPERATIONAL') def test_two_nodes_on_the_bus(self): - self.local_node.sdo["Manufacturer device name"].raw = "Some cool device" - device_name = self.remote_node.sdo["Manufacturer device name"].data + self.local_node.sdo["Manufacturer device name"].set_raw("Some cool device") + device_name = self.remote_node.sdo["Manufacturer device name"].get_data() self.assertEqual(device_name, b"Some cool device") - self.local_node2.sdo["Manufacturer device name"].raw = "Some cool device2" - device_name = self.remote_node2.sdo["Manufacturer device name"].data + self.local_node2.sdo["Manufacturer device name"].set_raw("Some cool device2") + device_name = self.remote_node2.sdo["Manufacturer device name"].get_data() self.assertEqual(device_name, b"Some cool device2") def test_abort(self): @@ -145,7 +145,7 @@ def test_abort(self): self.assertEqual(cm.exception.code, 0x06090011) with self.assertRaises(canopen.SdoAbortedError) as cm: - _ = self.remote_node.sdo[0x1001].data + _ = self.remote_node.sdo[0x1001].get_data() # Should be Resource not available self.assertEqual(cm.exception.code, 0x060A0023) @@ -197,18 +197,18 @@ def tearDownClass(cls): cls.network2.disconnect() def test_start_two_remote_nodes(self): - self.remote_node.nmt.state = 'OPERATIONAL' + self.remote_node.nmt.set_state('OPERATIONAL') # Line below is just so that we are sure the client have received the command # before we do the check time.sleep(0.1) - slave_state = self.local_node.nmt.state + slave_state = self.local_node.nmt.get_state() self.assertEqual(slave_state, 'OPERATIONAL') - self.remote_node2.nmt.state = 'OPERATIONAL' + self.remote_node2.nmt.set_state('OPERATIONAL') # Line below is just so that we are sure the client have received the command # before we do the check time.sleep(0.1) - slave_state = self.local_node2.nmt.state + slave_state = self.local_node2.nmt.get_state() self.assertEqual(slave_state, 'OPERATIONAL') def test_stop_two_remote_nodes_using_broadcast(self): @@ -219,18 +219,18 @@ def test_stop_two_remote_nodes_using_broadcast(self): # Line below is just so that we are sure the slaves have received the command # before we do the check time.sleep(0.1) - slave_state = self.local_node.nmt.state + slave_state = self.local_node.nmt.get_state() self.assertEqual(slave_state, 'STOPPED') - slave_state = self.local_node2.nmt.state + slave_state = self.local_node2.nmt.get_state() self.assertEqual(slave_state, 'STOPPED') def test_heartbeat(self): - # self.assertEqual(self.remote_node.nmt.state, 'INITIALISING') - # self.assertEqual(self.local_node.nmt.state, 'INITIALISING') - self.local_node.nmt.state = 'OPERATIONAL' - self.local_node.sdo[0x1017].raw = 100 + # self.assertEqual(self.remote_node.nmt.get_state(), 'INITIALISING') + # self.assertEqual(self.local_node.nmt.get_state(), 'INITIALISING') + self.local_node.nmt.set_state('OPERATIONAL') + self.local_node.sdo[0x1017].set_raw(100) time.sleep(0.2) - self.assertEqual(self.remote_node.nmt.state, 'OPERATIONAL') + self.assertEqual(self.remote_node.nmt.get_state(), 'OPERATIONAL') self.local_node.nmt.stop_heartbeat() diff --git a/test/test_network.py b/test/test_network.py index 04129271..85b7ce32 100644 --- a/test/test_network.py +++ b/test/test_network.py @@ -28,7 +28,7 @@ def test_notify(self): self.network.notify(0x82, b'\x01\x20\x02\x00\x01\x02\x03\x04', 1473418396.0) self.assertEqual(len(node.emcy.active), 1) self.network.notify(0x702, b'\x05', 1473418396.0) - self.assertEqual(node.nmt.state, 'OPERATIONAL') + self.assertEqual(node.nmt.get_state(), 'OPERATIONAL') self.assertListEqual(self.network.scanner.nodes, [2]) def test_send(self): diff --git a/test/test_pdo.py b/test/test_pdo.py index 6bba18fe..92c10c8b 100644 --- a/test/test_pdo.py +++ b/test/test_pdo.py @@ -18,42 +18,42 @@ def test_bit_mapping(self): map.add_variable('BOOLEAN value 2', length=1) # 0x2006 # Write some values - map['INTEGER16 value'].raw = -3 - map['UNSIGNED8 value'].raw = 0xf - map['INTEGER8 value'].raw = -2 - map['INTEGER32 value'].raw = 0x01020304 - map['BOOLEAN value'].raw = False - map['BOOLEAN value 2'].raw = True + map['INTEGER16 value'].set_raw(-3) + map['UNSIGNED8 value'].set_raw(0xf) + map['INTEGER8 value'].set_raw(-2) + map['INTEGER32 value'].set_raw(0x01020304) + map['BOOLEAN value'].set_raw(False) + map['BOOLEAN value 2'].set_raw(True) # Check expected data self.assertEqual(map.data, b'\xfd\xff\xef\x04\x03\x02\x01\x02') # Read values from data - self.assertEqual(map['INTEGER16 value'].raw, -3) - self.assertEqual(map['UNSIGNED8 value'].raw, 0xf) - self.assertEqual(map['INTEGER8 value'].raw, -2) - self.assertEqual(map['INTEGER32 value'].raw, 0x01020304) - self.assertEqual(map['BOOLEAN value'].raw, False) - self.assertEqual(map['BOOLEAN value 2'].raw, True) - - self.assertEqual(node.tpdo[1]['INTEGER16 value'].raw, -3) - self.assertEqual(node.tpdo[1]['UNSIGNED8 value'].raw, 0xf) - self.assertEqual(node.tpdo[1]['INTEGER8 value'].raw, -2) - self.assertEqual(node.tpdo[1]['INTEGER32 value'].raw, 0x01020304) - self.assertEqual(node.tpdo['INTEGER32 value'].raw, 0x01020304) - self.assertEqual(node.tpdo[1]['BOOLEAN value'].raw, False) - self.assertEqual(node.tpdo[1]['BOOLEAN value 2'].raw, True) + self.assertEqual(map['INTEGER16 value'].get_raw(), -3) + self.assertEqual(map['UNSIGNED8 value'].get_raw(), 0xf) + self.assertEqual(map['INTEGER8 value'].get_raw(), -2) + self.assertEqual(map['INTEGER32 value'].get_raw(), 0x01020304) + self.assertEqual(map['BOOLEAN value'].get_raw(), False) + self.assertEqual(map['BOOLEAN value 2'].get_raw(), True) + + self.assertEqual(node.tpdo[1]['INTEGER16 value'].get_raw(), -3) + self.assertEqual(node.tpdo[1]['UNSIGNED8 value'].get_raw(), 0xf) + self.assertEqual(node.tpdo[1]['INTEGER8 value'].get_raw(), -2) + self.assertEqual(node.tpdo[1]['INTEGER32 value'].get_raw(), 0x01020304) + self.assertEqual(node.tpdo['INTEGER32 value'].get_raw(), 0x01020304) + self.assertEqual(node.tpdo[1]['BOOLEAN value'].get_raw(), False) + self.assertEqual(node.tpdo[1]['BOOLEAN value 2'].get_raw(), True) # Test different types of access - self.assertEqual(node.pdo[0x1600]['INTEGER16 value'].raw, -3) - self.assertEqual(node.pdo['INTEGER16 value'].raw, -3) - self.assertEqual(node.pdo.tx[1]['INTEGER16 value'].raw, -3) - self.assertEqual(node.pdo[0x2001].raw, -3) - self.assertEqual(node.tpdo[0x2001].raw, -3) - self.assertEqual(node.pdo[0x2002].raw, 0xf) - self.assertEqual(node.pdo['0x2002'].raw, 0xf) - self.assertEqual(node.tpdo[0x2002].raw, 0xf) - self.assertEqual(node.pdo[0x1600][0x2002].raw, 0xf) + self.assertEqual(node.pdo[0x1600]['INTEGER16 value'].get_raw(), -3) + self.assertEqual(node.pdo['INTEGER16 value'].get_raw(), -3) + self.assertEqual(node.pdo.tx[1]['INTEGER16 value'].get_raw(), -3) + self.assertEqual(node.pdo[0x2001].get_raw(), -3) + self.assertEqual(node.tpdo[0x2001].get_raw(), -3) + self.assertEqual(node.pdo[0x2002].get_raw(), 0xf) + self.assertEqual(node.pdo['0x2002'].get_raw(), 0xf) + self.assertEqual(node.tpdo[0x2002].get_raw(), 0xf) + self.assertEqual(node.pdo[0x1600][0x2002].get_raw(), 0xf) if __name__ == "__main__": diff --git a/test/test_sdo.py b/test/test_sdo.py index c0ba086b..e97e81ad 100644 --- a/test/test_sdo.py +++ b/test/test_sdo.py @@ -42,7 +42,7 @@ def test_expedited_upload(self): (TX, b'\x40\x18\x10\x01\x00\x00\x00\x00'), (RX, b'\x43\x18\x10\x01\x04\x00\x00\x00') ] - vendor_id = self.network[2].sdo[0x1018][1].raw + vendor_id = self.network[2].sdo[0x1018][1].get_raw() self.assertEqual(vendor_id, 4) # UNSIGNED8 without padded data part (see issue #5) @@ -50,7 +50,7 @@ def test_expedited_upload(self): (TX, b'\x40\x00\x14\x02\x00\x00\x00\x00'), (RX, b'\x4f\x00\x14\x02\xfe') ] - trans_type = self.network[2].sdo[0x1400]['Transmission type RPDO 1'].raw + trans_type = self.network[2].sdo[0x1400]['Transmission type RPDO 1'].get_raw() self.assertEqual(trans_type, 254) def test_size_not_specified(self): @@ -67,7 +67,7 @@ def test_expedited_download(self): (TX, b'\x2b\x17\x10\x00\xa0\x0f\x00\x00'), (RX, b'\x60\x17\x10\x00\x00\x00\x00\x00') ] - self.network[2].sdo[0x1017].raw = 4000 + self.network[2].sdo[0x1017].set_raw(4000) def test_segmented_upload(self): self.data = [ @@ -82,7 +82,7 @@ def test_segmented_upload(self): (TX, b'\x70\x00\x00\x00\x00\x00\x00\x00'), (RX, b'\x15\x69\x6E\x73\x20\x21\x00\x00') ] - device_name = self.network[2].sdo[0x1008].raw + device_name = self.network[2].sdo[0x1008].get_raw() self.assertEqual(device_name, "Tiny Node - Mega Domains !") def test_segmented_download(self): @@ -94,7 +94,7 @@ def test_segmented_download(self): (TX, b'\x13\x73\x74\x72\x69\x6e\x67\x00'), (RX, b'\x30\x00\x20\x00\x00\x00\x00\x00') ] - self.network[2].sdo['Writable string'].raw = 'A long string' + self.network[2].sdo['Writable string'].set_raw('A long string') def test_block_download(self): self.data = [ @@ -156,7 +156,7 @@ def test_abort(self): (RX, b'\x80\x18\x10\x01\x11\x00\x09\x06') ] with self.assertRaises(canopen.SdoAbortedError) as cm: - _ = self.network[2].sdo[0x1018][1].raw + _ = self.network[2].sdo[0x1018][1].get_raw() self.assertEqual(cm.exception.code, 0x06090011) def test_add_sdo_channel(self):