This repository was archived by the owner on Jul 15, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathmain.py
254 lines (204 loc) · 9.69 KB
/
main.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
import os
import pygame
import signal
import sys
from time import sleep
import paho.mqtt.client as mqtt
from alarmpanel import Motion
from alarmpanel.button import \
STATE_DEFAULT, STATE_PRESSED,\
STATE_1, STATE_2, STATE_3, STATE_4_BAD, STATE_4_GOOD,\
STATE_ACTIVE, STATE_AVAILABLE
from alarmpanel.ui import UI
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
# Configuration -----------------------------------------------------------
# ALARM_PINS should be a comma-separated list of 4 digit pins
PINS = os.environ.get('PINS').split(',')
MQTT_HOST = os.environ.get('MQTT_HOST')
MQTT_PORT = int(os.environ.get('MQTT_PORT', 1883))
MQTT_USER = os.environ.get('MQTT_USER')
MQTT_PASS = os.environ.get('MQTT_PASS')
MQTT_CLIENT_ID = os.environ.get('MQTT_CLIENT_ID', 'alarmpanel')
MQTT_STATE_TOPIC = os.environ.get('MQTT_STATE_TOPIC', 'home/alarm')
MQTT_COMMAND_TOPIC = os.environ.get('MQTT_COMMAND_TOPIC', 'home/alarm/set')
PIR_GPIO_PIN = os.environ.get('PIR_GPIO_PIN', None)
os.chdir(os.path.dirname(os.path.realpath(__file__)))
pin_input_string = ''
current_state = 'armed_home'
pending_state = ''
connected = False
def on_button_press(n):
global pin_input_string, client, pending_state
print n
# Numeric buttons
if n < 10:
pin_input_string = pin_input_string + str(n)
input_length = len(pin_input_string)
if input_length == 1:
btn_input.set_state(STATE_1)
elif input_length == 2:
btn_input.set_state(STATE_2)
elif input_length == 3:
btn_input.set_state(STATE_3)
elif input_length == 4:
if pin_input_string in PINS:
btn_input.set_state(STATE_4_GOOD)
update_action_button_states()
else:
btn_input.set_state(STATE_4_BAD)
# Asterisk and pound buttons
if n == 11 or n == 12:
# These buttons don't have a purpose yet
# For now we'll make them clear the input
clear_input()
# The "button" showing masked input
if n == 13:
clear_input()
# The three different action buttons
if n >= 14:
# Only handle if a correct pin has been entered
if pin_input_string in PINS:
if n == 14:
pending_state = 'disarmed'
client.publish(MQTT_COMMAND_TOPIC, payload='DISARM', qos=2, retain=True)
elif n == 15:
pending_state = 'armed_home'
client.publish(MQTT_COMMAND_TOPIC, payload='ARM_HOME', qos=2, retain=True)
elif n == 16:
pending_state = 'armed_away'
client.publish(MQTT_COMMAND_TOPIC, payload='ARM_AWAY', qos=2, retain=True)
clear_input()
def signal_handler(signal, frame):
print 'got SIGTERM'
pygame.quit()
client.loop_stop()
client.disconnect()
if PIR_GPIO_PIN:
ui.on()
sys.exit()
def clear_input():
global pin_input_string
pin_input_string = ""
btn_input.set_state(0)
update_action_button_states()
def update_action_button_states():
inactive_state = STATE_AVAILABLE if pin_input_string in PINS else STATE_DEFAULT
print "pending: " + pending_state
print "state: " + current_state
btnDisarm.set_state(STATE_ACTIVE if current_state == 'disarmed' or pending_state == 'disarmed' else inactive_state)
btnArmHome.set_state(STATE_ACTIVE if current_state == 'armed_home' or pending_state == 'armed_home' else inactive_state)
btnArmAway.set_state(STATE_ACTIVE if current_state == 'armed_away' or pending_state == 'armed_away' else inactive_state)
# Initialization -----------------------------------------------------------
ui = UI('images/bg.png')
# Load all images from the `images` directory
ui.load_images('images')
# Create the two status line elements
status_line_1 = ui.create_status_line((24, 4, 480 - 24, 24))
status_line_2 = ui.create_status_line((24, 34, 480 - 24, 24))
status_line_1.set('Starting up...')
# Create the PIN input buttons
ui.create_button((24, 64, 72, 52), imageFiles={STATE_DEFAULT: '1', STATE_PRESSED: '1_pressed'}, cb=on_button_press, value=1)
ui.create_button((104, 64, 72, 52), imageFiles={STATE_DEFAULT: '2', STATE_PRESSED: '2_pressed'}, cb=on_button_press, value=2)
ui.create_button((184, 64, 72, 52), imageFiles={STATE_DEFAULT: '3', STATE_PRESSED: '3_pressed'}, cb=on_button_press, value=3)
ui.create_button((24, 124, 72, 52), imageFiles={STATE_DEFAULT: '4', STATE_PRESSED: '4_pressed'}, cb=on_button_press, value=4)
ui.create_button((104, 124, 72, 52), imageFiles={STATE_DEFAULT: '5', STATE_PRESSED: '5_pressed'}, cb=on_button_press, value=5)
ui.create_button((184, 124, 72, 52), imageFiles={STATE_DEFAULT: '6', STATE_PRESSED: '6_pressed'}, cb=on_button_press, value=6)
ui.create_button((24, 184, 72, 52), imageFiles={STATE_DEFAULT: '7', STATE_PRESSED: '7_pressed'}, cb=on_button_press, value=7)
ui.create_button((104, 184, 72, 52), imageFiles={STATE_DEFAULT: '8', STATE_PRESSED: '8_pressed'}, cb=on_button_press, value=8)
ui.create_button((184, 184, 72, 52), imageFiles={STATE_DEFAULT: '9', STATE_PRESSED: '9_pressed'}, cb=on_button_press, value=9)
ui.create_button((104, 244, 72, 52), imageFiles={STATE_DEFAULT: '0', STATE_PRESSED: '0_pressed'}, cb=on_button_press, value=0)
ui.create_button((24, 244, 72, 52), imageFiles={STATE_DEFAULT: 'asterisk', STATE_PRESSED: 'asterisk_pressed'}, cb=on_button_press, value=11)
ui.create_button((184, 244, 72, 52), imageFiles={STATE_DEFAULT: 'pound', STATE_PRESSED: 'pound_pressed'}, cb=on_button_press, value=12)
# Create the command buttons
btn_input = ui.create_button((304, 64, 152, 52), imageFiles={STATE_DEFAULT: 'input_empty', STATE_PRESSED: 'input_empty', STATE_1: 'input_1', STATE_2: 'input_2', STATE_3: 'input_3', STATE_4_GOOD: 'input_4_good', STATE_4_BAD: 'input_4_bad'}, cb=on_button_press, value=13)
btnDisarm = ui.create_button((304, 124, 152, 52), imageFiles={STATE_DEFAULT: 'disarm_inactive', STATE_PRESSED: 'disarm_inactive', STATE_AVAILABLE: 'disarm_available', STATE_ACTIVE: 'disarm_active'}, cb=on_button_press, value=14)
btnArmHome = ui.create_button((304, 184, 152, 52), imageFiles={STATE_DEFAULT: 'arm_home_inactive', STATE_PRESSED: 'arm_home_inactive', STATE_AVAILABLE: 'arm_home_available', STATE_ACTIVE: 'arm_home_active'}, cb=on_button_press, value=15)
btnArmAway = ui.create_button((304, 244, 152, 52), imageFiles={STATE_DEFAULT: 'arm_away_inactive', STATE_PRESSED: 'arm_away_inactive', STATE_AVAILABLE: 'arm_away_available', STATE_ACTIVE: 'arm_away_active'}, cb=on_button_press, value=16)
# Draw the screen
ui.update()
if PIR_GPIO_PIN:
# Get the PI sensor up-and-running
motion = Motion(ui, PIR_GPIO_PIN)
# Wait for everything
sleep(1)
def on_connect(client, userdata, flags, rc):
global connected, pending_state, status_line_1
pending_state = ''
if rc == 0:
print 'MQTT connection successful'
status_line_1.set('Connected')
connected = True
client.subscribe(MQTT_STATE_TOPIC, 2)
elif rc == 1:
print 'MQTT connection refused - incorrect protocol version'
status_line_1.set('Connection refused - incorrect protocol version')
connected = False
elif rc == 2:
print 'MQTT connection refused - invalid client identifier'
status_line_1.set('Connection refused - invalid client identifier')
connected = False
elif rc == 3:
print 'MQTT connection refused - server unavailable'
status_line_1.set('Connection refused - server unavailable')
connected = False
elif rc == 4:
print 'MQTT connection refused - bad username or password'
status_line_1.set('Connection refused - invalid credentials')
connected = False
elif rc == 5:
print 'MQTT connection refused - not authorized'
status_line_1.set('Connection refused - not authorized')
connected = False
else:
print 'Unknown connection error: code ' + str(rc)
status_line_1.set('Unknown connection error: ' + str(rc))
connected = False
def on_disconnect(client, userdata, rc):
global connected, status_line_1
connected = False
if rc != 0:
status_line_1.set('Disconnected unexpectedly')
def on_message(client, userdata, message):
global current_state, pending_state
current_state = message.payload
print "received payload: " + current_state
# HA will send `pending` or `triggered` when the state is about to change
# In these cases, we can't be 100% sure what the next state will be
# In all other cases, we definitely know the current state, so wipe out `pending_state`
if current_state != 'pending' or current_state != 'triggered':
pending_state = ''
# Update the corresponding button state
if current_state == 'disarmed':
btnDisarm.set_state(STATE_ACTIVE)
elif current_state == 'armed_home':
btnArmHome.set_state(STATE_ACTIVE)
elif current_state == 'armed_away':
btnArmAway.set_state(STATE_ACTIVE)
# Update the other button states
update_action_button_states()
client = mqtt.Client(MQTT_CLIENT_ID)
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set(MQTT_USER, MQTT_PASS)
client.connect_async(MQTT_HOST, MQTT_PORT)
# Main loop ----------------------------------------------------------------
signal.signal(signal.SIGTERM, signal_handler)
ui.update()
client.loop_start()
print "mainloop.."
while True:
if connected:
status_line_2.set('Status: ' + current_state)
ui.process_input()
ui.update()
else:
status_line_2.set('Connecting to MQTT...')
ui.update()
try:
client.reconnect()
except:
print "MQTT reconnect failed. Retrying..."
if PIR_GPIO_PIN:
motion.check()
sleep(0.005) if pin_input_string else sleep(0.02)