From 23cbc9ba130f56419ed779debc3ef40a4f0e7501 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 4 Jan 2022 19:49:37 +0100 Subject: [PATCH 01/11] Service Frame Class --- thingset/packet.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/thingset/packet.py b/thingset/packet.py index 2e96a91..00ea5f3 100644 --- a/thingset/packet.py +++ b/thingset/packet.py @@ -104,3 +104,101 @@ def data(self, data): @property def cbor(self): return self._cbor + +class ServiceFrame(TSPacket): + def __init__(self, priority=7, destination=0, source=0): + super().__init__() + self._messageType = False + self.priority = priority + self.source = source + self.destination = destination + + @property + def messageType(self): + return "Service message" + + @property + def priority(self): + return self._priority + + @priority.setter + def priority(self, priority): + if priority not in range(0,8): + raise ValueError("Priority must be integer between 0 and 7 (got: {})".format(priority)) + self._priority = priority + + @property + def destination(self): + return self._destination + + @destination.setter + def destination(self, destination): + if destination not in range(0,256): + raise ValueError("Destination ID must be integer between 0 and 255 (got: {})".format(destination)) + self._destination = destination + +class RequestFrame(ServiceFrame): + SINGLE_ID_MASK = (0b10 << 24) + + FRAME_TYPE_SINGLE = 0x0 + FRAME_TYPE_FIRST = 0x1 + FRAME_TYPE_CONSEC = 0x2 + FRAME_TYPE_FLOWC = 0x3 + + def __init__(self, priority=7, src=0, dst=0, data=bytes()): + super().__init__() + self.priority = priority + self.source = src + self.destination = dst + self.type = (data[0] & 0xf0) >> 4 + self.data = data + + if self.type == self.FRAME_TYPE_SINGLE: + self.framesize = data[0] & 0xf + if self.type == self.FRAME_TYPE_FIRST: + self.framesize = ((data[0] & 0xF) << 8) | data[1] + if self.type == self.FRAME_TYPE_CONSEC: + self.index = data[0] & 0xf + if self.type == self.FRAME_TYPE_FLOWC: + self.fcflag = data[0] & 0xf + self.blocksize = data[1] + self.delay = data[2] + + + def parseIdentifier(self, identifier): + if not isinstance(identifier, int): + raise ValueError("Identifier must be integer, not {}.".format(identifier)) + if identifier >= (1 << 30): + raise ValueError("Identifier too big. Cannot contain more than 29 bits") + if (identifier & TSPacket.TS_FRAME_FLAG): + raise ValueError("Not a request/response message.") + self.priority = identifier >> 26 + self.destination = (identifier & 0xffff) >> 8 + self.source = identifier & 0xff + + @property + def identifier(self): + id_prio = self._priority << 26 + id_fixed = 0xDA << 16 + id_dst = self._destination << 8 + return id_prio | self.SINGLE_ID_MASK | id_fixed | id_dst | self.source + + @property + def type(self): + return self._type + + @type.setter + def type(self, type): + self._type = type + + @property + def data(self): + return self._data + + @data.setter + def data(self, data): + if data is None: + self._data = bytes() + elif not isinstance(data, bytes): + raise TypeError("Wrong data type. Must be bytes, not {}".format(type(data))) + self._data = data From e0ac9b321b1dbe595eebadc0b45e7a152088bf40 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 4 Jan 2022 19:50:38 +0100 Subject: [PATCH 02/11] CANsocket with ISO-TP Handling --- thingset/cansocket.py | 188 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 10 deletions(-) diff --git a/thingset/cansocket.py b/thingset/cansocket.py index 39f3495..1e33c96 100644 --- a/thingset/cansocket.py +++ b/thingset/cansocket.py @@ -1,25 +1,193 @@ -import socket -import struct -from thingset.packet import TSPacket, SingleFrame +import datetime, socket, struct, time +from cbor2 import loads +from thingset.packet import TSPacket, SingleFrame, RequestFrame class CANsocket(object): FMT = ' None: + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if not addr in self.sub_addresses: + self.sub_addresses.append(addr) + + def receive(self) -> tuple: + '''Receive function with ThingSet/ISO-TP Frame handling + Returns a tuple with 4 entries: + 1: Is Publication Frame: bool + 2: Status or Data Object ID: int + 3: Data: Deserialized Object (bool, int, float, list, dict) + 4: Publication Frame Source ID + ''' + ret = None packet = self.s.recv(64) can_id, length, data = struct.unpack(self.FMT, packet) can_id &= socket.CAN_EFF_MASK - frame = SingleFrame(data=data) + if (can_id & TSPacket.TS_FRAME_FLAG): + frame = SingleFrame(data=data) + frame.parseIdentifier(can_id) + if frame.source in self.sub_addresses: + ret = (True, frame.dataobjectID, frame.cbor, frame.source) + else: + frame = RequestFrame(data=data) frame.parseIdentifier(can_id) + if frame.destination == self.address: + if frame.type == RequestFrame.FRAME_TYPE_SINGLE: + status = self.status_code[data[1]] + if status == 'Content': + ret = (False, status, loads(frame.data[2:]), None) + else: + ret = (False, status, None, None) + if frame.type == RequestFrame.FRAME_TYPE_FIRST: + self.packetsize = frame.framesize + self.databuffer.clear() + self.databuffer.extend(frame.data[2:]) + self.last_index = 0 + fc_frame = RequestFrame(src=self.address, dst=frame.source, data=b'\x30\x00\x00') + self.send(fc_frame) + if frame.type == RequestFrame.FRAME_TYPE_CONSEC: + expected_index = (self.last_index + 1) % 16 + if frame.index == expected_index: + self.last_index = expected_index + frame.data = data[1:] + self.databuffer.extend(frame.data) + if len(self.databuffer) >= self.packetsize: + status = self.status_code[self.databuffer[0]] + ret = (False, status, (loads(self.databuffer[1:])), None) + if frame.type == RequestFrame.FRAME_TYPE_FLOWC: + self.flow_control['flag'] = frame.fcflag + self.flow_control['blocksize'] = frame.fcflag + self.flow_control['delay'] = frame.fcflag - return(frame) + return ret - def send(self, message): + def send(self, message:RequestFrame) -> bool: + '''Send function with basic ThingSet/ISO-TP handling + Returns True on Success + ''' can_id = message.identifier | socket.CAN_EFF_FLAG - can_packet = struct.pack(self.FMT, can_id, len(message.data), message.data) - self.s.send(can_packet) + + if message.type == RequestFrame.FRAME_TYPE_FLOWC: + can_packet = struct.pack(self.FMT, can_id, len(message.data), message.data) + self.s.send(can_packet) + else: + if len(message.data) <= 7: + # data fits in single frame + frame_data = bytearray() + frame_data.append(len(message.data)) + frame_data.extend(message.data) + can_packet = struct.pack(self.FMT, can_id, len(frame_data), frame_data) + self.s.send(can_packet) + else: + # First Frame + data = bytearray(message.data) + index = 0 + block = 0 + length = len(data) + len_bytes = length.to_bytes(2, byteorder='little') + frame_data = bytearray() + frame_data.append(0x10 | len_bytes[1]) + frame_data.append(len_bytes[0]) + frame_data.extend(data[:6]) + del data[:6] + can_packet = struct.pack(self.FMT, can_id, 8, frame_data) + self.s.send(can_packet) + if self.waitFC(): + # Consecutive Frames + flag, blocksize, delay_s = self.evalFCFlags() + if flag == 'Abort': + return False + self.flow_control.clear() + while len(data) != 0: + if len(data) >= 7: + frame_data = bytearray() + index += 1 + index %= 16 + frame_data.append(0x20 | index) + frame_data.extend(data[:7]) + del data[:7] + self.s.send(struct.pack(self.FMT, can_id, len(frame_data), frame_data)) + block += 1 + if block == blocksize: + block = 0 + if self.waitFC(): + flag, blocksize, delay_s = self.evalFCFlags() + if flag == 'Abort': + return False + self.flow_control.clear() + else: + print("Flow Control Timeout while sending") + return False + else: + time.sleep(delay_s) + else: + frame_data = bytearray() + index += 1 + frame_data.append(0x20 | index) + frame_data.extend(data) + self.s.send(struct.pack(self.FMT, can_id, len(frame_data), frame_data)) + del data[:] + else: + print("Flow Control Timeout before sending") + return False + return True + + + def waitFC(self, timeout:float=0.5): + '''Wait for Flow Control Frame and return False on Timeout and True on Success''' + ret = bool(False) + timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) + while datetime.datetime.now() <= timeout: + if self.flow_control: + ret = True + break + return ret + + def evalFCFlags(self): + fcflag = self.flow_control['flag'] + blocksize = self.flow_control['blocksize'] + delay_code = self.flow_control['delay'] + if delay_code <= 127: + delay_s = float(delay_code) / 1000.0 + elif delay_code >= 0xF1 and delay_code <= 0xF9: + delay_s = float(delay_code & 0xF) / 10000.0 + else: + delay_s = 0.0 + if self.fc_flags[fcflag] == 'Wait': + time.sleep(1.0) + return (self.fc_flags[fcflag], blocksize, delay_s) From 72ebaa8de8c318d3e5f4cb1d41395a366fd6bec3 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 4 Jan 2022 19:51:39 +0100 Subject: [PATCH 03/11] ThingSet_CAN Class --- thingset/thingset_can.py | 140 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 thingset/thingset_can.py diff --git a/thingset/thingset_can.py b/thingset/thingset_can.py new file mode 100644 index 0000000..29a891b --- /dev/null +++ b/thingset/thingset_can.py @@ -0,0 +1,140 @@ +import threading, time, datetime +from thingset.cansocket import CANsocket +from thingset.packet import RequestFrame +from cbor2 import loads, dumps + +class ThingSet_CAN(threading.Thread): + + __response = None + __own_id = int() + __pub_callback = None + + data = dict() + + def __init__(self, if_name:str, own_id:int): + super().__init__() + if own_id not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(own_id)) + self.__own_id = own_id + self.__sock = CANsocket(if_name, self.__own_id) # or other interface + + def setPubCallback(self, callback): + if hasattr(callback, '__call__'): + self.__pub_callback = callback + + def subscribe(self, addr:int): + self.data[addr] = {} + self.__sock.subscribe(addr) + + def get(self, addr:int, id:int): + '''Retrieve all data from a path''' + ret = None + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + data = bytearray(b'\x01') + data.extend(dumps(id)) + frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(data)) + self.__sock.send(frame) + if self.__waitResponse(0.5): + ret = self.__getResponse() + else: + print("GET Timeout!") + return ret + + def post(self, addr:int, id:int, data): + '''Append data to an object or execute a function''' + ret = None + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + frame_data = bytearray(b'\x02') + frame_data.extend(dumps(id)) + frame_data.extend(dumps(data)) + frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(frame_data)) + self.__sock.send(frame) + if self.__waitResponse(0.5): + ret = self.__getResponse() + else: + print("POST Timeout!") + return ret + + def delete(self, addr:int, data): + '''Delete data from an object''' + ret = None + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + frame_data = bytearray(b'\x04') + frame_data.extend(dumps(data)) + frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(frame_data)) + self.__sock.send(frame) + if self.__waitResponse(0.5): + ret = self.__getResponse() + else: + print("DELETE Timeout!") + return ret + + def fetch(self, addr:int, path_id:int, array:list): + '''Retrieve a subset of data from a path''' + ret = None + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + data = bytearray(b'\x05') + data.extend(dumps(path_id)) + data.extend(dumps(array)) + frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(data)) + self.__sock.send(frame) + if self.__waitResponse(1.5): + ret = self.__getResponse() + else: + print("FETCH Timeout!") + return ret + + def patch(self, addr: int, obj_id: int, map:dict): + '''Update (overwrite) data of a path''' + ret = None + if addr not in range(0,256): + raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + data = bytearray(b'\x07') + data.extend(dumps(obj_id)) + data.extend(dumps(map)) + self.__sock.send(RequestFrame(src=self.__own_id, dst=addr, data=bytes(data))) + if self.__waitResponse(): + ret = self.__getResponse() + else: + print("PATCH Timeout!") + + return ret + + def __waitResponse(self, timeout:float=1.5): + '''Wait for response and return False on Timeout and True on Success''' + ret = bool(False) + timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) + while datetime.datetime.now() <= timeout: + if self.__response != None: + ret = True + break + return ret + + def __getResponse(self): + '''Returns Response Code/Response Data''' + status, data = self.__response + self.__response = None + if data == None: + ret = status + else: + ret = data + return ret + + def receive(self): + '''Reception Thread Function''' + while True: + rx = self.__sock.receive() + if rx != None: + pub_frame, status, data, src_id = rx + if pub_frame == True: + id = status + self.data[src_id].update({id: data}) + if self.__pub_callback != None: + self.__pub_callback(src_id, id, data) + else: + self.__response = (status, data) + From ee0e1ab90a0eafd2de8d8410b318c9b9bae8cff5 Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 4 Jan 2022 19:52:15 +0100 Subject: [PATCH 04/11] Tests --- test.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/test.py b/test.py index 04ebbf7..9979dbe 100755 --- a/test.py +++ b/test.py @@ -1,10 +1,75 @@ +import threading, time +from thingset.thingset_can import ThingSet_CAN -from thingset.cansocket import CANsocket -sock = CANsocket('can0') # or other interface +own_id = 0x01 +bms_id = 0x0A +mppt_id = 0x14 -while(True): - frame = sock.receive() - if isinstance(frame.cbor, float): - print("device: 0x%x data id: 0x%x value: %.2f" % (frame.source, frame.dataobjectID, frame.cbor)) - else: - print("device:", hex(frame.source), " data id:", hex(frame.dataobjectID), " value:", frame.cbor) +def pub_callback(src:int, id:int, data): + '''Callback function for received Publication Frames''' + print(f'Message from ID {src} -> {id}: {data}') + +ts = ThingSet_CAN(if_name='can0', own_id=own_id) +ts.subscribe(bms_id) +ts.subscribe(mppt_id) +ts.setPubCallback(pub_callback) +rx_thread = threading.Thread(target=ts.receive) +rx_thread.setDaemon(True) +rx_thread.start() + +print("Python ThingSet Client for CAN (Binary Mode)") +print("--------------------------------------------") + +# GET +print(" > Get - INFO Path: " + str(ts.get(bms_id, 0x01))) +print(" > Get - MEAS Path: " + str(ts.get(bms_id, 0x02))) +print(" > Get - INPUT Path: " + str(ts.get(bms_id, 0x05))) +print(" > Get - CONF Path: " + str(ts.get(bms_id, 0x06))) +print(" > Get - can.Enable: " + str(ts.get(bms_id, 0xF6))) +print(" > Get - meas.Bat_V: " + str(ts.get(bms_id, 0x71))) +print(" > Get - meas.Cell_V: " + str(ts.get(bms_id, 0x80))) + +# PATCH +print(" > iPatch (bool) - Discharge disable: " + str(ts.patch(bms_id, 0x05, {0x61: False}))) +print(" > iPatch (bool) - Discharge enable: " + str(ts.patch(bms_id, 0x05, {0x61: True}))) +print(" > iPatch (string) - Password = abcd: " + str(ts.patch(bms_id, 0xEE, {0xEF: 'abcd'}))) +#print(" iPatch (int) - PcbDisSC_us = 200: " + str(ts.patch(bms_id, 0x06, {0x41: 200}))) +#print(" iPatch (bool) - .pub.can.enable = False: " + str(ts.patch(bms_id, 0xF5, {0xF6: False}))) + +# FETCH +print(" > Fetch - Request BatNom_Ah from CONF Path: " + str(ts.fetch(bms_id, 0x06, [0x31]))) +print(" > Fetch - Request Array: " + str(ts.fetch(bms_id, 0x06, [0x31, 0x41]))) + +# POST - Execute Function +print(" > Post - print-registers: " + str(ts.post(bms_id, 0xEC, []))) +print(" > Post - print-register: " + str(ts.post(bms_id, 0xEA, [0x4B]))) +print(" > Post - auth: " + str(ts.post(bms_id, 0xEE, ["maker456"]))) +print(" > Post - auth: " + str(ts.post(bms_id, 0xEE, ["expert123"]))) + +# ISSUES ======================================================= + +# print(" > Fetch - MEAS item IDs: " + str(ts.fetch(bms_id, 0x02, 0xF7))) +# Returs: Error - Response too large (0xE1) + +# print(" > Post - Append : " + str(ts.post(bms_id, 0x100, 0x74))) + +# print(" > Get - .pub Path: " + str(ts.get(bms_id, 0x100))) +# returns: 1EDA010A 06 85 A2 18 F1 18 F5 +# cbor2.loads(b'\xa2\x18\xf1\x18\xf5') -> premature end of stream (expected to read 1 bytes, got 0 instead) + +# print(" > Get - serial: " + str(ts.get(bms_id, 0xF3))) +# Returns: 1EDA010A 01 85 +# print(" > Get - can: " + str(ts.get(bms_id, 0xF7))) +# Returns: 1EDA010A 01 85 + +# print(" > Get - serial: " + str(ts.get(bms_id, 0xF1))) +# Returns: 1EDA010A 07 85 A2 18 F3 18 F2 F4 +# cbor2.loads(b'\xa2\x18\xf3\x18\xf2\xf4') -> premature end of stream (expected to read 1 bytes, got 0 instead) +# print(" > Get - can: " + str(ts.get(bms_id, 0xF5))) +# Returns: 1EDA010A 07 85 A2 18 F7 18 F6 F5 +# cbor2.loads(b'\xa2\x18\xf7\x18\xf6\xf5') -> premature end of stream (expected to read 1 bytes, got 0 instead) + +time.sleep(2) + +print(">> BMS Pub/Sub Data: " + str(ts.data[bms_id])) +print(">> MPPT Pub/Sub Data: " + str(ts.data[mppt_id])) From 41fe5c511821f96f7ff918bfef5f6f45f2bafe31 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 17:33:40 +0100 Subject: [PATCH 05/11] Readme --- Readme.md | 116 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/Readme.md b/Readme.md index 8e0710b..bf74728 100644 --- a/Readme.md +++ b/Readme.md @@ -27,47 +27,93 @@ deactivate ``` ## Usage: -At the moment you have to use the python shell. I script for nicer commands is on the way. - -### listen to a single packet -``` python -from thingset.cansocket import CANsocket -sock = CANsocket('vcan0') # or other interface -frame = sock.receive() -print(frame.data) # raw data -print(frame.cbor) # cbor decoded data -print(frame.priority) -print(frame.dataobjectID) -print(frame.source) + +### Initialization + +```python +from thingset.thingset_can import ThingSet_CAN + +# Define some addresses +own_id = 0x01 +bms_id = 0x0A +mppt_id = 0x14 + +# Create ThingSet client object with CAN interface and source ID +ts = ThingSet_CAN(if_name='can0', own_id=own_id) +# Subscribe to publication messages of specific devices +ts.subscribe(bms_id) +ts.subscribe(mppt_id) +# Start reception process +rx_thread.start() ``` -### listen to packets in a loop: -``` python -from thingset.cansocket import CANsocket -sock = CANsocket('vcan0') # or other interface -while(True) - frame = sock.receive() +### Callback Mechanism + +It is possible to register a callback function that is called on every received publication frame to process the frame directly after reception. + +```python +def pub_callback(src:int, id:int, data): + '''Callback function for received Publication Frames''' + print(f'Message from ID {src} -> {id}: {data}') + +ts.setPubCallback(pub_callback) +``` + +### Request API + +* Return data is dependent on request type + * can be: + * status code (string) + * bool + * int + * float + * string + * array (Python List) + * map (Python Dictionary) + +#### get + +```python +# Retrieve all data from a path +meas_id = ts.get(bms_id, 0x02))) +meas_str = ts.get(bms_id, 'meas'))) + +# Retrieve single data items +deviceID = ts.get(bms_id, 0x1D))) +can_enable = ts.get(bms_id, 0xF6))) +bat_V = ts.get(bms_id, 0x71))) + +# Retrieve array data items +cell_V = ts.get(bms_id, 0x80))) ``` -### parse can trace -Assuming the trace file ist called "trace123.csv": -``` python -from thingset.parser import playback -playback('trace123.csv") +#### fetch + +```python +# fetch BatNom_Ah from CONF Path +batNom_Ah = ts.fetch(bms_id, 0x06, [0x31]) + +# fetch multiple items with an array +response = ts.fetch(bms_id, 0x06, [0x31, 0x41]) ``` -## send dummy data via cansend to test: -``` shell -cansend vcan0 014#8000FA3FF33333 -cansend vcan0 014#8000820C16 -cansend vcan0 014#8000C48221196AB3 -cansend vcan0 014#8080C48221196AB3 +#### post + +```python +# call print-registers function +response = ts.post(bms_id, 0xEC, []) + +# call print-register function with argument +response = ts.post(bms_id, 0xEA, [0x4B]))) + +# Athenticate +response = ts.post(bms_id, 0xEE, ["maker456"]) +response = ts.post(bms_id, 0xEE, ["expert123"]) ``` -The output should look like this: -``` shell -Message from ID 20 -> 0: 1.899999976158142 -Message from ID 20 -> 0: [12, 22] -Message from ID 20 -> 0: 273.15 -Error receiving package from ID 20. Error code: 0x80 +#### patch + +```python +# iPatch (bool) - can.enable = False +response = ts.patch(bms_id, 0xF5, {0xF6: False}) ``` From 1e3bbcad46264c84adc65236e7e35d0a977408ab Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 20:01:05 +0100 Subject: [PATCH 06/11] Formating --- thingset/cansocket.py | 40 +++++++++++++++---------- thingset/packet.py | 47 ++++++++++++++++++----------- thingset/thingset_can.py | 64 ++++++++++++++++++++++++---------------- 3 files changed, 93 insertions(+), 58 deletions(-) diff --git a/thingset/cansocket.py b/thingset/cansocket.py index 1e33c96..041bf54 100644 --- a/thingset/cansocket.py +++ b/thingset/cansocket.py @@ -1,7 +1,11 @@ -import datetime, socket, struct, time +import datetime +import socket +import struct +import time from cbor2 import loads from thingset.packet import TSPacket, SingleFrame, RequestFrame + class CANsocket(object): FMT = ' None: - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + def subscribe(self, addr: int) -> None: + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) if not addr in self.sub_addresses: self.sub_addresses.append(addr) @@ -57,7 +62,7 @@ def receive(self) -> tuple: packet = self.s.recv(64) can_id, length, data = struct.unpack(self.FMT, packet) can_id &= socket.CAN_EFF_MASK - + if (can_id & TSPacket.TS_FRAME_FLAG): frame = SingleFrame(data=data) frame.parseIdentifier(can_id) @@ -78,7 +83,8 @@ def receive(self) -> tuple: self.databuffer.clear() self.databuffer.extend(frame.data[2:]) self.last_index = 0 - fc_frame = RequestFrame(src=self.address, dst=frame.source, data=b'\x30\x00\x00') + fc_frame = RequestFrame( + src=self.address, dst=frame.source, data=b'\x30\x00\x00') self.send(fc_frame) if frame.type == RequestFrame.FRAME_TYPE_CONSEC: expected_index = (self.last_index + 1) % 16 @@ -88,7 +94,8 @@ def receive(self) -> tuple: self.databuffer.extend(frame.data) if len(self.databuffer) >= self.packetsize: status = self.status_code[self.databuffer[0]] - ret = (False, status, (loads(self.databuffer[1:])), None) + ret = (False, status, + (loads(self.databuffer[1:])), None) if frame.type == RequestFrame.FRAME_TYPE_FLOWC: self.flow_control['flag'] = frame.fcflag self.flow_control['blocksize'] = frame.fcflag @@ -96,14 +103,15 @@ def receive(self) -> tuple: return ret - def send(self, message:RequestFrame) -> bool: + def send(self, message: RequestFrame) -> bool: '''Send function with basic ThingSet/ISO-TP handling Returns True on Success ''' can_id = message.identifier | socket.CAN_EFF_FLAG if message.type == RequestFrame.FRAME_TYPE_FLOWC: - can_packet = struct.pack(self.FMT, can_id, len(message.data), message.data) + can_packet = struct.pack( + self.FMT, can_id, len(message.data), message.data) self.s.send(can_packet) else: if len(message.data) <= 7: @@ -111,7 +119,8 @@ def send(self, message:RequestFrame) -> bool: frame_data = bytearray() frame_data.append(len(message.data)) frame_data.extend(message.data) - can_packet = struct.pack(self.FMT, can_id, len(frame_data), frame_data) + can_packet = struct.pack( + self.FMT, can_id, len(frame_data), frame_data) self.s.send(can_packet) else: # First Frame @@ -141,7 +150,8 @@ def send(self, message:RequestFrame) -> bool: frame_data.append(0x20 | index) frame_data.extend(data[:7]) del data[:7] - self.s.send(struct.pack(self.FMT, can_id, len(frame_data), frame_data)) + self.s.send(struct.pack( + self.FMT, can_id, len(frame_data), frame_data)) block += 1 if block == blocksize: block = 0 @@ -160,15 +170,15 @@ def send(self, message:RequestFrame) -> bool: index += 1 frame_data.append(0x20 | index) frame_data.extend(data) - self.s.send(struct.pack(self.FMT, can_id, len(frame_data), frame_data)) + self.s.send(struct.pack( + self.FMT, can_id, len(frame_data), frame_data)) del data[:] else: print("Flow Control Timeout before sending") return False return True - - def waitFC(self, timeout:float=0.5): + def waitFC(self, timeout: float = 0.5): '''Wait for Flow Control Frame and return False on Timeout and True on Success''' ret = bool(False) timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) diff --git a/thingset/packet.py b/thingset/packet.py index 00ea5f3..0245ac0 100644 --- a/thingset/packet.py +++ b/thingset/packet.py @@ -1,6 +1,7 @@ from socket import CAN_EFF_FLAG from cbor2 import loads + class TSPacket(object): TS_FRAME_FLAG = (1 << 24) @@ -14,8 +15,9 @@ def source(self): @source.setter def source(self, source): - if source not in range(0,256): - raise ValueError("Source ID must be integer between 0 and 255 (got: {})".format(source)) + if source not in range(0, 256): + raise ValueError( + "Source ID must be integer between 0 and 255 (got: {})".format(source)) self._source = source @property @@ -25,7 +27,8 @@ def timestamp(self): @timestamp.setter def timestamp(self, timestamp): if not isinstance(timestamp, float): - raise TypeError("Timestamp must be float (got: {})".format(type(timestamp))) + raise TypeError( + "Timestamp must be float (got: {})".format(type(timestamp))) self._timestamp = timestamp @@ -46,8 +49,9 @@ def dataobjectID(self): @dataobjectID.setter def dataobjectID(self, dataobjectID): - if not dataobjectID in range(0,65537): - raise ValueError("Data object ID must be integer between 0 and 65536 (got: {}).".format(dataobjectID)) + if not dataobjectID in range(0, 65537): + raise ValueError( + "Data object ID must be integer between 0 and 65536 (got: {}).".format(dataobjectID)) self._dataobjectID = dataobjectID @@ -65,9 +69,11 @@ def __init__(self, data=None, dataobjectID=0, priority=6, source=0, timestamp=0. def parseIdentifier(self, identifier): if not isinstance(identifier, int): - raise ValueError("Identifier must be integer, not {}.".format(identifier)) + raise ValueError( + "Identifier must be integer, not {}.".format(identifier)) if identifier >= (1 << 30): - raise ValueError("Identifier too big. Cannot contain more than 29 bits") + raise ValueError( + "Identifier too big. Cannot contain more than 29 bits") if not (identifier & TSPacket.TS_FRAME_FLAG): raise ValueError("Not a publication message.") self.priority = identifier >> 26 @@ -97,7 +103,8 @@ def data(self, data): if data is None: self._data = bytes() elif not isinstance(data, bytes): - raise TypeError("Wrong data type. Must be bytes, not {}".format(type(data))) + raise TypeError( + "Wrong data type. Must be bytes, not {}".format(type(data))) self._data = data self._cbor = loads(self._data) @@ -105,6 +112,7 @@ def data(self, data): def cbor(self): return self._cbor + class ServiceFrame(TSPacket): def __init__(self, priority=7, destination=0, source=0): super().__init__() @@ -123,8 +131,9 @@ def priority(self): @priority.setter def priority(self, priority): - if priority not in range(0,8): - raise ValueError("Priority must be integer between 0 and 7 (got: {})".format(priority)) + if priority not in range(0, 8): + raise ValueError( + "Priority must be integer between 0 and 7 (got: {})".format(priority)) self._priority = priority @property @@ -133,10 +142,12 @@ def destination(self): @destination.setter def destination(self, destination): - if destination not in range(0,256): - raise ValueError("Destination ID must be integer between 0 and 255 (got: {})".format(destination)) + if destination not in range(0, 256): + raise ValueError( + "Destination ID must be integer between 0 and 255 (got: {})".format(destination)) self._destination = destination + class RequestFrame(ServiceFrame): SINGLE_ID_MASK = (0b10 << 24) @@ -164,12 +175,13 @@ def __init__(self, priority=7, src=0, dst=0, data=bytes()): self.blocksize = data[1] self.delay = data[2] - def parseIdentifier(self, identifier): if not isinstance(identifier, int): - raise ValueError("Identifier must be integer, not {}.".format(identifier)) + raise ValueError( + "Identifier must be integer, not {}.".format(identifier)) if identifier >= (1 << 30): - raise ValueError("Identifier too big. Cannot contain more than 29 bits") + raise ValueError( + "Identifier too big. Cannot contain more than 29 bits") if (identifier & TSPacket.TS_FRAME_FLAG): raise ValueError("Not a request/response message.") self.priority = identifier >> 26 @@ -190,7 +202,7 @@ def type(self): @type.setter def type(self, type): self._type = type - + @property def data(self): return self._data @@ -200,5 +212,6 @@ def data(self, data): if data is None: self._data = bytes() elif not isinstance(data, bytes): - raise TypeError("Wrong data type. Must be bytes, not {}".format(type(data))) + raise TypeError( + "Wrong data type. Must be bytes, not {}".format(type(data))) self._data = data diff --git a/thingset/thingset_can.py b/thingset/thingset_can.py index 29a891b..b64140a 100644 --- a/thingset/thingset_can.py +++ b/thingset/thingset_can.py @@ -1,8 +1,11 @@ -import threading, time, datetime +import threading +import time +import datetime from thingset.cansocket import CANsocket from thingset.packet import RequestFrame from cbor2 import loads, dumps + class ThingSet_CAN(threading.Thread): __response = None @@ -11,26 +14,29 @@ class ThingSet_CAN(threading.Thread): data = dict() - def __init__(self, if_name:str, own_id:int): + def __init__(self, if_name: str, own_id: int = 0x01): super().__init__() - if own_id not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(own_id)) + if own_id not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(own_id)) self.__own_id = own_id self.__sock = CANsocket(if_name, self.__own_id) # or other interface + self.setDaemon(True) def setPubCallback(self, callback): if hasattr(callback, '__call__'): self.__pub_callback = callback - def subscribe(self, addr:int): + def subscribe(self, addr: int): self.data[addr] = {} self.__sock.subscribe(addr) - def get(self, addr:int, id:int): + def get(self, addr: int, id: int): '''Retrieve all data from a path''' ret = None - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) data = bytearray(b'\x01') data.extend(dumps(id)) frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(data)) @@ -41,15 +47,17 @@ def get(self, addr:int, id:int): print("GET Timeout!") return ret - def post(self, addr:int, id:int, data): + def post(self, addr: int, id: int, data): '''Append data to an object or execute a function''' ret = None - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) frame_data = bytearray(b'\x02') frame_data.extend(dumps(id)) frame_data.extend(dumps(data)) - frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(frame_data)) + frame = RequestFrame(src=self.__own_id, dst=addr, + data=bytes(frame_data)) self.__sock.send(frame) if self.__waitResponse(0.5): ret = self.__getResponse() @@ -57,14 +65,16 @@ def post(self, addr:int, id:int, data): print("POST Timeout!") return ret - def delete(self, addr:int, data): + def delete(self, addr: int, data): '''Delete data from an object''' ret = None - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) frame_data = bytearray(b'\x04') frame_data.extend(dumps(data)) - frame = RequestFrame(src=self.__own_id, dst=addr, data=bytes(frame_data)) + frame = RequestFrame(src=self.__own_id, dst=addr, + data=bytes(frame_data)) self.__sock.send(frame) if self.__waitResponse(0.5): ret = self.__getResponse() @@ -72,11 +82,12 @@ def delete(self, addr:int, data): print("DELETE Timeout!") return ret - def fetch(self, addr:int, path_id:int, array:list): + def fetch(self, addr: int, path_id: int, array: list): '''Retrieve a subset of data from a path''' ret = None - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) data = bytearray(b'\x05') data.extend(dumps(path_id)) data.extend(dumps(array)) @@ -88,15 +99,17 @@ def fetch(self, addr:int, path_id:int, array:list): print("FETCH Timeout!") return ret - def patch(self, addr: int, obj_id: int, map:dict): + def patch(self, addr: int, obj_id: int, map: dict): '''Update (overwrite) data of a path''' ret = None - if addr not in range(0,256): - raise ValueError("Address must be integer between 0 and 255 (got: {})".format(addr)) + if addr not in range(0, 256): + raise ValueError( + "Address must be integer between 0 and 255 (got: {})".format(addr)) data = bytearray(b'\x07') data.extend(dumps(obj_id)) data.extend(dumps(map)) - self.__sock.send(RequestFrame(src=self.__own_id, dst=addr, data=bytes(data))) + self.__sock.send(RequestFrame( + src=self.__own_id, dst=addr, data=bytes(data))) if self.__waitResponse(): ret = self.__getResponse() else: @@ -104,7 +117,7 @@ def patch(self, addr: int, obj_id: int, map:dict): return ret - def __waitResponse(self, timeout:float=1.5): + def __waitResponse(self, timeout: float = 1.5): '''Wait for response and return False on Timeout and True on Success''' ret = bool(False) timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) @@ -124,7 +137,7 @@ def __getResponse(self): ret = data return ret - def receive(self): + def run(self): '''Reception Thread Function''' while True: rx = self.__sock.receive() @@ -137,4 +150,3 @@ def receive(self): self.__pub_callback(src_id, id, data) else: self.__response = (status, data) - From 729f8a5a3db4b444ff56fdf8cee196098851d44f Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 20:47:23 +0100 Subject: [PATCH 07/11] Adapted Log Script --- thingset_log.py | 93 ++++++++++++++++++++++--------------------------- 1 file changed, 41 insertions(+), 52 deletions(-) diff --git a/thingset_log.py b/thingset_log.py index 3b6229b..a4ccd05 100755 --- a/thingset_log.py +++ b/thingset_log.py @@ -1,10 +1,8 @@ - import json import time from datetime import datetime -from thingset.cansocket import CANsocket -sock = CANsocket('can0') # or other interface +from thingset.thingset_can import ThingSet_CAN mppt_id = 0x14 bms_id = 0xa @@ -32,7 +30,7 @@ 0x71: 'Bat_V', 0x72: 'Bat_A', 0x73: 'Bat_degC', - 0x74: 'IC_degC', + 0x76: 'MOSFETs_degC', 0x7C: 'SOC_pct', 0x7E: 'ErrorFlags', 0x7F: 'BmsState', @@ -42,15 +40,12 @@ 0x9D: 'BalancingStatus', } -mppt_data = {} -mppt_updated = False -mppt_file = open("data/%s_mppt.csv" % datetime.now().strftime("%Y%m%d_%H%M%S"), "a") +mppt_file = open("data/%s_mppt.csv" % + datetime.now().strftime("%Y%m%d_%H%M%S"), "a") -bms_data = {} -bms_updated = False -bms_file = open("data/%s_bms.csv" % datetime.now().strftime("%Y%m%d_%H%M%S"), "a") +bms_file = open("data/%s_bms.csv" % + datetime.now().strftime("%Y%m%d_%H%M%S"), "a") -last_update = int(time.time()) def csv_header(file, id_map): names = ['Timestamp_s'] @@ -58,6 +53,7 @@ def csv_header(file, id_map): names.append(id_map[key]) file.write(','.join(names) + '\n') + def csv_data(file, id_map, timestamp, data): values = [str(timestamp)] for key in id_map: @@ -68,55 +64,48 @@ def csv_data(file, id_map, timestamp, data): file.write(','.join(values) + '\n') file.flush() + +ts = ThingSet_CAN(if_name='can0') +ts.subscribe(bms_id) +ts.subscribe(mppt_id) +ts.start() + csv_header(mppt_file, mppt_map) csv_header(bms_file, bms_map) +last_update = int(time.time()) + try: while True: - frame = sock.receive() - - data_name = str(frame.dataobjectID) - if frame.source == mppt_id and frame.dataobjectID in mppt_map: - data_name = mppt_map[frame.dataobjectID] - mppt_data[data_name] = frame.cbor - mppt_updated = True - elif frame.source == bms_id and frame.dataobjectID in bms_map: - data_name = bms_map[frame.dataobjectID] - bms_data[data_name] = frame.cbor - bms_updated = True + time.sleep(1) now = int(time.time()) - # store data in CSV file - if now > last_update: - try: - print("BMS: Bat %.2fV %.2fA, Cells %.2fV < %.2fV < %.2fV, Err %d " % ( \ - bms_data['Bat_V'], bms_data['Bat_A'], bms_data['CellMin_V'], \ - bms_data['CellAvg_V'], bms_data['CellMax_V'], bms_data['ErrorFlags']), end='' \ - ) - except: - pass - - try: - print("MPPT: Bat %.2fV %.2fA, Solar %.2fV, Load %.2fA, Err %d" % ( \ - mppt_data['Bat_V'], mppt_data['Bat_A'], mppt_data['Solar_V'], \ - mppt_data['Load_A'], mppt_data['ErrorFlags']) \ - ) - except: - print("") - #pass - - if mppt_updated: - #print(json.dumps(mppt_data)) - csv_data(mppt_file, mppt_map, now, mppt_data) - mppt_data = {} - mppt_updated = False - if bms_updated: - #print(json.dumps(bms_data)) - csv_data(bms_file, bms_map, now, bms_data) - bms_data = {} - bms_updated = False - last_update = now + try: + bms_data = ThingSet_CAN.translate(ts.data[bms_id], bms_map) + csv_data(bms_file, bms_map, now, bms_data) + #print(json.dumps(bms_data)) + print("BMS: Bat %.2fV %.2fA, Cells %.2fV < %.2fV < %.2fV, Err %d" % ( + bms_data['Bat_V'], bms_data['Bat_A'], bms_data['CellMin_V'], + bms_data['CellAvg_V'], bms_data['CellMax_V'], bms_data['ErrorFlags']), end='' + ) + except KeyError as exc: + #print(exc) + pass + + try: + mppt_data = ThingSet_CAN.translate(ts.data[mppt_id], mppt_map) + csv_data(mppt_file, mppt_map, now, mppt_data) + # print(json.dumps(mppt_data)) + print("MPPT: Bat %.2fV %.2fA, Solar %.2fV, Load %.2fA, Err %d" % ( + mppt_data['Bat_V'], mppt_data['Bat_A'], mppt_data['Solar_V'], + mppt_data['Load_A'], mppt_data['ErrorFlags']) + ) + except KeyError as exc: + # print(exc) + print("") + + except KeyboardInterrupt: mppt_file.close() From e6a17de68a7872eec4b2b305c4e66df369145ab5 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 20:48:15 +0100 Subject: [PATCH 08/11] Method for ID to String Translation --- thingset/thingset_can.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/thingset/thingset_can.py b/thingset/thingset_can.py index b64140a..4020a76 100644 --- a/thingset/thingset_can.py +++ b/thingset/thingset_can.py @@ -31,6 +31,16 @@ def subscribe(self, addr: int): self.data[addr] = {} self.__sock.subscribe(addr) + @staticmethod + def translate(data:dict, id_name_map:dict): + '''Translates data with id as keys to data with strings as keys''' + str_dict = None + try: + str_dict = dict((id_name_map[key], value) for (key, value) in data.items()) + except KeyError as e: + print(f'Cannot translate ID! Maybe missing in map. {KeyError}') + return str_dict + def get(self, addr: int, id: int): '''Retrieve all data from a path''' ret = None From a62aa55e9f04952dd5a17be1046e28cc997357f4 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 20:50:48 +0100 Subject: [PATCH 09/11] Simple Test File --- test.py | 49 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/test.py b/test.py index 9979dbe..503e3b3 100755 --- a/test.py +++ b/test.py @@ -1,40 +1,50 @@ -import threading, time +# Tested with BMS-8S50-IC +# HW v0.1.1 +# FW v21.0-57-g6c0fb5f-dirty + +import threading +import time from thingset.thingset_can import ThingSet_CAN own_id = 0x01 bms_id = 0x0A mppt_id = 0x14 -def pub_callback(src:int, id:int, data): + +def pub_callback(src: int, id: int, data): '''Callback function for received Publication Frames''' print(f'Message from ID {src} -> {id}: {data}') + ts = ThingSet_CAN(if_name='can0', own_id=own_id) ts.subscribe(bms_id) ts.subscribe(mppt_id) ts.setPubCallback(pub_callback) -rx_thread = threading.Thread(target=ts.receive) -rx_thread.setDaemon(True) -rx_thread.start() +ts.start() print("Python ThingSet Client for CAN (Binary Mode)") print("--------------------------------------------") +# WORKING ===================================================== + # GET print(" > Get - INFO Path: " + str(ts.get(bms_id, 0x01))) +print(" > Get - INFO Path: " + str(ts.get(bms_id, 'info'))) print(" > Get - MEAS Path: " + str(ts.get(bms_id, 0x02))) +print(" > Get - MEAS Path: " + str(ts.get(bms_id, 'meas'))) print(" > Get - INPUT Path: " + str(ts.get(bms_id, 0x05))) print(" > Get - CONF Path: " + str(ts.get(bms_id, 0x06))) print(" > Get - can.Enable: " + str(ts.get(bms_id, 0xF6))) print(" > Get - meas.Bat_V: " + str(ts.get(bms_id, 0x71))) print(" > Get - meas.Cell_V: " + str(ts.get(bms_id, 0x80))) +print(" > Get - DeviceID: " + str(ts.get(bms_id, 0x1D))) # PATCH print(" > iPatch (bool) - Discharge disable: " + str(ts.patch(bms_id, 0x05, {0x61: False}))) print(" > iPatch (bool) - Discharge enable: " + str(ts.patch(bms_id, 0x05, {0x61: True}))) print(" > iPatch (string) - Password = abcd: " + str(ts.patch(bms_id, 0xEE, {0xEF: 'abcd'}))) -#print(" iPatch (int) - PcbDisSC_us = 200: " + str(ts.patch(bms_id, 0x06, {0x41: 200}))) -#print(" iPatch (bool) - .pub.can.enable = False: " + str(ts.patch(bms_id, 0xF5, {0xF6: False}))) +print(" iPatch (int) - PcbDisSC_us = 200: " + str(ts.patch(bms_id, 0x06, {0x41: 200}))) +print(" iPatch (bool) - .pub.can.enable = False: " + str(ts.patch(bms_id, 0xF5, {0xF6: False}))) # FETCH print(" > Fetch - Request BatNom_Ah from CONF Path: " + str(ts.fetch(bms_id, 0x06, [0x31]))) @@ -51,10 +61,10 @@ def pub_callback(src:int, id:int, data): # print(" > Fetch - MEAS item IDs: " + str(ts.fetch(bms_id, 0x02, 0xF7))) # Returs: Error - Response too large (0xE1) -# print(" > Post - Append : " + str(ts.post(bms_id, 0x100, 0x74))) +# print(" > Post - Append : " + str(ts.post(bms_id, 0x100, 0x74))) -# print(" > Get - .pub Path: " + str(ts.get(bms_id, 0x100))) -# returns: 1EDA010A 06 85 A2 18 F1 18 F5 +# print(" > Get - .pub Path: " + str(ts.get(bms_id, 0x100))) +# returns: 1EDA010A 06 85 A2 18 F1 18 F5 # cbor2.loads(b'\xa2\x18\xf1\x18\xf5') -> premature end of stream (expected to read 1 bytes, got 0 instead) # print(" > Get - serial: " + str(ts.get(bms_id, 0xF3))) @@ -69,7 +79,20 @@ def pub_callback(src:int, id:int, data): # Returns: 1EDA010A 07 85 A2 18 F7 18 F6 F5 # cbor2.loads(b'\xa2\x18\xf7\x18\xf6\xf5') -> premature end of stream (expected to read 1 bytes, got 0 instead) -time.sleep(2) +# Fetch Names returns values not strings (diff from spec) +# print(" > Fetch - Names: " + str(ts.fetch(bms_id, 0x17, [0x40, 0x41]))) +# returns: 1EDA010A 10 09 85 82 FA 00 00 00 +# 1EDA010A 21 BE 18 C8 + +# ============================================================== + +print("Exit with Ctrl+C ") + +try: + while True: + time.sleep(1) +except KeyboardInterrupt: + print('interrupted!') -print(">> BMS Pub/Sub Data: " + str(ts.data[bms_id])) -print(">> MPPT Pub/Sub Data: " + str(ts.data[mppt_id])) +print("Latest BMS Pub. Data: " + str(ts.data[bms_id])) +print("Latest MPPT Pub. Data: " + str(ts.data[mppt_id])) From 370d291bbed20a890307022106fc1243a1f12137 Mon Sep 17 00:00:00 2001 From: Steffen Date: Wed, 5 Jan 2022 20:53:05 +0100 Subject: [PATCH 10/11] Re-enable CAN in test.py --- test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test.py b/test.py index 503e3b3..a5170a1 100755 --- a/test.py +++ b/test.py @@ -45,6 +45,7 @@ def pub_callback(src: int, id: int, data): print(" > iPatch (string) - Password = abcd: " + str(ts.patch(bms_id, 0xEE, {0xEF: 'abcd'}))) print(" iPatch (int) - PcbDisSC_us = 200: " + str(ts.patch(bms_id, 0x06, {0x41: 200}))) print(" iPatch (bool) - .pub.can.enable = False: " + str(ts.patch(bms_id, 0xF5, {0xF6: False}))) +print(" iPatch (bool) - .pub.can.enable = True: " + str(ts.patch(bms_id, 0xF5, {0xF6: True}))) # FETCH print(" > Fetch - Request BatNom_Ah from CONF Path: " + str(ts.fetch(bms_id, 0x06, [0x31]))) From b643f3c582bd4cbdcbcb8e71c51bba5dd620dbe3 Mon Sep 17 00:00:00 2001 From: Steffen Date: Thu, 6 Jan 2022 13:33:57 +0100 Subject: [PATCH 11/11] Bugfixes & Refactoring --- thingset/cansocket.py | 39 +++++++++++++++++++-------------------- thingset/thingset_can.py | 8 ++++---- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/thingset/cansocket.py b/thingset/cansocket.py index 041bf54..ab4c3c7 100644 --- a/thingset/cansocket.py +++ b/thingset/cansocket.py @@ -1,4 +1,3 @@ -import datetime import socket import struct import time @@ -12,7 +11,7 @@ class CANsocket(object): databuffer = bytearray() packetsize = int() last_index = int() - flow_control = {} + flow_control = None sub_addresses = list() address = int() @@ -90,16 +89,17 @@ def receive(self) -> tuple: expected_index = (self.last_index + 1) % 16 if frame.index == expected_index: self.last_index = expected_index - frame.data = data[1:] - self.databuffer.extend(frame.data) + self.databuffer.extend(data[1:]) if len(self.databuffer) >= self.packetsize: status = self.status_code[self.databuffer[0]] ret = (False, status, (loads(self.databuffer[1:])), None) + else: + print("Received Consecutive Frame with invalid Index.") + ret = (False, None, None, None) + if frame.type == RequestFrame.FRAME_TYPE_FLOWC: - self.flow_control['flag'] = frame.fcflag - self.flow_control['blocksize'] = frame.fcflag - self.flow_control['delay'] = frame.fcflag + self.flow_control = (frame.fcflag, frame.blocksize, frame.delay) return ret @@ -140,13 +140,12 @@ def send(self, message: RequestFrame) -> bool: # Consecutive Frames flag, blocksize, delay_s = self.evalFCFlags() if flag == 'Abort': + print("Abort by Flow Control Flag from Device") return False - self.flow_control.clear() while len(data) != 0: if len(data) >= 7: frame_data = bytearray() - index += 1 - index %= 16 + index += 1 % 16 frame_data.append(0x20 | index) frame_data.extend(data[:7]) del data[:7] @@ -158,8 +157,8 @@ def send(self, message: RequestFrame) -> bool: if self.waitFC(): flag, blocksize, delay_s = self.evalFCFlags() if flag == 'Abort': + print("Abort by Flow Control Flag from Device") return False - self.flow_control.clear() else: print("Flow Control Timeout while sending") return False @@ -167,7 +166,7 @@ def send(self, message: RequestFrame) -> bool: time.sleep(delay_s) else: frame_data = bytearray() - index += 1 + index += 1 % 16 frame_data.append(0x20 | index) frame_data.extend(data) self.s.send(struct.pack( @@ -181,23 +180,23 @@ def send(self, message: RequestFrame) -> bool: def waitFC(self, timeout: float = 0.5): '''Wait for Flow Control Frame and return False on Timeout and True on Success''' ret = bool(False) - timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) - while datetime.datetime.now() <= timeout: + timeout = time.perf_counter() + timeout + while time.perf_counter() <= timeout: if self.flow_control: ret = True break + time.sleep(0.001) return ret def evalFCFlags(self): - fcflag = self.flow_control['flag'] - blocksize = self.flow_control['blocksize'] - delay_code = self.flow_control['delay'] - if delay_code <= 127: + fcflag, blocksize, delay_code = self.flow_control + self.flow_control = None + if delay_code > 0 and delay_code <= 127: delay_s = float(delay_code) / 1000.0 elif delay_code >= 0xF1 and delay_code <= 0xF9: delay_s = float(delay_code & 0xF) / 10000.0 else: delay_s = 0.0 - if self.fc_flags[fcflag] == 'Wait': + if fcflag == 'Wait': time.sleep(1.0) - return (self.fc_flags[fcflag], blocksize, delay_s) + return (fcflag, blocksize, delay_s) diff --git a/thingset/thingset_can.py b/thingset/thingset_can.py index 4020a76..4f55c94 100644 --- a/thingset/thingset_can.py +++ b/thingset/thingset_can.py @@ -1,9 +1,8 @@ import threading import time -import datetime from thingset.cansocket import CANsocket from thingset.packet import RequestFrame -from cbor2 import loads, dumps +from cbor2 import dumps class ThingSet_CAN(threading.Thread): @@ -130,11 +129,12 @@ def patch(self, addr: int, obj_id: int, map: dict): def __waitResponse(self, timeout: float = 1.5): '''Wait for response and return False on Timeout and True on Success''' ret = bool(False) - timeout = datetime.datetime.now() + datetime.timedelta(seconds=timeout) - while datetime.datetime.now() <= timeout: + timeout = time.perf_counter() + timeout + while time.perf_counter() <= timeout: if self.__response != None: ret = True break + time.sleep(0.001) return ret def __getResponse(self):