diff --git a/canopen/emcy.py b/canopen/emcy.py index 1bbdeb75..8d085a9a 100644 --- a/canopen/emcy.py +++ b/canopen/emcy.py @@ -2,7 +2,10 @@ import logging import threading import time -from typing import Callable, List, Optional +from typing import Callable, List, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from canopen.network import Network # Error code, error register, vendor specific data EMCY_STRUCT = struct.Struct(" None: if self._state == 0: logger.info("Sending boot-up message") + if self.network is None: + raise RuntimeError("Cannot send without network") self.network.send_message(0x700 + self.id, [0]) # The heartbeat service should start on the transition @@ -246,6 +255,8 @@ def start_heartbeat(self, heartbeat_time_ms: int): self.stop_heartbeat() if heartbeat_time_ms > 0: logger.info("Start the heartbeat timer, interval is %d ms", self._heartbeat_time_ms) + if self.network is None: + raise RuntimeError("Cannot send without network") self._send_task = self.network.send_periodic( 0x700 + self.id, [self._state], heartbeat_time_ms / 1000.0) diff --git a/canopen/node/base.py b/canopen/node/base.py index bf72d959..bd1eb389 100644 --- a/canopen/node/base.py +++ b/canopen/node/base.py @@ -1,6 +1,9 @@ -from typing import TextIO, Union +from typing import TextIO, Union, Optional, TYPE_CHECKING from canopen.objectdictionary import ObjectDictionary, import_od +if TYPE_CHECKING: + from canopen.network import Network + class BaseNode: """A CANopen node. @@ -17,7 +20,7 @@ def __init__( node_id: int, object_dictionary: Union[ObjectDictionary, str, TextIO], ): - self.network = None + self.network: Optional[Network] = None if not isinstance(object_dictionary, ObjectDictionary): object_dictionary = import_od(object_dictionary, node_id) diff --git a/canopen/node/local.py b/canopen/node/local.py index eb74b98d..02486d28 100644 --- a/canopen/node/local.py +++ b/canopen/node/local.py @@ -1,5 +1,6 @@ +from __future__ import annotations import logging -from typing import Dict, Union +from typing import Dict, Union, TYPE_CHECKING from canopen.node.base import BaseNode from canopen.sdo import SdoServer, SdoAbortedError @@ -9,6 +10,9 @@ from canopen.objectdictionary import ObjectDictionary from canopen import objectdictionary +if TYPE_CHECKING: + from canopen.network import Network + logger = logging.getLogger(__name__) @@ -34,7 +38,7 @@ def __init__( self.add_write_callback(self.nmt.on_write) self.emcy = EmcyProducer(0x80 + self.id) - def associate_network(self, network): + def associate_network(self, network: Network): self.network = network self.sdo.network = network self.tpdo.network = network @@ -45,8 +49,9 @@ def associate_network(self, network): network.subscribe(0, self.nmt.on_command) def remove_network(self): - self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request) - self.network.unsubscribe(0, self.nmt.on_command) + if self.network is not None: + self.network.unsubscribe(self.sdo.rx_cobid, self.sdo.on_request) + self.network.unsubscribe(0, self.nmt.on_command) self.network = None self.sdo.network = None self.tpdo.network = None diff --git a/canopen/node/remote.py b/canopen/node/remote.py index 4f3281db..feeaab0b 100644 --- a/canopen/node/remote.py +++ b/canopen/node/remote.py @@ -1,5 +1,6 @@ +from __future__ import annotations import logging -from typing import Union, TextIO +from typing import Union, TextIO, TYPE_CHECKING from canopen.sdo import SdoClient, SdoCommunicationError, SdoAbortedError from canopen.nmt import NmtMaster @@ -8,6 +9,9 @@ from canopen.objectdictionary import ODRecord, ODArray, ODVariable, ObjectDictionary from canopen.node.base import BaseNode +if TYPE_CHECKING: + from canopen.network import Network + logger = logging.getLogger(__name__) @@ -46,7 +50,7 @@ def __init__( if load_od: self.load_configuration() - def associate_network(self, network): + def associate_network(self, network: Network): self.network = network self.sdo.network = network self.pdo.network = network @@ -60,11 +64,12 @@ def associate_network(self, network): network.subscribe(0, self.nmt.on_command) def remove_network(self): - for sdo in self.sdo_channels: - self.network.unsubscribe(sdo.tx_cobid, sdo.on_response) - self.network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat) - self.network.unsubscribe(0x80 + self.id, self.emcy.on_emcy) - self.network.unsubscribe(0, self.nmt.on_command) + if self.network is not None: + for sdo in self.sdo_channels: + self.network.unsubscribe(sdo.tx_cobid, sdo.on_response) + self.network.unsubscribe(0x700 + self.id, self.nmt.on_heartbeat) + self.network.unsubscribe(0x80 + self.id, self.emcy.on_emcy) + self.network.unsubscribe(0, self.nmt.on_command) self.network = None self.sdo.network = None self.pdo.network = None diff --git a/canopen/objectdictionary/eds.py b/canopen/objectdictionary/eds.py index 3884d809..889b6a96 100644 --- a/canopen/objectdictionary/eds.py +++ b/canopen/objectdictionary/eds.py @@ -1,3 +1,5 @@ +from __future__ import annotations +from typing import TYPE_CHECKING import copy import logging import re @@ -7,6 +9,9 @@ from canopen.objectdictionary import ObjectDictionary, datatypes from canopen.sdo import SdoClient +if TYPE_CHECKING: + from canopen.network import Network + logger = logging.getLogger(__name__) # Object type. Don't confuse with Data type @@ -172,7 +177,7 @@ def import_eds(source, node_id): return od -def import_from_node(node_id, network): +def import_from_node(node_id, network: Network): """ Download the configuration from the remote node :param int node_id: Identifier of the node :param network: network object diff --git a/canopen/pdo/base.py b/canopen/pdo/base.py index f2a7d205..34496b1c 100644 --- a/canopen/pdo/base.py +++ b/canopen/pdo/base.py @@ -464,6 +464,8 @@ def subscribe(self) -> None: known to match what's stored on the node. """ if self.enabled: + if self.pdo_node.network is None: + raise RuntimeError("A Network is required") logger.info("Subscribing to enabled PDO 0x%X on the network", self.cob_id) self.pdo_node.network.subscribe(self.cob_id, self.on_message) @@ -511,6 +513,8 @@ def add_variable( def transmit(self) -> None: """Transmit the message once.""" + if self.pdo_node.network is None: + raise RuntimeError("A Network is required") self.pdo_node.network.send_message(self.cob_id, self.data) def start(self, period: Optional[float] = None) -> None: @@ -521,6 +525,9 @@ def start(self, period: Optional[float] = None) -> None: on the object before. :raises ValueError: When neither the argument nor the :attr:`period` is given. """ + if self.pdo_node.network is None: + raise RuntimeError("A Network is required") + # Stop an already running transmission if we have one, otherwise we # overwrite the reference and can lose our handle to shut it down self.stop() @@ -551,6 +558,8 @@ def remote_request(self) -> None: Silently ignore if not allowed. """ if self.enabled and self.rtr_allowed: + if self.pdo_node.network is None: + raise RuntimeError("A Network is required") self.pdo_node.network.send_message(self.cob_id, bytes(), remote=True) def wait_for_reception(self, timeout: float = 10) -> float: diff --git a/canopen/sdo/base.py b/canopen/sdo/base.py index 0bb068b4..416b0338 100644 --- a/canopen/sdo/base.py +++ b/canopen/sdo/base.py @@ -1,13 +1,16 @@ from __future__ import annotations import binascii -from typing import Iterator, Optional, Union +from typing import Iterator, Optional, Union, TYPE_CHECKING from collections.abc import Mapping from canopen import objectdictionary from canopen import variable from canopen.utils import pretty_index +if TYPE_CHECKING: + from canopen.network import Network + class CrcXmodem: """Mimics CrcXmodem from crccheck.""" @@ -43,7 +46,7 @@ def __init__( """ self.rx_cobid = rx_cobid self.tx_cobid = tx_cobid - self.network = None + self.network: Optional[Network] = None self.od = od def __getitem__( diff --git a/canopen/sdo/client.py b/canopen/sdo/client.py index e4c50aa8..2b858948 100644 --- a/canopen/sdo/client.py +++ b/canopen/sdo/client.py @@ -45,6 +45,8 @@ def on_response(self, can_id, data, timestamp): self.responses.put(bytes(data)) def send_request(self, request): + if self.network is None: + raise RuntimeError("A Network is required to send a request") retries_left = self.MAX_RETRIES if self.PAUSE_BEFORE_SEND: time.sleep(self.PAUSE_BEFORE_SEND) diff --git a/canopen/sdo/server.py b/canopen/sdo/server.py index e2a16e61..a9c44889 100644 --- a/canopen/sdo/server.py +++ b/canopen/sdo/server.py @@ -172,6 +172,8 @@ def segmented_download(self, command, request): self.send_response(response) def send_response(self, response): + if self.network is None: + raise RuntimeError("A Network is required to send") self.network.send_message(self.tx_cobid, response) def abort(self, abort_code=0x08000000): diff --git a/canopen/sync.py b/canopen/sync.py index d3734512..61ce1f7b 100644 --- a/canopen/sync.py +++ b/canopen/sync.py @@ -1,4 +1,8 @@ -from typing import Optional +from __future__ import annotations +from typing import Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from canopen.network import Network class SyncProducer: @@ -7,8 +11,8 @@ class SyncProducer: #: COB-ID of the SYNC message cob_id = 0x80 - def __init__(self, network): - self.network = network + def __init__(self, network: Network): + self.network: Network = network self.period: Optional[float] = None self._task = None diff --git a/canopen/timestamp.py b/canopen/timestamp.py index f3004da2..b6798c6d 100644 --- a/canopen/timestamp.py +++ b/canopen/timestamp.py @@ -1,6 +1,11 @@ +from __future__ import annotations +from typing import Optional, TYPE_CHECKING import time import struct -from typing import Optional + +if TYPE_CHECKING: + from canopen.network import Network + # 1 Jan 1984 OFFSET = 441763200 @@ -16,8 +21,8 @@ class TimeProducer: #: COB-ID of the SYNC message cob_id = 0x100 - def __init__(self, network): - self.network = network + def __init__(self, network: Network): + self.network: Network = network def transmit(self, timestamp: Optional[float] = None): """Send out the TIME message once.