1
1
"""
2
- p1738 .py
2
+ paradox2mqtt .py
3
3
This script listens for debug serial port packets from a Paradox Spectra 1738
4
4
alarm panel and sends decoded messages to a MQTT broker.
5
5
"""
6
+
7
+
6
8
import logging
7
9
import sys
8
10
import struct
9
11
import serial
10
12
import paho .mqtt .client as mqtt
11
13
import yaml
12
14
13
- print ("""
14
- ██╗███████╗██████╗ █████╗
15
- ███║╚════██║╚════██╗██╔══██╗
16
- ╚██║ ██╔╝ █████╔╝╚█████╔╝
17
- ██║ ██╔╝ ╚═══██╗██╔══██╗
18
- ██║ ██║ ██████╔╝╚█████╔╝
19
- ╚═╝ ╚═╝ ╚═════╝ ╚════╝
20
- A MQTT bridge for Paradox Spectra alarm panel
21
- """ )
22
15
23
16
# Notes on the Paradox P1738 Serial Output packets
24
17
# This info comes from looking at the different packets received on the serial
33
26
# | |T| E | | Zone | | Hours | | Minutes | |
34
27
# +-------------------------------+-------------------------------+
35
28
29
+
36
30
def parse_msg (packet ):
37
31
""" Decodes a serial packet into event code and timestamp parts """
38
32
raw = struct .unpack ("!BBH" , packet )
@@ -41,19 +35,22 @@ def parse_msg(packet):
41
35
minutes = (raw [2 ] >> 4 ) & 0b111111
42
36
return code , hours , minutes
43
37
38
+
44
39
def find_mapping (code ):
45
40
""" Looks-up an event code for a matching message mapping """
46
- for mapping in MESSAGE_MAPPING :
41
+ for mapping in _message_mapping :
47
42
if mapping ['bytes' ] == code :
48
43
return mapping
49
44
45
+
50
46
def log_msg_bytes (packet ):
51
47
""" Logs the packet content """
52
48
raw = struct .unpack ("BBBB" , packet )
53
49
text = (hex (raw [0 ]) + " " + hex (raw [1 ]) + " " + hex (raw [2 ]) + " "
54
50
+ hex (raw [3 ]))
55
- MQTT_CLIENT .publish ("p1738/debug" , payload = text , qos = 1 )
56
- logging .info ("In: " + text )
51
+ _mqtt_client .publish ("paradox2mqtt/debug" , payload = text , qos = 1 )
52
+ logging .info ("In: %s" , text )
53
+
57
54
58
55
def run_loop ():
59
56
"""
@@ -62,58 +59,78 @@ def run_loop():
62
59
"""
63
60
while True :
64
61
# Read first byte
65
- rcv = SERIAL .read (1 )
62
+ rcv = _serial .read (1 )
66
63
if len (rcv ) == 1 :
67
64
# Read the following 3 bytes
68
- rcv += SERIAL .read (3 )
65
+ rcv += _serial .read (3 )
69
66
if len (rcv ) == 4 :
70
67
log_msg_bytes (rcv )
71
68
(code , _hours , _minutes ) = parse_msg (rcv )
72
69
mapping = find_mapping (code )
73
70
if mapping :
74
71
topic = mapping ['topic' ]
75
72
message = mapping ['message' ]
76
- logging .info ("Out: " + repr (topic ) + ", " + repr (message ))
77
- MQTT_CLIENT .publish (topic , payload = message , qos = 1 )
73
+ logging .info ("Out: %s, %s" , repr (topic ), repr (message ))
74
+ _mqtt_client .publish (topic , payload = message , qos = 1 )
78
75
else :
79
- logging .warning ("The following bytes have been discarded: " +
80
- repr (rcv ))
76
+ logging .warning ("The following bytes have been discarded: %s" , repr (rcv ))
77
+
78
+
79
+ _PROGRAM_START_MESSAGE = """
80
+
81
+ paradox2mqtt
82
+
83
+ An MQTT bridge for the Paradox Spectra 1738 alarm panel
84
+
85
+ """
86
+
87
+ _message_mapping = []
88
+ _mqtt_client = None
89
+ _serial = None
90
+
91
+
92
+ def main ():
93
+ print (_PROGRAM_START_MESSAGE )
94
+
95
+ logging .getLogger ().setLevel (logging .INFO )
96
+ logging_file_handler = logging .FileHandler ('paradox2mqtt.log' , 'w' )
97
+ logging_file_handler .setLevel (logging .WARNING )
98
+ logging_stream_handler = logging .StreamHandler (sys .stdout )
99
+ logging_stream_handler .setLevel (logging .INFO )
100
+ logging .getLogger ().addHandler (logging_file_handler )
101
+ logging .getLogger ().addHandler (logging_stream_handler )
102
+
103
+ # Read configuration
104
+ logging .info ("Reading configuration file" )
105
+ config = yaml .safe_load (open ("config.yaml" ))
81
106
82
- logging .getLogger ().setLevel (logging .INFO )
83
- LOGGING_FILE_HANDLER = logging .FileHandler ('p1738.log' , 'w' )
84
- LOGGING_FILE_HANDLER .setLevel (logging .WARNING )
85
- LOGGING_STREAM_HANDLER = logging .StreamHandler (sys .stdout )
86
- LOGGING_STREAM_HANDLER .setLevel (logging .INFO )
87
- logging .getLogger ().addHandler (LOGGING_FILE_HANDLER )
88
- logging .getLogger ().addHandler (LOGGING_STREAM_HANDLER )
107
+ mqtt_broker = config ['mqtt_broker' ]
108
+ serial_device = config ['serial_device' ]
109
+ status_topic = config ['status' ]['topic' ]
110
+ status_message_connect = config ['status' ]['message_connect' ]
111
+ status_message_disconnect = config ['status' ]['message_disconnect' ]
112
+ _message_mapping = config ['message_mapping' ]
89
113
90
- # Read configuration
91
- logging .info ("Reading configuration file" )
92
- CONFIG = yaml .safe_load (open ("config.yaml" ))
114
+ logging .info ("Connecting to MQTT Broker: %s" , mqtt_broker )
93
115
94
- MQTT_BROKER = CONFIG ['mqtt_broker' ]
95
- SERIAL_DEVICE = CONFIG ['serial_device' ]
96
- STATUS_TOPIC = CONFIG ['status' ]['topic' ]
97
- STATUS_MESSAGE_CONNECT = CONFIG ['status' ]['message_connect' ]
98
- STATUS_MESSAGE_DISCONNECT = CONFIG ['status' ]['message_disconnect' ]
99
- MESSAGE_MAPPING = CONFIG ['message_mapping' ]
116
+ _mqtt_client = mqtt .Client ()
117
+ _mqtt_client .will_set (status_topic , payload = status_message_disconnect , qos = 1 ,
118
+ retain = True )
119
+ _mqtt_client .connect (mqtt_broker )
120
+ _mqtt_client .loop_start ()
100
121
101
- logging .info ("Connecting to MQTT Broker: " + MQTT_BROKER )
122
+ _mqtt_client .publish (status_topic , payload = status_message_connect , qos = 1 ,
123
+ retain = True )
102
124
103
- MQTT_CLIENT = mqtt .Client ()
104
- MQTT_CLIENT .will_set (STATUS_TOPIC , payload = STATUS_MESSAGE_DISCONNECT , qos = 1 ,
105
- retain = True )
106
- MQTT_CLIENT .connect (MQTT_BROKER )
107
- MQTT_CLIENT .loop_start ()
125
+ logging .info ("Connected" )
108
126
109
- MQTT_CLIENT . publish ( STATUS_TOPIC , payload = STATUS_MESSAGE_CONNECT , qos = 1 ,
110
- retain = True )
127
+ logging . info ( "Opening serial device: %s" , serial_device )
128
+ _serial = serial . Serial ( serial_device , baudrate = 9600 , timeout = 5 )
111
129
112
- logging .info ("Connected " )
130
+ logging .info ("Listening for packets " )
113
131
114
- logging .info ("Opening serial device: " + SERIAL_DEVICE )
115
- SERIAL = serial .Serial (SERIAL_DEVICE , baudrate = 9600 , timeout = 5 )
132
+ run_loop ()
116
133
117
- logging .info ("Listening for packets" )
118
134
119
- run_loop ()
135
+ if __name__ == '__main__' :
136
+ main ()
0 commit comments