diff --git a/examples/Arduino/KeypadInterface-MQTT/KeypadInterface-MQTT.ino b/examples/Arduino/KeypadInterface-MQTT/KeypadInterface-MQTT.ino new file mode 100644 index 0000000..ca131e3 --- /dev/null +++ b/examples/Arduino/KeypadInterface-MQTT/KeypadInterface-MQTT.ino @@ -0,0 +1,350 @@ +/* + * DSC Keypad Interface-MQTT 1.1 (Arduino with Ethernet) + * + * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of + * DSC keypads as physical inputs for any general purpose. + * + * This interface uses a different wiring setup from the standard Keybus interface, adding + * an NPN transistor on dscClockPin. The DSC keypads require a 12v DC power source, though + * lower voltages down to 7v may work for key presses (the LEDs will be dim). + * + * Supported features: + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) + * + * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone + * 1.0 - Initial release + * + * Wiring: + * DSC Keypad R --- 12v DC + * + * DSC Keypad B --- Arduino ground + * + * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscClockPin // Arduino Uno: 3 + * Ground --- NPN emitter --/ + * + * DSC Keypad G ---+--- 1k ohm resistor --- 12v DC + * | + * +--- 15k ohm resistor ---+--- dscReadPin // Arduino Uno: 5 + * | | + * | +--- 10k ohm resistor --- Ground + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscWritePin // Arduino Uno: 6 + * Ground --- NPN emitter --/ + * + * The keypad interface uses NPN transistors to pull the clock and data lines low - most small + * signal NPN transistors should be suitable, for example: + * - 2N3904 + * - BC547, BC548, BC549 + * + * Issues and (especially) pull requests are welcome: + * https://github.com/taligentx/dscKeybusInterface + * + * This example code is in the public domain. + */ +#define dscKeypad + +#include +#include +#include +#include + +// Settings +byte mac[] = { 0xAA, 0x61, 0x0A, 0x00, 0x00, 0x01 }; // Set a MAC address unique to the local network +const char* mqttServer = ""; // MQTT server domain name or IP address +const int mqttPort = 1883; // MQTT server port +const char* mqttUsername = ""; // Optional, leave blank if not required +const char* mqttPassword = ""; // Optional, leave blank if not required + +// MQTT topics +const char* mqttClientName = "dscKeypadInterface"; +const char* mqttKeyTopic = "dsc/Key"; // Sends keypad keys +const char* mqttSubscribeTopic = "dsc/Set"; // Receives messages to send to the keypad + +// Configures the Keybus interface with the specified pins +#define dscClockPin 3 // Arduino Uno hardware interrupt pin: 2,3 +#define dscReadPin 5 // Arduino Uno: 2-12 +#define dscWritePin 6 // Arduino Uno: 2-12 + +// Initialize components +dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 50; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; +EthernetClient ipClient; +PubSubClient mqtt(mqttServer, mqttPort, ipClient); +unsigned long mqttPreviousTime; + + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println(); + Serial.println(); + + // Initializes ethernet with DHCP + Serial.print(F("Ethernet....")); + while(!Ethernet.begin(mac)) { + Serial.println(F("DHCP failed. Retrying...")); + delay(5000); + } + Serial.print(F("connected: ")); + Serial.println(Ethernet.localIP()); + + mqtt.setCallback(mqttCallback); + if (mqttConnect()) mqttPreviousTime = millis(); + else mqttPreviousTime = 0; + + Serial.print(F("Keybus...")); + dsc.begin(); + Serial.println(F("connected.")); + Serial.println(F("DSC Keypad Interface is online.")); +} + +void loop() { + mqttHandle(); + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } + } + } + + dsc.loop(); + + // Checks for a keypad key press + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { + case 0x00: mqtt.publish(mqttKeyTopic, "0", false); break; + case 0x05: mqtt.publish(mqttKeyTopic, "1", false); break; + case 0x0A: mqtt.publish(mqttKeyTopic, "2", false); break; + case 0x0F: mqtt.publish(mqttKeyTopic, "3", false); break; + case 0x11: mqtt.publish(mqttKeyTopic, "4", false); break; + case 0x16: mqtt.publish(mqttKeyTopic, "5", false); break; + case 0x1B: mqtt.publish(mqttKeyTopic, "6", false); break; + case 0x1C: mqtt.publish(mqttKeyTopic, "7", false); break; + case 0x22: mqtt.publish(mqttKeyTopic, "8", false); break; + case 0x27: mqtt.publish(mqttKeyTopic, "9", false); break; + case 0x28: mqtt.publish(mqttKeyTopic, "*", false); break; + case 0x2D: mqtt.publish(mqttKeyTopic, "#", false); break; + case 0x82: mqtt.publish(mqttKeyTopic, "Enter", false); break; + case 0xAF: mqtt.publish(mqttKeyTopic, "Arm: Stay", false); break; + case 0xB1: mqtt.publish(mqttKeyTopic, "Arm: Away", false); break; + case 0xBB: mqtt.publish(mqttKeyTopic, "Door chime", false); break; + case 0xDA: mqtt.publish(mqttKeyTopic, "Reset", false); break; + case 0xE1: mqtt.publish(mqttKeyTopic, "Quick exit", false); break; + case 0xF7: mqtt.publish(mqttKeyTopic, "Menu navigation", false); break; + case 0x0B: mqtt.publish(mqttKeyTopic, "Fire alarm", false); break; + case 0x0D: mqtt.publish(mqttKeyTopic, "Aux alarm", false); break; + case 0x0E: mqtt.publish(mqttKeyTopic, "Panic alarm", false); break; + } + mqtt.subscribe(mqttSubscribeTopic); + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; + } + else break; + } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); +} + + +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { + if (lightOff) { + lightOff = false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Handles messages received in the mqttSubscribeTopic +void mqttCallback(char* topic, byte* payload, unsigned int length) { + + // Handles unused parameters + (void)topic; + + for (unsigned int i = 0; i < length; i++) { + input[i] = payload[i]; + } + + input[length] = '\0'; + if (input[0] == '\0') inputReceived = false; + else inputReceived = true; +} + + +void mqttHandle() { + if (!mqtt.connected()) { + unsigned long mqttCurrentTime = millis(); + if (mqttCurrentTime - mqttPreviousTime > 5000) { + mqttPreviousTime = mqttCurrentTime; + if (mqttConnect()) { + Serial.println(F("MQTT disconnected, successfully reconnected.")); + mqttPreviousTime = 0; + mqtt.subscribe(mqttSubscribeTopic); + } + else Serial.println(F("MQTT disconnected, failed to reconnect.")); + } + } + else mqtt.loop(); +} + + +bool mqttConnect() { + Serial.print(F("MQTT....")); + if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { + Serial.print(F("connected: ")); + Serial.println(mqttServer); + mqtt.subscribe(mqttSubscribeTopic); + } + else { + Serial.print(F("connection error: ")); + Serial.println(mqttServer); + } + return mqtt.connected(); +} diff --git a/examples/Arduino/KeypadInterface/KeypadInterface.ino b/examples/Arduino/KeypadInterface/KeypadInterface.ino index 5bd06db..b4306f4 100644 --- a/examples/Arduino/KeypadInterface/KeypadInterface.ino +++ b/examples/Arduino/KeypadInterface/KeypadInterface.ino @@ -1,5 +1,5 @@ /* - * DSC Keypad Interface 1.0 (Arduino) + * DSC Keypad Interface 1.1 (Arduino) * * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of * DSC keypads as physical inputs for any general purpose. @@ -9,16 +9,23 @@ * lower voltages down to 7v may work for key presses (the LEDs will be dim). * * Supported features: - * - Read keypad key button presses, including fire/aux/panic alarms - * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8 + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) * * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone * 1.0 - Initial release * * Wiring: * DSC Keypad R --- 12v DC * - * DSC Keypad B --- Ground + * DSC Keypad B --- Arduino ground * * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC * | @@ -50,12 +57,19 @@ #include +// Configures the Keybus interface with the specified pins #define dscClockPin 3 // Arduino Uno hardware interrupt pin: 2,3 #define dscReadPin 5 // Arduino Uno: 2-12 #define dscWritePin 6 // Arduino Uno: 2-12 dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool lightOff; + +// Initialize components +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 50; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; + void setup() { Serial.begin(115200); @@ -63,57 +77,67 @@ void setup() { Serial.println(); Serial.println(); - dsc.begin(); Serial.print(F("Keybus...")); - unsigned long keybusTime = millis(); - while (millis() - keybusTime < 4000) { // Waits for the keypad to be powered on - if (!digitalRead(dscReadPin)) keybusTime = millis(); - } + dsc.begin(); Serial.println(F("connected.")); - Serial.println(F("DSC Keybus Interface is online.")); + Serial.println(F("DSC Keypad Interface is online.")); } void loop() { - // Sets keypad lights via serial, send "-" before a light key to turn off - if (Serial.available() > 0) { - char input = Serial.read(); - switch (input) { - case '-': lightOff = true; break; - case '1': dsc.Zone1 = setLight(); break; - case '2': dsc.Zone2 = setLight(); break; - case '3': dsc.Zone3 = setLight(); break; - case '4': dsc.Zone4 = setLight(); break; - case '5': dsc.Zone5 = setLight(); break; - case '6': dsc.Zone6 = setLight(); break; - case '7': dsc.Zone7 = setLight(); break; - case '8': dsc.Zone8 = setLight(); break; - case 'r': - case 'R': dsc.Ready = setLight(); break; - case 'a': - case 'A': dsc.Armed = setLight(); break; - case 'm': - case 'M': dsc.Memory = setLight(); break; - case 'b': - case 'B': dsc.Bypass = setLight(); break; - case 't': - case 'T': dsc.Trouble = setLight(); break; - case 'p': - case 'P': dsc.Program = setLight(); break; - case 'f': - case 'F': dsc.Fire = setLight(); break; - case 'l': - case 'L': dsc.Backlight = setLight(); break; - default: break; + inputSerial(); // Stores Serial data in input[], requires a newline character (NL, CR, or both) + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } } } dsc.loop(); // Checks for a keypad key press - if (dsc.KeyAvailable) { - dsc.KeyAvailable = false; - switch (dsc.Key) { + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { case 0x00: Serial.println("0"); break; case 0x05: Serial.println("1"); break; case 0x0A: Serial.println("2"); break; @@ -136,16 +160,133 @@ void loop() { case 0x0B: Serial.println(F("Fire alarm")); break; case 0x0D: Serial.println(F("Aux alarm")); break; case 0x0E: Serial.println(F("Panic alarm")); break; + default: break; + } + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; } + else break; } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); } -// Sets keypad lights state -bool setLight() { +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { if (lightOff) { lightOff = false; - return false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Stores Serial data in input[], requires a newline character (NL, CR, or both) +void inputSerial() { + static byte inputCount = 0; + if (!inputReceived) { + while (Serial.available() > 0 && inputCount < inputLimit) { + input[inputCount] = Serial.read(); + if (input[inputCount] == '\n' || input[inputCount] == '\r') { + input[inputCount] = '\0'; + inputCount = 0; + inputReceived = true; + break; + } + else inputCount++; + } + if (input[0] == '\0') inputReceived = false; } - else return true; } diff --git a/examples/esp32/KeypadInterface-MQTT/KeypadInterface-MQTT.ino b/examples/esp32/KeypadInterface-MQTT/KeypadInterface-MQTT.ino new file mode 100644 index 0000000..0e8b575 --- /dev/null +++ b/examples/esp32/KeypadInterface-MQTT/KeypadInterface-MQTT.ino @@ -0,0 +1,351 @@ +/* + * DSC Keypad Interface-MQTT 1.1 (esp32) + * + * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of + * DSC keypads as physical inputs for any general purpose. + * + * This interface uses a different wiring setup from the standard Keybus interface, adding + * an NPN transistor on dscClockPin. The DSC keypads require a 12v DC power source, though + * lower voltages down to 7v may work for key presses (the LEDs will be dim). + * + * Supported features: + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) + * + * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone + * 1.0 - Initial release + * + * Wiring: + * DSC Keypad R --- 12v DC + * + * DSC Keypad B --- esp32 ground + * + * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscClockPin // esp32: 18 + * Ground --- NPN emitter --/ + * + * DSC Keypad G ---+--- 1k ohm resistor --- 12v DC + * | + * +--- 33k ohm resistor ---+--- dscReadPin // esp32: 19 + * | | + * | +--- 10k ohm resistor --- Ground + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscWritePin // esp32: 21 + * Ground --- NPN emitter --/ + * + * The keypad interface uses NPN transistors to pull the clock and data lines low - most small + * signal NPN transistors should be suitable, for example: + * - 2N3904 + * - BC547, BC548, BC549 + * + * Issues and (especially) pull requests are welcome: + * https://github.com/taligentx/dscKeybusInterface + * + * This example code is in the public domain. + */ +#define dscKeypad + +#include +#include +#include + +// Settings +const char* wifiSSID = ""; +const char* wifiPassword = ""; +const char* mqttServer = ""; // MQTT server domain name or IP address +const int mqttPort = 1883; // MQTT server port +const char* mqttUsername = ""; // Optional, leave blank if not required +const char* mqttPassword = ""; // Optional, leave blank if not required + +// MQTT topics +const char* mqttClientName = "dscKeypadInterface"; +const char* mqttKeyTopic = "dsc/Key"; // Sends keypad keys +const char* mqttSubscribeTopic = "dsc/Set"; // Receives messages to send to the keypad + +// Configures the Keybus interface with the specified pins +#define dscClockPin 18 // 4,13,16-39 +#define dscReadPin 19 // 4,13,16-39 +#define dscWritePin 21 // 4,13,16-33 + +// Initialize components +dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 255; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; +WiFiClient ipClient; +PubSubClient mqtt(mqttServer, mqttPort, ipClient); +unsigned long mqttPreviousTime; + + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println(); + Serial.println(); + + Serial.print(F("WiFi")); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSSID, wifiPassword); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); + + mqtt.setCallback(mqttCallback); + if (mqttConnect()) mqttPreviousTime = millis(); + else mqttPreviousTime = 0; + + Serial.print(F("Keybus...")); + dsc.begin(); + Serial.println(F("connected.")); + Serial.println(F("DSC Keypad Interface is online.")); +} + +void loop() { + mqttHandle(); + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } + } + } + + dsc.loop(); + + // Checks for a keypad key press + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { + case 0x00: mqtt.publish(mqttKeyTopic, "0", false); break; + case 0x05: mqtt.publish(mqttKeyTopic, "1", false); break; + case 0x0A: mqtt.publish(mqttKeyTopic, "2", false); break; + case 0x0F: mqtt.publish(mqttKeyTopic, "3", false); break; + case 0x11: mqtt.publish(mqttKeyTopic, "4", false); break; + case 0x16: mqtt.publish(mqttKeyTopic, "5", false); break; + case 0x1B: mqtt.publish(mqttKeyTopic, "6", false); break; + case 0x1C: mqtt.publish(mqttKeyTopic, "7", false); break; + case 0x22: mqtt.publish(mqttKeyTopic, "8", false); break; + case 0x27: mqtt.publish(mqttKeyTopic, "9", false); break; + case 0x28: mqtt.publish(mqttKeyTopic, "*", false); break; + case 0x2D: mqtt.publish(mqttKeyTopic, "#", false); break; + case 0x82: mqtt.publish(mqttKeyTopic, "Enter", false); break; + case 0xAF: mqtt.publish(mqttKeyTopic, "Arm: Stay", false); break; + case 0xB1: mqtt.publish(mqttKeyTopic, "Arm: Away", false); break; + case 0xBB: mqtt.publish(mqttKeyTopic, "Door chime", false); break; + case 0xDA: mqtt.publish(mqttKeyTopic, "Reset", false); break; + case 0xE1: mqtt.publish(mqttKeyTopic, "Quick exit", false); break; + case 0xF7: mqtt.publish(mqttKeyTopic, "Menu navigation", false); break; + case 0x0B: mqtt.publish(mqttKeyTopic, "Fire alarm", false); break; + case 0x0D: mqtt.publish(mqttKeyTopic, "Aux alarm", false); break; + case 0x0E: mqtt.publish(mqttKeyTopic, "Panic alarm", false); break; + } + mqtt.subscribe(mqttSubscribeTopic); + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; + } + else break; + } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); +} + + +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { + if (lightOff) { + lightOff = false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Handles messages received in the mqttSubscribeTopic +void mqttCallback(char* topic, byte* payload, unsigned int length) { + + // Handles unused parameters + (void)topic; + + for (unsigned int i = 0; i < length; i++) { + input[i] = payload[i]; + } + + input[length] = '\0'; + if (input[0] == '\0') inputReceived = false; + else inputReceived = true; +} + + +void mqttHandle() { + if (!mqtt.connected()) { + unsigned long mqttCurrentTime = millis(); + if (mqttCurrentTime - mqttPreviousTime > 5000) { + mqttPreviousTime = mqttCurrentTime; + if (mqttConnect()) { + Serial.println(F("MQTT disconnected, successfully reconnected.")); + mqttPreviousTime = 0; + mqtt.subscribe(mqttSubscribeTopic); + } + else Serial.println(F("MQTT disconnected, failed to reconnect.")); + } + } + else mqtt.loop(); +} + + +bool mqttConnect() { + Serial.print(F("MQTT....")); + if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { + Serial.print(F("connected: ")); + Serial.println(mqttServer); + mqtt.subscribe(mqttSubscribeTopic); + } + else { + Serial.print(F("connection error: ")); + Serial.println(mqttServer); + } + return mqtt.connected(); +} diff --git a/examples/esp32/KeypadInterface/KeypadInterface.ino b/examples/esp32/KeypadInterface/KeypadInterface.ino index 581ee0a..0cbe79f 100644 --- a/examples/esp32/KeypadInterface/KeypadInterface.ino +++ b/examples/esp32/KeypadInterface/KeypadInterface.ino @@ -1,5 +1,5 @@ /* - * DSC Keypad Interface 1.0 (esp32) + * DSC Keypad Interface 1.1 (esp32) * * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of * DSC keypads as physical inputs for any general purpose. @@ -9,16 +9,23 @@ * lower voltages down to 7v may work for key presses (the LEDs will be dim). * * Supported features: - * - Read keypad key button presses, including fire/aux/panic alarms - * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8 + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) * * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone * 1.0 - Initial release * * Wiring: * DSC Keypad R --- 12v DC * - * DSC Keypad B --- Ground + * DSC Keypad B --- esp32 ground * * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC * | @@ -50,12 +57,19 @@ #include +// Configures the Keybus interface with the specified pins #define dscClockPin 18 // 4,13,16-39 #define dscReadPin 19 // 4,13,16-39 #define dscWritePin 21 // 4,13,16-33 dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool lightOff; + +// Initialize components +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 50; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; + void setup() { Serial.begin(115200); @@ -63,58 +77,67 @@ void setup() { Serial.println(); Serial.println(); - dsc.begin(); Serial.print(F("Keybus...")); - unsigned long keybusTime = millis(); - while (millis() - keybusTime < 4000) { // Waits for the keypad to be powered on - if (!digitalRead(dscReadPin)) keybusTime = millis(); - yield(); - } + dsc.begin(); Serial.println(F("connected.")); - Serial.println(F("DSC Keybus Interface is online.")); + Serial.println(F("DSC Keypad Interface is online.")); } void loop() { - // Sets keypad lights via serial, send "-" before a light key to turn off - if (Serial.available() > 0) { - char input = Serial.read(); - switch (input) { - case '-': lightOff = true; break; - case '1': dsc.Zone1 = setLight(); break; - case '2': dsc.Zone2 = setLight(); break; - case '3': dsc.Zone3 = setLight(); break; - case '4': dsc.Zone4 = setLight(); break; - case '5': dsc.Zone5 = setLight(); break; - case '6': dsc.Zone6 = setLight(); break; - case '7': dsc.Zone7 = setLight(); break; - case '8': dsc.Zone8 = setLight(); break; - case 'r': - case 'R': dsc.Ready = setLight(); break; - case 'a': - case 'A': dsc.Armed = setLight(); break; - case 'm': - case 'M': dsc.Memory = setLight(); break; - case 'b': - case 'B': dsc.Bypass = setLight(); break; - case 't': - case 'T': dsc.Trouble = setLight(); break; - case 'p': - case 'P': dsc.Program = setLight(); break; - case 'f': - case 'F': dsc.Fire = setLight(); break; - case 'l': - case 'L': dsc.Backlight = setLight(); break; - default: break; + inputSerial(); // Stores Serial data in input[], requires a newline character (NL, CR, or both) + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } } } dsc.loop(); // Checks for a keypad key press - if (dsc.KeyAvailable) { - dsc.KeyAvailable = false; - switch (dsc.Key) { + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { case 0x00: Serial.println("0"); break; case 0x05: Serial.println("1"); break; case 0x0A: Serial.println("2"); break; @@ -137,16 +160,134 @@ void loop() { case 0x0B: Serial.println(F("Fire alarm")); break; case 0x0D: Serial.println(F("Aux alarm")); break; case 0x0E: Serial.println(F("Panic alarm")); break; + default: break; + } + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; } + else break; } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); } -// Sets keypad lights state -bool setLight() { +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { if (lightOff) { lightOff = false; - return false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Stores Serial data in input[], requires a newline character (NL, CR, or both) +void inputSerial() { + static byte inputCount = 0; + if (!inputReceived) { + while (Serial.available() > 0 && inputCount < inputLimit) { + input[inputCount] = Serial.read(); + if (input[inputCount] == '\n' || input[inputCount] == '\r') { + input[inputCount] = '\0'; + inputCount = 0; + inputReceived = true; + break; + } + else inputCount++; + yield(); + } + if (input[0] == '\0') inputReceived = false; } - else return true; } diff --git a/examples/esp8266/KeypadInterface-MQTT/KeypadInterface-MQTT.ino b/examples/esp8266/KeypadInterface-MQTT/KeypadInterface-MQTT.ino new file mode 100644 index 0000000..4d87ffb --- /dev/null +++ b/examples/esp8266/KeypadInterface-MQTT/KeypadInterface-MQTT.ino @@ -0,0 +1,351 @@ +/* + * DSC Keypad Interface-MQTT 1.1 (esp8266) + * + * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of + * DSC keypads as physical inputs for any general purpose. + * + * This interface uses a different wiring setup from the standard Keybus interface, adding + * an NPN transistor on dscClockPin. The DSC keypads require a 12v DC power source, though + * lower voltages down to 7v may work for key presses (the LEDs will be dim). + * + * Supported features: + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) + * + * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone + * 1.0 - Initial release + * + * Wiring: + * DSC Keypad R --- 12v DC + * + * DSC Keypad B --- esp8266 ground + * + * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscClockPin // esp8266: D1, GPIO 5 + * Ground --- NPN emitter --/ + * + * DSC Keypad G ---+--- 1k ohm resistor --- 12v DC + * | + * +--- 33k ohm resistor ---+--- dscReadPin // esp8266: D2, GPIO 4 + * | | + * | +--- 10k ohm resistor --- Ground + * | + * +--- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscWritePin // esp8266: D8, GPIO 15 + * Ground --- NPN emitter --/ + * + * The keypad interface uses NPN transistors to pull the clock and data lines low - most small + * signal NPN transistors should be suitable, for example: + * - 2N3904 + * - BC547, BC548, BC549 + * + * Issues and (especially) pull requests are welcome: + * https://github.com/taligentx/dscKeybusInterface + * + * This example code is in the public domain. + */ +#define dscKeypad + +#include +#include +#include + +// Settings +const char* wifiSSID = ""; +const char* wifiPassword = ""; +const char* mqttServer = ""; // MQTT server domain name or IP address +const int mqttPort = 1883; // MQTT server port +const char* mqttUsername = ""; // Optional, leave blank if not required +const char* mqttPassword = ""; // Optional, leave blank if not required + +// MQTT topics +const char* mqttClientName = "dscKeypadInterface"; +const char* mqttKeyTopic = "dsc/Key"; // Sends keypad keys +const char* mqttSubscribeTopic = "dsc/Set"; // Receives messages to send to the keypad + +// Configures the Keybus interface with the specified pins +#define dscClockPin D1 // GPIO 5 +#define dscReadPin D2 // GPIO 4 +#define dscWritePin D8 // GPIO 15 + +// Initialize components +dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 255; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; +WiFiClient ipClient; +PubSubClient mqtt(mqttServer, mqttPort, ipClient); +unsigned long mqttPreviousTime; + + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println(); + Serial.println(); + + Serial.print(F("WiFi")); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSSID, wifiPassword); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); + + mqtt.setCallback(mqttCallback); + if (mqttConnect()) mqttPreviousTime = millis(); + else mqttPreviousTime = 0; + + Serial.print(F("Keybus...")); + dsc.begin(); + Serial.println(F("connected.")); + Serial.println(F("DSC Keypad Interface is online.")); +} + +void loop() { + mqttHandle(); + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } + } + } + + dsc.loop(); + + // Checks for a keypad key press + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { + case 0x00: mqtt.publish(mqttKeyTopic, "0", false); break; + case 0x05: mqtt.publish(mqttKeyTopic, "1", false); break; + case 0x0A: mqtt.publish(mqttKeyTopic, "2", false); break; + case 0x0F: mqtt.publish(mqttKeyTopic, "3", false); break; + case 0x11: mqtt.publish(mqttKeyTopic, "4", false); break; + case 0x16: mqtt.publish(mqttKeyTopic, "5", false); break; + case 0x1B: mqtt.publish(mqttKeyTopic, "6", false); break; + case 0x1C: mqtt.publish(mqttKeyTopic, "7", false); break; + case 0x22: mqtt.publish(mqttKeyTopic, "8", false); break; + case 0x27: mqtt.publish(mqttKeyTopic, "9", false); break; + case 0x28: mqtt.publish(mqttKeyTopic, "*", false); break; + case 0x2D: mqtt.publish(mqttKeyTopic, "#", false); break; + case 0x82: mqtt.publish(mqttKeyTopic, "Enter", false); break; + case 0xAF: mqtt.publish(mqttKeyTopic, "Arm: Stay", false); break; + case 0xB1: mqtt.publish(mqttKeyTopic, "Arm: Away", false); break; + case 0xBB: mqtt.publish(mqttKeyTopic, "Door chime", false); break; + case 0xDA: mqtt.publish(mqttKeyTopic, "Reset", false); break; + case 0xE1: mqtt.publish(mqttKeyTopic, "Quick exit", false); break; + case 0xF7: mqtt.publish(mqttKeyTopic, "Menu navigation", false); break; + case 0x0B: mqtt.publish(mqttKeyTopic, "Fire alarm", false); break; + case 0x0D: mqtt.publish(mqttKeyTopic, "Aux alarm", false); break; + case 0x0E: mqtt.publish(mqttKeyTopic, "Panic alarm", false); break; + } + mqtt.subscribe(mqttSubscribeTopic); + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; + } + else break; + } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); +} + + +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { + if (lightOff) { + lightOff = false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Handles messages received in the mqttSubscribeTopic +void mqttCallback(char* topic, byte* payload, unsigned int length) { + + // Handles unused parameters + (void)topic; + + for (unsigned int i = 0; i < length; i++) { + input[i] = payload[i]; + } + + input[length] = '\0'; + if (input[0] == '\0') inputReceived = false; + else inputReceived = true; +} + + +void mqttHandle() { + if (!mqtt.connected()) { + unsigned long mqttCurrentTime = millis(); + if (mqttCurrentTime - mqttPreviousTime > 5000) { + mqttPreviousTime = mqttCurrentTime; + if (mqttConnect()) { + Serial.println(F("MQTT disconnected, successfully reconnected.")); + mqttPreviousTime = 0; + mqtt.subscribe(mqttSubscribeTopic); + } + else Serial.println(F("MQTT disconnected, failed to reconnect.")); + } + } + else mqtt.loop(); +} + + +bool mqttConnect() { + Serial.print(F("MQTT....")); + if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { + Serial.print(F("connected: ")); + Serial.println(mqttServer); + mqtt.subscribe(mqttSubscribeTopic); + } + else { + Serial.print(F("connection error: ")); + Serial.println(mqttServer); + } + return mqtt.connected(); +} diff --git a/examples/esp8266/KeypadInterface/KeypadInterface.ino b/examples/esp8266/KeypadInterface/KeypadInterface.ino index 524c296..f5f241e 100644 --- a/examples/esp8266/KeypadInterface/KeypadInterface.ino +++ b/examples/esp8266/KeypadInterface/KeypadInterface.ino @@ -1,5 +1,5 @@ /* - * DSC Keypad Interface 1.0 (esp8266) + * DSC Keypad Interface 1.1 (esp8266) * * Interfaces directly to a DSC PowerSeries keypad (without a DSC panel) to enable use of * DSC keypads as physical inputs for any general purpose. @@ -9,16 +9,23 @@ * lower voltages down to 7v may work for key presses (the LEDs will be dim). * * Supported features: - * - Read keypad key button presses, including fire/aux/panic alarms - * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8 + * - Read keypad key button presses, including fire/aux/panic alarm keys: dsc.key + * - Set keypad lights: Ready, Armed, Trouble, Memory, Bypass, Fire, Program, Backlight, Zones 1-8: dsc.lightReady, dsc.lightZone1, etc + * - Set keypad beeps, 1-128: dsc.beep(3) + * - Set keypad buzzer in seconds, 1-255: dsc.tone(5) + * - Set keypad tone pattern with a number of beeps, an optional constant tone, and the interval in seconds between beeps: + * 2 beeps, no constant tone, 4 second interval: dsc.tone(2, false, 4) + * 3 beeps, constant tone, 2 second interval: dsc.tone(3, true, 2) + * Disable the tone: dsc.tone() or dsc.tone(0, false, 0) * * Release notes: + * 1.1 - Add keypad beep, buzzer, constant tone * 1.0 - Initial release * * Wiring: * DSC Keypad R --- 12v DC * - * DSC Keypad B --- Ground + * DSC Keypad B --- esp8266 ground * * DSC Keypad Y ---+--- 1k ohm resistor --- 12v DC * | @@ -50,12 +57,19 @@ #include +// Configures the Keybus interface with the specified pins #define dscClockPin D1 // GPIO 5 #define dscReadPin D2 // GPIO 4 #define dscWritePin D8 // GPIO 15 dscKeypadInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool lightOff; + +// Initialize components +bool lightOff, lightBlink, inputReceived; +const byte inputLimit = 50; +char input[inputLimit]; +byte beepLength, buzzerLength, toneLength; + void setup() { Serial.begin(115200); @@ -63,58 +77,67 @@ void setup() { Serial.println(); Serial.println(); - dsc.begin(); Serial.print(F("Keybus...")); - unsigned long keybusTime = millis(); - while (millis() - keybusTime < 4000) { // Waits for the keypad to be powered on - if (!digitalRead(dscReadPin)) keybusTime = millis(); - yield(); - } + dsc.begin(); Serial.println(F("connected.")); - Serial.println(F("DSC Keybus Interface is online.")); + Serial.println(F("DSC Keypad Interface is online.")); } void loop() { - // Sets keypad lights via serial, send "-" before a light key to turn off - if (Serial.available() > 0) { - char input = Serial.read(); - switch (input) { - case '-': lightOff = true; break; - case '1': dsc.Zone1 = setLight(); break; - case '2': dsc.Zone2 = setLight(); break; - case '3': dsc.Zone3 = setLight(); break; - case '4': dsc.Zone4 = setLight(); break; - case '5': dsc.Zone5 = setLight(); break; - case '6': dsc.Zone6 = setLight(); break; - case '7': dsc.Zone7 = setLight(); break; - case '8': dsc.Zone8 = setLight(); break; - case 'r': - case 'R': dsc.Ready = setLight(); break; - case 'a': - case 'A': dsc.Armed = setLight(); break; - case 'm': - case 'M': dsc.Memory = setLight(); break; - case 'b': - case 'B': dsc.Bypass = setLight(); break; - case 't': - case 'T': dsc.Trouble = setLight(); break; - case 'p': - case 'P': dsc.Program = setLight(); break; - case 'f': - case 'F': dsc.Fire = setLight(); break; - case 'l': - case 'L': dsc.Backlight = setLight(); break; - default: break; + inputSerial(); // Stores Serial data in input[], requires a newline character (NL, CR, or both) + + /* + * Sets keypad status via serial with the listed keys. Light status uses custom + * values for control: off, on, blink (example: dsc.lightReady = blink;) + * + * Light on: Send the keys listed below. Turning on the armed light: "a" + * Light off: Send "-" before a light key to turn it off. Turning off the zone 4 light: "-4" + * Light blink: Send "!" before a light key to blink. Blinking the ready light: "!r" + * Beep: Send "b" followed by the number of beeps, 1-128. Setting 2 beeps: "b2" + * Buzzer: Send "z" followed by the buzzer length in seconds, 1-255. Setting the buzzer to 5 seconds: "z5" + * Tone pattern: Send "n" followed by the number of beeps 1-7, constant tone true "t" or false "f", interval between beeps 1-15s + * Setting a tone pattern with 2 beeps, no constant tone, 4 second interval: "n2f4" + * Setting a tone pattern with 3 beeps, constant tone, 2 second interval: "n3t2" + * Disabling the tone pattern: "n" + */ + if (inputReceived) { + inputReceived = false; + + for (byte i = 0; i < strlen(input); i++) { + switch (input[i]) { + case 'r': case 'R': dsc.lightReady = setLight(); break; + case 'a': case 'A': dsc.lightArmed = setLight(); break; + case 'm': case 'M': dsc.lightMemory = setLight(); break; + case 'y': case 'Y': dsc.lightBypass = setLight(); break; + case 't': case 'T': dsc.lightTrouble = setLight(); break; + case 'p': case 'P': dsc.lightProgram = setLight(); break; + case 'f': case 'F': dsc.lightFire = setLight(); break; + case 'l': case 'L': dsc.lightBacklight = setLight(); break; + case '1': dsc.lightZone1 = setLight(); break; + case '2': dsc.lightZone2 = setLight(); break; + case '3': dsc.lightZone3 = setLight(); break; + case '4': dsc.lightZone4 = setLight(); break; + case '5': dsc.lightZone5 = setLight(); break; + case '6': dsc.lightZone6 = setLight(); break; + case '7': dsc.lightZone7 = setLight(); break; + case '8': dsc.lightZone8 = setLight(); break; + case 'b': case 'B': sendBeeps(i); i += beepLength; break; + case 'n': case 'N': sendTone(i); i+= toneLength; break; + case 'z': case 'Z': sendBuzzer(i); i+= buzzerLength; break; + case '-': lightOff = true; break; + case '!': lightBlink = true; break; + default: break; + } } } dsc.loop(); // Checks for a keypad key press - if (dsc.KeyAvailable) { - dsc.KeyAvailable = false; - switch (dsc.Key) { + if (dsc.keyAvailable) { + dsc.keyAvailable = false; + switch (dsc.key) { case 0x00: Serial.println("0"); break; case 0x05: Serial.println("1"); break; case 0x0A: Serial.println("2"); break; @@ -137,16 +160,134 @@ void loop() { case 0x0B: Serial.println(F("Fire alarm")); break; case 0x0D: Serial.println(F("Aux alarm")); break; case 0x0E: Serial.println(F("Panic alarm")); break; + default: break; + } + } +} + + +// Parse the number of beeps from the input +void sendBeeps(byte position) { + char inputNumber[4]; + byte beeps = 0; + beepLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[beepLength] = input[i]; + beepLength++; + if (beepLength >= 3) break; } + else break; } + + inputNumber[beepLength] = '\0'; + beeps = atoi(inputNumber); + if (beeps > 128) beeps = 128; + + dsc.beep(beeps); } -// Sets keypad lights state -bool setLight() { +// Parse the buzzer length in seconds from the input +void sendBuzzer(byte position) { + char inputNumber[4]; + byte buzzerSeconds = 0; + buzzerLength = 0; + + for (byte i = position + 1; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + inputNumber[buzzerLength] = input[i]; + buzzerLength++; + if (buzzerLength >= 3) break; + } + else break; + } + + inputNumber[buzzerLength] = '\0'; + buzzerSeconds = atoi(inputNumber); + dsc.buzzer(buzzerSeconds); +} + + +// Parse the tone pattern number of beeps, constant tone state, and interval in seconds from the input +void sendTone(byte position) { + byte beeps = 0, interval = 0, intervalLength = 0; + char beepNumber[2]; + bool toneState; + char intervalNumber[3]; + toneLength = 0; + + if (strlen(input) < 4) { + dsc.tone(0, false, 0); + return; + } + + // Parse beeps 0-7 + if (input[position + 1] >= '0' && input[position + 1] <= '9') { + beepNumber[0] = input[position + 1]; + beeps = atoi(beepNumber); + if (beeps > 7) beeps = 7; + toneLength++; + } + else return; + + // Parse constant tone value + switch (input[position + 2]) { + case 't': + case 'T': toneState = true; toneLength++; break; + case 'f': + case 'F': toneState = false; toneLength++; break; + default: toneLength--; return; + } + + // Parse interval + for (byte i = position + 3; i < strlen(input); i++) { + if (input[i] >= '0' && input[i] <= '9') { + intervalNumber[intervalLength] = input[i]; + intervalLength++; + toneLength++; + if (intervalLength >= 2) break; + } + else break; + } + intervalNumber[intervalLength] = '\0'; + interval = atoi(intervalNumber); + if (interval > 15) interval = 15; + + dsc.tone(beeps, toneState, interval); +} + + +// Sets keypad lights state - lights use custom values for control: off, on, blink (example: dsc.lightReady = blink;) +Light setLight() { if (lightOff) { lightOff = false; - return false; + return off; + } + else if (lightBlink) { + lightBlink = false; + return blink; + } + else return on; +} + + +// Stores Serial data in input[], requires a newline character (NL, CR, or both) +void inputSerial() { + static byte inputCount = 0; + if (!inputReceived) { + while (Serial.available() > 0 && inputCount < inputLimit) { + input[inputCount] = Serial.read(); + if (input[inputCount] == '\n' || input[inputCount] == '\r') { + input[inputCount] = '\0'; + inputCount = 0; + inputReceived = true; + break; + } + else inputCount++; + yield(); + } + if (input[0] == '\0') inputReceived = false; } - else return true; } diff --git a/keywords.txt b/keywords.txt index 95e52b6..ab824fe 100644 --- a/keywords.txt +++ b/keywords.txt @@ -76,24 +76,43 @@ status KEYWORD2 panelData KEYWORD2 pc16Data KEYWORD2 panelVersion KEYWORD2 -Zone1 KEYWORD2 -Zone2 KEYWORD2 -Zone3 KEYWORD2 -Zone4 KEYWORD2 -Zone5 KEYWORD2 -Zone6 KEYWORD2 -Zone7 KEYWORD2 -Zone8 KEYWORD2 -Ready KEYWORD2 -Armed KEYWORD2 -Memory KEYWORD2 -Bypass KEYWORD2 -Trouble KEYWORD2 -Program KEYWORD2 -Fire KEYWORD2 -Backlight KEYWORD2 -KeyAvailable KEYWORD2 -Key KEYWORD2 +lightReady KEYWORD2 +lightArmed KEYWORD2 +lightMemory KEYWORD2 +lightBypass KEYWORD2 +lightTrouble KEYWORD2 +lightProgram KEYWORD2 +lightFire KEYWORD2 +lightBacklight KEYWORD2 +blinkReady KEYWORD2 +blinkArmed KEYWORD2 +blinkMemory KEYWORD2 +blinkBypass KEYWORD2 +blinkTrouble KEYWORD2 +blinkProgram KEYWORD2 +blinkFire KEYWORD2 +blinkBacklight KEYWORD2 +lightZone1 KEYWORD2 +lightZone2 KEYWORD2 +lightZone3 KEYWORD2 +lightZone4 KEYWORD2 +lightZone5 KEYWORD2 +lightZone6 KEYWORD2 +lightZone7 KEYWORD2 +lightZone8 KEYWORD2 +blinkZone1 KEYWORD2 +blinkZone2 KEYWORD2 +blinkZone3 KEYWORD2 +blinkZone4 KEYWORD2 +blinkZone5 KEYWORD2 +blinkZone6 KEYWORD2 +blinkZone7 KEYWORD2 +blinkZone8 KEYWORD2 +keyAvailable KEYWORD2 +key KEYWORD2 +beep KEYWORD2 +tone KEYWORD2 +buzzer KEYWORD2 printPanelBinary KEYWORD2 printPanelCommand KEYWORD2 diff --git a/src/dscKeybusInterface.cpp b/src/dscKeybusInterface.cpp index 12f60fe..014eb4e 100644 --- a/src/dscKeybusInterface.cpp +++ b/src/dscKeybusInterface.cpp @@ -519,34 +519,23 @@ void dscKeybusInterface::setWriteKey(const char receivedKey) { case '9': writeKey = 0x27; break; case '*': writeKey = 0x28; if (status[writePartition - 1] < 0x9E) starKeyCheck = true; break; case '#': writeKey = 0x2D; break; - case 'F': - case 'f': writeKey = 0xBB; writeAlarm = true; break; // Keypad fire alarm - case 'b': - case 'B': writeKey = 0x82; break; // Enter event buffer - case '>': writeKey = 0x87; break; // Event buffer right arrow - case '<': writeKey = 0x88; break; // Event buffer left arrow - case 'l': - case 'L': writeKey = 0xA5; break; // LCD keypad data request - case 's': - case 'S': writeKey = 0xAF; writeArm[writePartition - 1] = true; break; // Arm stay - case 'w': - case 'W': writeKey = 0xB1; writeArm[writePartition - 1] = true; break; // Arm away - case 'n': - case 'N': writeKey = 0xB6; writeArm[writePartition - 1] = true; break; // Arm with no entry delay (night arm) - case 'A': - case 'a': writeKey = 0xDD; writeAlarm = true; break; // Keypad auxiliary alarm - case 'c': - case 'C': writeKey = 0xBB; break; // Door chime - case 'r': - case 'R': writeKey = 0xDA; break; // Reset - case 'P': - case 'p': writeKey = 0xEE; writeAlarm = true; break; // Keypad panic alarm - case 'x': - case 'X': writeKey = 0xE1; break; // Exit - case '[': writeKey = 0xD5; break; // Command output 1 - case ']': writeKey = 0xDA; break; // Command output 2 - case '{': writeKey = 0x70; break; // Command output 3 - case '}': writeKey = 0xEC; break; // Command output 4 + case 'f': case 'F': writeKey = 0xBB; writeAlarm = true; break; // Keypad fire alarm + case 'b': case 'B': writeKey = 0x82; break; // Enter event buffer + case '>': writeKey = 0x87; break; // Event buffer right arrow + case '<': writeKey = 0x88; break; // Event buffer left arrow + case 'l': case 'L': writeKey = 0xA5; break; // LCD keypad data request + case 's': case 'S': writeKey = 0xAF; writeArm[writePartition - 1] = true; break; // Arm stay + case 'w': case 'W': writeKey = 0xB1; writeArm[writePartition - 1] = true; break; // Arm away + case 'n': case 'N': writeKey = 0xB6; writeArm[writePartition - 1] = true; break; // Arm with no entry delay (night arm) + case 'a': case 'A': writeKey = 0xDD; writeAlarm = true; break; // Keypad auxiliary alarm + case 'c': case 'C': writeKey = 0xBB; break; // Door chime + case 'r': case 'R': writeKey = 0xDA; break; // Reset + case 'p': case 'P': writeKey = 0xEE; writeAlarm = true; break; // Keypad panic alarm + case 'x': case 'X': writeKey = 0xE1; break; // Exit + case '[': writeKey = 0xD5; break; // Command output 1 + case ']': writeKey = 0xDA; break; // Command output 2 + case '{': writeKey = 0x70; break; // Command output 3 + case '}': writeKey = 0xEC; break; // Command output 4 default: { validKey = false; break; diff --git a/src/dscKeybusInterface.h b/src/dscKeybusInterface.h index 0562189..8a4fd03 100644 --- a/src/dscKeybusInterface.h +++ b/src/dscKeybusInterface.h @@ -86,6 +86,7 @@ ISR(TIMER1_OVF_vect) { // DSC Keypad Interface #elif defined dscKeypad + #include "dscKeypad.h" byte dscKeypadInterface::dscClockPin; @@ -93,12 +94,12 @@ byte dscKeypadInterface::dscReadPin; byte dscKeypadInterface::dscWritePin; int dscKeypadInterface::clockInterval; volatile byte dscKeypadInterface::keyData; -volatile byte dscKeypadInterface::alarmKeyData; +volatile byte dscKeypadInterface::keyBufferLength; +volatile byte dscKeypadInterface::keyBuffer[dscBufferSize]; +volatile bool dscKeypadInterface::bufferOverflow; volatile bool dscKeypadInterface::commandReady; volatile bool dscKeypadInterface::moduleDataDetected; -volatile bool dscKeypadInterface::moduleDataCaptured; volatile bool dscKeypadInterface::alarmKeyDetected; -volatile bool dscKeypadInterface::alarmKeyCaptured; volatile bool dscKeypadInterface::alarmKeyResponsePending; volatile byte dscKeypadInterface::clockCycleCount; volatile byte dscKeypadInterface::clockCycleTotal; diff --git a/src/dscKeybusPrintData.cpp b/src/dscKeybusPrintData.cpp index a43e0ad..b5717c4 100644 --- a/src/dscKeybusPrintData.cpp +++ b/src/dscKeybusPrintData.cpp @@ -92,7 +92,7 @@ void dscKeybusInterface::printPanelMessage() { case 0x57: printPanel_0x57(); return; // Wireless key query | Structure: complete | Content: *incomplete case 0x58: printPanel_0x58(); return; // Module status query | Structure: complete | Content: *incomplete case 0x5D: - case 0x63: printPanel_0x5D_63(); return; // Flash panel lights: status and zones 1-32, partition 2 | Structure: complete | Content: complete + case 0x63: printPanel_0x5D_63(); return; // Flash panel lights: status and zones 1-32, partitions 1-2 | Structure: complete | Content: complete case 0x64: printPanel_0x64(); return; // Beep, partition 1 | Structure: complete | Content: complete case 0x69: printPanel_0x69(); return; // Beep, partition 2 | Structure: complete | Content: complete case 0x6E: printPanel_0x6E(); return; // LCD keypad display | Structure: complete | Content: complete diff --git a/src/dscKeypad.cpp b/src/dscKeypad.cpp index 3895cca..2f4d7f1 100644 --- a/src/dscKeypad.cpp +++ b/src/dscKeypad.cpp @@ -38,7 +38,6 @@ dscKeypadInterface::dscKeypadInterface(byte setClockPin, byte setReadPin, byte s dscClockPin = setClockPin; dscReadPin = setReadPin; dscWritePin = setWritePin; - alarmKeyData = 0xFF; commandReady = true; keyData = 0xFF; clockInterval = 57800; // Sets AVR timer 1 to trigger an overflow interrupt every ~500us to generate a 1kHz clock signal @@ -82,6 +81,14 @@ void dscKeypadInterface::begin(Stream &_stream) { #endif // ESP32 intervalStart = millis(); + + unsigned long keybusTime = millis(); + while (millis() - keybusTime < 4000) { // Waits for the keypad to be powered on + if (!digitalRead(dscReadPin)) keybusTime = millis(); + #if defined(ESP8266) || defined(ESP32) + yield(); + #endif + } } @@ -184,6 +191,39 @@ bool dscKeypadInterface::loop() { panelCommandByteTotal = 7; } + else if (panelBlink != previousBlink || panelZonesBlink != previousZonesBlink) { + previousBlink = panelBlink; + previousZonesBlink = panelZonesBlink; + panelCommand5D[1] = panelBlink; + panelCommand5D[2] = panelZonesBlink; + + int dataSum = 0; + for (byte panelByte = 0; panelByte < 6; panelByte++) dataSum += panelCommand5D[panelByte]; + panelCommand5D[6] = dataSum % 256; + + for (byte i = 0; i < 7; i++) panelCommand[i] = panelCommand5D[i]; + panelCommandByteTotal = 7; + } + + // Sets next panel command to 0x64 beep if beep() is called + else if (setBeep) { + setBeep = false; + for (byte i = 0; i < 3; i++) panelCommand[i] = panelCommand64[i]; + panelCommandByteTotal = 3; + } + + else if (setTone) { + setTone = false; + for (byte i = 0; i < 3; i++) panelCommand[i] = panelCommand75[i]; + panelCommandByteTotal = 3; + } + + else if (setBuzzer) { + setBuzzer = false; + for (byte i = 0; i < 3; i++) panelCommand[i] = panelCommand7F[i]; + panelCommandByteTotal = 3; + } + // Sets next panel command to 0x05 status command else { for (byte i = 0; i < 5; i++) panelCommand[i] = panelCommand05[i]; @@ -208,87 +248,170 @@ bool dscKeypadInterface::loop() { else if (!commandReady) intervalStart = millis(); // Sets panel lights - if (Ready) bitWrite(panelLights, 0, 1); - else bitWrite(panelLights, 0, 0); - if (Armed) bitWrite(panelLights, 1, 1); - else bitWrite(panelLights, 1, 0); - if (Memory) bitWrite(panelLights, 2, 1); - else bitWrite(panelLights, 2, 0); - if (Bypass) bitWrite(panelLights, 3, 1); - else bitWrite(panelLights, 3, 0); - if (Trouble) bitWrite(panelLights, 4, 1); - else bitWrite(panelLights, 4, 0); - if (Program) bitWrite(panelLights, 5, 1); - else bitWrite(panelLights, 5, 0); - if (Fire) bitWrite(panelLights, 6, 1); - else bitWrite(panelLights, 6, 0); - if (Backlight) bitWrite(panelLights, 7, 1); - else bitWrite(panelLights, 7, 0); + panelLight(lightReady, 0); + panelLight(lightArmed, 1); + panelLight(lightMemory, 2); + panelLight(lightBypass, 3); + panelLight(lightTrouble, 4); + panelLight(lightProgram, 5); + panelLight(lightFire, 6); + panelLight(lightBacklight, 7); // Sets zone lights - if (Zone1) bitWrite(panelZones, 0, 1); - else bitWrite(panelZones, 0, 0); - if (Zone2) bitWrite(panelZones, 1, 1); - else bitWrite(panelZones, 1, 0); - if (Zone3) bitWrite(panelZones, 2, 1); - else bitWrite(panelZones, 2, 0); - if (Zone4) bitWrite(panelZones, 3, 1); - else bitWrite(panelZones, 3, 0); - if (Zone5) bitWrite(panelZones, 4, 1); - else bitWrite(panelZones, 4, 0); - if (Zone6) bitWrite(panelZones, 5, 1); - else bitWrite(panelZones, 5, 0); - if (Zone7) bitWrite(panelZones, 6, 1); - else bitWrite(panelZones, 6, 0); - if (Zone8) bitWrite(panelZones, 7, 1); - else bitWrite(panelZones, 7, 0); - - if (moduleDataCaptured) { - moduleDataCaptured = false; - if (alarmKeyData != 0xFF) { - KeyAvailable = true; - switch (alarmKeyData) { - case 0xBB: Key = 0x0B; break; // Fire alarm - case 0xDD: Key = 0x0D; break; // Aux alarm - case 0xEE: Key = 0x0E; break; // Panic alarm - default: KeyAvailable = false; break; - } - alarmKeyData = 0xFF; - } - else if (keyData != 0xFF) { - KeyAvailable = true; - switch (keyData) { - case 0x00: Key = 0x00; break; // 0 - case 0x05: Key = 0x05; break; // 1 - case 0x0A: Key = 0x0A; break; // 2 - case 0x0F: Key = 0x0F; break; // 3 - case 0x11: Key = 0x11; break; // 4 - case 0x16: Key = 0x16; break; // 5 - case 0x1B: Key = 0x1B; break; // 6 - case 0x1C: Key = 0x1C; break; // 7 - case 0x22: Key = 0x22; break; // 8 - case 0x27: Key = 0x27; break; // 9 - case 0x28: Key = 0x28; break; // * - case 0x2D: Key = 0x2D; break; // # - case 0x82: Key = 0x82; break; // Enter - case 0x87: Key = 0x87; break; // Right arrow - case 0x88: Key = 0x88; break; // Left arrow - case 0xAF: Key = 0xAF; break; // Arm: Stay - case 0xB1: Key = 0xB1; break; // Arm: Away - case 0xBB: Key = 0xBB; break; // Door chime - case 0xDA: Key = 0xDA; break; // Reset - case 0xE1: Key = 0xE1; break; // Quick exit - case 0xF7: Key = 0xF7; break; // LCD keypad navigation - default: KeyAvailable = false; break; - } - keyData = 0xFF; + zoneLight(lightZone1, 0); + zoneLight(lightZone2, 1); + zoneLight(lightZone3, 2); + zoneLight(lightZone4, 3); + zoneLight(lightZone5, 4); + zoneLight(lightZone6, 5); + zoneLight(lightZone7, 6); + zoneLight(lightZone8, 7); + + // Skips key processing if the key buffer is empty + if (keyBufferLength == 0) return false; + + // Copies data from the buffer to keyData + static byte keyBufferIndex = 1; + byte dataIndex = keyBufferIndex - 1; + keyData = keyBuffer[dataIndex]; + keyBufferIndex++; + + // Resets counters when the buffer is cleared + #if defined(ESP32) + portENTER_CRITICAL(&timer0Mux); + #else + noInterrupts(); + #endif + + if (keyBufferIndex > keyBufferLength) { + keyBufferIndex = 1; + keyBufferLength = 0; + } + + #if defined(ESP32) + portEXIT_CRITICAL(&timer0Mux); + #else + interrupts(); + #endif + + if (keyData != 0xFF) { + keyAvailable = true; + switch (keyData) { + case 0x00: key = 0x00; break; // 0 + case 0x05: key = 0x05; break; // 1 + case 0x0A: key = 0x0A; break; // 2 + case 0x0F: key = 0x0F; break; // 3 + case 0x11: key = 0x11; break; // 4 + case 0x16: key = 0x16; break; // 5 + case 0x1B: key = 0x1B; break; // 6 + case 0x1C: key = 0x1C; break; // 7 + case 0x22: key = 0x22; break; // 8 + case 0x27: key = 0x27; break; // 9 + case 0x28: key = 0x28; break; // * + case 0x2D: key = 0x2D; break; // # + case 0x82: key = 0x82; break; // Enter + case 0x87: key = 0x87; break; // Right arrow + case 0x88: key = 0x88; break; // Left arrow + case 0xAF: key = 0xAF; break; // Arm: Stay + case 0xB1: key = 0xB1; break; // Arm: Away + case 0xBB: key = 0xBB; break; // Door chime + case 0xDA: key = 0xDA; break; // Reset + case 0xE1: key = 0xE1; break; // Quick exit + case 0xF7: key = 0xF7; break; // LCD keypad navigation + case 0x0B: key = 0x0B; break; // Fire alarm + case 0x0D: key = 0x0D; break; // Aux alarm + case 0x0E: key = 0x0E; break; // Panic alarm + default: keyAvailable = false; break; // Skips other DSC key values and invalid data } + keyData = 0xFF; } return true; } +void dscKeypadInterface::panelLight(Light lightPanel, byte zoneBit) { + if (lightPanel == on) { + bitWrite(panelLights, zoneBit, 1); + bitWrite(panelBlink, zoneBit, 0); + } + else if (lightPanel == blink) bitWrite(panelBlink, zoneBit, 1); + else { + bitWrite(panelLights, zoneBit, 0); + bitWrite(panelBlink, zoneBit, 0); + } +} + + +void dscKeypadInterface::zoneLight(Light lightZone, byte zoneBit) { + if (lightZone == on ) { + bitWrite(panelZones, zoneBit, 1); + bitWrite(panelZonesBlink, zoneBit, 0); + } + else if (lightZone == blink) bitWrite(panelZonesBlink, zoneBit, 1); + else { + bitWrite(panelZones, zoneBit, 0); + bitWrite(panelZonesBlink, zoneBit, 0); + } +} + + + +void dscKeypadInterface::beep(byte beeps) { + if (!beeps) { + setBeep = false; + return; + } + + if (beeps >= 128) beeps = 255; + else beeps *= 2; + panelCommand64[1] = beeps; + + int dataSum = 0; + for (byte panelByte = 0; panelByte < 2; panelByte++) dataSum += panelCommand64[panelByte]; + panelCommand64[2] = dataSum % 256; + + setBeep = true; +} + + +void dscKeypadInterface::tone(byte beep, bool tone, byte interval) { + panelCommand75[1] = 0; + + if (tone >= 1) panelCommand75[1] |= 0x80; + + if (beep > 7) beep = 7; + if (beep >= 1) { + panelCommand75[1] |= beep << 4; + } + + if (interval > 15) interval = 15; + panelCommand75[1] |= interval; + + int dataSum = 0; + for (byte panelByte = 0; panelByte < 2; panelByte++) dataSum += panelCommand75[panelByte]; + panelCommand75[2] = dataSum % 256; + + setTone = true; +} + + +void dscKeypadInterface::buzzer(byte seconds) { + if (!seconds) { + setBuzzer = false; + return; + } + + panelCommand7F[1] = seconds; + + int dataSum = 0; + for (byte panelByte = 0; panelByte < 2; panelByte++) dataSum += panelCommand7F[panelByte]; + panelCommand7F[2] = dataSum % 256; + + setBuzzer = true; +} + + #if defined(__AVR__) void dscKeypadInterface::dscClockInterrupt() { #elif defined(ESP8266) @@ -343,29 +466,46 @@ void IRAM_ATTR dscKeypadInterface::dscClockInterrupt() { } // Write panel data + + // Panel command byte 0 complete if (isrPanelBitTotal == 8) { digitalWrite(dscWritePin, HIGH); // Stop bit + + // Checks for an alarm key sent during 0x1C alarm key verification command to save in the key buffer if (panelCommand[0] == 0x1C) { alarmKeyResponsePending = false; - alarmKeyData = isrModuleData[0]; + + if (isrModuleData[0] != 0xFF) { + if (keyBufferLength >= dscBufferSize) bufferOverflow = true; + else { + + // Converts the DSC alarm key value to handle a conflict with the door chime key (0xBB) + switch (isrModuleData[0]) { + case 0xBB: keyBuffer[keyBufferLength] = 0x0B; keyBufferLength++; break; // Fire alarm + case 0xDD: keyBuffer[keyBufferLength] = 0x0D; keyBufferLength++; break; // Aux alarm + case 0xEE: keyBuffer[keyBufferLength] = 0x0E; keyBufferLength++; break; // Panic alarm + default: break; + } + } + } } isrPanelBitTotal++; } + + // Panel command bytes bit 7 else if (isrPanelBitCount == 7) { if (!bitRead(panelCommand[panelCommandByteCount], 0)) digitalWrite(dscWritePin, HIGH); isrPanelBitCount = 0; isrPanelBitTotal++; panelCommandByteCount++; } + + // Panel command bytes bits 0-6 else if (panelCommandByteCount < panelCommandByteTotal) { - switch (isrPanelBitCount) { - case 0: if (!bitRead(panelCommand[panelCommandByteCount], 7)) digitalWrite(dscWritePin, HIGH); break; - case 1: if (!bitRead(panelCommand[panelCommandByteCount], 6)) digitalWrite(dscWritePin, HIGH); break; - case 2: if (!bitRead(panelCommand[panelCommandByteCount], 5)) digitalWrite(dscWritePin, HIGH); break; - case 3: if (!bitRead(panelCommand[panelCommandByteCount], 4)) digitalWrite(dscWritePin, HIGH); break; - case 4: if (!bitRead(panelCommand[panelCommandByteCount], 3)) digitalWrite(dscWritePin, HIGH); break; - case 5: if (!bitRead(panelCommand[panelCommandByteCount], 2)) digitalWrite(dscWritePin, HIGH); break; - case 6: if (!bitRead(panelCommand[panelCommandByteCount], 1)) digitalWrite(dscWritePin, HIGH); break; + byte bitCount = 0; + for (byte i = 7; i > 0; i--) { + if (isrPanelBitCount == bitCount && !bitRead(panelCommand[panelCommandByteCount], i)) digitalWrite(dscWritePin, HIGH); + bitCount++; } isrPanelBitCount++; isrPanelBitTotal++; @@ -381,17 +521,20 @@ void IRAM_ATTR dscKeypadInterface::dscClockInterrupt() { // Checks for module data if (moduleDataDetected) { moduleDataDetected = false; - moduleDataCaptured = true; for (byte i = 0; i < dscReadSize; i++) moduleData[i] = isrModuleData[i]; - // Checks for an alarm key press + // Checks for an alarm key press and sets a flag to send panel command 0x1C alarm key verification if (isrModuleData[0] != 0xFF && panelCommand[0] != 0x1C) { alarmKeyDetected = true; } - // Checks for a partition 1 key + // Checks for a partition 1 key to save in the key buffer if (isrModuleData[2] != 0xFF && panelCommand[0] == 0x05) { - keyData = isrModuleData[2]; + if (keyBufferLength >= dscBufferSize) bufferOverflow = true; + else { + keyBuffer[keyBufferLength] = isrModuleData[2]; + keyBufferLength++; + } } } @@ -404,6 +547,7 @@ void IRAM_ATTR dscKeypadInterface::dscClockInterrupt() { isrPanelBitTotal = 0; isrPanelBitCount = 0; commandReady = true; + #if defined(__AVR__) TIMSK1 = 0; // Disables AVR Timer 1 interrupt #elif defined(ESP8266) diff --git a/src/dscKeypad.h b/src/dscKeypad.h index 4001ac2..eb8de73 100644 --- a/src/dscKeypad.h +++ b/src/dscKeypad.h @@ -22,35 +22,46 @@ #include +#if defined(__AVR__) +const byte dscBufferSize = 10; // Number of keys to buffer if the sketch is busy +#elif defined(ESP8266) || defined (ESP32) +const byte dscBufferSize = 50; +#endif const byte dscReadSize = 16; // Maximum bytes of a Keybus command +enum Light {off, on, blink}; // Custom values for keypad lights status + class dscKeypadInterface { public: dscKeypadInterface(byte setClockPin, byte setReadPin, byte setWritePin); // Interface control - void begin(Stream &_stream = Serial); // Initializes the stream output to Serial by default - bool loop(); // Returns true if valid panel data is available + void begin(Stream &_stream = Serial); // Initializes the stream output to Serial by default + bool loop(); // Returns true if valid panel data is available + void beep(byte beeps = 0); // Keypad beep, 1-128 beeps + void tone(byte beep = 0, bool tone = false, byte interval = 0); // Keypad tone pattern, 1-7 beeps at 1-15s interval, with optional constant tone + void buzzer(byte seconds = 0); // Keypad buzzer, 1-255 seconds // Keypad key - byte Key, KeyAvailable; - - bool Ready = true, Armed, Memory, Bypass, Trouble, Program, Fire, Backlight = true; - bool Zone1, Zone2, Zone3, Zone4, Zone5, Zone6, Zone7, Zone8; + byte key, keyAvailable; - // Status tracking - bool keybusConnected, keybusChanged; // True if data is detected on the Keybus + // Keypad lights + Light lightReady = on, lightArmed, lightMemory, lightBypass, lightTrouble, lightProgram, lightFire, lightBacklight = on; + Light lightZone1, lightZone2, lightZone3, lightZone4, lightZone5, lightZone6, lightZone7, lightZone8; // Panel Keybus commands - byte panelCommand05[5] = {0x05, 0x81, 0x01, 0x10, 0xC7}; // Partition 1: Ready Backlight - Partition ready | Partition 2: disabled - byte panelCommand16[5] = {0x16, 0x0E, 0x23, 0xF1, 0x38}; // Panel version: v2.3 | Zone wiring: NC | Code length: 4 digits | *8 programming: no - byte panelCommand27[7] = {0x27, 0x81, 0x01, 0x10, 0xC7, 0x00, 0x80}; // Partition 1: Ready Backlight - Partition ready | Partition 2: disabled | Zones 1-8 open: none - byte panelCommand4C[12] = {0x4C, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; - byte panelCommand5D[7] = {0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D}; // Partition 1 | Status lights flashing: none | Zones 1-32 flashing: none - byte panelCommandA5[8] = {0xA5, 0x18, 0x0E, 0xED, 0x80, 0x00, 0x00, 0x38}; - byte panelCommandD5[9] = {0xD5, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; - byte panelCommandB1[10] = {0xB1, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xAD}; + byte panelCommand05[5] = {0x05, 0x81, 0x01, 0x10, 0xC7}; // Partition 1: Ready Backlight - Partition ready | Partition 2: disabled + byte panelCommand16[5] = {0x16, 0x0E, 0x23, 0xF1, 0x38}; // Panel version: v2.3 | Zone wiring: NC | Code length: 4 digits | *8 programming: no + byte panelCommand27[7] = {0x27, 0x81, 0x01, 0x10, 0xC7, 0x00, 0x80}; // Partition 1: Ready Backlight - Partition ready | Partition 2: disabled | Zones 1-8 open: none + byte panelCommand4C[12] = {0x4C, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; // Module tamper query + byte panelCommand5D[7] = {0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D}; // Partition 1 | Status lights flashing: none | Zones 1-32 flashing: none + byte panelCommand64[3] = {0x64, 0x0, 0x64}; // Beep pattern, 1-128 beeps + byte panelCommand75[3] = {0x75, 0x0, 0x75}; // Tone pattern, beeps at interval with optional constant tone + byte panelCommand7F[3] = {0x7F, 0x0, 0x7F}; // Buzzer, 1-255 seconds + byte panelCommandA5[8] = {0xA5, 0x18, 0x0E, 0xED, 0x80, 0x00, 0x00, 0x38}; // Date, time, system status messages - partitions 1-2 + byte panelCommandB1[10] = {0xB1, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xAD}; // Enabled zones 1-32 + byte panelCommandD5[9] = {0xD5, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}; // Keypad zone query /* * moduleData[] stores keypad data in an array: command [0], stop bit by itself [1], followed by the @@ -59,13 +70,24 @@ class dscKeypadInterface { */ static volatile byte moduleData[dscReadSize]; + // Key data buffer overflow, true if dscBufferSize needs to be increased + static volatile bool bufferOverflow; + // Timer interrupt function to capture data - declared as public for use by AVR Timer1 static void dscClockInterrupt(); private: + + void zoneLight(Light lightZone, byte zoneBit); + void panelLight(Light lightPanel, byte zoneBit); + Stream* stream; - byte panelLights = 0x81, panelZones, previousLights = 0x81, previousZones; + byte panelLights = 0x81, previousLights = 0x81; + byte panelBlink, previousBlink; + byte panelZones, previousZones; + byte panelZonesBlink, previousZonesBlink; bool startupCycle = true; + bool setBeep, setTone, setBuzzer; byte commandInterval = 5; // Sets the milliseconds between panel commands unsigned long intervalStart; @@ -79,9 +101,10 @@ class dscKeypadInterface { static int clockInterval; static byte dscClockPin, dscReadPin, dscWritePin; static volatile byte keyData; - static volatile byte alarmKeyData; - static volatile bool commandReady, moduleDataDetected, moduleDataCaptured; - static volatile bool alarmKeyDetected, alarmKeyCaptured, alarmKeyResponsePending; + static volatile byte keyBufferLength; + static volatile byte keyBuffer[dscBufferSize]; + static volatile bool commandReady, moduleDataDetected; + static volatile bool alarmKeyDetected, alarmKeyResponsePending; static volatile byte clockCycleCount, clockCycleTotal; static volatile byte panelCommand[dscReadSize], panelCommandByteCount, panelCommandByteTotal; static volatile byte isrPanelBitTotal, isrPanelBitCount;