def _process_tuya(self, reply): a = BitArray(reply) for s in a.split('0x55aa', bytealigned=True): sbytes = s.tobytes() if sbytes[:2] == b'\x55\xaa': count = int.from_bytes(sbytes[3:4], byteorder='little') if self.request_cnt == count: self.received_footer = True cmd = int.from_bytes(sbytes[9:10], byteorder='little') lendata = int.from_bytes(sbytes[13:14], byteorder='little') dataend = 14 + (lendata - 8) data = sbytes[18:dataend] if cmd == 7: self._process_contole_result(data) elif cmd == 8: self.results.append(self._process_status_result(data)) elif cmd == 9: self._process_heartbeat_result(data) elif cmd == 10: self.results.append(self._process_query_result(data)) elif cmd == 13: self._process_contole_new_result(data) elif cmd == 16: self._process_query_new_result(data)
def _process_raw_reply(device: dict, raw_reply: bytes): """ Splits the raw reply(s) into chuncks and decrypts it. returns json str or str (error) """ a = BitArray(raw_reply) #TODO: don't overwrite variables for s in a.split('0x000055aa', bytealigned=True): sbytes = s.tobytes() payload = None # Skip invalid messages if len(sbytes) < 28 or not s.endswith('0x0000aa55'): continue # Parse header seq = int.from_bytes(sbytes[4:8], byteorder='big') cmd = int.from_bytes(sbytes[8:12], byteorder='big') sz = int.from_bytes(sbytes[12:16], byteorder='big') rc = int.from_bytes(sbytes[16:20], byteorder='big') has_return_code = (rc & 0xFFFFFF00) == 0 crc = int.from_bytes(sbytes[-8:-4], byteorder='big') # Check CRC if crc != binascii.crc32(sbytes[:-8]): continue if device['protocol'] == '3.1': data = sbytes[20:-8] if sbytes[20:21] == b'{': if not isinstance(data, str): payload = data.decode() elif sbytes[20:23] == b'3.1': logger.info('we\'ve got a 3.1 reply, code untested') data = data[3:] # remove version header data = data[ 16:] # remove (what I'm guessing, but not confirmed is) 16-bytes of MD5 hexdigest of payload payload = aescipher.decrypt(device['localkey'], data) elif device['protocol'] == '3.3': if sz > 12: data = sbytes[20:8 + sz] if cmd == tf.STATUS: data = data[15:] payload = aescipher.decrypt(device['localkey'], data, False) msg = {"cmd": cmd, "seq": seq, "data": payload} if has_return_code: msg['rc'] = rc logger.debug( "(%s) received msg (seq %s): [%x:%s] rc: [%s] payload: [%s]", device["ip"], msg['seq'], msg['cmd'], tf.cmd_to_string.get(msg['cmd'], f'UNKNOWN'), rc if has_return_code else '-', msg.get('data', '')) yield msg
def testByteAligned(self): bitstring.bytealigned = True a = BitArray("0x00 ff 0f f") l = list(a.findall("0xff")) self.assertEqual(l, [8]) p = a.find("0x0f")[0] self.assertEqual(p, 16) p = a.rfind("0xff")[0] self.assertEqual(p, 8) s = list(a.split("0xff")) self.assertEqual(s, ["0x00", "0xff0ff"]) a.replace("0xff", "") self.assertEqual(a, "0x000ff")
def testByteAligned(self): bitstring.bytealigned = True a = BitArray('0x00 ff 0f f') l = list(a.findall('0xff')) self.assertEqual(l, [8]) p = a.find('0x0f')[0] self.assertEqual(p, 16) p = a.rfind('0xff')[0] self.assertEqual(p, 8) s = list(a.split('0xff')) self.assertEqual(s, ['0x00', '0xff0ff']) a.replace('0xff', '') self.assertEqual(a, '0x000ff')
def testByteAligned(self): bitstring.settings.bytealigned = True a = BitArray('0x00 ff 0f f') l = list(a.findall('0xff')) self.assertEqual(l, [8]) p = a.find('0x0f')[0] self.assertEqual(p, 16) p = a.rfind('0xff')[0] self.assertEqual(p, 8) s = list(a.split('0xff')) self.assertEqual(s, ['0x00', '0xff0ff']) a.replace('0xff', '') self.assertEqual(a, '0x000ff')
def testNotByteAligned(self): bitstring.bytealigned = False a = BitArray('0x00 ff 0f f') l = list(a.findall('0xff')) self.assertEqual(l, [8, 20]) p = a.find('0x0f')[0] self.assertEqual(p, 4) p = a.rfind('0xff')[0] self.assertEqual(p, 20) s = list(a.split('0xff')) self.assertEqual(s, ['0x00', '0xff0', '0xff']) a.replace('0xff', '') self.assertEqual(a, '0x000')
def testNotByteAligned(self): bitstring.settings.bytealigned = False a = BitArray('0x00 ff 0f f') l = list(a.findall('0xff')) self.assertEqual(l, [8, 20]) p = a.find('0x0f')[0] self.assertEqual(p, 4) p = a.rfind('0xff')[0] self.assertEqual(p, 20) s = list(a.split('0xff')) self.assertEqual(s, ['0x00', '0xff0', '0xff']) a.replace('0xff', '') self.assertEqual(a, '0x000')
def _process_raw_reply(device: dict, raw_reply: bytes): a = BitArray(raw_reply) for s in a.split('0x000055aa', bytealigned=True): sbytes = s.tobytes() cmd = int.from_bytes(sbytes[11:12], byteorder='little') if cmd in [STATUS, DP_QUERY, DP_QUERY_NEW]: data = sbytes[20:8+int.from_bytes(sbytes[15:16], byteorder='little')] if cmd == STATUS: data = data[15:] yield aescipher.decrypt(device['localkey'], data, False)
def testSplit(self): a = BitArray('0x4700004711472222') l = list(a.split('0x47', bytealigned=True)) self.assertEqual(l, ['', '0x472222', '0x4711', '0x470000'])
def parse(cls, address, data): '''Used to parse data received from RuuviTag. Currently supports versions 3 and 5 of the protocol. Arguments: address (str): MAC address of RuuviTag. data (bytes): received data in bytes. ''' b = BitArray(bytes=data) # Try to find protocol version 3 # https://github.com/ruuvi/ruuvi-sensor-protocols#data-format-3-protocol-specification if b.find('0xFF990403'): # If it's found, parse data b = list(b.split('0xFF990403', count=2))[-1] _, humidity, temperature_sign, temperature, temperature_fraction, pressure, \ accel_x, accel_y, accel_z, battery_voltage = b.unpack( # Ignore the packet type and manufacturer specs, # as they've been handled before 'uint:32,' + # Parse the actual payload 'uint:8, int:1, uint:7, uint:8, uint:16,' + 'int:16, int:16, int:16, uint:16') temperature_sign = -1 if temperature_sign == -1 else 1 # ... and return an instance of the calling class return cls(address, 3, temperature=temperature_sign * (float(temperature) + temperature_fraction / 100.0), humidity=humidity / 2.0, pressure=(pressure + 50000) / 100.0, acceleration_x=accel_x / 1000.0, acceleration_y=accel_y / 1000.0, acceleration_z=accel_z / 1000.0, battery_voltage=battery_voltage / 1000.0) # Try to find protocol version 5 # https://github.com/ruuvi/ruuvi-sensor-protocols#data-format-5-protocol-specification if b.find('0xFF990405'): # If it's found, parse data b = list(b.split('0xFF990405', count=2))[-1] _, temperature, humidity, pressure, \ accel_x, accel_y, accel_z, \ battery_voltage, tx_power, \ movement_counter, measurement_sequence, mac = b.unpack( # Ignore the packet type and manufacturer specs, # as they've been handled before 'uint:32,' + # Parse the actual payload 'int:16, uint:16, uint:16,' + 'int:16, int:16, int:16,' + 'uint:11, uint:5,' + 'uint:8, uint:16, uint:48') # Not sure what to do with MAC at the moment? # Maybe compare it to the one received by btle and # raise an exception if doesn't match? mac = '%x' % mac mac = ':'.join(mac[i:i + 2] for i in range(0, 12, 2)) # ... and return an instance of the calling class return cls(address, 5, temperature=float(temperature) * 0.005, humidity=humidity * 0.0025, pressure=(pressure + 50000) / 100.0, acceleration_x=accel_x / 1000.0, acceleration_y=accel_y / 1000.0, acceleration_z=accel_z / 1000.0, battery_voltage=(battery_voltage + 1600) / 1000.0, tx_power=-40 + tx_power, movement_counter=movement_counter, measurement_sequence=measurement_sequence)
# originalfilename_1_GBA.vpk # originalfilename_2_GC.vpk CARD_TYPE = 'Animal Crossing' # CARD_TYPE could be expanded to other cards later; # only Animal Crossing is currently supported. BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + '/cards' IN_DIR = BASE_DIR + '/bin/' OUT_DIR = BASE_DIR + '/vpk/' for file in os.listdir(IN_DIR): if file.endswith(".bin"): full_path = IN_DIR + file # https://pythonhosted.org/bitstring/creation.html#from-a-file a = BitArray(filename=full_path) # vpk0 = 76 70 6B 30. vpk0 is the header for each chunk of data we want for chunk_num, chunk in enumerate( a.split('0x76706B30', bytealigned=True)): card_extension = '.vpk' note = '_' # skip chunk 0 (when batch processing *.vpk files). # it's a header rather than a compressed VPK chunk if chunk_num == 0: card_extension = '.header' if CARD_TYPE == 'Animal Crossing': if chunk_num == 1: # hunch, bigger because of graphics data note = note + "GBA" if chunk_num == 2: # almost certain, I've sent stuff to GC from this chunk note = note + "GC" if note == '_': note = ''