Forked from https://bitbucket.org/AlekseevAV/noolite-mtrf-to-mqtt/
[mqtt-noolite.git] / nmd / nl_serial.py
1 import time
2 import logging
3
4 import serial
5
6 from .utils import Singleton
7
8 # Logging config
9 logger = logging.getLogger(__name__)
10 formatter = logging.Formatter('%(asctime)s - %(filename)s:%(lineno)s - %(levelname)s - %(message)s')
11
12
13 class NooLiteMessage:
14     def __init__(self, ch, cmd, mode, id0, id1, id2, id3):
15         self.mode = mode
16         self.ch = ch
17         self.cmd = cmd
18         self.id0 = id0
19         self.id1 = id1
20         self.id2 = id2
21         self.id3 = id3
22
23     def __eq__(self, other):
24         return all([
25             self.mode == other.mode,
26             self.ch == other.ch,
27             self.id0 == other.id0,
28             self.id1 == other.id1,
29             self.id2 == other.id2,
30             self.id3 == other.id3
31         ])
32
33     def is_id(self):
34         return any([
35             self.id0 != 0,
36             self.id1 != 0,
37             self.id2 != 0,
38             self.id3 != 0
39         ])
40
41
42 class NooLiteResponse(NooLiteMessage):
43     def __init__(self, st, mode, ctr, togl, ch, cmd, fmt, d0, d1, d2, d3, id0, id1, id2, id3, crc, sp):
44         super().__init__(ch, cmd, mode, id0, id1, id2, id3)
45         self.st = st
46         self.ctr = ctr
47         self.togl = togl
48         self.fmt = fmt
49         self.d0 = d0
50         self.d1 = d1
51         self.d2 = d2
52         self.d3 = d3
53         self.crc = crc
54         self.sp = sp
55         self.time = time.time()
56
57     def __str__(self):
58         return '{}'.format(self.to_list())
59
60     def __repr__(self):
61         return '{}'.format(self.to_list())
62
63     @property
64     def success(self):
65         return self.ctr == 0
66
67     def to_list(self):
68         return [
69             self.st,
70             self.mode,
71             self.ctr,
72             self.togl,
73             self.ch,
74             self.cmd,
75             self.fmt,
76             self.d0,
77             self.d1,
78             self.d2,
79             self.d3,
80             self.id0,
81             self.id1,
82             self.id2,
83             self.id3,
84             self.crc,
85             self.sp
86         ]
87
88
89 class NooLiteCommand(NooLiteMessage):
90     def __init__(self, ch, cmd, mode=0, ctr=0, res=0, fmt=0, d0=0, d1=0, d2=0, d3=0, id0=0, id1=0, id2=0, id3=0):
91         super().__init__(ch, cmd, mode, id0, id1, id2, id3)
92         self.st = 171
93         self.ctr = ctr
94         self.res = res
95         self.fmt = fmt
96         self.d0 = d0
97         self.d1 = d1
98         self.d2 = d2
99         self.d3 = d3
100         self.sp = 172
101
102     def __str__(self):
103         return '{}'.format(self.to_list())
104
105     def __repr__(self):
106         return '{}'.format(self.to_list())
107
108     @property
109     def crc(self):
110         crc = sum([
111             self.st,
112             self.mode,
113             self.ctr,
114             self.res,
115             self.ch,
116             self.cmd,
117             self.fmt,
118             self.d0,
119             self.d1,
120             self.d2,
121             self.d3,
122             self.id0,
123             self.id1,
124             self.id2,
125             self.id3,
126         ])
127         return crc if crc < 256 else divmod(crc, 256)[1]
128
129     def to_list(self):
130         return [
131             self.st,
132             self.mode,
133             self.ctr,
134             self.res,
135             self.ch,
136             self.cmd,
137             self.fmt,
138             self.d0,
139             self.d1,
140             self.d2,
141             self.d3,
142             self.id0,
143             self.id1,
144             self.id2,
145             self.id3,
146             self.crc,
147             self.sp
148         ]
149
150     def to_bytes(self):
151         return bytearray([
152             self.st,
153             self.mode,
154             self.ctr,
155             self.res,
156             self.ch,
157             self.cmd,
158             self.fmt,
159             self.d0,
160             self.d1,
161             self.d2,
162             self.d3,
163             self.id0,
164             self.id1,
165             self.id2,
166             self.id3,
167             self.crc,
168             self.sp
169         ])
170
171     def description(self):
172         return {
173             'ST':   {'value': self.st, 'description': 'Стартовый байт'},
174             'MODE': {'value': self.mode, 'description': 'Режим работы адаптера'},
175             'CTR':  {'value': self.ctr, 'description': 'Управление адаптером'},
176             'RES':  {'value': self.res, 'description': 'Зарезервирован, не используется'},
177             'CH':   {'value': self.ch, 'description': 'Адрес канала, ячейки привязки'},
178             'CMD':  {'value': self.cmd, 'description': 'Команда'},
179             'FMT':  {'value': self.fmt, 'description': 'Формат'},
180             'DATA': {'value': [self.d0, self.d1, self.d2, self.d3], 'description': 'Данные'},
181             'ID':   {'value': [self.id0, self.id1, self.id2, self.id3], 'description': 'Адрес блока'},
182             'CRC':  {'value': self.crc, 'description': 'Контрольная сумма'},
183             'SP':   {'value': self.sp, 'description': 'Стоповый байт'}
184         }
185
186
187 class NooliteSerial(metaclass=Singleton):
188     def __init__(self, loop, tty_name, input_command_callback_method):
189         self.tty = self._get_tty(tty_name)
190         self.responses = []
191         self.input_command_callback_method = input_command_callback_method
192         self.loop = loop
193
194         # Если приходят данные на адаптер, то они обрабатываются в этом методе
195         self.loop.add_reader(self.tty.fd, self.inf_reading)
196
197     def inf_reading(self):
198         while self.tty.in_waiting >= 17:
199             in_bytes = self.tty.read(17)
200             resp = NooLiteResponse(*list(in_bytes))
201             logger.debug('Incoming command: {}'.format(resp))
202             self.loop.create_task(self.input_command_callback_method(resp))
203
204     async def send_command(self, ch, cmd, mode=0, ctr=0, res=0, fmt=0, d0=0, d1=0, d2=0, d3=0, id0=0, id1=0, id2=0, id3=0):
205         command = NooLiteCommand(ch, cmd, mode, ctr, res, fmt, d0, d1, d2, d3, id0, id1, id2, id3)
206
207         # Write
208         logger.info('> {}'.format(command))
209         before = time.time()
210         self.tty.write(command.to_bytes())
211         logger.info('Time to write: {}'.format(time.time() - before))
212
213     @staticmethod
214     def _get_tty(tty_name):
215         serial_port = serial.Serial(tty_name, timeout=2)
216         if not serial_port.is_open:
217             serial_port.open()
218         serial_port.flushInput()
219         serial_port.flushOutput()
220         return serial_port