![]() |
managing command codes for external controller box - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: managing command codes for external controller box (/thread-21192.html) |
managing command codes for external controller box - Oolongtea - Sep-19-2019 I am trying to create a library to encode commands sent over TCP to a controller box and decode the responses. To makes things simple, each command corresponds to a hex code with a number of arguments, to which the controller box outputs response. The aim of the code below is to implement the encoding/decoding of bytes exchanged with the controller with the following constraints:
The first two points are easy enough, however the 3rd point requires to write specific decoding methods for a majority of commands. I have written 2 different approaches, which don't really satisfy me some various reasons. The first example would be the simplest if I did not have to implement specific decoding. The 2nd is my preferred method in this case, although I am not sure using subclassing like this is the way to go. Any thoughts or suggestions? [Example 1: one class] In this example, a Command is instanciated with one of the command names: the codes dictionary in the __init__ gives the correct format codes to use with struct.pack/unpack. The main drawback is that all the specific decoding methods will have to be declared in the command class, likely resulting in a code that is harder to read and to maintain. CLOSE_LNK = 0x25 GET_VERSION = 0x2A5 READ_NB_AXES = 0x34D class Command: def __init__(self, code): codes = {CLOSE_LNK: {}, GET_VERSION: {'in_fmt': '<?', 'out_fmt': '< 4I'}, READ_NB_AXES: {'out_fmt': '< IBI', 'custom_decode': self.dec_0x34D} # list goes on... } properties = codes[code] self.command = code self.in_fmt = properties.get('in_fmt') self.out_fmt = properties.get('out_fmt') self.c_encode = properties.get('custom_encode') self.c_decode = properties.get('custom_decode') def encode(self, *args): # cls._check_argnb(*args) if self.c_encode is not None: return self.c_encode(*args) if self.in_fmt is None: return b'' return struct.pack(self.in_fmt, *args) def decode(self, in_bytes): if self.c_decode is not None: return self.c_decode(in_bytes) if self.out_fmt is None: return b'' return struct.unpack(self.out_fmt, in_bytes) # define custom encode/decode methods def dec_0x34D(self, in_bytes): return struct.unpack(self.out_fmt, in_bytes + b'\x00') # ..... # ..... if __name__=="__main__": a = Command(GET_VERSION) print(a.encode(True)) print(a.decode(b'\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12'))[Example 2: subclassing] Here I am making a lot of subclasses, one per command type. In this case I can easily reimplement the encode and decode methods to suit my needs, and it's easier to add and remove commands. class Command: @classmethod def encode(cls, *args): return struct.pack(cls._in_fmt, *args) @classmethod def decode(cls, in_bytes): return struct.unpack(cls._out_fmt, in_bytes) @classmethod def get_id(cls): return cls._id class CLOSE_LNK(Command): _id = 0x25 @classmethod def encode(cls): return b'' @classmethod def decode(cls, in_bytes): return b'' class GET_VERSION(Command): _id = 0x2A5 _in_fmt = '<?' _out_fmt = '< 4I' _argnb = 1 class READ_NB_AXES(Command): _id = 0x34D _out_fmt = '< IBI' @classmethod def encode(cls): return b'' @classmethod def decode(cls, in_bytes): out = super().decode(in_bytes + b'\x00') return out if __name__=="__main__": print(GET_VERSION.encode(True)) print(GET_VERSION.decode(b'\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12\x2D\x00\x12')) |