Skip to content

Commit 7d0ee63

Browse files
committed
KeypadInterface - add beep, buzzer, tone control, add MQTT example, add key data buffering #200
1 parent b443596 commit 7d0ee63

File tree

12 files changed

+1953
-302
lines changed

12 files changed

+1953
-302
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/*
2+
* DSC Keypad Interface-MQTT 1.1 (Arduino with Ethernet)
3+
*
4+
* Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of
5+
* DSC keypads as physical inputs for any general purpose.
6+
*
7+
* This interface uses a different wiring setup from the standard Keybus interface, adding
8+
* an NPN transistor on dscClockPin. The DSC keypads require a 12v DC power source, though
9+
* lower voltages down to 7v may work for key presses (the LEDs will be dim).
10+
*
11+
* Supported features:
12+
* - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key
13+
* - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc
14+
* - Set keypad beeps, 1-128: dsc.beep(3)
15+
* - Set keypad buzzer in seconds, 1-255: dsc.tone(5)
16+
* - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps:
17+
* 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4)
18+
* 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2)
19+
* Disable the tone: dsc.tone() or dsc.tone(0, false, 0)
20+
*
21+
* Release notes:
22+
* 1.1 - Add keypad beep, buzzer, constant tone
23+
* 1.0 - Initial release
24+
*
25+
* Wiring:
26+
* DSC Keypad R --- 12v DC
27+
*
28+
* DSC Keypad B --- Arduino ground
29+
*
30+
* DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC
31+
* |
32+
* +--- NPN collector --\
33+
* |-- NPN base --- 1k ohm resistor --- dscClockPin // Arduino Uno: 3
34+
* Ground --- NPN emitter --/
35+
*
36+
* DSC Keypad G ---+--- 1k ohm resistor --- 12v DC
37+
* |
38+
* +--- 15k ohm resistor ---+--- dscReadPin // Arduino Uno: 5
39+
* | |
40+
* | +--- 10k ohm resistor --- Ground
41+
* |
42+
* +--- NPN collector --\
43+
* |-- NPN base --- 1k ohm resistor --- dscWritePin // Arduino Uno: 6
44+
* Ground --- NPN emitter --/
45+
*
46+
* The keypad interface uses NPN transistors to pull the clock and data lines low - most small
47+
* signal NPN transistors should be suitable, for example:
48+
* - 2N3904
49+
* - BC547, BC548, BC549
50+
*
51+
* Issues and (especially) pull requests are welcome:
52+
* https://github.com/taligentx/dscKeybusInterface
53+
*
54+
* This example code is in the public domain.
55+
*/
56+
#define dscKeypad
57+
58+
#include <SPI.h>
59+
#include <Ethernet.h>
60+
#include <PubSubClient.h>
61+
#include <dscKeybusInterface.h>
62+
63+
// Settings
64+
byte mac[] = { 0xAA, 0x61, 0x0A, 0x00, 0x00, 0x01 }; // Set a MAC address unique to the local network
65+
const char* mqttServer = ""; // MQTT server domain name or IP address
66+
const int mqttPort = 1883; // MQTT server port
67+
const char* mqttUsername = ""; // Optional, leave blank if not required
68+
const char* mqttPassword = ""; // Optional, leave blank if not required
69+
70+
// MQTT topics
71+
const char* mqttClientName = "dscKeypadInterface";
72+
const char* mqttKeyTopic = "dsc/Key"; // Sends keypad keys
73+
const char* mqttSubscribeTopic = "dsc/Set"; // Receives messages to send to the keypad
74+
75+
// Configures the Keybus interface with the specified pins
76+
#define dscClockPin 3 // Arduino Uno hardware interrupt pin: 2,3
77+
#define dscReadPin 5 // Arduino Uno: 2-12
78+
#define dscWritePin 6 // Arduino Uno: 2-12
79+
80+
// Initialize components
81+
dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin);
82+
bool lightOff, lightBlink, inputReceived;
83+
const byte inputLimit = 50;
84+
char input[inputLimit];
85+
byte beepLength, buzzerLength, toneLength;
86+
EthernetClient ipClient;
87+
PubSubClient mqtt(mqttServer, mqttPort, ipClient);
88+
unsigned long mqttPreviousTime;
89+
90+
91+
void setup() {
92+
Serial.begin(115200);
93+
delay(1000);
94+
Serial.println();
95+
Serial.println();
96+
97+
// Initializes ethernet with DHCP
98+
Serial.print(F("Ethernet...."));
99+
while(!Ethernet.begin(mac)) {
100+
Serial.println(F("DHCP failed. Retrying..."));
101+
delay(5000);
102+
}
103+
Serial.print(F("connected: "));
104+
Serial.println(Ethernet.localIP());
105+
106+
mqtt.setCallback(mqttCallback);
107+
if (mqttConnect()) mqttPreviousTime = millis();
108+
else mqttPreviousTime = 0;
109+
110+
Serial.print(F("Keybus..."));
111+
dsc.begin();
112+
Serial.println(F("connected."));
113+
Serial.println(F("DSC Keypad Interface is online."));
114+
}
115+
116+
void loop() {
117+
mqttHandle();
118+
119+
/*
120+
* Sets keypad status via serial with the listed keys. Light status uses custom
121+
* values for control: off, on, blink (example: dsc.lightReady = blink;)
122+
*
123+
* Light on: Send the keys listed below. Turning on the armed light: "a"
124+
* Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4"
125+
* Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r"
126+
* Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2"
127+
* Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5"
128+
* Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s
129+
* Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4"
130+
* Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2"
131+
* Disabling the tone pattern: "n"
132+
*/
133+
if (inputReceived) {
134+
inputReceived = false;
135+
136+
for (byte i = 0; i < strlen(input); i++) {
137+
switch (input[i]) {
138+
case 'r': case 'R': dsc.lightReady = setLight(); break;
139+
case 'a': case 'A': dsc.lightArmed = setLight(); break;
140+
case 'm': case 'M': dsc.lightMemory = setLight(); break;
141+
case 'y': case 'Y': dsc.lightBypass = setLight(); break;
142+
case 't': case 'T': dsc.lightTrouble = setLight(); break;
143+
case 'p': case 'P': dsc.lightProgram = setLight(); break;
144+
case 'f': case 'F': dsc.lightFire = setLight(); break;
145+
case 'l': case 'L': dsc.lightBacklight = setLight(); break;
146+
case '1': dsc.lightZone1 = setLight(); break;
147+
case '2': dsc.lightZone2 = setLight(); break;
148+
case '3': dsc.lightZone3 = setLight(); break;
149+
case '4': dsc.lightZone4 = setLight(); break;
150+
case '5': dsc.lightZone5 = setLight(); break;
151+
case '6': dsc.lightZone6 = setLight(); break;
152+
case '7': dsc.lightZone7 = setLight(); break;
153+
case '8': dsc.lightZone8 = setLight(); break;
154+
case 'b': case 'B': sendBeeps(i); i += beepLength; break;
155+
case 'n': case 'N': sendTone(i); i+= toneLength; break;
156+
case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break;
157+
case '-': lightOff = true; break;
158+
case '!': lightBlink = true; break;
159+
default: break;
160+
}
161+
}
162+
}
163+
164+
dsc.loop();
165+
166+
// Checks for a keypad key press
167+
if (dsc.keyAvailable) {
168+
dsc.keyAvailable = false;
169+
switch (dsc.key) {
170+
case 0x00: mqtt.publish(mqttKeyTopic, "0", false); break;
171+
case 0x05: mqtt.publish(mqttKeyTopic, "1", false); break;
172+
case 0x0A: mqtt.publish(mqttKeyTopic, "2", false); break;
173+
case 0x0F: mqtt.publish(mqttKeyTopic, "3", false); break;
174+
case 0x11: mqtt.publish(mqttKeyTopic, "4", false); break;
175+
case 0x16: mqtt.publish(mqttKeyTopic, "5", false); break;
176+
case 0x1B: mqtt.publish(mqttKeyTopic, "6", false); break;
177+
case 0x1C: mqtt.publish(mqttKeyTopic, "7", false); break;
178+
case 0x22: mqtt.publish(mqttKeyTopic, "8", false); break;
179+
case 0x27: mqtt.publish(mqttKeyTopic, "9", false); break;
180+
case 0x28: mqtt.publish(mqttKeyTopic, "*", false); break;
181+
case 0x2D: mqtt.publish(mqttKeyTopic, "#", false); break;
182+
case 0x82: mqtt.publish(mqttKeyTopic, "Enter", false); break;
183+
case 0xAF: mqtt.publish(mqttKeyTopic, "Arm: Stay", false); break;
184+
case 0xB1: mqtt.publish(mqttKeyTopic, "Arm: Away", false); break;
185+
case 0xBB: mqtt.publish(mqttKeyTopic, "Door chime", false); break;
186+
case 0xDA: mqtt.publish(mqttKeyTopic, "Reset", false); break;
187+
case 0xE1: mqtt.publish(mqttKeyTopic, "Quick exit", false); break;
188+
case 0xF7: mqtt.publish(mqttKeyTopic, "Menu navigation", false); break;
189+
case 0x0B: mqtt.publish(mqttKeyTopic, "Fire alarm", false); break;
190+
case 0x0D: mqtt.publish(mqttKeyTopic, "Aux alarm", false); break;
191+
case 0x0E: mqtt.publish(mqttKeyTopic, "Panic alarm", false); break;
192+
}
193+
mqtt.subscribe(mqttSubscribeTopic);
194+
}
195+
}
196+
197+
198+
// Parse the number of beeps from the input
199+
void sendBeeps(byte position) {
200+
char inputNumber[4];
201+
byte beeps = 0;
202+
beepLength = 0;
203+
204+
for (byte i = position + 1; i < strlen(input); i++) {
205+
if (input[i] >= '0' && input[i] <= '9') {
206+
inputNumber[beepLength] = input[i];
207+
beepLength++;
208+
if (beepLength >= 3) break;
209+
}
210+
else break;
211+
}
212+
213+
inputNumber[beepLength] = '\0';
214+
beeps = atoi(inputNumber);
215+
if (beeps > 128) beeps = 128;
216+
217+
dsc.beep(beeps);
218+
}
219+
220+
221+
// Parse the buzzer length in seconds from the input
222+
void sendBuzzer(byte position) {
223+
char inputNumber[4];
224+
byte buzzerSeconds = 0;
225+
buzzerLength = 0;
226+
227+
for (byte i = position + 1; i < strlen(input); i++) {
228+
if (input[i] >= '0' && input[i] <= '9') {
229+
inputNumber[buzzerLength] = input[i];
230+
buzzerLength++;
231+
if (buzzerLength >= 3) break;
232+
}
233+
else break;
234+
}
235+
236+
inputNumber[buzzerLength] = '\0';
237+
buzzerSeconds = atoi(inputNumber);
238+
dsc.buzzer(buzzerSeconds);
239+
}
240+
241+
242+
// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input
243+
void sendTone(byte position) {
244+
byte beeps = 0, interval = 0, intervalLength = 0;
245+
char beepNumber[2];
246+
bool toneState;
247+
char intervalNumber[3];
248+
toneLength = 0;
249+
250+
if (strlen(input) < 4) {
251+
dsc.tone(0, false, 0);
252+
return;
253+
}
254+
255+
// Parse beeps 0-7
256+
if (input[position + 1] >= '0' && input[position + 1] <= '9') {
257+
beepNumber[0] = input[position + 1];
258+
beeps = atoi(beepNumber);
259+
if (beeps > 7) beeps = 7;
260+
toneLength++;
261+
}
262+
else return;
263+
264+
// Parse constant tone value
265+
switch (input[position + 2]) {
266+
case 't':
267+
case 'T': toneState = true; toneLength++; break;
268+
case 'f':
269+
case 'F': toneState = false; toneLength++; break;
270+
default: toneLength--; return;
271+
}
272+
273+
// Parse interval
274+
for (byte i = position + 3; i < strlen(input); i++) {
275+
if (input[i] >= '0' && input[i] <= '9') {
276+
intervalNumber[intervalLength] = input[i];
277+
intervalLength++;
278+
toneLength++;
279+
if (intervalLength >= 2) break;
280+
}
281+
else break;
282+
}
283+
intervalNumber[intervalLength] = '\0';
284+
interval = atoi(intervalNumber);
285+
if (interval > 15) interval = 15;
286+
287+
dsc.tone(beeps, toneState, interval);
288+
}
289+
290+
291+
// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;)
292+
Light setLight() {
293+
if (lightOff) {
294+
lightOff = false;
295+
return off;
296+
}
297+
else if (lightBlink) {
298+
lightBlink = false;
299+
return blink;
300+
}
301+
else return on;
302+
}
303+
304+
305+
// Handles messages received in the mqttSubscribeTopic
306+
void mqttCallback(char* topic, byte* payload, unsigned int length) {
307+
308+
// Handles unused parameters
309+
(void)topic;
310+
311+
for (unsigned int i = 0; i < length; i++) {
312+
input[i] = payload[i];
313+
}
314+
315+
input[length] = '\0';
316+
if (input[0] == '\0') inputReceived = false;
317+
else inputReceived = true;
318+
}
319+
320+
321+
void mqttHandle() {
322+
if (!mqtt.connected()) {
323+
unsigned long mqttCurrentTime = millis();
324+
if (mqttCurrentTime - mqttPreviousTime > 5000) {
325+
mqttPreviousTime = mqttCurrentTime;
326+
if (mqttConnect()) {
327+
Serial.println(F("MQTT disconnected, successfully reconnected."));
328+
mqttPreviousTime = 0;
329+
mqtt.subscribe(mqttSubscribeTopic);
330+
}
331+
else Serial.println(F("MQTT disconnected, failed to reconnect."));
332+
}
333+
}
334+
else mqtt.loop();
335+
}
336+
337+
338+
bool mqttConnect() {
339+
Serial.print(F("MQTT...."));
340+
if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) {
341+
Serial.print(F("connected: "));
342+
Serial.println(mqttServer);
343+
mqtt.subscribe(mqttSubscribeTopic);
344+
}
345+
else {
346+
Serial.print(F("connection error: "));
347+
Serial.println(mqttServer);
348+
}
349+
return mqtt.connected();
350+
}

0 commit comments

Comments
 (0)