|
| 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