Forked from https://bitbucket.org/AlekseevAV/noolite-mtrf-to-mqtt/
[mqtt-noolite.git] / nmd / nl_sensors.py
1 import time
2 import asyncio
3
4 from ..noolite_mqtt import NooLiteMqtt
5
6
7 class NooLiteSensor:
8     def __init__(self, channel, loop):
9         self.channel = channel
10         self.battery_status = None
11         self.last_update = time.time()
12         self.loop = loop
13         self.noolite_mqtt = NooLiteMqtt()
14
15
16 class TempHumSensor(NooLiteSensor):
17     def __init__(self, channel, loop):
18         super().__init__(channel, loop)
19         self.sensor_type = None
20         self.temp = None
21         self.hum = None
22         self.analog_sens = None
23
24     def __str__(self):
25         return 'Ch: {}, battery: {}, temp: {}, hum: {}'.format(self.channel, self.battery_status, self.temp, self.hum)
26
27     def to_json(self):
28         json_data = {
29             'type': self.sensor_type,
30             'temperature': self.temp,
31             'humidity': self.hum,
32             'battery': self.battery_status,
33             'analog_sens': self.analog_sens
34         }
35         return json_data
36
37     async def read_response_data(self, response):
38         temp_bits = '{:08b}'.format(response.d1)[4:] + '{:08b}'.format(response.d0)
39
40         # Тип датчика:
41         #   000-зарезервировано
42         #   001-датчик температуры (PT112)
43         #   010-датчик температуры/влажности (PT111)
44         self.sensor_type = '{:08b}'.format(response.d1)[1:4]
45
46         # Если первый бит 0 - температура считается выше нуля
47         if temp_bits[0] == '0':
48             self.temp = int(temp_bits, 2) / 10.
49         # Если 1 - ниже нуля. В этом случае необходимо от 4096 отнять полученное значение
50         elif temp_bits[0] == '1':
51             self.temp = -((4096 - int(temp_bits, 2)) / 10.)
52
53         # Если датчик PT111 (с влажностью), то получаем влажность из 3 байта данных
54         if self.sensor_type == '010':
55             self.hum = response.d2
56
57         # Состояние батареи: 1-разряжена, 0-заряд батареи в норме
58         self.battery_status = int('{:08b}'.format(response.d1)[0])
59
60         # Значение, считываемое с аналогового входа датчика; 8 бит; (по умолчанию = 255)
61         self.analog_sens = response.d3
62
63         self.last_update = time.time()
64
65         accessories = self.noolite_mqtt.accessory_filter(channel=self.channel)
66
67         for accessory_name, accessory_data in accessories.items():
68
69             accessory_characteristics = []
70             [accessory_characteristics.extend(list(characteristics.keys())) for characteristics in
71              accessory_data['characteristics'].values()]
72
73             if self.temp is not None and 'CurrentTemperature' in accessory_characteristics:
74                 await self.noolite_mqtt.characteristic_set(
75                     accessory_name=accessory_name,
76                     characteristic='CurrentTemperature',
77                     value=self.temp
78                 )
79
80             if self.hum is not None and 'CurrentRelativeHumidity' in accessory_characteristics:
81                 await self.noolite_mqtt.characteristic_set(
82                     accessory_name=accessory_name,
83                     characteristic='CurrentRelativeHumidity',
84                     value=self.hum
85                 )
86
87
88 class MotionSensor(NooLiteSensor):
89     def __init__(self, channel, loop):
90         super().__init__(channel, loop)
91         self.active_time = None
92
93     def __str__(self):
94         return 'Ch: {}, battery: {}, active_time: {}'.format(self.channel, self.battery_status, self.active_time)
95
96     async def read_response_data(self, response):
97         self.active_time = response.d0 * 5
98
99         # Состояние батареи: 1-разряжена, 0-заряд батареи в норме
100         self.battery_status = int('{:08b}'.format(response.d1)[0])
101
102         self.last_update = time.time()
103         await self.set_active_state(self.active_time)
104
105     async def set_active_state(self, delay):
106
107         accessory = await self.noolite_mqtt.accessory_get(channel=self.channel)
108
109         for accessory_name, accessory_data in accessory.items():
110             await self.noolite_mqtt.characteristic_set(
111                 accessory_name=accessory_name,
112                 characteristic='MotionDetected',
113                 value=True
114             )
115
116             # Выключаем активное состояние
117             await asyncio.sleep(delay)
118             await self.noolite_mqtt.characteristic_set(
119                 accessory_name=accessory_name,
120                 characteristic='MotionDetected',
121                 value=False
122             )
123
124     def is_active(self):
125         return self.last_update + self.active_time >= time.time()
126
127     def to_json(self):
128         json_data = {
129             'battery': self.battery_status,
130             'state': 1 if self.is_active() else 0
131         }
132         return json_data
133
134
135 class SensorManager:
136     def __init__(self, nl_mqtt):
137         self.nl_mqtt = nl_mqtt
138         self.sensors = []
139         self.sensors_map = {
140             'temp': TempHumSensor,
141             'hum': TempHumSensor,
142             'motion': MotionSensor
143         }
144
145     async def new_data(self, response):
146         if response.cmd == 21:
147             sensor = self.get_sensor(response.ch, 'temp') or TempHumSensor(channel=response.ch, loop=self.loop)
148         elif response.cmd == 25:
149             sensor = self.get_sensor(response.ch, 'motion') or MotionSensor(channel=response.ch, loop=self.loop)
150         else:
151             print('Unknown response: {}'.format(response))
152             return
153
154         await sensor.read_response_data(response=response)
155
156         if sensor not in self.sensors:
157             self.sensors.append(sensor)
158         print(sensor)
159
160     def get_sensor(self, channel, sensor_type):
161         sensor_type = self.sensors_map[sensor_type]
162         for sensor in self.sensors:
163             if sensor.channel == channel and isinstance(sensor, sensor_type):
164                 return sensor
165         return None
166
167     def get_multiple_sensors(self, channels, sensor_type):
168         sensors = []
169         sensor_type = self.sensors_map[sensor_type]
170         for sensor in self.sensors:
171             if sensor.channel in channels and isinstance(sensor, sensor_type):
172                 sensors.append(sensor)
173         return sensors