def escape(data): """ escape: byte string -> byte string When a 'special' byte is encountered in the given data string, it is preceded by an escape byte and XORed with 0x20. """ escaped_data = b"" for byte in data: if intToByte(byteToInt(byte)) in APIFrame.ESCAPE_BYTES: escaped_data += APIFrame.ESCAPE_BYTE escaped_data += intToByte(0x20 ^ byteToInt(byte)) else: escaped_data += intToByte(byteToInt(byte)) return escaped_data
def fill(self, byte): """ fill: byte -> None Adds the given raw byte to this APIFrame. If this APIFrame is marked as escaped and this byte is an escape byte, the next byte in a call to fill() will be unescaped. """ if self._unescape_next_byte: byte = intToByte(byteToInt(byte) ^ 0x20) self._unescape_next_byte = False elif self.escaped and byte == APIFrame.ESCAPE_BYTE: self._unescape_next_byte = True return self.raw_data += intToByte(byteToInt(byte))
def fill(self, byte): """ fill: byte -> None Adds the given raw byte to this APIFrame. If this APIFrame is marked as escaped and this byte is an escape byte, the next byte in a call to fill() will be unescaped. """ self.escaped = True if self._unescape_next_byte: byte = intToByte(byteToInt(byte) ^ 0x20) self._unescape_next_byte = False elif self.escaped and byte == APIFrame.ESCAPE_BYTE: self._unescape_next_byte = True return self.raw_data += intToByte(byteToInt(byte))
def _parse_samples_header(self, io_bytes): """ _parse_samples_header: binary data in XBee ZB IO data format -> (int, [int ...], [int ...], int, int) _parse_samples_header will read the first three bytes of the binary data given and will return the number of samples which follow, a list of enabled digital inputs, a list of enabled analog inputs, the dio_mask, and the size of the header in bytes _parse_samples_header is overloaded here to support the additional IO lines offered by the XBee ZB """ header_size = 4 # number of samples (always 1?) is the first byte sample_count = byteToInt(io_bytes[0]) # bytes 1 and 2 are the DIO mask; bits 9 and 8 aren't used dio_mask = (byteToInt(io_bytes[1]) << 8 | byteToInt(io_bytes[2])) & 0x1CFF # byte 3 is the AIO mask aio_mask = byteToInt(io_bytes[3]) # sorted lists of enabled channels; value is position of bit in mask dio_chans = [] aio_chans = [] for i in range(0, 13): if dio_mask & (1 << i): dio_chans.append(i) dio_chans.sort() for i in range(0, 8): if aio_mask & (1 << i): aio_chans.append(i) aio_chans.sort() return (sample_count, dio_chans, aio_chans, dio_mask, header_size)
def test_invalid_checksum(self): """ when an invalid frame is read, an exception must be raised """ api_frame = APIFrame() frame = b'\x7E\x00\x01\x00\xF6' for byte in frame: api_frame.fill(intToByte(byteToInt(byte))) self.assertRaises(ValueError, api_frame.parse)
def verify(self, chksum): """ verify: 1 byte -> boolean verify checksums the frame, adds the expected checksum, and determines whether the result is correct. The result should be 0xFF. """ total = 0 # Add together all bytes for byte in self.data: total += byteToInt(byte) # Add checksum too total += byteToInt(chksum) # Only keep low bits total &= 0xFF # Check result return total == 0xFF
def _parse_samples_header(self, io_bytes): """ _parse_samples_header: binary data in XBee IO data format -> (int, [int ...], [int ...], int, int) _parse_samples_header will read the first three bytes of the binary data given and will return the number of samples which follow, a list of enabled digital inputs, a list of enabled analog inputs, the dio_mask, and the size of the header in bytes """ header_size = 3 # number of samples (always 1?) is the first byte sample_count = byteToInt(io_bytes[0]) # part of byte 1 and byte 2 are the DIO mask ( 9 bits ) dio_mask = (byteToInt(io_bytes[1]) << 8 | byteToInt(io_bytes[2])) & 0x01FF # upper 7 bits of byte 1 is the AIO mask aio_mask = (byteToInt(io_bytes[1]) & 0xFE) >> 1 # sorted lists of enabled channels; value is position of bit in mask dio_chans = [] aio_chans = [] for i in range(0, 9): if dio_mask & (1 << i): dio_chans.append(i) dio_chans.sort() for i in range(0, 7): if aio_mask & (1 << i): aio_chans.append(i) aio_chans.sort() return (sample_count, dio_chans, aio_chans, dio_mask, header_size)
def _parse_samples_header(self, io_bytes): """ _parse_samples_header: binary data in XBee IO data format -> (int, [int ...], [int ...], int, int) _parse_samples_header will read the first three bytes of the binary data given and will return the number of samples which follow, a list of enabled digital inputs, a list of enabled analog inputs, the dio_mask, and the size of the header in bytes """ header_size = 3 # number of samples (always 1?) is the first byte sample_count = byteToInt(io_bytes[0]) # part of byte 1 and byte 2 are the DIO mask ( 9 bits ) dio_mask = (byteToInt(io_bytes[1]) << 8 | byteToInt(io_bytes[2])) \ & 0x01FF # upper 7 bits of byte 1 is the AIO mask aio_mask = (byteToInt(io_bytes[1]) & 0xFE) >> 1 # sorted lists of enabled channels; value is position of bit in mask dio_chans = [] aio_chans = [] for i in range(0, 9): if dio_mask & (1 << i): dio_chans.append(i) dio_chans.sort() for i in range(0, 7): if aio_mask & (1 << i): aio_chans.append(i) aio_chans.sort() return (sample_count, dio_chans, aio_chans, dio_mask, header_size)
def test_single_byte(self): """ read a frame containing a single byte """ api_frame = APIFrame() frame = b'\x7E\x00\x01\x00\xFF' expected_data = b'\x00' for byte in frame: api_frame.fill(intToByte(byteToInt(byte))) api_frame.parse() self.assertEqual(api_frame.data, expected_data)
def checksum(self): """ checksum: None -> single checksum byte checksum adds all bytes of the binary, unescaped data in the frame, saves the last byte of the result, and subtracts it from 0xFF. The final result is the checksum """ total = 0 # Add together all bytes for byte in self.data: total += byteToInt(byte) # Only keep the last byte total = total & 0xFF return intToByte(0xFF - total)
def _parse_samples(self, io_bytes): """ _parse_samples: binary data in XBee IO data format -> [ {"dio-0":True, "dio-1":False, "adc-0":100"}, ...] _parse_samples reads binary data from an XBee device in the IO data format specified by the API. It will then return a dictionary indicating the status of each enabled IO port. """ sample_count, dio_chans, aio_chans, dio_mask, header_size = \ self._parse_samples_header(io_bytes) samples = [] # split the sample data into a list, so it can be pop()'d sample_bytes = [byteToInt(c) for c in io_bytes[header_size:]] # repeat for every sample provided for sample_ind in range(0, sample_count): tmp_samples = {} if dio_chans: # we have digital data digital_data_set = (sample_bytes.pop(0) << 8 | sample_bytes.pop(0)) digital_values = dio_mask & digital_data_set for i in dio_chans: tmp_samples['dio-{0}'.format( i)] = True if (digital_values >> i) & 1 else False for i in aio_chans: # only first 10 bits are significant analog_sample = (sample_bytes.pop(0) << 8 | sample_bytes.pop(0)) & 0x03FF tmp_samples['adc-{0}'.format(i)] = analog_sample samples.append(tmp_samples) return samples
def _parse_samples(self, io_bytes): """ _parse_samples: binary data in XBee IO data format -> [ {"dio-0":True, "dio-1":False, "adc-0":100"}, ...] _parse_samples reads binary data from an XBee device in the IO data format specified by the API. It will then return a dictionary indicating the status of each enabled IO port. """ sample_count, dio_chans, aio_chans, dio_mask, header_size = \ self._parse_samples_header(io_bytes) samples = [] # split the sample data into a list, so it can be pop()'d sample_bytes = [byteToInt(c) for c in io_bytes[header_size:]] if len( sample_bytes ) > 0: # repeat for every sample provided for sample_ind in range(0, sample_count): tmp_samples = {} if dio_chans: # we have digital data digital_data_set = (sample_bytes.pop(0) << 8 | sample_bytes.pop(0)) digital_values = dio_mask & digital_data_set for i in dio_chans: tmp_samples['dio-{0}'.format(i)] = True if (digital_values >> i) & 1 else False for i in aio_chans: # only first 10 bits are significant analog_sample = (sample_bytes.pop(0) << 8 | sample_bytes.pop(0)) & 0x03FF tmp_samples['adc-{0}'.format(i)] = analog_sample samples.append(tmp_samples) return samples