|
| 1 | +import logging |
| 2 | +import struct |
| 3 | +import time |
| 4 | + |
| 5 | +try: |
| 6 | + import queue |
| 7 | +except ImportError: |
| 8 | + import Queue as queue |
| 9 | + |
| 10 | + |
| 11 | +logger = logging.getLogger(__name__) |
| 12 | + |
| 13 | +SWITCH_MODE_GLOBAL = 0x04 |
| 14 | +CONFIGURE_NODE_ID = 0x11 |
| 15 | +CONFIGURE_BIT_TIMING = 0x13 |
| 16 | +STORE_CONFIGURATION = 0x17 |
| 17 | +INQUIRE_NODE_ID = 0x5E |
| 18 | + |
| 19 | +ERROR_NONE = 0 |
| 20 | +ERROR_INADMISSIBLE = 1 |
| 21 | + |
| 22 | +ERROR_STORE_NONE = 0 |
| 23 | +ERROR_STORE_NOT_SUPPORTED = 1 |
| 24 | +ERROR_STORE_ACCESS_PROBLEM = 2 |
| 25 | + |
| 26 | +ERROR_VENDOR_SPECIFIC = 0xff |
| 27 | + |
| 28 | + |
| 29 | +class LssMaster(object): |
| 30 | + """The Master of Layer Setting Services""" |
| 31 | + |
| 32 | + LSS_TX_COBID = 0x7E5 |
| 33 | + LSS_RX_COBID = 0x7E4 |
| 34 | + |
| 35 | + NORMAL_MODE = 0x00 |
| 36 | + CONFIGURATION_MODE = 0x01 |
| 37 | + |
| 38 | + #: Max retries for any LSS request |
| 39 | + MAX_RETRIES = 3 |
| 40 | + |
| 41 | + #: Max time in seconds to wait for response from server |
| 42 | + RESPONSE_TIMEOUT = 0.5 |
| 43 | + |
| 44 | + def __init__(self): |
| 45 | + self.network = None |
| 46 | + self._node_id = 0 |
| 47 | + self._data = None |
| 48 | + self._mode_state = self.NORMAL_MODE |
| 49 | + self.responses = queue.Queue() |
| 50 | + |
| 51 | + def send_switch_mode_global(self, mode): |
| 52 | + """switch mode to CONFIGURATION_MODE or NORMAL_MODE. |
| 53 | + There is no reply for this request |
| 54 | +
|
| 55 | + :param int mode: |
| 56 | + CONFIGURATION_MODE or NORMAL_MODE |
| 57 | + """ |
| 58 | + # LSS messages are always a full 8 bytes long. |
| 59 | + # Unused bytes are reserved and should be initialized with 0. |
| 60 | + |
| 61 | + message = [0]*8 |
| 62 | + |
| 63 | + if self._mode_state != mode: |
| 64 | + message[0] = SWITCH_MODE_GLOBAL |
| 65 | + message[1] = mode |
| 66 | + self._mode_state = mode |
| 67 | + self.__send_command(message) |
| 68 | + |
| 69 | + def __send_inquire_node_id(self): |
| 70 | + """ |
| 71 | + :return: int current node id |
| 72 | + """ |
| 73 | + message = [0]*8 |
| 74 | + message[0] = INQUIRE_NODE_ID |
| 75 | + current_node_id, nothing = self.__send_command(message) |
| 76 | + |
| 77 | + return current_node_id |
| 78 | + |
| 79 | + def __send_configure(self, key, value1=0, value2=0): |
| 80 | + """Send a message to set a key with values |
| 81 | + :return: int error_code, int error_extension |
| 82 | + """ |
| 83 | + message = [0]*8 |
| 84 | + message[0] = key |
| 85 | + message[1] = value1 |
| 86 | + message[2] = value2 |
| 87 | + error_code, error_extension = self.__send_command(message) |
| 88 | + if error_code != ERROR_NONE: |
| 89 | + error_msg = "LSS Error: %d" %error_code |
| 90 | + raise LssError(error_msg) |
| 91 | + |
| 92 | + def inquire_node_id(self): |
| 93 | + """Read the node id. |
| 94 | + CANopen node id must be within the range from 1 to 127. |
| 95 | +
|
| 96 | + :return: int node id |
| 97 | + 0 means it is not read by LSS protocol |
| 98 | + """ |
| 99 | + self.send_switch_mode_global(self.CONFIGURATION_MODE) |
| 100 | + return self.__send_inquire_node_id() |
| 101 | + |
| 102 | + def configure_node_id(self, new_node_id): |
| 103 | + """Set the node id |
| 104 | +
|
| 105 | + :param int new_node_id: |
| 106 | + new node id to set |
| 107 | + """ |
| 108 | + self.send_switch_mode_global(self.CONFIGURATION_MODE) |
| 109 | + self.__send_configure(CONFIGURE_NODE_ID, new_node_id) |
| 110 | + |
| 111 | + def configure_bit_timing(self, new_bit_timing): |
| 112 | + """Set the bit timing. |
| 113 | +
|
| 114 | + :param int new_bit_timing: |
| 115 | + bit timing index. |
| 116 | + 0: 1 MBit/sec, 1: 800 kBit/sec, |
| 117 | + 2: 500 kBit/sec, 3: 250 kBit/sec, |
| 118 | + 4: 125 kBit/sec 5: 100 kBit/sec, |
| 119 | + 6: 50 kBit/sec, 7: 20 kBit/sec, |
| 120 | + 8: 10 kBit/sec |
| 121 | + """ |
| 122 | + self.send_switch_mode_global(self.CONFIGURATION_MODE) |
| 123 | + self.__send_configure(CONFIGURE_BIT_TIMING, 0, new_bit_timing) |
| 124 | + |
| 125 | + def store_configuration(self): |
| 126 | + """Store node id and baud rate. |
| 127 | + """ |
| 128 | + self.__send_configure(STORE_CONFIGURATION) |
| 129 | + |
| 130 | + def __send_command(self, message): |
| 131 | + """Send a LSS operation code to the network |
| 132 | +
|
| 133 | + :param byte list message: |
| 134 | + LSS request message. |
| 135 | + """ |
| 136 | + |
| 137 | + retries_left = self.MAX_RETRIES |
| 138 | + |
| 139 | + message_str = "".join(["{:02x} ".format(x) for x in message]) |
| 140 | + logger.info( |
| 141 | + "Sending LSS message %s", message_str) |
| 142 | + |
| 143 | + response = None |
| 144 | + if not self.responses.empty(): |
| 145 | + # logger.warning("There were unexpected messages in the queue") |
| 146 | + self.responses = queue.Queue() |
| 147 | + |
| 148 | + while retries_left: |
| 149 | + # Wait for node to respond |
| 150 | + self.network.send_message(self.LSS_TX_COBID, message) |
| 151 | + |
| 152 | + # There is no response for SWITCH_MODE_GLOBAL message |
| 153 | + if message[0] == SWITCH_MODE_GLOBAL: |
| 154 | + return |
| 155 | + |
| 156 | + try: |
| 157 | + response = self.responses.get( |
| 158 | + block=True, timeout=self.RESPONSE_TIMEOUT) |
| 159 | + except queue.Empty: |
| 160 | + retries_left -= 1 |
| 161 | + else: |
| 162 | + break |
| 163 | + |
| 164 | + if not response: |
| 165 | + raise LssError("No LSS response received") |
| 166 | + if retries_left < self.MAX_RETRIES: |
| 167 | + logger.warning("There were some issues while communicating with the node") |
| 168 | + res_command, = struct.unpack("B", response[0:1]) |
| 169 | + if res_command != message[0]: |
| 170 | + raise LssError(abort_code) |
| 171 | + else: |
| 172 | + self._mode_state = self.CONFIGURATION_MODE |
| 173 | + message1, = struct.unpack("B", response[1:2]) |
| 174 | + message2, = struct.unpack("B", response[2:3]) |
| 175 | + return message1, message2 |
| 176 | + |
| 177 | + def on_message_received(self, can_id, data, timestamp): |
| 178 | + self.responses.put(bytes(data)) |
| 179 | + |
| 180 | +class LssError(Exception): |
| 181 | + """Some LSS operation failed.""" |
0 commit comments