|
#!/usr/bin/env python |
|
# *-- encoding: utf-8 --* |
|
|
|
from bitarray import bitarray |
|
|
|
# Protocol Format |
|
# byte * 2 | byte | byte | byte | byte | byte | byte |
|
# Preamble? | id | mode | fixed? | level | cksum | trailing (is it needed?) |
|
# 1111110000 | 1000 (8) | 0001 (shock) | 00001001 | 11001010 | 00010100 | 01111110 | 0 (id + mode + inverted + inversed) |
|
# | 1111 (15) | 0010 (vibrate) | |
|
# | 0100 (beep) | |
|
# | 1000 (blink) | |
|
|
|
# DTC - Dog Training Collar |
|
class DTC(object): |
|
|
|
# Symbol lookup table |
|
symbols = { |
|
0: [0, 0, 0, 1], |
|
1: [1, 1, 0, 1] |
|
} |
|
|
|
baud_rate = 944 * 4 # Each bit has 944hz and each simbol has 4 bits |
|
|
|
# Preamble WIP - This doesn't look right, I need to review it |
|
preamble = [0b00000011, 0b11110000] |
|
|
|
# Action lookup table |
|
actions = { |
|
'shock': 0b1, |
|
'vibrate': 0b10, |
|
'sound': 0b100, |
|
'light': 0b1000 |
|
} |
|
|
|
def __init__(self, collar_id=8, level=0, action='vibrate'): |
|
self._collar_id = collar_id |
|
self._level = level |
|
self._action = action |
|
|
|
# This is the first nibble of the first byte after preamble |
|
def getCollarId(self): |
|
return self._collar_id |
|
collar_id = property(getCollarId) |
|
|
|
# This is the second nibble of the first byte after preamble |
|
def getAction(self): |
|
return self.actions[self._action] |
|
action = property(getAction) |
|
|
|
# Join collar id and action nibbles into a single byte |
|
def getIdMode(self): |
|
return (self.collar_id << 4) | (self.action & 0x0F) |
|
id_mode = property(getIdMode) |
|
|
|
# First fixed byte in the protocol |
|
# At least is fixed on the units I have |
|
def getFixedOne(self): |
|
return 0b00001001 |
|
fixed_one = property(getFixedOne) |
|
|
|
# Second fixed byte in the protocol |
|
# At least is fixed on the units I have |
|
def getFixedTwo(self): |
|
return 0b11001010 |
|
fixed_two = property(getFixedTwo) |
|
|
|
# Level is just binary representation of the level number |
|
def getLevel(self): |
|
return self._level |
|
level = property(getLevel) |
|
|
|
# This looks like a mechanism to avoid spurious activation than a checksum itself |
|
# It invert and reverse bits |
|
# eg: if the first byte (id + action) = 0b01100001 the result will be 0b01111001 |
|
# https://stackoverflow.com/questions/19204750/how-do-i-perform-a-circular-rotation-of-a-byte |
|
def getChecksum(self): |
|
cksum = self.id_mode ^ 0xFF # invert bits |
|
cksum = (cksum & 0x55) << 1 | (cksum & 0xAA) >> 1 # swap adjacent bits |
|
cksum = (cksum & 0x33) << 2 | (cksum & 0xCC) >> 2 # swap adjacent pairs |
|
cksum = (cksum & 0x0F) << 4 | (cksum & 0xF0) >> 4 # swap nibbles |
|
return cksum |
|
checksum = property(getChecksum) |
|
|
|
# Convert bitarray to symbols based on the lookup table |
|
def bitsToSymbols(self, value): |
|
bits = [(value >> bit) & 1 for bit in range(8 - 1, -1, -1)] |
|
for position, bit in enumerate(bits): |
|
bits[position] = self.symbols[bit] |
|
return sum(bits, []) # (+) operator overload, flatten multi dimentional list |
|
|
|
# Convert raw bits to bitarray format without symbol lookup |
|
def rawBits(self, value): |
|
return [(value >> bit) & 1 for bit in range(8 - 1, -1, -1)] |
|
|
|
# Assemble the whole packet |
|
def getPacket(self): |
|
packet = self.rawBits(self.preamble[0]) |
|
packet += self.rawBits(self.preamble[1]) |
|
packet += self.bitsToSymbols(self.id_mode) |
|
packet += self.bitsToSymbols(self.fixed_one) |
|
packet += self.bitsToSymbols(self.fixed_two) |
|
packet += self.bitsToSymbols(self.level) |
|
packet += self.bitsToSymbols(self.checksum) |
|
packet += self.rawBits(0) |
|
return bitarray(packet).tobytes() |
|
|
|
if __name__ == "__main__": |
|
Collar = DTC(8, 20, 'shock') |
|
print("getIdMode: %s" % bin(Collar.getIdMode())) |
|
print("getChecksum: %s" % bin(Collar.getChecksum())) |
|
print("getLevel: %s" % bin(Collar.getLevel())) |
|
packet = ''.join(format(x, '08b') for x in bytearray(Collar.getPacket())) |
|
print("getPacket bits: %s" % packet) |
|
print("getPacket bin: %s" % Collar.getPacket()) |