def receive(conn): """ Attempt to read messages from a serial connection """ data = bytearray() while True: buf = conn.read(READ_BYTES) if buf: if DEBUG: print(">>> RAW RECEIVE:", buf) data.extend(buf) if (not buf) or len(buf) < READ_BYTES: break idx = 0 while idx + 9 <= len(data): if data[idx] != STX: idx += 1 continue stx, req, addr, size = struct.unpack('>BBBB', data[idx:idx + 4]) if req not in (ENQ, ACK, NAK): print( "Bad req value: {:02x} (should be one of ENQ/ACK/NAK)".format( req)) idx += 1 continue if idx + 4 + size >= len(data): print("Can't read %d bytes from buffer" % size) idx += 1 continue msg, lsb, msb, etx = struct.unpack('>%dsBBB' % size, data[idx + 4:idx + 7 + size]) if etx != ETX: print("Bad ETX value: {:02x}".format(etx)) idx += 1 continue crc_calc = crc16.calcData(data[idx + 1:idx + 4 + size]) crc_msg = msb << 8 | lsb if crc_calc != crc_msg: print("Bad CRC check: %s <> %s" % (binascii.hexlify(crc_calc), binascii.hexlify(crc_msg))) idx += 1 continue if DEBUG: print(">>> RECV:", binascii.hexlify(data), "=>", binascii.hexlify(msg)) yield { "stx": stx, "req": req, "addr": addr, "size": size, "msg": msg, "lsb": lsb, "msb": msb, "etx": etx, } idx += 4 + size
def send(conn, req, cmd, subcmd, data=b'', addr=1): """ Send cmd/subcmd (e.g. 0x60/0x01) and optional data to the RS485 bus """ assert req in (ENQ, ACK, NAK) # req should be one of ENQ, ACK, NAK msg = struct.pack('BBBBB', req, addr, 2 + len(data), cmd, subcmd) if len(data) > 0: msg = struct.pack('5s%ds' % len(data), msg, data) crcval = crc16.calcData(msg) lsb = crcval & (0xff) msb = (crcval >> 8) & 0xff data = struct.pack('B%dsBBB' % len(msg), STX, msg, lsb, msb, ETX) if DEBUG: print(">>> SEND:", binascii.hexlify(msg), "=>", binascii.hexlify(data)) conn.write(data) conn.flush()
def send_request(connection, inv_id, cmd): """ Send command (e.g. b'\x60\x01') to the inverter with id inv_id """ # Borrowed from DeltaPVOutput length = len(cmd) msgbody = struct.pack('BBB%ds' % length, 5, inv_id, length, cmd) crcval = crc16.calcData(msgbody) lsb = crcval & (0xff) msb = (crcval >> 8) & 0xff data = struct.pack('BBBB%dsBBB' % length, 2, 5, inv_id, length, cmd, lsb, msb, 3) if debugging: print("Sending data query to inverter", inv_id) connection.write(data) connection.flush()
def decode_response(data): """ Try to decode an inverter-messages from serial data and return a dictionary with message parameters (including length, inverter_id, command, subcommand). Checks message validity, consistency and CRC, and returns None if a message is not valid. Request-messages are parsed, but are currently not returned. """ try: stx = data[0] enqack = data[1] if stx != 0x02: if verbose: print("Invalid message, STX =", stx) return None if enqack != 0x05 and enqack != 0x06: if verbose: print("Invalid message, ENQ/ACK =", enqack) return None inv_id = data[2] length = data[3] if ( len(data) ) < length + 4 + 3: # should be 4 bytes (STX + ACK + ID + LEN) + data length + 3 bytes (CRC16 + ETX) if verbose: print("Incomplete data block of", len(data), "bytes, should be", length + 7, "bytes") return None cmd = data[4] # Command ID subcmd = data[5] # Subcommand ID data_offset = 6 # Start of data data_length = length - 2 # Length of data crc_lsb = data[ 4 + length] # Least-significant byte of CRC-16 over preceding bytes after STX crc_msb = data[ 4 + length + 1] # Most-significant byte of CRC-16 over preceding bytes after STX etx = data[4 + length + 2] # ETX-byte to signify end of message, should be 0x03 rvals = {'enqack': enqack, 'inv_id': inv_id, 'length': length, 'cmd': cmd, 'subcmd': subcmd, \ 'data_offset': data_offset, 'data_length': data_length} if etx != 0x03: # ETX isn't 0x03, data probably isn't valid if verbose: print("ETX at", length + 2, "is", etx, "but should be 3") print(rvals) return None else: # ETX is 0x03, we probably have a valid data block crc_calc = crc16.calcData( data[1:4 + length]) # Calculate CRC-16 over message, excluding STX crc_msg = crc_msb << 8 | crc_lsb # Compare with CRC transmitted at end of message if crc_calc != crc_msg: print("WARNING: CRC-16 is", hex(crc_calc), " but should be", hex(crc_msg)) return None else: if enqack == 0x05: # ENQ, marks start of request message if debugging: print("Found request-message for inverter", inv_id, "with length", length, "and CMD", cmd, "SUB", subcmd) return None # Currently we do not return requests, only replies if debugging: print("Found valid response:", rvals) return rvals except: print("Error decoding response:", str(sys.exc_info()[0])) return None