-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathNorthcliff_Home_Manager_Gen.py
5657 lines (5444 loc) · 421 KB
/
Northcliff_Home_Manager_Gen.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
#Northcliff Home Manager - 11.33 - Gen
#Requires Aircon Controller 5.0 or higher
import paho.mqtt.client as mqtt
import struct
import time
from datetime import datetime
import string
import json
import socket
import requests
import os
import asyncio
import aiohttp
from luftdaten import Luftdaten
import http.client
import urllib
class NorthcliffHomeManagerClass(object):
def __init__(self, log_aircon_cost_data, log_aircon_damper_data, log_aircon_temp_data, load_previous_aircon_effectiveness, perform_homebridge_config_check):
#print ('Instantiated Home Manager')
self.log_aircon_cost_data = log_aircon_cost_data # Flags if the aircon cost data is to be logged
self.log_aircon_damper_data = log_aircon_damper_data # Flags if the aircon damper data is to be logged
self.log_aircon_temp_data = log_aircon_temp_data # Flags if the aircon temperature data is to be logged
self.load_previous_aircon_effectiveness = load_previous_aircon_effectiveness # Flags if the aircon data is to be loaded on startup
self.perform_homebridge_config_check = perform_homebridge_config_check # Flags that a homebridge config check is to be performed on start-up
self.home_manager_file_name = '<Your Home Manager File Path and Name>'
self.key_state_log_file_name = '<Your Key State Log File Path and Name>'
self.watchdog_file_name = '<Your Watchdog File Path and Name>'
self.light_dimmers_present = False # Enables Domoticz Light Dimmer control. Now False due to replacing Z-Wave dimmers with Dynalite
self.multisensors_present = True # Enable Multisensor monitoring
self.doorbell_present = False # Enables doorbell control. False due to updating Fermax intercom system
self.door_sensors_present = True # Enables door sensor monitoring
self.powerpoints_present = True # Enables powerpoint control
self.flood_sensors_present = True # Enables flood sensor monitoring
self.window_blinds_present = False # Enables window blind control. Now False due to updating blind system to Luxaflex PowerView
self.aircons_present = True # Enables aircon control
self.air_purifiers_present = False # Enables air purifier control. Now False due to Blueair no longer supported on Foobot
self.garage_door_present = True # Enables garage door control
self.aquarium_monitor_present = True # Enables aquarium monitoring
self.enviro_monitors_present = True # Enables outdoor air quality monitoring
self.enable_reboot = False # Enables remote reboot function
self.ev_charger_present = True # Enables EV Charger function
self.fan_monitor_present = True # Enables Fan Monitor function
self.hm_display_present = False # Enables HM Display brightness homebridge config function
self.main_room_window_shades_present = True # Enables Homebridge config for main room window shades
self.enviro_wind_monitors_present = True # Enable estimation of wind velocity and direction using two enviro monitors
self.dynalite_homebridge_gateway_present = True # Enables configuration of homebridge dynalite controls
# List the multisensor names
self.multisensor_names = ['Living', 'Study', 'Kitchen', 'North', 'South', 'Main', 'Rear Balcony', 'North Balcony', 'South Balcony', 'Comms']
# List the outdoor sensors
self.outdoor_multisensor_names = ['Rear Balcony', 'North Balcony', 'South Balcony']
# Group outdoor sensors as services under one homebridge accessory name for passing to the homebridge object
self.outdoor_sensors_homebridge_name = 'Balconies'
# Name each door sensor and identify the room that contains that door sensor
self.door_sensor_names_locations = {'North Living Room': 'Living Room', 'South Living Room': 'Living Room', 'Entry': 'Entry'}
# Name each powerpoint and map to its device id
self.powerpoint_names_device_id = {'Coffee': 646, 'South Balcony': 1029, 'North Balcony': 1035} #{'Coffee': 646, 'South Balcony': 626, 'North Balcony': 647}
# List the flood sensors
self.flood_sensor_names = ['Kitchen', 'Aquarium', 'Laundry']
# Name each light dimmer and map to its device id
self.light_dimmer_names_device_id = {'Lounge Light': 325, 'Entry Light': 324, 'Dining Light': 323, 'Study Light': 648, 'Kitchen Light': 504, 'Hallway Light': 328, 'North Light': 463,
'South Light': 475, 'Main Light': 451, 'North Balcony Light': 517, 'South Balcony Light': 518}
self.colour_light_dimmer_names = [] # Dimmers in self.light_dimmer_names_device_id that require hue and saturation characteristics
# Set up the config for each aircon, including their mqtt topics
self.aircon_config = {'Aircon': {'mqtt Topics': {'Outgoing':'AirconControl', 'Incoming': 'AirconStatus'}, 'Day Zone': ['Living', 'Study', 'Kitchen'],
'Night Zone': ['North', 'South', 'Main'], 'Room Dampers': True, 'Master': 'Master', 'Outdoor Temp Sensor': 'North Balcony', 'Cost Log': '<Your Cost Log File Path and Name>',
'Effectiveness Log': '<Your Effectiveness Log File Path and Name>', 'Spot Temperature History Log': '<Your Spot Temperature History Log File Path and Name>',
'Damper Log': '<Your Damper Log File Path and Name>'}}
# List the temperature sensors that control the aircons
self.aircon_temp_sensor_names = []
for aircon in self.aircon_config:
self.aircon_temp_sensor_names = self.aircon_temp_sensor_names + self.aircon_config[aircon]['Day Zone'] + self.aircon_config[aircon]['Night Zone']
# Map each sensor to its relevant aircon
self.aircon_sensor_name_aircon_map = {}
for sensor in self.aircon_temp_sensor_names:
for aircon in self.aircon_config:
if sensor in self.aircon_config[aircon]['Day Zone'] or sensor in self.aircon_config[aircon]['Night Zone'] or sensor in self.aircon_config[aircon]['Master']:
self.aircon_sensor_name_aircon_map[sensor] = aircon
#print('Aircon sensor name aircon map', self.aircon_sensor_name_aircon_map)
# Set up other mqtt topics
self.homebridge_incoming_mqtt_topic = 'homebridge/from/set'
self.homebridge_incoming_config_mqtt_topic = 'homebridge/from/response'
self.domoticz_incoming_mqtt_topic = 'domoticz/out'
self.doorbell_incoming_mqtt_topic = 'DoorbellStatus'
self.garage_door_incoming_mqtt_topic = 'GarageStatus'
self.enviro_monitor_incoming_mqtt_topics = ['Outdoor EM0', 'Indoor EM1', 'Outdoor EM12']
self.ev_charger_incoming_mqtt_topic = 'ttn/<Your TTN EV Charger Application ID>/up'
#self.ev_charger_incoming_down_ack_topic = 'ttn/<Your TTN EV Charger Application ID>/down/ack'
self.fan_monitor_incoming_mqtt_topic = 'ttn1/<Your TTN Fan Monitor Application ID>/up'
# Set up the config for each window blind
self.window_blind_light_sensor = 'South Balcony'
self.window_blind_threshold_1 = 12000
self.window_blind_threshold_2 = 20000
self.previous_window_blind_state = 0
self.window_blind_config = {'Living Room Blinds': {'blind host name': '<mylink host name>', 'blind port': 44100, 'light sensor': 'South Balcony',
'temp sensor': 'North Balcony', 'sunlight threshold 0': 100,'sunlight threshold 1': 1000,
'sunlight threshold 2': 12000, 'sunlight threshold 3': 20000, 'high_temp_threshold': 28,
'low_temp_threshold': 15, 'sunny_season_start': 10, 'sunny_season_finish': 3, 'non_sunny_season_sunlight_level_3_4_persist_time': 1800,
'sunny_season_sunlight_level_3_4_persist_time': 600, 'blind_doors': {'North Living Room': {'door_state': 'Open','door_state_changed': False},
'South Living Room': {'door_state': 'Open', 'door_state_changed': False}},
'status':{'Left Window': 'Open', 'Left Door': 'Open', 'Right Door': 'Open',
'Right Window': 'Open', 'All Blinds': 'Open', 'All Doors': 'Open', 'All Windows': 'Open'},
'blind commands': {'up Left Window': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.1","auth":"<mylink auth>"},"id":1}',
'up Left Door': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.2","auth":"<mylink auth>"},"id":1}',
'up Right Door': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.3","auth":"<mylink auth>"},"id":1}',
'up Right Window': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.4","auth":"<mylink auth>"},"id":1}',
'up All Blinds': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.5","auth":"<mylink auth>"},"id":1}',
'up All Doors': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.6","auth":"<mylink auth>"},"id":1}',
'up All Windows': b'{"method": "mylink.move.up","params":{"targetID":"<mylink targetID>.7","auth":"<mylink auth>"},"id":1}',
'down Left Window': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.1","auth":"<mylink auth>"},"id":1}',
'down Left Door': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.2","auth":"<mylink auth>"},"id":1}',
'down Right Door': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.3","auth":"<mylink auth>"},"id":1}',
'down Right Window': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.4","auth":"<mylink auth>"},"id":1}',
'down All Blinds': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.5","auth":"<mylink auth>"},"id":1}',
'down All Doors': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.6","auth":"<mylink auth>"},"id":1}',
'down All Windows': b'{"method": "mylink.move.down","params":{"targetID":"<mylink targetID>.7","auth":"<mylink auth>"},"id":1}',
'stop Left Window': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.1","auth":"<mylink auth>"},"id":1}',
'stop Left Door': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.2","auth":"<mylink auth>"},"id":1}',
'stop Right Door': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.3","auth":"<mylink auth>"},"id":1}',
'stop Right Window': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.4","auth":"<mylink auth>"},"id":1}',
'stop All Blinds': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.5","auth":"<mylink auth>"},"id":1}',
'stop All Doors': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.6","auth":"<mylink auth>"},"id":1}',
'stop All Windows': b'{"method": "mylink.move.stop","params":{"targetID":"<mylink targetID>.7","auth":"<mylink auth>"},"id":1}'}}}
# List the light sensors that control blinds
self.blind_light_sensor_names = [self.window_blind_config[blind]['light sensor'] for blind in self.window_blind_config]
# When True, flags that a blind change has been manually invoked, referencing the relevant blind, blind_id and position
self.call_control_blinds = {'State': False, 'Blind': '', 'Blind_id': '', 'Blind_position': ''}
self.auto_blind_override_changed = {'Changed': False, 'Blind': '', 'State': False}
# When True, flags that a change in sunlight had occurred, referencing the relevant blind and light level
self.call_room_sunlight_control = {'State': False, 'Blind': '', 'Light Level': 100}
# When True, flags that a blind-impacting door has been opened, referencing the relevant blind
self.blind_control_door_changed = {'State': False, 'Blind': '', 'Changed': False}
# Identify the door that controls the doorbell "Auto Possible" mode
self.doorbell_door = 'Entry'
# Set up air purifier dictionary with names, foobot device numbers and auto/manual flag.
self.air_purifier_names = {'Living': {'Foobot Device': 1, 'Auto': True}, 'Main': {'Foobot Device': 0, 'Auto': False}}
self.auto_air_purifier_names = []
for name in self.air_purifier_names:
if self.air_purifier_names[name]['Auto']:
self.auto_air_purifier_names.append(name)
self.universal_air_purifier_fan_speed = 1
self.air_purifier_linking_times = {'Start': 9, 'Stop': 20}
heartbeat_check_start_time = time.time()
enviro_capture_time = heartbeat_check_start_time
self.enviro_config = {'Outdoor': {'mqtt Topic': 'Outdoor EM0', 'Capture Non AQI': True, 'Homebridge Display': True, 'Wind': 'East', 'Capture Time': enviro_capture_time, 'Luftdaten Sensor ID': 99999,
'Device IDs': {'P1': 784, 'P2.5': 778, 'P10': 779, 'AQI': 780, 'NH3': 781, 'Oxi': 782, 'Red': 783,
'Temp': 819, 'Hum': 819, 'Dew': 1063, 'Bar': 819, 'Lux':821, 'Noise': 838, 'Wind': 912}},
'Indoor': {'mqtt Topic': 'Indoor EM1', 'Capture Non AQI': True, 'Homebridge Display': True,
'Device IDs': {'P1': 789, 'P2.5': 790, 'P10': 791, 'AQI': 792, 'NH3': 795, 'Oxi': 793, 'Red': 794,
'Temp': 824, 'Hum': 824, 'Dew': 1062,'Bar': 824, 'Lux':820, 'CO2': 825, 'VOC': 826, 'Noise': 837}},
'Front Outdoor': {'mqtt Topic': 'Outdoor EM012', 'Capture Non AQI': True, 'Homebridge Display': False, 'Wind': 'West',
'Device IDs': {'P1': 909, 'P2.5': 908, 'P10': 907, 'AQI': 906, 'NH3': 905, 'Oxi': 904, 'Red': 903,
'Temp': 899, 'Hum': 899, 'Dew': 1061, 'Bar': 899, 'Lux':901, 'Noise': 902, 'Wind': 912}}}
self.enable_outdoor_enviro_monitor_luftdaten_backup = True # Enable Luftdaten readings if no PM readings from outdoor Enviro Monitor
self.enviro_wind_config = {'Front Outdoor': {'Air Pressure': None, 'Direction': 'West', 'Offset': 0.0}, 'Outdoor': {'Air Pressure': None, 'Direction': 'East', 'Offset': -0.25}}
self.enviro_wind_distance = 23 #The distance between enviro air pressure sources in metres
self.previous_enviro_wind_source = None #To ensure that alternate enviro sources are used to measure wind speed
self.enviro_wind_results = [None for x in range(10)] # Set up historical wind_data
# Set up EV Charger heartbeat check
self.ev_charger_update_time = heartbeat_check_start_time
self.ev_charger_max_heartbeat_period = 11700 # Longest update interval is 3 hours and 15 min
# Set up fan monitor heartbeat check
self.fan_monitor_update_time = heartbeat_check_start_time
self.fan_monitor_max_heartbeat_period = 11700 # Longest update interval is 3 hours and 15 min
# Set up Fan Monitor indicator light names
self.fan_monitor_list = ["Exhaust", "Outside", "Garage"] # List of fans to be monitored. Fan with LoRaWAN lsb is first in the list
self.fan_indicator_list = ["Fault", "Run"] # List of fan indicators. Indicator with LoRaWAN lsb is first in the list
# Compose fan_light_list that has an indicator for each monitored fan. Fan/Indicator with LoRaWAN lsb is first in the list
self.fan_light_list = []
for i in range(len(self.fan_monitor_list)):
for j in range(len(self.fan_indicator_list)):
self.fan_light_list.append(self.fan_monitor_list[i] + " " + self.fan_indicator_list[j])
self.watchdog_update_time = 0
def on_connect(self, client, userdata, flags, rc):
# Sets up the mqtt subscriptions. Subscribing in on_connect() means that if we lose the connection and reconnect then subscriptions will be renewed.
self.print_update('Northcliff Home Manager Connected with result code '+str(rc)+' on ')
print('')
time.sleep(1)
client.subscribe(self.homebridge_incoming_mqtt_topic) #Subscribe to Homebridge status for interworking with Apple Home
client.subscribe(self.homebridge_incoming_config_mqtt_topic) #Subscribe to Homebridge config for interworking with Apple Home
client.subscribe(self.domoticz_incoming_mqtt_topic) # Subscribe to Domoticz for access to its devices
client.subscribe(self.doorbell_incoming_mqtt_topic) # Subscribe to the Doorbell Monitor
client.subscribe(self.garage_door_incoming_mqtt_topic) # Subscribe to the Garage Door Controller
client.subscribe(self.ev_charger_incoming_mqtt_topic) # Subscribe to the EV Charger Controller
#client.subscribe(self.ev_charger_incoming_mqtt_down_ack_topic) # Subscribe to the EV Charger Controller downlink acks
client.subscribe(self.fan_monitor_incoming_mqtt_topic) # Subscribe to the Fan Monitor
for enviro_name in self.enviro_config:
client.subscribe(self.enviro_config[enviro_name]['mqtt Topic']) # Subscribe to the Enviro Monitors
for aircon_name in self.aircon_config: # Subscribe to the Aircon Controllers
client.subscribe(self.aircon_config[aircon_name]['mqtt Topics']['Incoming'])
def on_message(self, client, userdata, msg):
# Calls the relevant methods for the Home Manager, based on the mqtt publish messages received from the doorbell monitor, the homebridge buttons,
# Domoticz, the aircon controller and the garage door controller
decoded_payload = str(msg.payload.decode("utf-8"))
parsed_json = json.loads(decoded_payload)
#print(msg.topic, parsed_json)
if msg.topic == self.homebridge_incoming_mqtt_topic: # If it's a homebridge status message
homebridge.capture_homebridge_buttons(parsed_json) # Capture the homebridge button
elif msg.topic == self.homebridge_incoming_config_mqtt_topic: # If it's a homebridge config message
homebridge.config_response(parsed_json) # Handle homebridge config responses
elif msg.topic == self.domoticz_incoming_mqtt_topic: # If coming from domoticz
domoticz.process_device_data(parsed_json) # Process the domoticz device data
elif msg.topic == self.garage_door_incoming_mqtt_topic: # If coming from the Garage Door Controller
garage_door.capture_status(parsed_json) # Capture garage door status
elif msg.topic == self.doorbell_incoming_mqtt_topic: # If coming from the Doorbell Monitor
doorbell.capture_doorbell_status(parsed_json) # Capture doorbell status
elif msg.topic == self.ev_charger_incoming_mqtt_topic:
#print('EV Charger Message', parsed_json)
self.ev_charger_update_time = time.time()
ev_charger.capture_ev_charger_state(parsed_json['uplink_message']['decoded_payload']['state'])
#elif msg.topic == self.ev_charger_incoming_mqtt_down_ack_topic:
#print('EV Charger Downlink Ack Message', parsed_json)
elif msg.topic == self.fan_monitor_incoming_mqtt_topic:
self.fan_monitor_update_time = time.time()
fan_monitor.capture_fan_light_state(parsed_json['uplink_message']['decoded_payload']['state'])
else: # Test for enviro or aircon messages
identified_message = False
for enviro_name in self.enviro_config:
if msg.topic == self.enviro_config[enviro_name]['mqtt Topic']: # If coming from an Enviro Monitor
#self.print_update(enviro_name + ' Northcliff Enviro Monitor Data:' + str(parsed_json) + ' on ')
if enviro_name == 'Outdoor':
self.enviro_config[enviro_name]['Capture Time'] = time.time()
identified_message = True
enviro_monitor[enviro_name].capture_readings('Enviro', parsed_json) # Capture enviro readings
for aircon_name in self.aircon_config:
if msg.topic == self.aircon_config[aircon_name]['mqtt Topics']['Incoming']: # If coming from an aircon
identified_message = True
aircon[aircon_name].capture_status(parsed_json) # Capture aircon status
if identified_message == False: # If the mqtt topic is unknown
print ('Unknown mqtt message received', msg.topic)
def print_update(self, print_message): # Prints with a date and time stamp
today = datetime.now()
print('')
print(print_message + today.strftime('%A %d %B %Y @ %H:%M:%S'))
def log_key_states(self, reason):
# Log Door, Blind and Powerpoint States
key_state_log = {}
key_state_log["Reason"] = reason
if self.door_sensors_present:
key_state_log["Door State"] = {name: door_sensor[name].current_door_opened for name in self.door_sensor_names_locations}
if self.powerpoints_present:
key_state_log['Powerpoint State'] = {name: powerpoint[name].powerpoint_state for name in self.powerpoint_names_device_id}
if self.window_blinds_present:
key_state_log['Blind Status'] = {blind: window_blind[blind].window_blind_config['status'] for blind in self.window_blind_config}
key_state_log['Blind Door State'] = {blind: window_blind[blind].window_blind_config['blind_doors'] for blind in self.window_blind_config}
key_state_log['Blind High Temp'] = {blind: window_blind[blind].window_blind_config['high_temp_threshold'] for blind in self.window_blind_config}
key_state_log['Blind Low Temp'] = {blind: window_blind[blind].window_blind_config['low_temp_threshold'] for blind in self.window_blind_config}
key_state_log['Blind Auto Override'] = {blind: window_blind[blind].auto_override for blind in self.window_blind_config}
if self.aircons_present:
key_state_log['Aircon Thermostat Status'] = {aircon_name: {thermostat: aircon[aircon_name].thermostat_status[thermostat]
for thermostat in (self.aircon_config[aircon_name]['Day Zone'] +
self.aircon_config[aircon_name]['Night Zone'])} for aircon_name in self.aircon_config}
key_state_log['Aircon Thermo Mode'] = {aircon_name: aircon[aircon_name].settings['indoor_thermo_mode'] for aircon_name in self.aircon_config}
key_state_log['Aircon Thermo Active'] = {aircon_name: aircon[aircon_name].settings['indoor_zone_sensor_active'] for aircon_name in self.aircon_config}
key_state_log['Aircon Fan Speed'] = {aircon_name: aircon[aircon_name].settings['fan_speed'] for aircon_name in self.aircon_config}
if self.air_purifiers_present:
key_state_log['Air Purifier Max Co2'] = {air_purifier_name: air_purifier[air_purifier_name].max_co2 for air_purifier_name in self.auto_air_purifier_names}
if self.enviro_monitors_present:
for enviro_name in self.enviro_config:
if enviro_name == 'Indoor' and 'CO2' in self.enviro_config[enviro_name]['Device IDs']:
key_state_log['Enviro Max CO2'] = enviro_monitor[enviro_name].max_CO2
#print('Wrote Enviro Max CO2 to Log', reason, enviro_name, enviro_monitor[enviro_name].max_CO2)
if self.ev_charger_present:
key_state_log['EV Charger State'] = ev_charger.state
key_state_log['EV Charger Command State'] = ev_charger.command_state
key_state_log['EV Charger Locked State'] = ev_charger.locked_state # Only use for versions prior to 9.9
key_state_log['EV Charger Comms State'] = ev_charger.comms_ok
if self.fan_monitor_present:
key_state_log['Fan Light Monitor State'] = fan_monitor.fan_light_state
key_state_log['Fan Monitor Comms State'] = fan_monitor.comms_ok
with open(self.key_state_log_file_name, 'w') as f:
f.write(json.dumps(key_state_log))
def retrieve_key_states(self):
with open(self.key_state_log_file_name, 'r') as f:
parsed_key_states = json.loads(f.read())
print('Retrieved Key States', parsed_key_states)
print ('Previous logging reason was', parsed_key_states['Reason'])
if self.door_sensors_present and 'Door State' in parsed_key_states:
for name in parsed_key_states['Door State']:
door_sensor[name].current_door_opened = parsed_key_states['Door State'][name]
door_sensor[name].previous_door_opened = parsed_key_states['Door State'][name]
homebridge.update_door_state(name, self.door_sensor_names_locations[name], parsed_key_states['Door State'][name], False)
if mgr.doorbell_present:
if door_sensor[name].doorbell_door:
doorbell.update_doorbell_door_state(self.doorbell_door, parsed_key_states['Door State'][name])
if self.window_blinds_present and 'Blind Status' in parsed_key_states:
for blind in parsed_key_states['Blind Status']:
window_blind[blind].window_blind_config['status'] = parsed_key_states['Blind Status'][blind]
homebridge.update_blind_status(blind, window_blind[blind].window_blind_config)
window_blind[blind].window_blind_config['blind_doors'] = parsed_key_states['Blind Door State'][blind]
window_blind[blind].window_blind_config['high_temp_threshold'] = parsed_key_states['Blind High Temp'][blind]
window_blind[blind].window_blind_config['low_temp_threshold'] = parsed_key_states['Blind Low Temp'][blind]
homebridge.update_blind_target_temps(blind, parsed_key_states['Blind High Temp'][blind], parsed_key_states['Blind Low Temp'][blind])
window_blind[blind].auto_override = parsed_key_states['Blind Auto Override'][blind]
homebridge.set_auto_blind_override_button(blind, parsed_key_states['Blind Auto Override'][blind])
if self.aircons_present and 'Aircon Thermostat Status' in parsed_key_states:
for aircon_name in self.aircon_config:
for thermostat in parsed_key_states['Aircon Thermostat Status'][aircon_name]:
aircon[aircon_name].thermostat_status[thermostat]['Target Temperature'] = parsed_key_states['Aircon Thermostat Status'][aircon_name][thermostat]['Target Temperature']
homebridge.update_thermostat_target_temp(aircon_name, thermostat, parsed_key_states['Aircon Thermostat Status'][aircon_name][thermostat]['Target Temperature'])
aircon[aircon_name].thermostat_status[thermostat]['Mode'] = parsed_key_states['Aircon Thermostat Status'][aircon_name][thermostat]['Mode']
homebridge.update_aircon_thermostat(aircon_name, thermostat, parsed_key_states['Aircon Thermostat Status'][aircon_name][thermostat]['Mode'])
aircon[aircon_name].thermostat_status[thermostat]['Active'] = parsed_key_states['Aircon Thermostat Status'][aircon_name][thermostat]['Active']
aircon[aircon_name].settings['indoor_thermo_mode'] = parsed_key_states['Aircon Thermo Mode'][aircon_name]
aircon[aircon_name].settings['indoor_zone_sensor_active'] = parsed_key_states['Aircon Thermo Active'][aircon_name]
aircon[aircon_name].update_zone_temps()
if 'Aircon Fan Speed' in parsed_key_states:
aircon[aircon_name].settings['fan_speed'] = parsed_key_states['Aircon Fan Speed'][aircon_name]
aircon[aircon_name].settings['previous_fan_speed_command'] = 'Off'
if self.air_purifiers_present and 'Air Purifier Max Co2' in parsed_key_states:
for air_purifier_name in parsed_key_states['Air Purifier Max Co2']:
air_purifier[air_purifier_name].max_co2 = parsed_key_states['Air Purifier Max Co2'][air_purifier_name]
if self.enviro_monitors_present and 'Enviro Max CO2' in parsed_key_states:
for enviro_name in self.enviro_config:
if enviro_name == 'Indoor' and 'CO2' in self.enviro_config[enviro_name]['Device IDs']:
enviro_monitor[enviro_name].max_CO2 = parsed_key_states['Enviro Max CO2']
#print('Retrieved Enviro Max CO2 from Log', parsed_key_states['Reason'], enviro_name, enviro_monitor[enviro_name].max_CO2)
if self.ev_charger_present and 'EV Charger State' in parsed_key_states:
ev_charger.state = parsed_key_states['EV Charger State']
if 'EV Charger Command State' in parsed_key_states: # Only load ev_charger.command_state if it's in the log (backwards compatibility)
ev_charger.command_state = parsed_key_states['EV Charger Command State']
if self.ev_charger_present and 'EV Charger Comms State' in parsed_key_states:
ev_charger.comms_ok = parsed_key_states['EV Charger Comms State']
if self.fan_monitor_present and 'Fan Light Monitor State' in parsed_key_states:
fan_monitor.fan_light_state = parsed_key_states['Fan Light Monitor State']
if self.fan_monitor_present and 'Fan Monitor Comms State' in parsed_key_states:
fan_monitor.comms_ok = parsed_key_states['Fan Monitor Comms State']
if self.powerpoints_present and 'Powerpoint State' in parsed_key_states:
for name in parsed_key_states['Powerpoint State']:
if name in self.powerpoint_names_device_id:
powerpoint[name].on_off(parsed_key_states['Powerpoint State'][name])
homebridge.update_powerpoint_state(name, parsed_key_states['Powerpoint State'][name])
if self.enviro_monitors_present:
homebridge.reset_enviro_wind()
def air_purifier_linking_hour(self):
today = datetime.now()
hour = int(today.strftime('%H'))
if hour >= self.air_purifier_linking_times['Start'] and hour < self.air_purifier_linking_times['Stop']:
return True
else:
return False
def update_manual_air_purifier_fan_speeds(self, control_purifier, fan_speed):
for name in self.air_purifier_names:
if not self.air_purifier_names[name]['Auto']:
print('Setting', name, 'Fan Speed to', fan_speed, 'due to change in', control_purifier, 'auto air purifier fan change')
air_purifier[name].set_fan_speed(str(fan_speed))
def shutdown(self, reason):
self.log_key_states(reason)
# Shut down Aircons
for aircon_name in self.aircon_config:
aircon[aircon_name].shut_down()
client.loop_stop() # Stop mqtt monitoring
self.print_update('Home Manager Shut Down due to ' + reason + ' on ')
def run(self): # The main Home Manager start-up, loop and shut-down code
try:
if self.perform_homebridge_config_check:
homebridge.check_and_fix_config()
# Retrieve logged key states
self.retrieve_key_states()
if self.enable_reboot:
homebridge.reset_reboot_button()
if self.air_purifiers_present:
# Capture Air Purifier readings and settings on startup and update homebridge
for name in self.air_purifier_names:
if self.air_purifier_names[name]['Auto']: # Readings only come from auto units
self.purifier_readings_update_time, part_2_5, co2, voc, max_aqi, max_co2, co2_threshold, part_2_5_threshold = air_purifier[name].capture_readings()
homebridge.update_blueair_aqi(name, part_2_5, co2, voc, max_aqi, max_co2, co2_threshold, part_2_5_threshold)
domoticz.update_blueair_aqi(name, part_2_5, co2, voc, max_aqi)
settings_changed, self.purifier_settings_update_time, mode, fan_speed, child_lock, led_brightness,filter_status = air_purifier[name].capture_settings()
homebridge.set_air_purifier_state(name, mode, fan_speed, child_lock, led_brightness, filter_status)
if self.aircons_present:
# Start up Aircons
for aircon_name in mgr.aircon_config:
aircon[aircon_name].start_up(self.load_previous_aircon_effectiveness)
if self.doorbell_present:
doorbell.update_doorbell_status() # Get doorbell status on startup
if self.multisensors_present:
# Initialise multisensor readings on homebridge to start-up settings
for name in self.multisensor_names:
homebridge.update_temperature(name, multisensor[name].sensor_types_with_value['Temperature'])
homebridge.update_humidity(name, multisensor[name].sensor_types_with_value['Humidity'])
homebridge.update_light_level(name, multisensor[name].sensor_types_with_value['Light Level'])
homebridge.update_motion(name, multisensor[name].sensor_types_with_value['Motion'])
# Initialise Garage Door state
if self.garage_door_present:
homebridge.update_garage_door('Closing')
homebridge.update_garage_door('Closed')
# Initialise EV Charger States
if self.ev_charger_present:
homebridge.update_ev_charger_state(ev_charger.state)
homebridge.update_ev_charger_command_state(ev_charger.command_state)
homebridge.update_ev_charger_comms(ev_charger.comms_ok)
# Initialise Fan Monitor States
if self.fan_monitor_present:
homebridge.update_fan_light_state(fan_monitor.fan_light_state)
homebridge.update_fan_monitor_comms(fan_monitor.comms_ok)
previous_aquarium_capture_time = 0 # Initialise aquarium sensor capture time
previous_aquarium_reading_time = 0 # Initialise aquarium sensor reading time
previous_luftdaten_capture_time = 0 # Initialise luftdaten capture time
while True: # The main Home Manager Loop
if time.time() - self.watchdog_update_time >= 60: # Write to the watchdog log every minute
with open(self.watchdog_file_name, 'w') as f:
f.write('Home Manager Script Alive')
self.watchdog_update_time = time.time()
if self.aircons_present:
for aircon_name in mgr.aircon_config:
aircon[aircon_name].control_aircon() # For each aircon, call the method that controls the aircon.
if self.window_blinds_present:
# The following tests and method calls are here in the main code loop, rather than the on_message method to avoid time.sleep calls in the window blind object delaying incoming mqtt message handling
if self.call_room_sunlight_control['State']: # If there's a new reading from the blind control light sensor
blind = self.call_room_sunlight_control['Blind'] # Identify the blind
light_level = self.call_room_sunlight_control['Light Level'] # Capture the light level
window_blind[blind].room_sunlight_control(light_level) # Call the blind's sunlight control method, passing the light level
self.call_room_sunlight_control['State'] = False # Reset this flag because any light level update has now been actioned
if self.blind_control_door_changed['Changed']: # If a blind control door has changed state
blind = self.blind_control_door_changed['Blind'] # Identify the blind that is controlled by the door
light_level = self.call_room_sunlight_control['Light Level'] # Capture the light level
window_blind[blind].room_sunlight_control(light_level) # Call the blind's sunlight control method, passing the light level
self.blind_control_door_changed['Changed'] = False # Reset Door Changed Flag because any change of door state has now been actioned
if self.auto_blind_override_changed['Changed']: # If a blind auto override button has changed state
blind = self.auto_blind_override_changed['Blind'] # Identify the blind that is controlled by the button
light_level = self.call_room_sunlight_control['Light Level'] # Capture the light level
window_blind[blind].room_sunlight_control(light_level) # Call the blind's sunlight control method, passing the light level
self.auto_blind_override_changed['Changed'] = False # Reset Auto Override Flag because any change of override state has now been actioned
if self.call_control_blinds['State']: # If a manual blind change has been invoked
blind = self.call_control_blinds['Blind'] # Identify the blind that has been changed
window_blind[blind].control_blinds(blind, self.call_control_blinds) # Call the blind's manual control method
self.call_control_blinds['State'] = False # Reset Control Blinds Flag because any control blind request has now been actioned
if self.air_purifiers_present:
purifier_readings_check_time = time.time()
if (purifier_readings_check_time - self.purifier_readings_update_time) >= 300: # Update air purifier readings if last update was >= 5 minutes ago
for name in self.air_purifier_names:
if self.air_purifier_names[name]['Auto']:# Readings only come from auto units
self.purifier_readings_update_time, part_2_5, co2, voc, max_aqi, max_co2, co2_threshold, part_2_5_threshold = air_purifier[name].capture_readings()
homebridge.update_blueair_aqi(name, part_2_5, co2, voc, max_aqi, max_co2, co2_threshold, part_2_5_threshold)
domoticz.update_blueair_aqi(name, part_2_5, co2, voc, max_aqi)
purifier_settings_check_time = time.time()
if (purifier_settings_check_time - self.purifier_settings_update_time) >= 5: # Update air purifier settings if last update was >= 5 seconds ago
for name in self.air_purifier_names:
settings_changed, self.purifier_settings_update_time, mode, fan_speed, child_lock, led_brightness, filter_status = air_purifier[name].capture_settings()
if settings_changed: # Only update Homebridge if a setting has changed (To minimise mqtt traffic)
homebridge.set_air_purifier_state(name, mode, fan_speed, child_lock, led_brightness, filter_status)
if self.air_purifier_names[name]['Auto']: # Change manual air purifier fan speeds if there's a change in the auto air purifier fan speed during linking hours
if self.universal_air_purifier_fan_speed != fan_speed:
self.universal_air_purifier_fan_speed = fan_speed
if self.air_purifier_linking_hour():
self.update_manual_air_purifier_fan_speeds(name, fan_speed)
if self.aquarium_monitor_present:
if time.time() - previous_aquarium_capture_time > 600: # Capture aquarium reading every 10 minutes
#print('Aquarium Capture Time:', datetime.fromtimestamp(time.time()).strftime('%A %d %B %Y @ %H:%M:%S'))
valid_aquarium_reading, message, ph, temp, nh3, reading_time = aquarium_sensor.latest() # Capture Seneye device readings
if message == 'Seneye Comms Good': #If there were no Seneye comms errors
if reading_time > previous_aquarium_reading_time: # Only update Domoticz if there is a new reading
#print('Aquarium Reading Time:', datetime.fromtimestamp(reading_time).strftime('%A %d %B %Y @ %H:%M:%S'), 'ph:', ph, 'nh3:', nh3, 'Temp:', temp)
if valid_aquarium_reading:
domoticz.update_aquarium(ph, temp, nh3)
else:
domoticz.update_aquarium("7", temp, "0") # Still send temp if the slide is out of date
homebridge.update_aquarium_temp(temp)
previous_aquarium_reading_time = reading_time
else:
print('Seneye Message Ignored: Comms Error') # Ignore bad Seneye comms messages
previous_aquarium_capture_time = time.time()
if self.enviro_monitors_present and self.enable_outdoor_enviro_monitor_luftdaten_backup:
if time.time() - self.enviro_config['Outdoor']['Capture Time'] > 600: # Capture Luftdaten Air Quality if the Outdoor Enviro Monitor is unavailable
if time.time() - previous_luftdaten_capture_time > 900:
print('No message from Outdoor Northcliff Enviro Monitor, using Luftdaten from station', self.enviro_config['Outdoor']['Luftdaten Sensor ID'])
enviro_monitor['Outdoor'].capture_luftdaten_data(self.enviro_config['Outdoor']['Luftdaten Sensor ID'])
previous_luftdaten_capture_time = time.time()
if self.ev_charger_present and ev_charger.comms_ok: # Check EV Charger communications
if time.time() - self.ev_charger_update_time > self.ev_charger_max_heartbeat_period:
ev_charger.lost_comms()
if self.fan_monitor_present and fan_monitor.comms_ok: # Check Fan Monitor Communications
if time.time() - self.fan_monitor_update_time > self.fan_monitor_max_heartbeat_period:
fan_monitor.lost_comms()
except KeyboardInterrupt:
self.shutdown('Keyboard Interrupt')
class HomebridgeClass(object):
def __init__(self, outdoor_multisensor_names, outdoor_sensors_name, aircon_config, auto_air_purifier_names, window_blind_config, door_sensor_names_locations,
light_dimmer_names_device_id, colour_light_dimmer_names, air_purifier_names, multisensor_names, powerpoint_names_device_id, flood_sensor_names, enviro_config,
window_blind_threshold_1, window_blind_threshold_2, previous_window_blind_state):
#print ('Instantiated Homebridge', self)
self.outgoing_mqtt_topic = 'homebridge/to/set'
self.outgoing_config_mqtt_topic = 'homebridge/to/get'
self.outgoing_add_accessory_mqtt_topic = 'homebridge/to/add'
self.outgoing_add_service_mqtt_topic = 'homebridge/to/add/service'
self.outgoing_remove_accessory_mqtt_topic = 'homebridge/to/remove'
self.outgoing_remove_service_mqtt_topic = 'homebridge/to/remove/service'
self.all_configs = {'name': '*'}
self.all_configs_props = {'name': '*_props'}
self.outdoor_multisensor_names = outdoor_multisensor_names
self.outdoor_sensors_name = outdoor_sensors_name
self.aircon_config = aircon_config
self.auto_air_purifier_names = auto_air_purifier_names
self.window_blind_config = window_blind_config
self.door_sensor_names_locations = door_sensor_names_locations
self.light_dimmer_names_device_id = light_dimmer_names_device_id
self.colour_light_dimmer_names = colour_light_dimmer_names
self.air_purifier_names = air_purifier_names
self.multisensor_names = multisensor_names
self.powerpoint_names_device_id = powerpoint_names_device_id
self.flood_sensor_names = flood_sensor_names
self.temperature_format = {'name': ' Temperature', 'service': 'TemperatureSensor', 'service_name': ' Temperature', 'characteristics_properties': {}}
self.humidity_format = {'name': ' Humidity', 'service': 'HumiditySensor', 'service_name': ' Humidity', 'characteristics_properties': {}}
self.light_level_format = {'name': ' Lux', 'service': 'LightSensor', 'service_name': ' Lux', 'characteristics_properties': {}}
self.motion_format = {'name': ' Motion', 'service': 'MotionSensor', 'service_name': ' Motion', 'characteristics_properties': {}}
self.door_format = {'name': ' Door', 'service': 'ContactSensor', 'service_name': ' Door', 'characteristics_properties':{'StatusLowBattery': {}}}
self.door_state_map = {'door_opened':{False: 0, True: 1}, 'low_battery':{False: 0, True: 1}}
self.dimmer_format = {'name': ' Light', 'service': 'Lightbulb', 'service_name': ' Light', 'characteristics_properties': {'Brightness': {}}}
self.colour_light_dimmer_characteristics_properties = {'Brightness': {}, 'Hue': {}, 'Saturation': {}}
self.dimmer_state_map = {0: False, 1: True}
self.powerpoint_state_map = {0: False, 1: True}
self.blinds_format = {'name': ' Blinds', 'service': 'WindowCovering', 'characteristics_properties': {'TargetPosition': {'minStep':50}}}
self.blinds_temp_format = {'service': 'Thermostat', 'target_characteristic': 'TargetTemperature', 'low_temp_service_name': 'Blind Low Temp', 'high_temp_service_name': 'Blind High Temp',
'high_temperature_characteristics_properties': {'TargetTemperature': {'minValue': 25, 'maxValue': 37, 'minStep': 1}, 'CurrentTemperature': {'minValue': -5, 'maxValue': 60, 'minStep': 0.1},
'TargetHeatingCoolingState': {'minValue': 0, 'maxValue': 2}, 'CurrentHeatingCoolingState': {'minValue': 0, 'maxValue': 2}},
'low_temperature_characteristics_properties': {'TargetTemperature':{'minValue': 5, 'maxValue': 16, 'minStep': 1}, 'CurrentTemperature': {'minValue': -5, 'maxValue': 60, 'minStep': 0.1},
'TargetHeatingCoolingState': {'minValue': 0, 'maxValue': 2}, 'CurrentHeatingCoolingState': {'minValue': 0, 'maxValue': 2}}}
self.auto_blind_override_button_format = {'service_name': 'Auto Blind Override', 'service': 'Switch', 'characteristics_properties': {}}
self.blind_incoming_position_map = {100: 'Open', 50: 'Venetian', 0: 'Closed'}
self.blind_outgoing_position_map = {'Open': 100, 'Venetian': 50, 'Closed': 0}
self.window_blind_threshold_1 = window_blind_threshold_1
self.window_blind_threshold_2 = window_blind_threshold_2
self.previous_window_blind_state = previous_window_blind_state
self.doorbell_name_identifier = 'Doorbell'
self.doorbell_homebridge_json_name_map = {'Idle': 'Doorbell Status', 'Automatic': 'Doorbell Automatic', 'Auto Possible': 'Doorbell Status', 'Manual': 'Doorbell Manual',
'Triggered': 'Doorbell Status', 'Terminated': 'Doorbell Status', 'Ringing': 'Doorbell Status', 'Open Door': 'Doorbell Open Door'}
self.doorbell_characteristics_properties = {'Doorbell Status': {'ContactSensor': {}}, 'Others': {'Switch': {}}}
# Set up homebridge switch types for doorbell (Switch or TimedMomentary)
self.doorbell_button_type = {'Open Door': 'Momentary', 'Automatic': 'Switch', 'Manual': 'Switch'}
self.powerpoint_format = {'name': ' Powerpoint', 'service': 'Outlet', 'service_name': '', 'characteristics_properties': {}}
self.garage_door_format = {'name': 'Garage', 'service_name': 'Garage Door', 'service': 'GarageDoorOpener', 'characteristics_properties': {}}
self.flood_state_format = {'name': ' Flood', 'service': 'LeakSensor', 'service_name': '', 'characteristics_properties': {'StatusLowBattery': {}}}
self.aircon_thermostat_mode_map = {0: 'Off', 1: 'Heat', 2: 'Cool'}
self.aircon_thermostat_incoming_mode_map = {'Off': 0, 'Heat': 1, 'Cool': 2}
# Set up aircon homebridge button types (Indicator, Position Indicator or Thermostat Control)
self.aircon_button_type = {'Damper': 'Position Indicator', 'Ventilation': 'Switch', 'Reset Effectiveness Log': 'Switch', 'Lo Fan Speed': 'Switch',
'Hi Fan Speed': 'Switch', 'Med Fan Speed': 'Switch'}
self.aircon_damper_position_state_map = {'Closing': 0, 'Opening': 1, 'Stopped': 2}
self.aircon_fan_speeds = ['Lo', 'Med', 'Hi']
self.aircon_thermostat_format = {}
self.aircon_ventilation_button_format = {}
self.aircon_hi_fan_button_format = {}
self.aircon_med_fan_button_format = {}
self.aircon_lo_fan_button_format = {}
self.aircon_filter_indicator_format = {}
self.aircon_reset_effectiveness_log_button_format = {}
self.aircon_control_thermostat_name = {}
self.aircon_thermostat_names = {}
self.aircon_damper_format = {}
self.aircon_status_format = {}
self.aircon_remote_operation_format = {}
self.aircon_heat_format = {}
self.aircon_cool_format = {}
self.aircon_fan_format = {}
self.aircon_fan_hi_format = {}
self.aircon_fan_lo_format = {}
self.aircon_lo_fan_format = {}
self.aircon_hi_fan_format = {}
self.aircon_med_fan_format = {}
self.aircon_compressor_format = {}
self.aircon_heating_format = {}
self.aircon_malfunction_format = {}
self.aircon_names = []
for aircon_name in self.aircon_config:
self.aircon_thermostat_format[aircon_name] = {'service': 'Thermostat', 'characteristics_properties': {'TargetTemperature': {'minValue': 18, 'maxValue': 28, 'minStep': 0.5},
'CurrentTemperature': {'minValue': -5, 'maxValue': 60, 'minStep': 0.1},
'TargetHeatingCoolingState': {'minValue': 0, 'maxValue': 2},
'CurrentHeatingCoolingState': {'minValue': 0, 'maxValue': 2}}}
self.aircon_ventilation_button_format[aircon_name] = {'name': aircon_name + ' Ventilation', 'service_name': 'Ventilation', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_hi_fan_button_format[aircon_name] = {'name': aircon_name + ' Hi Fan', 'service_name': 'Hi Fan Mode', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_med_fan_button_format[aircon_name] = {'name': aircon_name + ' Med Fan', 'service_name': 'Med Fan Mode', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_lo_fan_button_format[aircon_name] = {'name': aircon_name + ' Lo Fan', 'service_name': 'Lo Fan Mode', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_filter_indicator_format[aircon_name] = {'name': aircon_name + ' Filter', 'service_name': 'Filter', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_reset_effectiveness_log_button_format[aircon_name] = {'name': aircon_name + ' Reset Effectiveness Log', 'service_name': 'Reset Effectiveness Log', 'service': 'Switch', 'characteristics_properties': {}}
self.aircon_control_thermostat_name[aircon_name] = self.aircon_config[aircon_name]['Master']
self.aircon_thermostat_names[aircon_name] = self.aircon_config[aircon_name]['Day Zone'] + self.aircon_config[aircon_name]['Night Zone']
self.aircon_thermostat_names[aircon_name].append(self.aircon_config[aircon_name]['Master'])
self.aircon_status_format[aircon_name] = {'name': aircon_name + ' Status'}
self.aircon_remote_operation_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Remote Operation', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_damper_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Damper', 'service': 'Door', 'characteristics_properties': {'CurrentPosition':{'minValue': 0, 'maxValue': 100, 'minStep': 10}}}
self.aircon_heat_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Heat', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_cool_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Cool', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_fan_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Fan', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_fan_hi_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Fan Hi', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_fan_lo_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Fan Lo', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_compressor_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Compressor', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_heating_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Heating', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_malfunction_format[aircon_name] = {'name': aircon_name + ' Status', 'service_name': 'Malfunction', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.aircon_hi_fan_format[aircon_name] = {'name': aircon_name + ' Hi Fan', 'service_name': 'Hi Fan Speed', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_med_fan_format[aircon_name] = {'name': aircon_name + ' Med Fan', 'service_name': 'Med Fan Speed', 'service': 'Fan', 'characteristics_properties': {}}
self.aircon_lo_fan_format[aircon_name] = {'name': aircon_name + ' Lo Fan', 'service_name': 'Lo Fan Speed', 'service': 'Fan', 'characteristics_properties': {}}
for thermostat_name in self.aircon_thermostat_names[aircon_name]:
self.aircon_button_type[aircon_name + ' ' + thermostat_name] = 'Thermostat Control'
self.aircon_names.append(aircon_name)
# Set up Air Purifiers
self.air_purifier_format = {'name': ' Air Purifier', 'service' :'AirPurifier', 'service_name': '', 'characteristics_properties': {'RotationSpeed': {'minValue': 0, 'maxValue': 100, 'minStep': 25}, 'LockPhysicalControls': {}}}
self.air_purifier_LED_format = {'name': ' Air Purifier', 'service_name': ' LED', 'service' :'Lightbulb', 'characteristics_properties': {'Brightness': {}}}
self.air_purifier_filter_format = {'name': ' Air Purifier', 'service_name': ' Filter', 'service' :'FilterMaintenance', 'characteristics_properties': {}}
self.air_quality_format = {'name': ' Air Quality', 'service_name': ' Air Quality', 'service': 'AirQualitySensor', 'characteristics_properties': {'PM2_5Density': {}, 'VOCDensity': {}}}
self.CO2_level_format = {'name': ' CO2', 'service_name': ' CO2', 'service' :'CarbonDioxideSensor', 'characteristics_properties': {'CarbonDioxideLevel': {}, 'CarbonDioxidePeakLevel': {}}}
self.PM2_5_alert_format = {'name': ' PM2.5 Alert', 'service_name': ' PM2.5 Alert', 'service' :'MotionSensor', 'characteristics_properties': {'MotionDetected':{}}}
#self.air_quality_service_name_map = {name: name + self.air_quality_format['name'] for name in self.auto_air_purifier_names}
#self.CO2_service_name_map = {name: name + self.CO2_level_format['name'] for name in self.auto_air_purifier_names}
#self.PM2_5_service_name_map = {name: name + self.PM2_5_alert_format['name'] for name in self.auto_air_purifier_names}
# Set up reboot
self.reboot_format = {'name': 'Reboot'}
self.reboot_arm_format = {'service_name': 'Reboot Arm', 'service': 'Switch', 'characteristics_properties': {}}
self.reboot_trigger_format = {'service_name': 'Reboot Trigger', 'service': 'Switch', 'characteristics_properties': {}}
self.restart_trigger_format = {'service_name': 'Restart Trigger', 'service': 'Switch', 'characteristics_properties': {}}
self.reboot_armed = False
# Set up Enviro Monitors
self.enviro_config = enviro_config
self.enviro_aqi_format = {'name': ' AQI', 'service_name': ' AQI', 'service': 'AirQualitySensor',
'characteristics_properties': {'PM10Density': {}, 'PM2_5Density': {}, 'NitrogenDioxideDensity': {}}}
self.enviro_aqi_voc_format = {'name': ' AQI', 'service_name': ' AQI', 'service': 'AirQualitySensor',
'characteristics_properties': {'PM10Density': {}, 'PM2_5Density': {}, 'NitrogenDioxideDensity': {}, 'VOCDensity': {}}}
self.enviro_reducing_format = {'name': ' Reducing', 'service_name': ' Reducing', 'service': 'AirQualitySensor', 'characteristics_properties': {'NitrogenDioxideDensity': {}}}
self.enviro_ammonia_format = {'name': ' Ammonia', 'service_name': ' Ammonia', 'service': 'AirQualitySensor', 'characteristics_properties': {'NitrogenDioxideDensity': {}}}
self.enviro_PM2_5_alert_format = {'name': ' PM2.5 Alert', 'service_name': ' PM2.5 Alert', 'service' :'MotionSensor', 'characteristics_properties': {'MotionDetected':{}}}
self.enviro_temp_format = {'name': ' Env Temp', 'service': 'TemperatureSensor', 'service_name': ' Env Temp', 'characteristics_properties': {}}
self.enviro_hum_format = {'name': ' Env Hum', 'service': 'HumiditySensor', 'service_name': ' Env Hum', 'characteristics_properties': {}}
self.enviro_dew_format = {'name': ' Env Dew', 'service': 'TemperatureSensor', 'service_name': ' Env Dew', 'characteristics_properties': {}}
self.enviro_lux_format = {'name': ' Env Lux', 'service': 'LightSensor', 'service_name': ' Env Lux', 'characteristics_properties': {}}
self.enviro_CO2_level_format = {'name': ' CO2', 'service_name': ' CO2', 'service' :'CarbonDioxideSensor', 'characteristics_properties': {'CarbonDioxideLevel': {}, 'CarbonDioxidePeakLevel': {}}}
self.enviro_wind_format = {'name': 'Balcony Wind', 'service_name': 'Balcony Wind', 'service': 'Fan', 'characteristics_properties': {}}
self.enviro_chill_format = {'name': 'Balcony Chill', 'service_name': 'Balcony Chill', 'service': 'TemperatureSensor', 'characteristics_properties': {'CurrentTemperature': {'minValue': 0, 'maxValue': 100, 'minStep': 0.1}}}
self.enviro_wind_state = {'Active': False, 'Wind Speed': 0 , 'Direction': 0}
# Set up Aquarium Temperature'
self.aquarium_temp_format = {'name': 'Aquarium Temperature', 'service_name': 'Aquarium Temperature', 'service': 'TemperatureSensor', 'characteristics_properties': {'CurrentTemperature': {'minValue': 0, 'maxValue': 100, 'minStep': 0.1}}}
# Set up EV Charger
self.ev_charger_name_identifier = 'EV Charger'
self.ev_charger_state_format = {'name': 'EV Charger State'}
self.ev_charger_not_connected_format = {'service_name': 'Not Connected', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.ev_charger_connected_locked_format = {'service_name': 'Connected and Locked', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.ev_charger_charging_format = {'service_name': 'Charging', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.ev_charger_charged_format = {'service_name': 'Charged', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.ev_charger_disabled_format = {'service_name': 'Disabled', 'service': 'ContactSensor', 'characteristics_properties': {}}
self.ev_charger_comms_format = {'service': 'ContactSensor', 'service_name': 'Comms Lost', 'characteristics_properties':{}}
self.ev_charger_control_format = {'name': 'EV Charger Control'}
self.ev_charger_enable_charger_format = {'service_name': 'Enable Charger', 'service': 'Door', 'characteristics_properties': {'CurrentPosition':{'minValue': 0, 'maxValue': 100, 'minStep': 100}, "TargetPosition": {"minValue": 0,"maxValue": 100,"minStep": 100}}}
self.ev_charger_disable_charger_format = {'service_name': 'Disable Charger', 'service': 'Door', 'characteristics_properties': {'CurrentPosition':{'minValue': 0, 'maxValue': 100, 'minStep': 100}, "TargetPosition": {"minValue": 0,"maxValue": 100,"minStep": 100}}}
self.ev_charger_start_charging_format = {'service_name': 'Start Charging', 'service': 'Door', 'characteristics_properties': {'CurrentPosition':{'minValue': 0, 'maxValue': 100, 'minStep': 100}, "TargetPosition": {"minValue": 0,"maxValue": 100,"minStep": 100}}}
# Set up Fan Monitor
self.fan_monitor_state_format = {'name': 'Roof Fans', 'service': 'Fan', 'service_name': ' Fan State', 'characteristics_properties':{}}
self.fan_monitor_fault_format = {'name': 'Roof Fans', 'service': 'ContactSensor', 'service_name': ' Fan Fault', 'characteristics_properties':{}}
self.fan_monitor_stopped_format = {'name': 'Roof Fans', 'service': 'ContactSensor', 'service_name': ' Fan Stopped', 'characteristics_properties':{}}
self.fan_monitor_comms_format = {'name': 'Roof Fans', 'service': 'ContactSensor', 'service_name': 'Comms Lost', 'characteristics_properties':{}}
self.hm_display_format = {'name': 'HM Display', 'service': 'Lightbulb', 'service_name': 'HM Display Brightness', 'characteristics_properties': {'Brightness': {}}}
# Set up Window Shades
self.main_room_shades_format = {'name': 'Main Room Shades'}
self.main_room_shades_all_format = {'service': 'WindowCovering', 'service_name': 'All', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_day_fresh_format = {'service': 'WindowCovering', 'service_name': 'Day Fresh', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_both_blockout_format = {'service': 'WindowCovering', 'service_name': 'Both Blockout', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_both_shades_format = {'service': 'WindowCovering', 'service_name': 'Both Shades', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_door_blockout_format = {'service': 'WindowCovering', 'service_name': 'Door Blockout', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_window_blockout_format = {'service': 'WindowCovering', 'service_name': 'Window Blockout', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_door_shade_format = {'service': 'WindowCovering', 'service_name': 'Door Shade', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.main_room_shades_window_shade_format = {'service': 'WindowCovering', 'service_name': 'Window Shade', 'characteristics_properties': {'TargetPosition': {'minStep':10}}}
self.current_config = {}
self.ack_cache = {}
def check_and_fix_config(self): # Check and fix Homebridge Cached Accessories config against required config
print('Homebridge config check started. Please wait.')
homebridge_restart_required = False
time.sleep(5)
client.publish(self.outgoing_config_mqtt_topic, json.dumps(self.all_configs_props)) # Request config check
time.sleep(2)
current_homebridge_config = self.current_config
#current_homebridge_config['Dummy Accessory'] = {'Dummy Service Name': {'DummyService':{'DummyCharacteristic':{'DummyProperty':0}}}}
required_homebridge_config = self.indentify_required_homebridge_config()
missing_accessories, missing_accessories_services, additional_accessories_services, incorrect_accessories_services = self.find_incorrect_accessories(required_homebridge_config, self.current_config)
excess_accessories = self.find_excess_accessories(required_homebridge_config, self.current_config)
print('Current Config', self.current_config)
print('Required Config', required_homebridge_config)
print('Missing Accessories:', missing_accessories)
print('Excess Accessories:', excess_accessories)
print('Missing Services within Accessories:', missing_accessories_services)
print('Unrequired Services within Accessories', additional_accessories_services)
print('Incorrect Services within Accessories:', incorrect_accessories_services)
# Handle missing Accessories
if missing_accessories == []:
print('No missing accessories')
else:
for accessory in missing_accessories:
print(accessory, 'accessory is missing, along with the following config:-')
for service_name in required_homebridge_config[accessory]:
print('Service Name:', service_name, 'Service:', required_homebridge_config[accessory][service_name])
add_missing_accessories = input("Do you want to add the missing accessories? (y/n): ")
if add_missing_accessories == 'y':
homebridge_restart_required = True
self.add_missing_accessories(missing_accessories, required_homebridge_config)
# Handle excess Accessories
if excess_accessories == []:
print('No excess accessories')
else:
for accessory in excess_accessories:
print(accessory, 'accessory is not required')
remove_excess_accessories = input("Do you want to remove excess accessories? (y/n): ")
if remove_excess_accessories == 'y':
homebridge_restart_required = True
self.remove_excess_accessories(excess_accessories)
# Handle missing Services within Accessories
if missing_accessories_services == []:
print('No missing accessories services')
else:
for accessory in missing_accessories_services:
for key in accessory:
for service_name in accessory[key]:
print(key, "accessory's service name", service_name, "is missing, along with the config:", required_homebridge_config[key][service_name])
add_missing_accessories_services = input("Do you want to add the missing accessories' services? (y/n): ")
if add_missing_accessories_services == 'y':
homebridge_restart_required = True
self.add_missing_accessories_services(missing_accessories_services, required_homebridge_config)
# Handle unrequired Services within Accessories
if additional_accessories_services == []:
print('No additional accessories services')
else:
for accessory in additional_accessories_services:
for key in accessory:
for service_name in accessory[key]:
print('')
print(key, "accessory's service name", service_name, "is not required")
remove_additional_accessories_services = input("Do you want to delete the excess accessories' services? (y/n): ")
if remove_additional_accessories_services == 'y':
homebridge_restart_required = True
self.remove_additional_accessories_services(additional_accessories_services)
# Handle Services that are within Accessories but have a config error
if incorrect_accessories_services == []:
print('No incorrect accessories services')
else:
for accessory in incorrect_accessories_services:
for key in accessory:
print('')
print(key, 'accessory is incorrect')
for service_name in accessory[key]:
if service_name in current_homebridge_config[key]:
print('Service Name:', service_name, 'is incorrect')
else:
print('Service Name:', service_name, 'is missing')
fix_incorrect_accessories = input("Do you want to fix the incorrect accessories? (y/n): ")
if fix_incorrect_accessories == 'y':
homebridge_restart_required = True
self.fix_incorrect_accessories(incorrect_accessories_services, required_homebridge_config)
if homebridge_restart_required:
print('Waiting for config acknowledgements')
time.sleep(10)
self.check_ack_cache()
print('Please restart homebridge now to update the homebridge config cache')
homebridge_restarted = input("Please enter 'y' when you have restarted homebridge")
if homebridge_restarted == 'y':
print('Updated homebridge config now captured in the homebridge cache')
else:
print('Updated homebridge config has not been captured in the homebridge cache. THE CACHE STILL CONTAINS THE OLD CONFIG UNTIL HOMEBRIDGE IS RESTARTED')
print('Homebridge config check completed')
def indentify_required_homebridge_config(self):
# Blinds
if mgr.window_blinds_present:
blinds_homebridge_config = {blinds: {**{blind: {self.blinds_format['service']: self.blinds_format['characteristics_properties']} for blind in self.window_blind_config[blinds]['status']},
**{self.blinds_temp_format['high_temp_service_name']: {self.blinds_temp_format['service']: self.blinds_temp_format['high_temperature_characteristics_properties']},
self.blinds_temp_format['low_temp_service_name']: {self.blinds_temp_format['service']: self.blinds_temp_format['low_temperature_characteristics_properties']},
self.auto_blind_override_button_format['service_name']: {self.auto_blind_override_button_format['service']: self.auto_blind_override_button_format['characteristics_properties']}}}
for blinds in self.window_blind_config}
else:
blinds_homebridge_config = {}
# Doors
if mgr.door_sensors_present:
door_sensor_rooms = []
for door in self.door_sensor_names_locations:
if self.door_sensor_names_locations[door] not in door_sensor_rooms:
door_sensor_rooms.append(self.door_sensor_names_locations[door]) # Create a list of rooms with door sensors to create accessory names
door_sensors_homebridge_config = {room: {} for room in door_sensor_rooms} # Make an accessory name key for each door sensor room
for room in door_sensor_rooms: # Iterate through the list of rooms with door sensors
for door in self.door_sensor_names_locations:
if self.door_sensor_names_locations[door] == room: # Add the door sensor to the relevant room
door_sensors_homebridge_config[room][door + self.door_format['service_name']] = {self.door_format['service']: self.door_format['characteristics_properties']}
else:
door_sensors_homebridge_config = {}
# Garage
if mgr.garage_door_present == True:
garage_door_homebridge_config = {self.garage_door_format['name']: {self.garage_door_format['service_name']: {self.garage_door_format['service']: self.garage_door_format['characteristics_properties']}}}
else:
garage_door_homebridge_config = {}
# Reboot
if mgr.enable_reboot:
reboot_homebridge_config = {self.reboot_format['name']: {self.reboot_arm_format['service_name']: {self.reboot_arm_format['service']: self.reboot_arm_format['characteristics_properties']},
self.reboot_trigger_format['service_name']: {self.reboot_trigger_format['service']: self.reboot_trigger_format['characteristics_properties']},
self.restart_trigger_format['service_name']: {self.restart_trigger_format['service']: self.restart_trigger_format['characteristics_properties']}}}
else:
reboot_homebridge_config = {}
# Light Dimmers
if mgr.light_dimmers_present:
light_dimmers_homebridge_config = {light: {light: {self.dimmer_format['service']: self.dimmer_format['characteristics_properties']}} for light in self.light_dimmer_names_device_id}
for light in self.colour_light_dimmer_names:
light_dimmers_homebridge_config[light][light][self.dimmer_format['service']] = self.colour_light_dimmer_characteristics_properties # Add hue and saturation characteristics to colour dimmer
else:
light_dimmers_homebridge_config = {}
# Aircon
if mgr.aircons_present:
aircon_homebridge_config = {}
for aircon_name in self.aircon_config:
aircon_homebridge_config[self.aircon_ventilation_button_format[aircon_name]['name']] = {self.aircon_ventilation_button_format[aircon_name]['service_name']:
{self.aircon_ventilation_button_format[aircon_name]['service']:
self.aircon_ventilation_button_format[aircon_name]['characteristics_properties']}}
aircon_homebridge_config[self.aircon_filter_indicator_format[aircon_name]['name']] = {self.aircon_filter_indicator_format[aircon_name]['service_name']: {self.aircon_filter_indicator_format[aircon_name]['service']:
self.aircon_filter_indicator_format[aircon_name]['characteristics_properties']}}
aircon_homebridge_config[self.aircon_reset_effectiveness_log_button_format[aircon_name]['name']] = {self.aircon_reset_effectiveness_log_button_format[aircon_name]['service_name']:
{self.aircon_reset_effectiveness_log_button_format[aircon_name]['service']:
self.aircon_reset_effectiveness_log_button_format[aircon_name]['characteristics_properties']}}
if self.aircon_config[aircon_name]['Room Dampers']:
aircon_homebridge_config[self.aircon_status_format[aircon_name]['name']] = {self.aircon_remote_operation_format[aircon_name]['service_name']:
{self.aircon_remote_operation_format[aircon_name]['service']:
self.aircon_remote_operation_format[aircon_name]['characteristics_properties']},
self.aircon_heat_format[aircon_name]['service_name']:
{self.aircon_heat_format[aircon_name]['service']:
self.aircon_heat_format[aircon_name]['characteristics_properties']},
self.aircon_cool_format[aircon_name]['service_name']:
{self.aircon_cool_format[aircon_name]['service']:
self.aircon_cool_format[aircon_name]['characteristics_properties']},
self.aircon_fan_format[aircon_name]['service_name']:
{self.aircon_fan_format[aircon_name]['service']:
self.aircon_fan_format[aircon_name]['characteristics_properties']},
self.aircon_fan_hi_format[aircon_name]['service_name']:
{self.aircon_fan_hi_format[aircon_name]['service']:
self.aircon_fan_hi_format[aircon_name]['characteristics_properties']},
self.aircon_fan_lo_format[aircon_name]['service_name']:
{self.aircon_fan_lo_format[aircon_name]['service']:
self.aircon_fan_lo_format[aircon_name]['characteristics_properties']},
self.aircon_compressor_format[aircon_name]['service_name']:
{self.aircon_compressor_format[aircon_name]['service']:
self.aircon_compressor_format[aircon_name]['characteristics_properties']},
self.aircon_heating_format[aircon_name]['service_name']:
{self.aircon_heating_format[aircon_name]['service']:
self.aircon_heating_format[aircon_name]['characteristics_properties']},
self.aircon_malfunction_format[aircon_name]['service_name']:
{self.aircon_malfunction_format[aircon_name]['service']:
self.aircon_malfunction_format[aircon_name]['characteristics_properties']}}
else:
aircon_homebridge_config[self.aircon_status_format[aircon_name]['name']] = {self.aircon_remote_operation_format[aircon_name]['service_name']:
{self.aircon_remote_operation_format[aircon_name]['service']:
self.aircon_remote_operation_format[aircon_name]['characteristics_properties']},
self.aircon_damper_format[aircon_name]['service_name']:
{self.aircon_damper_format[aircon_name]['service']:
self.aircon_damper_format[aircon_name]['characteristics_properties']},
self.aircon_heat_format[aircon_name]['service_name']:
{self.aircon_heat_format[aircon_name]['service']:
self.aircon_heat_format[aircon_name]['characteristics_properties']},
self.aircon_cool_format[aircon_name]['service_name']:
{self.aircon_cool_format[aircon_name]['service']:
self.aircon_cool_format[aircon_name]['characteristics_properties']},
self.aircon_fan_format[aircon_name]['service_name']:
{self.aircon_fan_format[aircon_name]['service']:
self.aircon_fan_format[aircon_name]['characteristics_properties']},
self.aircon_fan_hi_format[aircon_name]['service_name']:
{self.aircon_fan_hi_format[aircon_name]['service']:
self.aircon_fan_hi_format[aircon_name]['characteristics_properties']},
self.aircon_fan_lo_format[aircon_name]['service_name']:
{self.aircon_fan_lo_format[aircon_name]['service']:
self.aircon_fan_lo_format[aircon_name]['characteristics_properties']},
self.aircon_compressor_format[aircon_name]['service_name']:
{self.aircon_compressor_format[aircon_name]['service']:
self.aircon_compressor_format[aircon_name]['characteristics_properties']},
self.aircon_heating_format[aircon_name]['service_name']:
{self.aircon_heating_format[aircon_name]['service']:
self.aircon_heating_format[aircon_name]['characteristics_properties']},
self.aircon_malfunction_format[aircon_name]['service_name']:
{self.aircon_malfunction_format[aircon_name]['service']:
self.aircon_malfunction_format[aircon_name]['characteristics_properties']}}
aircon_homebridge_config[self.aircon_hi_fan_format[aircon_name]['name']] = {self.aircon_hi_fan_format[aircon_name]['service_name']:
{self.aircon_hi_fan_format[aircon_name]['service']:
self.aircon_hi_fan_format[aircon_name]['characteristics_properties']}}
aircon_homebridge_config[self.aircon_med_fan_format[aircon_name]['name']] = {self.aircon_med_fan_format[aircon_name]['service_name']:
{self.aircon_med_fan_format[aircon_name]['service']:
self.aircon_med_fan_format[aircon_name]['characteristics_properties']}}
aircon_homebridge_config[self.aircon_lo_fan_format[aircon_name]['name']] = {self.aircon_lo_fan_format[aircon_name]['service_name']:
{self.aircon_lo_fan_format[aircon_name]['service']:
self.aircon_lo_fan_format[aircon_name]['characteristics_properties']}}
aircon_thermostat_names = self.aircon_config[aircon_name]['Day Zone'] + self.aircon_config[aircon_name]['Night Zone'] + [self.aircon_config[aircon_name]['Master']]
for thermostat in aircon_thermostat_names:
aircon_homebridge_config[aircon_name + ' ' + thermostat] = {aircon_name + ' ' + thermostat: {self.aircon_thermostat_format[aircon_name]['service']:
self.aircon_thermostat_format[aircon_name]['characteristics_properties']}}
else:
aircon_homebridge_config = {}
# Doorbell
if mgr.doorbell_present:
doorbell_homebridge_config = {'Doorbell Status': {}} # Make a Doorbell Status Key
for button in self.doorbell_homebridge_json_name_map:
if self.doorbell_homebridge_json_name_map[button] == 'Doorbell Status':
doorbell_homebridge_config['Doorbell Status'][button] = self.doorbell_characteristics_properties['Doorbell Status']
else:
doorbell_homebridge_config[self.doorbell_homebridge_json_name_map[button]] = {button: self.doorbell_characteristics_properties['Others']}
#print("Required Doorbell HB Config", doorbell_homebridge_config)
else:
doorbell_homebridge_config = {}
# Air Purifiers
if mgr.air_purifiers_present:
air_purifiers_homebridge_config = {}
for air_purifier in self.air_purifier_names:
air_purifiers_homebridge_config[air_purifier + self.air_purifier_format['name']] = {air_purifier + self.air_purifier_format['service_name']: {self.air_purifier_format['service']: self.air_purifier_format['characteristics_properties']},
air_purifier + self.air_purifier_LED_format['service_name']: {self.air_purifier_LED_format['service']:
self.air_purifier_LED_format['characteristics_properties']},
air_purifier + self.air_purifier_filter_format['service_name']: {self.air_purifier_filter_format['service']:
self.air_purifier_filter_format['characteristics_properties']}}
for air_purifier in self.auto_air_purifier_names:
air_purifiers_homebridge_config[air_purifier + self.air_quality_format['name']] = {air_purifier + self.air_quality_format['service_name']: {self.air_quality_format['service']:
self.air_quality_format['characteristics_properties']}}
air_purifiers_homebridge_config[air_purifier + self.CO2_level_format['name']] = {air_purifier + self.CO2_level_format['service_name']: {self.CO2_level_format['service']:
self.CO2_level_format['characteristics_properties']}}
air_purifiers_homebridge_config[air_purifier + self.PM2_5_alert_format['name']] = {air_purifier + self.PM2_5_alert_format['service_name']: {self.PM2_5_alert_format['service']:
self.PM2_5_alert_format['characteristics_properties']}}
else:
air_purifiers_homebridge_config = {}
# Multisensors
if mgr.multisensors_present:
multisensors_homebridge_config = {self.outdoor_sensors_name: {}} # Make a key for the outdoor sensors' accessory name
for multisensor in self.multisensor_names:
if multisensor in self.outdoor_multisensor_names: # Outdoor sensors are grouped as services under one accessory
multisensors_homebridge_config[self.outdoor_sensors_name][multisensor + self.temperature_format['service_name']] = {self.temperature_format['service']: self.temperature_format['characteristics_properties']}
multisensors_homebridge_config[self.outdoor_sensors_name][multisensor + self.humidity_format['service_name']] = {self.humidity_format['service']: self.humidity_format['characteristics_properties']}
multisensors_homebridge_config[self.outdoor_sensors_name][multisensor + self.light_level_format['service_name']] = {self.light_level_format['service']: self.light_level_format['characteristics_properties']}
multisensors_homebridge_config[self.outdoor_sensors_name][multisensor + self.motion_format['service_name']] = {self.motion_format['service']: self.motion_format['characteristics_properties']}
else: # Indoor Sensors are separate accessories
multisensors_homebridge_config[multisensor + self.temperature_format['name']] = {multisensor + self.temperature_format['service_name']: {self.temperature_format['service']:
self.temperature_format['characteristics_properties']}}
multisensors_homebridge_config[multisensor + self.humidity_format['name']] = {multisensor + self.humidity_format['service_name']: {self.humidity_format['service']:
self.humidity_format['characteristics_properties']}}
multisensors_homebridge_config[multisensor + self.light_level_format['name']] = {multisensor + self.light_level_format['service_name']: {self.light_level_format['service']:
self.light_level_format['characteristics_properties']}}
multisensors_homebridge_config[multisensor + self.motion_format['name']] = {multisensor + self.motion_format['service_name']: {self.motion_format['service']:
self.motion_format['characteristics_properties']}}
else:
multisensors_homebridge_config = {}
# Powerpoints
if mgr.powerpoints_present:
powerpoints_homebridge_config = {powerpoint + self.powerpoint_format['name']: {powerpoint + self.powerpoint_format['service_name']: {self.powerpoint_format['service']: self.powerpoint_format['characteristics_properties']}}
for powerpoint in self.powerpoint_names_device_id}
else:
powerpoints_homebridge_config = {}
# Flood Sensors
if mgr.flood_sensors_present:
flood_sensors_homebridge_config = {flood_sensor + self.flood_state_format['name']: {flood_sensor + self.flood_state_format['service_name']: {self.flood_state_format['service']:
self.flood_state_format['characteristics_properties']}}
for flood_sensor in self.flood_sensor_names}
else:
flood_sensors_homebridge_config = {}
# Enviro Monitors
if mgr.enviro_monitors_present == True:
enviro_monitors_homebridge_config = {}
for enviro_monitor in self.enviro_config:
if self.enviro_config[enviro_monitor]['Homebridge Display']: # Only set up Homebridge if enabled
if 'VOC' in self.enviro_config[enviro_monitor]['Device IDs']: # Add VOC if it's an Indoor Plus Enviro Monitor
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_aqi_format['name']] = {enviro_monitor + self.enviro_aqi_voc_format['service_name']:
{self.enviro_aqi_voc_format['service']:
self.enviro_aqi_voc_format['characteristics_properties']}}
else:
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_aqi_format['name']] = {enviro_monitor + self.enviro_aqi_format['service_name']:
{self.enviro_aqi_format['service']:
self.enviro_aqi_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_reducing_format['name']] = {enviro_monitor + self.enviro_reducing_format['service_name']:
{self.enviro_reducing_format['service']:
self.enviro_reducing_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_ammonia_format['name']] = {enviro_monitor + self.enviro_ammonia_format['service_name']:
{self.enviro_ammonia_format['service']:
self.enviro_ammonia_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_PM2_5_alert_format['name']] = {enviro_monitor +
self.enviro_PM2_5_alert_format['service_name']:
{self.enviro_PM2_5_alert_format['service']:
self.enviro_PM2_5_alert_format['characteristics_properties']}}
if self.enviro_config[enviro_monitor]['Capture Non AQI']: # Set up Non AQI if enabled
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_temp_format['name']] = {enviro_monitor + self.enviro_temp_format['service_name']:
{self.enviro_temp_format['service']:
self.enviro_temp_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_hum_format['name']] = {enviro_monitor + self.enviro_hum_format['service_name']:
{self.enviro_hum_format['service']:
self.enviro_hum_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_dew_format['name']] = {enviro_monitor + self.enviro_dew_format['service_name']:
{self.enviro_dew_format['service']:
self.enviro_dew_format['characteristics_properties']}}
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_lux_format['name']] = {enviro_monitor + self.enviro_lux_format['service_name']:
{self.enviro_lux_format['service']:
self.enviro_lux_format['characteristics_properties']}}
if 'CO2' in self.enviro_config[enviro_monitor]['Device IDs']: # Add CO2 if it's an Indoor Plus Enviro Monitor
enviro_monitors_homebridge_config[enviro_monitor + self.enviro_CO2_level_format['name']] = {enviro_monitor + self.enviro_CO2_level_format['service_name']:
{self.enviro_CO2_level_format['service']: self.enviro_CO2_level_format['characteristics_properties']}}
else:
enviro_monitors_homebridge_config = {}
# EV Charger
if mgr.ev_charger_present: