diff --git a/README.md b/README.md index 3683a6c..3e53451 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ This library directly interfaces Arduino, esp8266, and esp32 microcontrollers to The built-in examples can be used as-is or as a base to adapt to other uses: * Home automation integration: [Home Assistant](https://www.home-assistant.io), [Apple HomeKit & Siri](https://www.apple.com/ios/home/), [OpenHAB](https://www.openhab.org), [Athom Homey](https://www.athom.com/en/) -* Notifications: [PushBullet](https://www.pushbullet.com), [Twilio SMS](https://www.twilio.com), MQTT, E-mail -* Direct control: Web interface, [Blynk](https://www.blynk.cc) mobile app -* Installer code: automatic code search to unlock panels with unknown installer codes +* Notifications: [Telegram](https://www.telegram.org) bot, [PushBullet](https://www.pushbullet.com), [Twilio SMS](https://www.twilio.com), E-mail +* Virtual keypad: Web interface, [Blynk](https://www.blynk.cc) mobile app +* Installer code unlocking: automatic code search to unlock panels with unknown installer codes See the [dscKeybusInterface-RTOS](https://github.com/taligentx/dscKeybusInterface-RTOS) repository for a port of this library to [esp-open-rtos](https://github.com/SuperHouse/esp-open-rtos) - this enables a standalone esp8266 HomeKit accessory using [esp-homekit](https://github.com/maximkulkin/esp-homekit). @@ -14,12 +14,14 @@ Screenshots: ![HomeKit](https://user-images.githubusercontent.com/12835671/61570833-c9bb9780-aa54-11e9-9477-8e0853609e91.png) * [Home Assistant](https://www.home-assistant.io): ![HomeAssistant](https://user-images.githubusercontent.com/12835671/61985900-38f33780-afd1-11e9-9d43-ab0b681b7b03.png) -* [OpenHAB](https://www.openhab.org): +* [OpenHAB](https://www.openhab.org) MQTT: ![OpenHAB](https://user-images.githubusercontent.com/12835671/61560425-daa6e180-aa31-11e9-9efe-0fcb44d2106a.png) * [Blynk](https://www.blynk.cc) app virtual keypad: ![VirtualKeypad-Blynk](https://user-images.githubusercontent.com/12835671/61568638-9fb0a800-aa49-11e9-94d0-e598431ea2ed.png) * Web virtual keypad: ![VirtualKeypad-Web](https://user-images.githubusercontent.com/12835671/61570049-e43f4200-aa4f-11e9-96bc-3448b6630990.png) +* [Telegram](https://www.telegram.org) bot: + ![Telegram](https://user-images.githubusercontent.com/12835671/102907524-c8598000-443b-11eb-81f0-18bc42306cbb.png) ## Why? **I Had**: _A DSC security system not being monitored by a third-party service._ @@ -77,13 +79,13 @@ Poking around with a logic analyzer and oscilloscope revealed that the errors ca * Development boards: NodeMCU ESP-32S, Doit ESP32 Devkit v1, Wemos Lolin D32, etc. * Includes [Arduino framework support](https://github.com/espressif/arduino-esp32), dual cores, WiFi, and Bluetooth for ~$5USD shipped. * Possible features (PRs welcome!): - - Virtual zone expander: Add new zones to the DSC panel emulated by the microcontroller based on GPIO pin states or software-based states. Requires decoding the DSC PC5108 zone expander data. - [DSC IT-100](https://cms.dsc.com/download.php?t=1&id=16238) emulation - Unlock 6-digit installer codes - DSC Classic series support: This protocol is [already decoded](https://github.com/dougkpowers/pc1550-interface), use with this library would require major changes. ## Release notes * develop + - New: [Telegram](https://www.telegram.org) bot example sketch - New: [OpenHAB](https://www.openhab.org) integration example sketch using MQTT - New: `Unlocker` example sketch - determines the panel installer code - New: `KeybusReaderIP` example sketch enables Keybus data access over IP, thanks to [aboulfad](https://github.com/aboulfad) for this contribution! @@ -171,6 +173,7 @@ The included examples demonstrate how to use the library and can be used as-is o * Partitions fire alarm * Zones open/closed * Zones in alarm + * PGM outputs 1-14 * Keypad fire/auxiliary/panic alarm * Get/set panel date and time * User access code number (1-40) @@ -179,20 +182,22 @@ The included examples demonstrate how to use the library and can be used as-is o * Panel trouble * Keybus connected -* **Homebridge-MQTT**: Integrates with Apple HomeKit, including the iOS Home app and Siri. This uses MQTT to interface with [Homebridge](https://github.com/nfarina/homebridge) and [homebridge-mqttthing](https://github.com/arachnetech/homebridge-mqttthing) and demonstrates using the armed and alarm states for the HomeKit securitySystem object, zone states for the contactSensor objects, and fire alarm states for the smokeSensor object. +* **Homebridge-MQTT**: Interfaces with [Homebridge](https://github.com/nfarina/homebridge) via MQTT to integrate with Apple HomeKit (including the iOS Home app and Siri). Demonstrates arming/disarming partitions and viewing the status of zones, PGM outputs, and fire alarms. - The [dscKeybusInterface-RTOS](https://github.com/taligentx/dscKeybusInterface-RTOS) library includes a native HomeKit implementation that runs directly on esp8266, without requiring a separate device running MQTT or Homebridge. -* **HomeAssistant-MQTT**: Integrates with [Home Assistant](https://www.home-assistant.io) via MQTT. This uses the armed and alarm states for the HomeAssistant [Alarm Control Panel](https://www.home-assistant.io/components/alarm_control_panel.mqtt) component, as well as zone, fire alarm, and trouble states for the [Binary Sensor](https://www.home-assistant.io/components/binary_sensor.mqtt) component. For esp8266/esp32, a partition status message is also sent as a sensor component. +* **HomeAssistant-MQTT**: Interfaces with [Home Assistant](https://www.home-assistant.io) via MQTT. Demonstrates arming/disarming partitions and viewing the status of zones, PGM outputs, fire alarms, and trouble. For esp8266/esp32, the partition status is available as a text message for display. -* **OpenHAB-MQTT**: Integrates with [OpenHAB](https://www.openhab.org) via MQTT. This uses the [OpenHAB MQTT binding](https://www.openhab.org/addons/bindings/mqtt/) and demonstrates using the panel and partitions states as OpenHAB switches and zone states as OpenHAB contacts. For esp8266/esp32, a panel status message is also sent as a string to OpenHAB. See https://github.com/jimtng/dscalarm-mqtt for an integration using the Homie convention for OpenHAB's Homie MQTT component. +* **OpenHAB-MQTT**: Interfaces with [OpenHAB](https://www.openhab.org) via MQTT. Demonstrates using the panel and partitions states as OpenHAB switches and zone states as OpenHAB contacts. For esp8266/esp32, a panel status message is also sent as a string to OpenHAB. See https://github.com/jimtng/dscalarm-mqtt for an integration using the Homie convention for OpenHAB's Homie MQTT component. * **Homey**: Integrates with [Athom Homey](https://www.athom.com/en/) and the [Homeyduino](https://github.com/athombv/homey-arduino-library/) library, including armed, alarm, and fire states (currently limited to one partition), and zone states. Thanks to [MagnusPer](https://github.com/MagnusPer) for contributing this example! -* **Pushbullet** (esp8266/esp32-only): Demonstrates how to send a push notification when the status has changed. This example sends notifications via [Pushbullet](https://www.pushbullet.com) and requires the esp8266/esp32 for SSL support. +* **Telegram** (esp8266/esp32-only): Demonstrates sending status updates and arming/disarming the security system via a [Telegram](https://www.telegram.org) bot. -* **Twilio-SMS** (esp8266/esp32-only): Demonstrates how to send an SMS text message when the status has changed. This example sends texts via [Twilio](https://www.twilio.com) and requires the esp8266/esp32 for SSL support - thanks to [ColingNG](https://github.com/ColinNg) for contributing this example! +* **Pushbullet** (esp8266/esp32-only): Demonstrates sending status updates as a push notification via [Pushbullet](https://www.pushbullet.com). -* **Email** (esp8266/esp32-only): Demonstrates how to send an email when the status has changed. Email is sent using SMTPS (port 465) with SSL for encryption - this is necessary on the esp8266/esp32 until STARTTLS can be supported. For example, this will work with Gmail after changing the account settings to [allow less secure apps](https://support.google.com/accounts/answer/6010255). +* **Twilio-SMS** (esp8266/esp32-only): Demonstrates sending status updates as an SMS text message via [Twilio](https://www.twilio.com) - thanks to [ColingNG](https://github.com/ColinNg) for contributing this example! + +* **Email** (esp8266/esp32-only): Demonstrates sending status updates as an email. Email is sent using SMTPS (port 465) with SSL for encryption - this is necessary on the esp8266/esp32 until STARTTLS can be supported. For example, this will work with Gmail after changing the account settings to [allow less secure apps](https://support.google.com/accounts/answer/6010255). This can be used to send SMS text messages if the number's service provider has an [email to SMS gateway](https://en.wikipedia.org/wiki/SMS_gateway#Email_clients) - examples for the US: * T-mobile: 5558675309@tmomail.net @@ -206,7 +211,7 @@ The included examples demonstrate how to use the library and can be used as-is o Note: Installing [Blynk as a local server](https://github.com/blynkkk/blynk-server) is recommended to keep control of the security system internal to your network. This also lets you use as many widgets as needed for free - local servers can setup users with any amount of Blynk Energy. Using the default Blynk cloud service with the above example layouts requires more of Blynk's Energy units than available on the free usage tier. -* **VirtualKeypad-Web** (esp8266-only): Provides a virtual keypad web interface, using the esp8266 itself as a standalone web server. This example uses the [ESP Async Web Server](https://github.com/me-no-dev/ESPAsyncWebServer) and Websockets - thanks to [Elektrik1](https://github.com/Elektrik1) for contributing this example! +* **VirtualKeypad-Web** (esp8266-only): Provides a virtual keypad web interface, using the esp8266 itself as a standalone web server - thanks to [Elektrik1](https://github.com/Elektrik1) for contributing this example! * **Unlocker**: Checks all possible 4-digit installer codes until a valid code is found, including handling keypad lockout if enabled. The valid code is output to serial as well as repeatedly flashed with the built-in LED. diff --git a/examples/Arduino/HomeAssistant-MQTT/HomeAssistant-MQTT.ino b/examples/Arduino/HomeAssistant-MQTT/HomeAssistant-MQTT.ino index 40e1e79..c3f3a91 100644 --- a/examples/Arduino/HomeAssistant-MQTT/HomeAssistant-MQTT.ino +++ b/examples/Arduino/HomeAssistant-MQTT/HomeAssistant-MQTT.ino @@ -215,17 +215,17 @@ unsigned long mqttPreviousTime; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); // Initializes ethernet with DHCP - Serial.print(F("Initializing Ethernet...")); + Serial.print(F("Ethernet....")); while(!Ethernet.begin(mac)) { Serial.println(F("DHCP failed. Retrying...")); delay(5000); } - Serial.println(F("success!")); - Serial.print(F("IP address: ")); + Serial.print(F("connected: ")); Serial.println(Ethernet.localIP()); mqtt.setCallback(mqttCallback); @@ -235,7 +235,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -282,7 +281,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag char publishTopic[strlen(mqttPartitionTopic) + 2]; appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number @@ -308,13 +306,13 @@ void loop() { // Publishes alarm status if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - if (dsc.alarm[partition]) { - char publishTopic[strlen(mqttPartitionTopic) + 2]; - appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number + char publishTopic[strlen(mqttPartitionTopic) + 2]; + appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number - mqtt.publish(publishTopic, "triggered", true); // Alarm tripped - } + if (dsc.alarm[partition]) mqtt.publish(publishTopic, "triggered", true); // Alarm tripped + else if (!dsc.armedChanged[partition]) mqtt.publish(publishTopic, "disarmed", true); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -430,7 +428,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); } @@ -454,13 +452,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/Arduino/Homebridge-MQTT/Homebridge-MQTT.ino b/examples/Arduino/Homebridge-MQTT/Homebridge-MQTT.ino index abee62f..8b2f949 100644 --- a/examples/Arduino/Homebridge-MQTT/Homebridge-MQTT.ino +++ b/examples/Arduino/Homebridge-MQTT/Homebridge-MQTT.ino @@ -216,17 +216,17 @@ char exitState; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); // Initializes ethernet with DHCP - Serial.print(F("Initializing Ethernet...")); + Serial.print(F("Ethernet....")); while(!Ethernet.begin(mac)) { Serial.println(F("DHCP failed. Retrying...")); delay(5000); } - Serial.println(F("success!")); - Serial.print(F("IP address: ")); + Serial.print(F("connected: ")); Serial.println(Ethernet.localIP()); mqtt.setCallback(mqttCallback); @@ -236,7 +236,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -270,8 +269,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { exitState = 0; @@ -343,7 +340,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, 0, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D", "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -478,7 +477,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // homebridge-mqttthing DISARM - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -503,13 +502,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/Arduino/KeybusReader/KeybusReader.ino b/examples/Arduino/KeybusReader/KeybusReader.ino index 154c8e4..aa5b82f 100644 --- a/examples/Arduino/KeybusReader/KeybusReader.ino +++ b/examples/Arduino/KeybusReader/KeybusReader.ino @@ -52,6 +52,7 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); @@ -64,7 +65,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } diff --git a/examples/Arduino/OpenHAB-MQTT/OpenHAB-MQTT.ino b/examples/Arduino/OpenHAB-MQTT/OpenHAB-MQTT.ino index 31e75b1..08c4a3e 100644 --- a/examples/Arduino/OpenHAB-MQTT/OpenHAB-MQTT.ino +++ b/examples/Arduino/OpenHAB-MQTT/OpenHAB-MQTT.ino @@ -154,17 +154,17 @@ unsigned long mqttPreviousTime; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); // Initializes ethernet with DHCP - Serial.print(F("Initializing Ethernet...")); + Serial.print(F("Ethernet....")); while(!Ethernet.begin(mac)) { Serial.println(F("DHCP failed. Retrying...")); delay(5000); } - Serial.println(F("success!")); - Serial.print(F("IP address: ")); + Serial.print(F("connected: ")); Serial.println(Ethernet.localIP()); mqtt.setCallback(mqttCallback); @@ -174,7 +174,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -221,8 +220,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { // Armed away @@ -256,7 +253,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -370,7 +369,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -396,13 +395,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/Arduino/Status/Status.ino b/examples/Arduino/Status/Status.ino index 54a2b42..78b0fc6 100644 --- a/examples/Arduino/Status/Status.ino +++ b/examples/Arduino/Status/Status.ino @@ -53,6 +53,7 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); @@ -99,29 +100,32 @@ void loop() { if (dsc.ready[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" ready")); + Serial.println(F(": Ready")); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" not ready")); + Serial.println(F(": Not ready")); } } // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.print(F(" armed")); - if (dsc.armedAway[partition]) Serial.println(F(" away")); - if (dsc.armedStay[partition]) Serial.println(F(" stay")); + Serial.print(F(": Armed ")); + + if (dsc.armedAway[partition]) Serial.print(F("away")); + else if (dsc.armedStay[partition]) Serial.print(F("stay")); + + if (dsc.noEntryDelay[partition]) Serial.println(F(" with no entry delay")); + else Serial.println(); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" disarmed")); + Serial.println(F(": Disarmed")); } } @@ -131,9 +135,15 @@ void loop() { if (dsc.alarm[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" in alarm")); + Serial.println(F(": Alarm")); + } + else if (!dsc.armedChanged[partition]) { + Serial.print(F("Partition ")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks exit delay status if (dsc.exitDelayChanged[partition]) { @@ -141,12 +151,12 @@ void loop() { if (dsc.exitDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" exit delay in progress")); + Serial.println(F(": Exit delay in progress")); } else if (!dsc.armed[partition]) { // Checks for disarm during exit delay Serial.print(F("Partition ")); - Serial.print(partition +1); - Serial.println(F(" disarmed")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } @@ -156,7 +166,7 @@ void loop() { if (dsc.entryDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" entry delay in progress")); + Serial.println(F(": Entry delay in progress")); } } @@ -166,29 +176,29 @@ void loop() { Serial.print(F("Partition ")); Serial.print(partition + 1); switch (dsc.accessCode[partition]) { - case 33: Serial.print(F(" duress")); break; - case 34: Serial.print(F(" duress")); break; - case 40: Serial.print(F(" master")); break; - case 41: Serial.print(F(" supervisor")); break; - case 42: Serial.print(F(" supervisor")); break; - default: Serial.print(F(" user")); break; + case 33: Serial.print(F(": Duress")); break; + case 34: Serial.print(F(": Duress")); break; + case 40: Serial.print(F(": Master")); break; + case 41: Serial.print(F(": Supervisor")); break; + case 42: Serial.print(F(": Supervisor")); break; + default: Serial.print(F(": User")); break; } Serial.print(F(" code ")); Serial.println(dsc.accessCode[partition]); } - // Checks fire status + // Checks fire alarm status if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag if (dsc.fire[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm")); + Serial.println(F(": Fire alarm")); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm restored")); + Serial.println(F(": Fire alarm restored")); } } } @@ -310,19 +320,19 @@ void loop() { // Checks keypad fire alarm triggered if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - Serial.println(F("Keypad fire alarm")); + Serial.println(F("Keypad Fire alarm")); } // Checks keypad auxiliary alarm triggered if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - Serial.println(F("Keypad aux alarm")); + Serial.println(F("Keypad Aux alarm")); } // Checks keypad panic alarm triggered if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - Serial.println(F("Keypad panic alarm")); + Serial.println(F("Keypad Panic alarm")); } } } diff --git a/examples/Arduino/Unlocker/Unlocker.ino b/examples/Arduino/Unlocker/Unlocker.ino index cec32fc..21b7d38 100644 --- a/examples/Arduino/Unlocker/Unlocker.ino +++ b/examples/Arduino/Unlocker/Unlocker.ino @@ -107,6 +107,7 @@ bool pauseTest = false; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); diff --git a/examples/esp32/Email/Email.ino b/examples/esp32/Email/Email.ino index ad097d4..0896289 100644 --- a/examples/esp32/Email/Email.ino +++ b/examples/esp32/Email/Email.ino @@ -43,9 +43,10 @@ #include #include -// WiFi settings +// Settings const char* wifiSSID = ""; const char* wifiPassword = ""; +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin 18 // esp32: 4,13,16-39 @@ -53,28 +54,33 @@ const char* wifiPassword = ""; // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); -WiFiClientSecure smtpClient; -bool wifiConnected = false; +WiFiClientSecure wifiClient; +bool wifiConnected = true; 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) delay(100); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); - // Sends an email on startup to verify connectivity - if (sendEmail("Security system initializing", "")) Serial.println(F("Initialization email sent successfully.")); - else Serial.println(F("Initialization email failed to send.")); + // Sends a message on startup to verify connectivity + Serial.print(F("Email....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -106,90 +112,120 @@ void loop() { dsc.bufferOverflow = false; } - if (dsc.keypadFireAlarm) { - dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendEmail("Security system fire alarm button pressed", ""); - } - - if (dsc.keypadAuxAlarm) { - dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendEmail("Security system aux alarm button pressed", ""); - } - - if (dsc.keypadPanicAlarm) { - dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendEmail("Security system panic alarm button pressed", ""); - } - - if (dsc.powerChanged) { - dsc.powerChanged = false; // Resets the power trouble status flag - if (dsc.powerTrouble) sendEmail("Security system AC power trouble", ""); - else sendEmail("Security system AC power restored", ""); - } - // Checks status per partition for (byte partition = 0; partition < dscPartitions; partition++) { // Skips processing if the partition is disabled or in installer programming if (dsc.disabled[partition]) continue; + // Checks alarm triggered status if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char emailBody[12] = "Partition "; - appendPartition(partition, emailBody); // Appends the email body with the partition number - - if (dsc.alarm[partition]) sendEmail("Security system in alarm", emailBody); - else sendEmail("Security system disarmed after alarm", emailBody); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent, messageContent); + } + else { + char messageContent[34] = "Disarmed after alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent, messageContent); + } } if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char emailBody[12] = "Partition "; - appendPartition(partition, emailBody); // Appends the email body with the partition number - - if (dsc.fire[partition]) sendEmail("Security system fire alarm", emailBody); - else sendEmail("Security system fire alarm restored", emailBody); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent, messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent, messageContent); + } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + + // Checks for AC power status + if (dsc.powerChanged) { + dsc.powerChanged = false; // Resets the battery trouble status flag + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); + } + + // Checks for keypad fire alarm status + if (dsc.keypadFireAlarm) { + dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag + sendMessage("Keypad Fire alarm"); + } + + // Checks for keypad aux auxiliary alarm status + if (dsc.keypadAuxAlarm) { + dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag + sendMessage("Keypad Aux alarm"); + } + + // Checks for keypad panic alarm status + if (dsc.keypadPanicAlarm) { + dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag + sendMessage("Keypad Panic alarm"); + } } } -// sendEmail() takes the email subject and body as separate parameters. Configure the settings for your SMTP +// sendMessage() takes the email subject and body as separate parameters. Configure the settings for your SMTP // server - the login and password must be base64 encoded. For example, on the macOS/Linux terminal: // $ echo -n 'mylogin@example.com' | base64 -w 0 -bool sendEmail(const char* emailSubject, const char* emailBody) { - if (!smtpClient.connect("smtp.example.com", 465)) return false; // Set the SMTP server address - for example: smtp.gmail.com +bool sendMessage(const char* messageContent) { + if (!wifiClient.connect("smtp.example.com", 465)) return false; // Set the SMTP server address - for example: smtp.gmail.com if(!smtpValidResponse()) return false; - smtpClient.println(F("HELO ESP32")); + wifiClient.println(F("HELO ESP32")); if(!smtpValidResponse()) return false; - smtpClient.println(F("AUTH LOGIN")); + wifiClient.println(F("AUTH LOGIN")); if(!smtpValidResponse()) return false; - smtpClient.println(F("myBase64encodedLogin")); // Set the SMTP server login in base64 + wifiClient.println(F("myBase64encodedLogin")); // Set the SMTP server login in base64 if(!smtpValidResponse()) return false; - smtpClient.println(F("myBase64encodedPassword")); // Set the SMTP server password in base64 + wifiClient.println(F("myBase64encodedPassword")); // Set the SMTP server password in base64 if(!smtpValidResponse()) return false; - smtpClient.println(F("MAIL FROM:")); // Set the sender address + wifiClient.println(F("MAIL FROM:")); // Set the sender address if(!smtpValidResponse()) return false; - smtpClient.println(F("RCPT TO:")); // Set the recipient address - repeat to add multiple recipients + wifiClient.println(F("RCPT TO:")); // Set the recipient address - repeat to add multiple recipients if(!smtpValidResponse()) return false; - smtpClient.println(F("RCPT TO:")); // An optional additional recipient + wifiClient.println(F("RCPT TO:")); // An optional additional recipient if(!smtpValidResponse()) return false; - smtpClient.println(F("DATA")); + wifiClient.println(F("DATA")); if(!smtpValidResponse()) return false; - smtpClient.println(F("From: Security System ")); // Set the sender displayed in the email header - smtpClient.println(F("To: Recipient ")); // Set the recipient displayed in the email header - smtpClient.print(F("Subject: ")); - smtpClient.println(emailSubject); - smtpClient.println(); // Required blank line between the header and body - smtpClient.println(emailBody); - smtpClient.println(F(".")); + wifiClient.println(F("From: Security System ")); // Set the sender displayed in the email header + wifiClient.println(F("To: Recipient ")); // Set the recipient displayed in the email header + wifiClient.print(F("Subject: ")); + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); + wifiClient.println(); // Required blank line between the header and body + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); + wifiClient.println(F(".")); if(!smtpValidResponse()) return false; - smtpClient.println(F("QUIT")); + wifiClient.println(F("QUIT")); if(!smtpValidResponse()) return false; - smtpClient.stop(); + wifiClient.stop(); return true; } @@ -198,11 +234,11 @@ bool smtpValidResponse() { // Waits for a response unsigned long previousMillis = millis(); - while (!smtpClient.available()) { + while (!wifiClient.available()) { dsc.loop(); // Processes Keybus data while waiting on the SMTP response if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - smtpClient.stop(); + wifiClient.stop(); return false; } yield(); @@ -210,11 +246,11 @@ bool smtpValidResponse() { // Checks the first character of the SMTP reply code - the command was successful if the reply code begins // with "2" or "3" - char replyCode = smtpClient.read(); + char replyCode = wifiClient.read(); // Successful, reads the remainder of the response to clear the client buffer if (replyCode == '2' || replyCode == '3') { - while (smtpClient.available()) smtpClient.read(); + while (wifiClient.available()) wifiClient.read(); return true; } @@ -222,11 +258,11 @@ bool smtpValidResponse() { else { Serial.println(F("Email send error, response:")); Serial.print(replyCode); - while (smtpClient.available()) Serial.print((char)smtpClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - smtpClient.println(F("QUIT")); + wifiClient.println(F("QUIT")); smtpValidResponse(); - smtpClient.stop(); + wifiClient.stop(); return false; } } diff --git a/examples/esp32/HomeAssistant-MQTT/HomeAssistant-MQTT.ino b/examples/esp32/HomeAssistant-MQTT/HomeAssistant-MQTT.ino index 6453c32..88e3324 100644 --- a/examples/esp32/HomeAssistant-MQTT/HomeAssistant-MQTT.ino +++ b/examples/esp32/HomeAssistant-MQTT/HomeAssistant-MQTT.ino @@ -212,13 +212,18 @@ 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -228,7 +233,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -278,7 +282,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag char publishTopic[strlen(mqttPartitionTopic) + 2]; appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number @@ -304,13 +307,13 @@ void loop() { // Publishes alarm status if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - if (dsc.alarm[partition]) { - char publishTopic[strlen(mqttPartitionTopic) + 2]; - appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number + char publishTopic[strlen(mqttPartitionTopic) + 2]; + appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number - mqtt.publish(publishTopic, "triggered", true); // Alarm tripped - } + if (dsc.alarm[partition]) mqtt.publish(publishTopic, "triggered", true); // Alarm tripped + else if (!dsc.armedChanged[partition]) mqtt.publish(publishTopic, "disarmed", true); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -426,7 +429,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); } @@ -451,13 +454,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp32/Homebridge-MQTT/Homebridge-MQTT.ino b/examples/esp32/Homebridge-MQTT/Homebridge-MQTT.ino index 8dd30e0..bf71139 100644 --- a/examples/esp32/Homebridge-MQTT/Homebridge-MQTT.ino +++ b/examples/esp32/Homebridge-MQTT/Homebridge-MQTT.ino @@ -212,13 +212,18 @@ char exitState; 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -228,7 +233,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -262,8 +266,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { exitState = 0; @@ -335,7 +337,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, 0, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D", "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -470,7 +474,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // homebridge-mqttthing DISARM - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -495,13 +499,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp32/Homey/Homey.ino b/examples/esp32/Homey/Homey.ino index a374509..8e2fe2c 100755 --- a/examples/esp32/Homey/Homey.ino +++ b/examples/esp32/Homey/Homey.ino @@ -62,19 +62,23 @@ const char* accessCode = ""; // An access code is required to disarm/night arm // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool wifiConnected = false; +bool wifiConnected = true; 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) delay(500); - wifiConnected = true; - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); // Initiate and starts the Homey interface diff --git a/examples/esp32/KeybusReader/KeybusReader.ino b/examples/esp32/KeybusReader/KeybusReader.ino index aaf5a17..91879f7 100644 --- a/examples/esp32/KeybusReader/KeybusReader.ino +++ b/examples/esp32/KeybusReader/KeybusReader.ino @@ -52,6 +52,7 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); @@ -64,7 +65,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } diff --git a/examples/esp32/OpenHAB-MQTT/OpenHAB-MQTT.ino b/examples/esp32/OpenHAB-MQTT/OpenHAB-MQTT.ino index bfae196..5a5c4b5 100644 --- a/examples/esp32/OpenHAB-MQTT/OpenHAB-MQTT.ino +++ b/examples/esp32/OpenHAB-MQTT/OpenHAB-MQTT.ino @@ -163,13 +163,18 @@ 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -179,7 +184,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -229,8 +233,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { // Armed away @@ -264,7 +266,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -378,7 +382,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -404,13 +408,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp32/Pushbullet/Pushbullet.ino b/examples/esp32/Pushbullet/Pushbullet.ino index 711945e..dd11581 100644 --- a/examples/esp32/Pushbullet/Pushbullet.ino +++ b/examples/esp32/Pushbullet/Pushbullet.ino @@ -1,10 +1,17 @@ /* - * Pushbullet Push Notification 1.0 (esp32) + * Pushbullet Push Notification 1.4 (esp32) * * Processes the security system status and demonstrates how to send a push notification when the status has changed. * This example sends notifications via Pushbullet: https://www.pushbullet.com * + * Usage: + * 1. Set the WiFi SSID and password in the sketch. + * 2. Create a PushBullet API access token: https://www.pushbullet.com/#settings + * 3. Copy the access token to pushbulletToken. + * 4. Upload the sketch. + * * Release notes: + * 1.4 - Add HTTPS certificate validation, add customizable message prefix * 1.0 - Initial release * * Wiring: @@ -43,35 +50,82 @@ const char* wifiSSID = ""; const char* wifiPassword = ""; const char* pushbulletToken = ""; // Set the access token generated in the Pushbullet account settings +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin 18 // esp32: 4,13,16-39 #define dscReadPin 19 // esp32: 4,13,16-39 +// HTTPS root certificate for api.pushbullet.com: GlobalSign Root CA - R2, expires 2021.12.15 +const char pushbulletCertificateRoot[] = R"=EOF=( +-----BEGIN CERTIFICATE----- +MIIESjCCAzKgAwIBAgINAeO0nXfN9AwGGRa24zANBgkqhkiG9w0BAQsFADBMMSAw +HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs +U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy +MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg +U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxRDIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCy2Xvh4dc/HJFy//kQzYcVeXS3PkeLsmFV/Qw2xn53Qjqy ++lJbC3GB1k3V6SskTSNeiytyXyFVtSnvRMvrglKrPiekkklBSt6o3THgPN9tek0t +1m0JsA7jYfKy/pBsWnsQZEm0CzwI8up5DGymGolqVjKgKaIwgo+BUQzzornZdbki +nicUukovLGNYh/FdEOZfkbu5W8xH4h51toyPzHVdVwXngsaEDnRyKss7VfVucOtm +acMkuziTNZtoYS+b1q6md3J8cUhYMxCv6YCCHbUHQBv2PeyirUedtJQpNLOML80l +A1g1wCWkVV/hswdWPcjQY7gg+4wdQyz4+anV7G+XAgMBAAGjggEzMIIBLzAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLHdMl3otzdy0s5czib+R3niAQjpMB8G +A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl +BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp +MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g +BDgwNjA0BgZngQwBAgEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y +ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAcUrEwyOu9+OyAnmME+hTjoDF +8OPvcWCpqXs0ZYU0vUc7A1cWAJlIOuDg8OrNtkg81aty8NAby2QtOw10aNd0iDF8 +aroO8IxNeM7aEPSKlkWXqZetxTUaGGTok7YNnR+5Xh2A6udbnI6uDqaE0tEXzrP7 +9oFPPOZon8/xpnbFfafz3X1YD+D2YQEcUY52MytInVyBUXIIF7r9AdPuRvn0smhA +mTEBbE8bxlbrgXPSeVIFkiZbcc2dxNLOI3cPQXppXiElxvi3/3r3R97CAHucWkWc +Kk5GkNl1LNj/jO7M3GnrbOYV0KP/SAusVd/fJZ1CtlGjZpVgxdAi5yJ6UaXMhw== +-----END CERTIFICATE----- +)=EOF="; + // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); -WiFiClientSecure pushClient; -bool wifiConnected = false; +WiFiClientSecure wifiClient; +bool wifiConnected = true; 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) delay(100); - Serial.print(F("WiFi connected: ")); + wifiClient.setCACert(pushbulletCertificateRoot); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); + Serial.print(F("NTP time...")); + configTime(0, 0, "pool.ntp.org"); + time_t now = time(nullptr); + while (now < 24 * 3600) + { + Serial.print("."); + delay(2000); + now = time(nullptr); + } + Serial.println(F("synchronized.")); + // Sends a push notification on startup to verify connectivity - if (sendPush("Security system initializing")) Serial.println(F("Initialization push notification sent successfully.")); - else Serial.println(F("Initialization push notification failed to send.")); + Serial.print(F("Pushbullet....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -106,8 +160,8 @@ void loop() { // Checks if the interface is connected to the Keybus if (dsc.keybusChanged) { dsc.keybusChanged = false; // Resets the Keybus data status flag - if (dsc.keybusConnected) sendPush("Security system connected"); - else sendPush("Security system disconnected"); + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); } // Checks status per partition @@ -118,24 +172,37 @@ void loop() { // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { + char messageContent[25]; - char pushMessage[40]; - if (dsc.armedAway[partition]) { - strcpy(pushMessage, "Security system armed away: partition "); - } - else if (dsc.armedStay[partition]) { - strcpy(pushMessage, "Security system armed stay: partition "); - } - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } else { - char pushMessage[39] = "Security system disarmed: partition "; + char pushMessage[22] = "Disarmed: Partition "; appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + sendMessage(pushMessage); + } + } + + // Checks exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } } @@ -143,22 +210,33 @@ void loop() { if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char pushMessage[38] = "Security system in alarm: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.alarm[partition]) sendPush(pushMessage); - else sendPush("Security system disarmed after alarm"); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks fire alarm status if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char pushMessage[40] = "Security system fire alarm: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.fire[partition]) sendPush(pushMessage); - else sendPush("Security system fire alarm restored"); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } } @@ -175,95 +253,110 @@ void loop() { if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm - char pushMessage[24] = "Security zone alarm: "; + char pushMessage[15] = "Zone alarm: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + sendMessage(pushMessage); } else { - char pushMessage[33] = "Security zone alarm restored: "; + char pushMessage[24] = "Zone alarm restored: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + sendMessage(pushMessage); } } } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + // Checks for AC power status if (dsc.powerChanged) { dsc.powerChanged = false; // Resets the battery trouble status flag - if (dsc.powerTrouble) sendPush("Security system AC power trouble"); - else sendPush("Security system AC power restored"); + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); } // Checks for keypad fire alarm status if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendPush("Security system fire alarm button pressed"); + sendMessage("Keypad Fire alarm"); } // Checks for keypad aux auxiliary alarm status if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendPush("Security system aux alarm button pressed"); + sendMessage("Keypad Aux alarm"); } // Checks for keypad panic alarm status if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendPush("Security system panic alarm button pressed"); + sendMessage("Keypad Panic alarm"); } } } -bool sendPush(const char* pushMessage) { +bool sendMessage(const char* pushMessage) { // Connects and sends the message as JSON - if (!pushClient.connect("api.pushbullet.com", 443)) return false; - pushClient.println(F("POST /v2/pushes HTTP/1.1")); - pushClient.println(F("Host: api.pushbullet.com")); - pushClient.println(F("User-Agent: ESP32")); - pushClient.println(F("Accept: */*")); - pushClient.println(F("Content-Type: application/json")); - pushClient.print(F("Content-Length: ")); - pushClient.println(strlen(pushMessage) + 25); // Length including JSON data - pushClient.print(F("Access-Token: ")); - pushClient.println(pushbulletToken); - pushClient.println(); - pushClient.print(F("{\"body\":\"")); - pushClient.print(pushMessage); - pushClient.print(F("\",\"type\":\"note\"}")); + if (!wifiClient.connect("api.pushbullet.com", 443)) return false; + wifiClient.println(F("POST /v2/pushes HTTP/1.1")); + wifiClient.println(F("Host: api.pushbullet.com")); + wifiClient.println(F("User-Agent: ESP32")); + wifiClient.println(F("Accept: */*")); + wifiClient.println(F("Content-Type: application/json")); + wifiClient.print(F("Content-Length: ")); + wifiClient.println(strlen(pushMessage) + strlen(messagePrefix) + 25); // Length including JSON data + wifiClient.print(F("Access-Token: ")); + wifiClient.println(pushbulletToken); + wifiClient.println(); + wifiClient.print(F("{\"body\":\"")); + wifiClient.print(messagePrefix); + wifiClient.print(pushMessage); + wifiClient.print(F("\",\"type\":\"note\"}")); // Waits for a response unsigned long previousMillis = millis(); - while (!pushClient.available()) { + while (!wifiClient.available()) { dsc.loop(); if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - pushClient.stop(); + wifiClient.stop(); return false; } yield(); } // Reads the response until the first space - the next characters will be the HTTP status code - while (pushClient.available()) { - if (pushClient.read() == ' ') break; + while (wifiClient.available()) { + if (wifiClient.read() == ' ') break; } // Checks the first character of the HTTP status code - the message was sent successfully if the status code // begins with "2" - char statusCode = pushClient.read(); + char statusCode = wifiClient.read(); // Successful, reads the remaining response to clear the client buffer if (statusCode == '2') { - while (pushClient.available()) pushClient.read(); - pushClient.stop(); + while (wifiClient.available()) wifiClient.read(); + wifiClient.stop(); return true; } @@ -271,9 +364,9 @@ bool sendPush(const char* pushMessage) { else { Serial.println(F("Push notification error, response:")); Serial.print(statusCode); - while (pushClient.available()) Serial.print((char)pushClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - pushClient.stop(); + wifiClient.stop(); return false; } } diff --git a/examples/esp32/Status/Status.ino b/examples/esp32/Status/Status.ino index 477f541..60191f3 100644 --- a/examples/esp32/Status/Status.ino +++ b/examples/esp32/Status/Status.ino @@ -52,13 +52,13 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -99,29 +99,32 @@ void loop() { if (dsc.ready[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" ready")); + Serial.println(F(": Ready")); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" not ready")); + Serial.println(F(": Not ready")); } } // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.print(F(" armed")); - if (dsc.armedAway[partition]) Serial.println(F(" away")); - if (dsc.armedStay[partition]) Serial.println(F(" stay")); + Serial.print(F(": Armed ")); + + if (dsc.armedAway[partition]) Serial.print(F("away")); + else if (dsc.armedStay[partition]) Serial.print(F("stay")); + + if (dsc.noEntryDelay[partition]) Serial.println(F(" with no entry delay")); + else Serial.println(); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" disarmed")); + Serial.println(F(": Disarmed")); } } @@ -131,9 +134,15 @@ void loop() { if (dsc.alarm[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" in alarm")); + Serial.println(F(": Alarm")); + } + else if (!dsc.armedChanged[partition]) { + Serial.print(F("Partition ")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks exit delay status if (dsc.exitDelayChanged[partition]) { @@ -141,12 +150,12 @@ void loop() { if (dsc.exitDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" exit delay in progress")); + Serial.println(F(": Exit delay in progress")); } else if (!dsc.armed[partition]) { // Checks for disarm during exit delay Serial.print(F("Partition ")); - Serial.print(partition +1); - Serial.println(F(" disarmed")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } @@ -156,22 +165,22 @@ void loop() { if (dsc.entryDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" entry delay in progress")); + Serial.println(F(": Entry delay in progress")); } } - // Checks if an access code is used to arm or disarm + // Checks the access code used to arm or disarm if (dsc.accessCodeChanged[partition]) { dsc.accessCodeChanged[partition] = false; // Resets the access code status flag Serial.print(F("Partition ")); Serial.print(partition + 1); switch (dsc.accessCode[partition]) { - case 33: Serial.print(F(" duress")); break; - case 34: Serial.print(F(" duress")); break; - case 40: Serial.print(F(" master")); break; - case 41: Serial.print(F(" supervisor")); break; - case 42: Serial.print(F(" supervisor")); break; - default: Serial.print(F(" user")); break; + case 33: Serial.print(F(": Duress")); break; + case 34: Serial.print(F(": Duress")); break; + case 40: Serial.print(F(": Master")); break; + case 41: Serial.print(F(": Supervisor")); break; + case 42: Serial.print(F(": Supervisor")); break; + default: Serial.print(F(": User")); break; } Serial.print(F(" code ")); Serial.println(dsc.accessCode[partition]); @@ -183,12 +192,12 @@ void loop() { if (dsc.fire[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm")); + Serial.println(F(": Fire alarm")); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm restored")); + Serial.println(F(": Fire alarm restored")); } } } @@ -310,19 +319,19 @@ void loop() { // Checks keypad fire alarm triggered if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - Serial.println(F("Keypad fire alarm")); + Serial.println(F("Keypad Fire alarm")); } // Checks keypad auxiliary alarm triggered if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - Serial.println(F("Keypad aux alarm")); + Serial.println(F("Keypad Aux alarm")); } // Checks keypad panic alarm triggered if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - Serial.println(F("Keypad panic alarm")); + Serial.println(F("Keypad Panic alarm")); } } } diff --git a/examples/esp32/Telegram/Telegram.ino b/examples/esp32/Telegram/Telegram.ino new file mode 100644 index 0000000..36e9f6a --- /dev/null +++ b/examples/esp32/Telegram/Telegram.ino @@ -0,0 +1,388 @@ +/* + * Telegram Bot 1.0 (esp32) + * + * Processes the security system status and allows for control via a Telegram bot: https://www.telegram.org + * + * Setup: + * 1. Install the UniversalTelegramBot library from the Arduino IDE/PlatformIO library manager: + * https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot + * 2. Install the ArduinoJSON library from the Arduino IDE/PlatformIO library manager: + * https://github.com/bblanchon/ArduinoJson + * 3. Set the WiFi SSID and password in the sketch. + * 4. Set an access code in the sketch to manage the security system. + * 5. Send a message to BotFather: https://t.me/botfather + * 6. Create a new bot through BotFather: /newbot + * 7. Copy the bot token to the sketch in telegramBotToken. + * 8. Send a message to the newly created bot to start a conversation. + * 9. Send a message to @myidbot: https://telegram.me/myidbot + * 10. Get your user ID: /getid + * 11. Copy the user ID to the sketch in telegramUserID. + * 12. Upload the sketch. + * + * Usage: + * - Set the partition number to manage: /X (where X = 1-8) + * - Arm stay: /armstay + * - Arm away: /armaway + * - Arm night (no entry delay): /armnight + * - Disarm: /disarm + * + * Release notes: + * 1.0 - Initial release + * + * Wiring: + * DSC Aux(+) --- 5v voltage regulator --- esp32 development board 5v pin + * + * DSC Aux(-) --- esp32 Ground + * + * +--- dscClockPin (esp32: 4,13,16-39) + * DSC Yellow --- 33k ohm resistor ---| + * +--- 10k ohm resistor --- Ground + * + * +--- dscReadPin (esp32: 4,13,16-39) + * DSC Green ---- 33k ohm resistor ---| + * +--- 10k ohm resistor --- Ground + * + * Virtual keypad (optional): + * DSC Green ---- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscWritePin (esp32: 4,13,16-33) + * Ground --- NPN emitter --/ + * + * Virtual keypad uses an NPN transistor to pull the data line 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. + */ + +#include +#include +#include +#include +#include + +// Settings +const char* wifiSSID = ""; +const char* wifiPassword = ""; +const char* accessCode = ""; // An access code is required to disarm/night arm and may be required to arm (based on panel configuration) +const char* telegramBotToken = ""; // Set the Telegram bot access token +const char* telegramUserID = ""; // Set the Telegram chat user ID +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages + +// Configures the Keybus interface with the specified pins. +#define dscClockPin 18 // esp32: 4,13,16-39 +#define dscReadPin 19 // esp32: 4,13,16-39 +#define dscWritePin 21 // esp32: 4,13,16-33 + +// Initialize components +dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); +WiFiClientSecure wifiClient; +UniversalTelegramBot telegramBot(telegramBotToken, wifiClient); +const int telegramCheckInterval = 1000; +bool wifiConnected = true; + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println(); + Serial.println(); + + Serial.print(F("WiFi...")); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSSID, wifiPassword); + wifiClient.setCACert(TELEGRAM_CERTIFICATE_ROOT); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); + + Serial.print(F("NTP time...")); + configTime(0, 0, "pool.ntp.org"); + time_t now = time(nullptr); + while (now < 24 * 3600) + { + Serial.print("."); + delay(2000); + now = time(nullptr); + } + Serial.println(F("synchronized.")); + + // Sends a message on startup to verify connectivity + Serial.print(F("Telegram....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); + + // Starts the Keybus interface + dsc.begin(); + Serial.println(F("DSC Keybus Interface is online.")); +} + + +void loop() { + + // Updates status if WiFi drops and reconnects + if (!wifiConnected && WiFi.status() == WL_CONNECTED) { + Serial.println("WiFi reconnected"); + wifiConnected = true; + dsc.pauseStatus = false; + dsc.statusChanged = true; + } + else if (WiFi.status() != WL_CONNECTED && wifiConnected) { + Serial.println("WiFi disconnected"); + wifiConnected = false; + dsc.pauseStatus = true; + } + + // Checks for incoming Telegram messages + static unsigned long telegramPreviousTime; + if (millis() - telegramPreviousTime > telegramCheckInterval) { + byte telegramMessages = telegramBot.getUpdates(telegramBot.last_message_received + 1); + while (telegramMessages) { + handleTelegram(telegramMessages); + telegramMessages = telegramBot.getUpdates(telegramBot.last_message_received + 1); + } + telegramPreviousTime = millis(); + } + + dsc.loop(); + + if (dsc.statusChanged) { // Checks if the security system status has changed + dsc.statusChanged = false; // Reset the status tracking flag + + // If the Keybus data buffer is exceeded, the sketch is too busy to process all Keybus commands. Call + // loop() more often, or increase dscBufferSize in the library: src/dscKeybusInterface.h + if (dsc.bufferOverflow) { + Serial.println(F("Keybus buffer overflow")); + dsc.bufferOverflow = false; + } + + // Checks if the interface is connected to the Keybus + if (dsc.keybusChanged) { + dsc.keybusChanged = false; // Resets the Keybus data status flag + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); + } + + // Sends the access code when needed by the panel for arming + if (dsc.accessCodePrompt) { + dsc.accessCodePrompt = false; + dsc.write(accessCode); + } + + // Checks status per partition + for (byte partition = 0; partition < dscPartitions; partition++) { + + // Skips processing if the partition is disabled or in installer programming + if (dsc.disabled[partition]) continue; + + // Checks armed status + if (dsc.armedChanged[partition]) { + if (dsc.armed[partition]) { + char messageContent[25]; + + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Publishes exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks alarm triggered status + if (dsc.alarmChanged[partition]) { + dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag + + + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag + + // Checks fire alarm status + if (dsc.fireChanged[partition]) { + dsc.fireChanged[partition] = false; // Resets the fire status flag + + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + } + + // Checks for zones in alarm + // Zone alarm status is stored in the alarmZones[] and alarmZonesChanged[] arrays using 1 bit per zone, up to 64 zones + // alarmZones[0] and alarmZonesChanged[0]: Bit 0 = Zone 1 ... Bit 7 = Zone 8 + // alarmZones[1] and alarmZonesChanged[1]: Bit 0 = Zone 9 ... Bit 7 = Zone 16 + // ... + // alarmZones[7] and alarmZonesChanged[7]: Bit 0 = Zone 57 ... Bit 7 = Zone 64 + if (dsc.alarmZonesStatusChanged) { + dsc.alarmZonesStatusChanged = false; // Resets the alarm zones status flag + for (byte zoneGroup = 0; zoneGroup < dscZones; zoneGroup++) { + for (byte zoneBit = 0; zoneBit < 8; zoneBit++) { + if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag + bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag + if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm + char messageContent[15] = "Zone alarm: "; + char zoneNumber[3]; + itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number + strcat(messageContent, zoneNumber); + sendMessage(messageContent); + } + else { + char messageContent[24] = "Zone alarm restored: "; + char zoneNumber[3]; + itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number + strcat(messageContent, zoneNumber); + sendMessage(messageContent); + } + } + } + } + } + + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + + // Checks for AC power status + if (dsc.powerChanged) { + dsc.powerChanged = false; // Resets the battery trouble status flag + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); + } + + // Checks for keypad fire alarm status + if (dsc.keypadFireAlarm) { + dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag + sendMessage("Keypad Fire alarm"); + } + + // Checks for keypad aux auxiliary alarm status + if (dsc.keypadAuxAlarm) { + dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag + sendMessage("Keypad Aux alarm"); + } + + // Checks for keypad panic alarm status + if (dsc.keypadPanicAlarm) { + dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag + sendMessage("Keypad Panic alarm"); + } + } +} + + +void handleTelegram(byte telegramMessages) { + static byte partition = 0; + + for (byte i = 0; i < telegramMessages; i++) { + + // Checks if a partition number 1-8 has been sent and sets the partition + if (telegramBot.messages[i].text[1] >= 0x31 && telegramBot.messages[i].text[1] <= 0x38) { + partition = telegramBot.messages[i].text[1] - 49; + char messageContent[17] = "Set: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + + // Resets status if attempting to change the armed mode while armed or not ready + if (telegramBot.messages[i].text != "/disarm" && !dsc.ready[partition]) { + dsc.armedChanged[partition] = true; + dsc.statusChanged = true; + return; + } + + // Arm stay + if (telegramBot.messages[i].text == "/armstay" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('s'); + } + + // Arm away + else if (telegramBot.messages[i].text == "/armaway" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('w'); + } + + // Arm night + else if (telegramBot.messages[i].text == "/armnight" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('n'); + } + + // Disarm + else if (telegramBot.messages[i].text == "/disarm" && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write(accessCode); + } + } +} + + +bool sendMessage(const char* messageContent) { + byte messageLength = strlen(messagePrefix) + strlen(messageContent) + 1; + char message[messageLength]; + strcpy(message, messagePrefix); + strcat(message, messageContent); + if (telegramBot.sendMessage(telegramUserID, message, "")) return true; + else return false; +} + + +void appendPartition(byte sourceNumber, char* message) { + char partitionNumber[2]; + itoa(sourceNumber + 1, partitionNumber, 10); + strcat(message, partitionNumber); +} diff --git a/examples/esp32/Twilio-SMS/Twilio-SMS.ino b/examples/esp32/Twilio-SMS/Twilio-SMS.ino index 7f9dee9..62d0d04 100644 --- a/examples/esp32/Twilio-SMS/Twilio-SMS.ino +++ b/examples/esp32/Twilio-SMS/Twilio-SMS.ino @@ -41,16 +41,15 @@ #include #include -// WiFi settings +// Settings const char* wifiSSID = ""; const char* wifiPassword = ""; - -// Twilio settings const char* AccountSID = ""; // Set the account SID from the Twilio Account Dashboard const char* AuthToken = ""; // Set the auth token from the Twilio Account Dashboard const char* Base64EncodedAuth = ""; // macOS/Linux terminal: $ echo -n "AccountSID:AuthToken" | base64 -w 0 const char* From = ""; // i.e. 16041234567 const char* To = ""; // i.e. 16041234567 +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin 18 // esp32: 4,13,16-39 @@ -58,28 +57,33 @@ const char* To = ""; // i.e. 16041234567 // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); -WiFiClientSecure pushClient; -bool wifiConnected = false; +WiFiClientSecure wifiClient; +bool wifiConnected = true; 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) delay(500); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); // Sends a message on startup to verify connectivity - if (sendPush("Security system initializing")) Serial.println(F("Initialization SMS sent successfully.")); - else Serial.println(F("Initialization SMS failed to send.")); + Serial.print(F("Twilio....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -114,8 +118,8 @@ void loop() { // Checks if the interface is connected to the Keybus if (dsc.keybusChanged) { dsc.keybusChanged = false; // Resets the Keybus data status flag - if (dsc.keybusConnected) sendPush("Security system connected"); - else sendPush("Security system disconnected"); + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); } // Checks status per partition @@ -126,24 +130,37 @@ void loop() { // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { + char messageContent[25]; - char pushMessage[40]; - if (dsc.armedAway[partition]) { - strcpy(pushMessage, "Security system armed away: partition "); - } - else if (dsc.armedStay[partition]) { - strcpy(pushMessage, "Security system armed stay: partition "); - } - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } else { - char pushMessage[39] = "Security system disarmed: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } } @@ -151,22 +168,33 @@ void loop() { if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char pushMessage[38] = "Security system in alarm: Partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.alarm[partition]) sendPush(pushMessage); - else sendPush("Security system disarmed after alarm"); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks fire alarm status if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char pushMessage[40] = "Security system fire alarm: Partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.fire[partition]) sendPush(pushMessage); - else sendPush("Security system fire alarm restored"); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } } @@ -183,101 +211,116 @@ void loop() { if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm - char pushMessage[24] = "Security zone alarm: "; + char messageContent[15] = "Zone alarm: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } else { - char pushMessage[33] = "Security zone alarm restored: "; + char messageContent[24] = "Zone alarm restored: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } } } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + // Checks for AC power status if (dsc.powerChanged) { dsc.powerChanged = false; // Resets the battery trouble status flag - if (dsc.powerTrouble) sendPush("Security system AC power trouble"); - else sendPush("Security system AC power restored"); + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); } // Checks for keypad fire alarm status if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendPush("Security system fire alarm button pressed"); + sendMessage("Keypad Fire alarm"); } // Checks for keypad aux auxiliary alarm status if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendPush("Security system aux alarm button pressed"); + sendMessage("Keypad Aux alarm"); } // Checks for keypad panic alarm status if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendPush("Security system panic alarm button pressed"); + sendMessage("Keypad Panic alarm"); } } } -bool sendPush(const char* pushMessage) { +bool sendMessage(const char* messageContent) { // Connects and sends the message as x-www-form-urlencoded - if (!pushClient.connect("api.twilio.com", 443)) return false; - pushClient.print(F("POST https://api.twilio.com/2010-04-01/Accounts/")); - pushClient.print(AccountSID); - pushClient.println(F("/Messages.json HTTP/1.1")); - pushClient.print(F("Authorization: Basic ")); - pushClient.println(Base64EncodedAuth); - pushClient.println(F("Host: api.twilio.com")); - pushClient.println(F("User-Agent: ESP32")); - pushClient.println(F("Accept: */*")); - pushClient.println(F("Content-Type: application/x-www-form-urlencoded")); - pushClient.print(F("Content-Length: ")); - pushClient.println(strlen(To) + strlen(From) + strlen(pushMessage) + 18); // Length including data - pushClient.println("Connection: Close"); - pushClient.println(); - pushClient.print(F("To=+")); - pushClient.print(To); - pushClient.print(F("&From=+")); - pushClient.print(From); - pushClient.print(F("&Body=")); - pushClient.println(pushMessage); + if (!wifiClient.connect("api.twilio.com", 443)) return false; + wifiClient.print(F("POST https://api.twilio.com/2010-04-01/Accounts/")); + wifiClient.print(AccountSID); + wifiClient.println(F("/Messages.json HTTP/1.1")); + wifiClient.print(F("Authorization: Basic ")); + wifiClient.println(Base64EncodedAuth); + wifiClient.println(F("Host: api.twilio.com")); + wifiClient.println(F("User-Agent: ESP32")); + wifiClient.println(F("Accept: */*")); + wifiClient.println(F("Content-Type: application/x-www-form-urlencoded")); + wifiClient.print(F("Content-Length: ")); + wifiClient.println(strlen(To) + strlen(From) + strlen(messagePrefix) + strlen(messageContent) + 18); // Length including data + wifiClient.println("Connection: Close"); + wifiClient.println(); + wifiClient.print(F("To=+")); + wifiClient.print(To); + wifiClient.print(F("&From=+")); + wifiClient.print(From); + wifiClient.print(F("&Body=")); + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); // Waits for a response unsigned long previousMillis = millis(); - while (!pushClient.available()) { + while (!wifiClient.available()) { dsc.loop(); if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - pushClient.stop(); + wifiClient.stop(); return false; } yield(); } // Reads the response until the first space - the next characters will be the HTTP status code - while (pushClient.available()) { - if (pushClient.read() == ' ') break; + while (wifiClient.available()) { + if (wifiClient.read() == ' ') break; } // Checks the first character of the HTTP status code - the message was sent successfully if the status code // begins with "2" - char statusCode = pushClient.read(); + char statusCode = wifiClient.read(); // Successful, reads the remaining response to clear the client buffer if (statusCode == '2') { - while (pushClient.available()) pushClient.read(); - pushClient.stop(); + while (wifiClient.available()) wifiClient.read(); + wifiClient.stop(); return true; } @@ -285,16 +328,16 @@ bool sendPush(const char* pushMessage) { else { Serial.println(F("SMS messaging error, response:")); Serial.print(statusCode); - while (pushClient.available()) Serial.print((char)pushClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - pushClient.stop(); + wifiClient.stop(); return false; } } -void appendPartition(byte sourceNumber, char* pushMessage) { +void appendPartition(byte sourceNumber, char* messageContent) { char partitionNumber[2]; itoa(sourceNumber + 1, partitionNumber, 10); - strcat(pushMessage, partitionNumber); + strcat(messageContent, partitionNumber); } diff --git a/examples/esp32/Unlocker/Unlocker.ino b/examples/esp32/Unlocker/Unlocker.ino index 43cc781..4e8b9c7 100644 --- a/examples/esp32/Unlocker/Unlocker.ino +++ b/examples/esp32/Unlocker/Unlocker.ino @@ -493,6 +493,7 @@ bool pauseTest = false; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); diff --git a/examples/esp32/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino b/examples/esp32/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino index 83fbe0f..a11cdfb 100644 --- a/examples/esp32/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino +++ b/examples/esp32/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino @@ -11,6 +11,7 @@ * 3. Select "Refresh" to generate a new auth token. * 4. Go back to Project Settings, copy the auth token, and paste it in an email or message to yourself. * 5. Add the auth token to the sketch below. + * 6. Upload the sketch. * * Installing Blynk as a local server (https://github.com/blynkkk/blynk-server) is recommended to keep control of the * security system internal to your network. This also lets you use as many widgets as needed for free - local @@ -75,11 +76,9 @@ #define BLYNK_PRINT Serial -// WiFi settings +// Settings char wifiSSID[] = ""; char wifiPassword[] = ""; - -// Blynk settings char blynkAuthToken[] = ""; // Token generated from within the Blynk app char blynkServer[] = ""; // Blynk local server address int blynkPort = 8080; // Blynk local server port @@ -92,7 +91,7 @@ int blynkPort = 8080; // Blynk local server port // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool wifiConnected = false; +bool wifiConnected = true; bool partitionChanged; byte viewPartition = 1; const char* lcdPartition = "Partition "; @@ -172,17 +171,24 @@ WidgetLED ledZone64(V124); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); + + Serial.print(F("Blynk...")); WiFi.mode(WIFI_STA); Blynk.begin(blynkAuthToken, wifiSSID, wifiPassword, blynkServer, blynkPort); - while (WiFi.status() != WL_CONNECTED) delay(100); - wifiConnected = true; + WiFi.begin(wifiSSID, wifiPassword); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -772,14 +778,14 @@ void setStatus(byte partition) { case 0x0B: lcd.print(0,1, " "); lcd.print(0,1, "Quick exit"); break; case 0x0C: lcd.print(0,1, " "); lcd.print(0,1, "Entry delay"); break; case 0x0D: lcd.print(0,1, " "); lcd.print(0,1, "Alarm memory"); break; - case 0x0E: lcd.print(0,1, " "); lcd.print(0,1, "Not available"); break; + case 0x0E: lcd.print(0,1, " "); lcd.print(0,1, "Not available"); break; case 0x10: lcd.print(0,1, " "); lcd.print(0,1, "Keypad lockout"); break; case 0x11: lcd.print(0,1, " "); lcd.print(0,1, "Alarm"); break; - case 0x12: lcd.print(0,1, " "); lcd.print(0,1, "Battery check"); break; + case 0x12: lcd.print(0,1, " "); lcd.print(0,1, "Battery check"); break; case 0x14: lcd.print(0,1, " "); lcd.print(0,1, "Auto-arm"); break; case 0x15: lcd.print(0,1, " "); lcd.print(0,1, "Arm with bypass"); break; case 0x16: lcd.print(0,1, " "); lcd.print(0,1, "No entry delay"); break; - case 0x19: lcd.print(0,1, " "); lcd.print(0,1, "Alarm occured"); break; + case 0x19: lcd.print(0,1, " "); lcd.print(0,1, "Alarm occured"); break; case 0x22: lcd.print(0,1, " "); lcd.print(0,1, "Recent closing"); break; case 0x2F: lcd.print(0,1, " "); lcd.print(0,1, "LCD Pixel check"); break; case 0x33: lcd.print(0,1, " "); lcd.print(0,1, "Busy"); break; @@ -812,10 +818,10 @@ void setStatus(byte partition) { case 0xB8: lcd.print(0,1, " "); lcd.print(0,1, "Enter * code"); break; case 0xB9: lcd.print(0,1, " "); lcd.print(0,1, "Zone tamper"); break; case 0xBA: lcd.print(0,1, " "); lcd.print(0,1, "Zones low batt."); break; - case 0xBC: lcd.print(0,1, " "); lcd.print(0,1, "New 6-digit code"); break; + case 0xBC: lcd.print(0,1, " "); lcd.print(0,1, "New 6-digit code"); break; case 0xC6: lcd.print(0,1, " "); lcd.print(0,1, "Zone fault menu"); break; case 0xC8: lcd.print(0,1, " "); lcd.print(0,1, "Service required"); break; - case 0xCE: lcd.print(0,1, " "); lcd.print(0,1, "Active cam. mon."); break; + case 0xCE: lcd.print(0,1, " "); lcd.print(0,1, "Active cam. mon."); break; case 0xD0: lcd.print(0,1, " "); lcd.print(0,1, "Keypads low batt"); break; case 0xD1: lcd.print(0,1, " "); lcd.print(0,1, "Wireless low bat"); break; case 0xE4: lcd.print(0,1, " "); lcd.print(0,1, "Installer menu"); break; @@ -828,17 +834,17 @@ void setStatus(byte partition) { case 0xEC: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; case 0xED: lcd.print(0,1, " "); lcd.print(0,1, "Input: 32 digits"); break; case 0xEE: lcd.print(0,1, " "); lcd.print(0,1, "Input: option"); break; - case 0xEF: lcd.print(0,1, " "); lcd.print(0,1, "Supv. modules"); break; + case 0xEF: lcd.print(0,1, " "); lcd.print(0,1, "Supv. modules"); break; case 0xF0: lcd.print(0,1, " "); lcd.print(0,1, "Function key 1"); break; case 0xF1: lcd.print(0,1, " "); lcd.print(0,1, "Function key 2"); break; case 0xF2: lcd.print(0,1, " "); lcd.print(0,1, "Function key 3"); break; case 0xF3: lcd.print(0,1, " "); lcd.print(0,1, "Function key 4"); break; case 0xF4: lcd.print(0,1, " "); lcd.print(0,1, "Function key 5"); break; - case 0xF5: lcd.print(0,1, " "); lcd.print(0,1, "Wls. place. test"); break; - case 0xF6: lcd.print(0,1, " "); lcd.print(0,1, "Activate device"); break; + case 0xF5: lcd.print(0,1, " "); lcd.print(0,1, "Wls. place. test"); break; + case 0xF6: lcd.print(0,1, " "); lcd.print(0,1, "Activate device"); break; case 0xF7: lcd.print(0,1, " "); lcd.print(0,1, "*8PGM subsection"); break; case 0xF8: lcd.print(0,1, " "); lcd.print(0,1, "Keypad program"); break; - case 0xFA: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; + case 0xFA: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; default: return; } } diff --git a/examples/esp8266/Email/Email.ino b/examples/esp8266/Email/Email.ino index 1bd1cdb..e55c667 100644 --- a/examples/esp8266/Email/Email.ino +++ b/examples/esp8266/Email/Email.ino @@ -2,7 +2,7 @@ * Email Notification 1.3 (esp8266) * * Processes the security system status and demonstrates how to send an email when the status has changed. Configure - * the email SMTP server settings in sendEmail(). + * the email SMTP server settings in sendMessage(). * * Email is sent using SMTPS (port 465) with SSL for encryption - this is necessary on the ESP8266 until STARTTLS can * be supported. For example, this will work with Gmail after changing the account settings to allow less secure @@ -48,12 +48,10 @@ #include #include -// WiFi settings +// Settings const char* wifiSSID = ""; const char* wifiPassword = ""; - -WiFiClientSecure smtpClient; -bool wifiConnected = false; +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin D1 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) @@ -61,31 +59,34 @@ bool wifiConnected = false; // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); +WiFiClientSecure wifiClient; +bool wifiConnected = true; 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) delay(100); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); + wifiClient.setInsecure(); - // Sets authentication method for BearSSL in esp8266 Arduino Core 2.5.0+ - #if HAS_ESP8266_VERSION_NUMERIC - if (esp8266::coreVersionNumeric() >= 20500000) smtpClient.setInsecure(); - #endif - - // Sends an email on startup to verify connectivity - if (sendEmail("Security system initializing", "")) Serial.println(F("Initialization email sent successfully.")); - else Serial.println(F("Initialization email failed to send.")); + // Sends a message on startup to verify connectivity + Serial.print(F("Email....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -117,90 +118,120 @@ void loop() { dsc.bufferOverflow = false; } - if (dsc.keypadFireAlarm) { - dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendEmail("Security system fire alarm button pressed", ""); - } - - if (dsc.keypadAuxAlarm) { - dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendEmail("Security system aux alarm button pressed", ""); - } - - if (dsc.keypadPanicAlarm) { - dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendEmail("Security system panic alarm button pressed", ""); - } - - if (dsc.powerChanged) { - dsc.powerChanged = false; // Resets the power trouble status flag - if (dsc.powerTrouble) sendEmail("Security system AC power trouble", ""); - else sendEmail("Security system AC power restored", ""); - } - // Checks status per partition for (byte partition = 0; partition < dscPartitions; partition++) { // Skips processing if the partition is disabled or in installer programming if (dsc.disabled[partition]) continue; + // Checks alarm triggered status if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char emailBody[12] = "Partition "; - appendPartition(partition, emailBody); // Appends the email body with the partition number - - if (dsc.alarm[partition]) sendEmail("Security system in alarm", emailBody); - else sendEmail("Security system disarmed after alarm", emailBody); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[34] = "Disarmed after alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char emailBody[12] = "Partition "; - appendPartition(partition, emailBody); // Appends the email body with the partition number - - if (dsc.fire[partition]) sendEmail("Security system fire alarm", emailBody); - else sendEmail("Security system fire alarm restored", emailBody); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + + // Checks for AC power status + if (dsc.powerChanged) { + dsc.powerChanged = false; // Resets the battery trouble status flag + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); + } + + // Checks for keypad fire alarm status + if (dsc.keypadFireAlarm) { + dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag + sendMessage("Keypad Fire alarm"); + } + + // Checks for keypad aux auxiliary alarm status + if (dsc.keypadAuxAlarm) { + dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag + sendMessage("Keypad Aux alarm"); + } + + // Checks for keypad panic alarm status + if (dsc.keypadPanicAlarm) { + dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag + sendMessage("Keypad Panic alarm"); + } } } -// sendEmail() takes the email subject and body as separate parameters. Configure the settings for your SMTP +// sendMessage() takes the email subject and body as separate parameters. Configure the settings for your SMTP // server - the login and password must be base64 encoded. For example, on the macOS/Linux terminal: // $ echo -n 'mylogin@example.com' | base64 -w 0 -bool sendEmail(const char* emailSubject, const char* emailBody) { - if (!smtpClient.connect("smtp.example.com", 465)) return false; // Set the SMTP server address - for example: smtp.gmail.com +bool sendMessage(const char* messageContent) { + if (!wifiClient.connect("smtp.example.com", 465)) return false; // Set the SMTP server address - for example: smtp.gmail.com if(!smtpValidResponse()) return false; - smtpClient.println(F("HELO ESP8266")); + wifiClient.println(F("HELO ESP8266")); if(!smtpValidResponse()) return false; - smtpClient.println(F("AUTH LOGIN")); + wifiClient.println(F("AUTH LOGIN")); if(!smtpValidResponse()) return false; - smtpClient.println(F("myBase64encodedLogin")); // Set the SMTP server login in base64 + wifiClient.println(F("myBase64encodedLogin")); // Set the SMTP server login in base64 if(!smtpValidResponse()) return false; - smtpClient.println(F("myBase64encodedPassword")); // Set the SMTP server password in base64 + wifiClient.println(F("myBase64encodedPassword")); // Set the SMTP server password in base64 if(!smtpValidResponse()) return false; - smtpClient.println(F("MAIL FROM:")); // Set the sender address + wifiClient.println(F("MAIL FROM:")); // Set the sender address if(!smtpValidResponse()) return false; - smtpClient.println(F("RCPT TO:")); // Set the recipient address - repeat to add multiple recipients + wifiClient.println(F("RCPT TO:")); // Set the recipient address - repeat to add multiple recipients if(!smtpValidResponse()) return false; - smtpClient.println(F("RCPT TO:")); // An optional additional recipient + wifiClient.println(F("RCPT TO:")); // An optional additional recipient if(!smtpValidResponse()) return false; - smtpClient.println(F("DATA")); + wifiClient.println(F("DATA")); if(!smtpValidResponse()) return false; - smtpClient.println(F("From: Security System ")); // Set the sender displayed in the email header - smtpClient.println(F("To: Recipient ")); // Set the recipient displayed in the email header - smtpClient.print(F("Subject: ")); - smtpClient.println(emailSubject); - smtpClient.println(); // Required blank line between the header and body - smtpClient.println(emailBody); - smtpClient.println(F(".")); + wifiClient.println(F("From: Security System ")); // Set the sender displayed in the email header + wifiClient.println(F("To: Recipient ")); // Set the recipient displayed in the email header + wifiClient.print(F("Subject: ")); + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); + wifiClient.println(); // Required blank line between the header and body + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); + wifiClient.println(F(".")); if(!smtpValidResponse()) return false; - smtpClient.println(F("QUIT")); + wifiClient.println(F("QUIT")); if(!smtpValidResponse()) return false; - smtpClient.stop(); + wifiClient.stop(); return true; } @@ -209,11 +240,11 @@ bool smtpValidResponse() { // Waits for a response unsigned long previousMillis = millis(); - while (!smtpClient.available()) { + while (!wifiClient.available()) { dsc.loop(); // Processes Keybus data while waiting on the SMTP response if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - smtpClient.stop(); + wifiClient.stop(); return false; } yield(); @@ -221,11 +252,11 @@ bool smtpValidResponse() { // Checks the first character of the SMTP reply code - the command was successful if the reply code begins // with "2" or "3" - char replyCode = smtpClient.read(); + char replyCode = wifiClient.read(); // Successful, reads the remainder of the response to clear the client buffer if (replyCode == '2' || replyCode == '3') { - while (smtpClient.available()) smtpClient.read(); + while (wifiClient.available()) wifiClient.read(); return true; } @@ -233,18 +264,18 @@ bool smtpValidResponse() { else { Serial.println(F("Email send error, response:")); Serial.print(replyCode); - while (smtpClient.available()) Serial.print((char)smtpClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - smtpClient.println(F("QUIT")); + wifiClient.println(F("QUIT")); smtpValidResponse(); - smtpClient.stop(); + wifiClient.stop(); return false; } } -void appendPartition(byte sourceNumber, char* pushMessage) { +void appendPartition(byte sourceNumber, char* messageContent) { char partitionNumber[2]; itoa(sourceNumber + 1, partitionNumber, 10); - strcat(pushMessage, partitionNumber); + strcat(messageContent, partitionNumber); } diff --git a/examples/esp8266/HomeAssistant-MQTT/HomeAssistant-MQTT.ino b/examples/esp8266/HomeAssistant-MQTT/HomeAssistant-MQTT.ino index 70d99e0..846ac55 100644 --- a/examples/esp8266/HomeAssistant-MQTT/HomeAssistant-MQTT.ino +++ b/examples/esp8266/HomeAssistant-MQTT/HomeAssistant-MQTT.ino @@ -220,13 +220,18 @@ 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -236,7 +241,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -286,7 +290,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag char publishTopic[strlen(mqttPartitionTopic) + 2]; appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number @@ -312,13 +315,13 @@ void loop() { // Publishes alarm status if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - if (dsc.alarm[partition]) { - char publishTopic[strlen(mqttPartitionTopic) + 2]; - appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number + char publishTopic[strlen(mqttPartitionTopic) + 2]; + appendPartition(mqttPartitionTopic, partition, publishTopic); // Appends the mqttPartitionTopic with the partition number - mqtt.publish(publishTopic, "triggered", true); // Alarm tripped - } + if (dsc.alarm[partition]) mqtt.publish(publishTopic, "triggered", true); // Alarm tripped + else if (!dsc.armedChanged[partition]) mqtt.publish(publishTopic, "disarmed", true); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -434,7 +437,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + else if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); } @@ -459,13 +462,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp8266/Homebridge-MQTT/Homebridge-MQTT.ino b/examples/esp8266/Homebridge-MQTT/Homebridge-MQTT.ino index fed078a..181ca63 100644 --- a/examples/esp8266/Homebridge-MQTT/Homebridge-MQTT.ino +++ b/examples/esp8266/Homebridge-MQTT/Homebridge-MQTT.ino @@ -218,13 +218,18 @@ char exitState; 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -234,7 +239,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -268,8 +272,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { exitState = 0; @@ -341,7 +343,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, 0, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D", "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -476,7 +480,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // homebridge-mqttthing DISARM - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -501,13 +505,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp8266/Homey/Homey.ino b/examples/esp8266/Homey/Homey.ino index f18ba36..45999f1 100755 --- a/examples/esp8266/Homey/Homey.ino +++ b/examples/esp8266/Homey/Homey.ino @@ -65,19 +65,23 @@ const char* accessCode = ""; // An access code is required to disarm/night arm // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool wifiConnected = false; +bool wifiConnected = true; 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) delay(500); - wifiConnected = true; - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); // Initiate and starts the Homey interface diff --git a/examples/esp8266/KeybusReader/KeybusReader.ino b/examples/esp8266/KeybusReader/KeybusReader.ino index f5beb34..edca203 100644 --- a/examples/esp8266/KeybusReader/KeybusReader.ino +++ b/examples/esp8266/KeybusReader/KeybusReader.ino @@ -53,6 +53,7 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); @@ -65,7 +66,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } diff --git a/examples/esp8266/KeybusReaderIP/KeybusReaderIP.ino b/examples/esp8266/KeybusReaderIP/KeybusReaderIP.ino index 5dc934f..2e665e3 100644 --- a/examples/esp8266/KeybusReaderIP/KeybusReaderIP.ino +++ b/examples/esp8266/KeybusReaderIP/KeybusReaderIP.ino @@ -69,13 +69,18 @@ bool newClient = true; 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) delay(100); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); if (!MDNS.begin(dnsHostname)) { diff --git a/examples/esp8266/OpenHAB-MQTT/OpenHAB-MQTT.ino b/examples/esp8266/OpenHAB-MQTT/OpenHAB-MQTT.ino index 6c1cfa0..7607c7d 100644 --- a/examples/esp8266/OpenHAB-MQTT/OpenHAB-MQTT.ino +++ b/examples/esp8266/OpenHAB-MQTT/OpenHAB-MQTT.ino @@ -164,13 +164,18 @@ 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) delay(500); - Serial.print("WiFi connected: "); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); mqtt.setCallback(mqttCallback); @@ -180,7 +185,6 @@ void setup() { // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -230,8 +234,6 @@ void loop() { // Publishes armed/disarmed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag - if (dsc.armed[partition]) { // Armed away @@ -265,7 +267,9 @@ void loop() { if (dsc.alarm[partition]) { publishState(mqttPartitionTopic, partition, "T"); } + else if (!dsc.armedChanged[partition]) publishState(mqttPartitionTopic, partition, "D"); } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Publishes fire alarm status if (dsc.fireChanged[partition]) { @@ -379,7 +383,7 @@ void mqttCallback(char* topic, byte* payload, unsigned int length) { } // Disarm - if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition])) { + if (payload[payloadIndex] == 'D' && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { dsc.writePartition = partition + 1; // Sets writes to the partition number dsc.write(accessCode); return; @@ -405,13 +409,14 @@ void mqttHandle() { bool mqttConnect() { + Serial.print(F("MQTT....")); if (mqtt.connect(mqttClientName, mqttUsername, mqttPassword, mqttStatusTopic, 0, true, mqttLwtMessage)) { - Serial.print(F("MQTT connected: ")); + Serial.print(F("connected: ")); Serial.println(mqttServer); dsc.resetStatus(); // Resets the state of all status components as changed to get the current status } else { - Serial.print(F("MQTT connection failed: ")); + Serial.print(F("connection error: ")); Serial.println(mqttServer); } return mqtt.connected(); diff --git a/examples/esp8266/Pushbullet/Pushbullet.ino b/examples/esp8266/Pushbullet/Pushbullet.ino index 732371a..4459242 100644 --- a/examples/esp8266/Pushbullet/Pushbullet.ino +++ b/examples/esp8266/Pushbullet/Pushbullet.ino @@ -1,10 +1,17 @@ /* - * Pushbullet Push Notification 1.3 (esp8266) + * Pushbullet Push Notification 1.4 (esp8266) * * Processes the security system status and demonstrates how to send a push notification when the status has changed. * This example sends notifications via Pushbullet: https://www.pushbullet.com * + * Usage: + * 1. Set the WiFi SSID and password in the sketch. + * 2. Create a PushBullet API access token: https://www.pushbullet.com/#settings + * 3. Copy the access token to pushbulletToken. + * 4. Upload the sketch. + * * Release notes: + * 1.4 - Add HTTPS certificate validation, add customizable message prefix * 1.3 - Updated esp8266 wiring diagram for 33k/10k resistors * 1.2 - Check if WiFi disconnects and wait to send updates until reconnection * Add appendPartition() to simplify sketch @@ -49,39 +56,83 @@ const char* wifiSSID = ""; const char* wifiPassword = ""; const char* pushbulletToken = ""; // Set the access token generated in the Pushbullet account settings +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin D1 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) #define dscReadPin D2 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) +// HTTPS root certificate for api.pushbullet.com: GlobalSign Root CA - R2, expires 2021.12.15 +const char pushbulletCertificateRoot[] = R"=EOF=( +-----BEGIN CERTIFICATE----- +MIIESjCCAzKgAwIBAgINAeO0nXfN9AwGGRa24zANBgkqhkiG9w0BAQsFADBMMSAw +HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs +U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy +MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg +U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxRDIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCy2Xvh4dc/HJFy//kQzYcVeXS3PkeLsmFV/Qw2xn53Qjqy ++lJbC3GB1k3V6SskTSNeiytyXyFVtSnvRMvrglKrPiekkklBSt6o3THgPN9tek0t +1m0JsA7jYfKy/pBsWnsQZEm0CzwI8up5DGymGolqVjKgKaIwgo+BUQzzornZdbki +nicUukovLGNYh/FdEOZfkbu5W8xH4h51toyPzHVdVwXngsaEDnRyKss7VfVucOtm +acMkuziTNZtoYS+b1q6md3J8cUhYMxCv6YCCHbUHQBv2PeyirUedtJQpNLOML80l +A1g1wCWkVV/hswdWPcjQY7gg+4wdQyz4+anV7G+XAgMBAAGjggEzMIIBLzAOBgNV +HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud +EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLHdMl3otzdy0s5czib+R3niAQjpMB8G +A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl +BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp +MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g +BDgwNjA0BgZngQwBAgEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y +ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAcUrEwyOu9+OyAnmME+hTjoDF +8OPvcWCpqXs0ZYU0vUc7A1cWAJlIOuDg8OrNtkg81aty8NAby2QtOw10aNd0iDF8 +aroO8IxNeM7aEPSKlkWXqZetxTUaGGTok7YNnR+5Xh2A6udbnI6uDqaE0tEXzrP7 +9oFPPOZon8/xpnbFfafz3X1YD+D2YQEcUY52MytInVyBUXIIF7r9AdPuRvn0smhA +mTEBbE8bxlbrgXPSeVIFkiZbcc2dxNLOI3cPQXppXiElxvi3/3r3R97CAHucWkWc +Kk5GkNl1LNj/jO7M3GnrbOYV0KP/SAusVd/fJZ1CtlGjZpVgxdAi5yJ6UaXMhw== +-----END CERTIFICATE----- +)=EOF="; + // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); -WiFiClientSecure pushClient; -bool wifiConnected = false; +X509List pushbulletCert(pushbulletCertificateRoot); +WiFiClientSecure wifiClient; +bool wifiConnected = true; + 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) delay(100); - Serial.print(F("WiFi connected: ")); + wifiClient.setTrustAnchors(&pushbulletCert); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); - // Sets authentication method for BearSSL in esp8266 Arduino Core 2.5.0+ - #if HAS_ESP8266_VERSION_NUMERIC - if (esp8266::coreVersionNumeric() >= 20500000) pushClient.setInsecure(); - #endif + Serial.print(F("NTP time")); + configTime(0, 0, "pool.ntp.org"); + time_t now = time(nullptr); + while (now < 24 * 3600) + { + Serial.print("."); + delay(2000); + now = time(nullptr); + } + Serial.println(F("synchronized.")); - // Sends a push notification on startup to verify connectivity - if (sendPush("Security system initializing")) Serial.println(F("Initialization push notification sent successfully.")); - else Serial.println(F("Initialization push notification failed to send.")); + // Sends a message on startup to verify connectivity + Serial.print(F("Pushbullet....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -116,8 +167,8 @@ void loop() { // Checks if the interface is connected to the Keybus if (dsc.keybusChanged) { dsc.keybusChanged = false; // Resets the Keybus data status flag - if (dsc.keybusConnected) sendPush("Security system connected"); - else sendPush("Security system disconnected"); + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); } // Checks status per partition @@ -128,24 +179,37 @@ void loop() { // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { + char messageContent[25]; - char pushMessage[40]; - if (dsc.armedAway[partition]) { - strcpy(pushMessage, "Security system armed away: partition "); - } - else if (dsc.armedStay[partition]) { - strcpy(pushMessage, "Security system armed stay: partition "); - } - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } else { - char pushMessage[39] = "Security system disarmed: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } } @@ -153,22 +217,33 @@ void loop() { if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char pushMessage[38] = "Security system in alarm: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.alarm[partition]) sendPush(pushMessage); - else sendPush("Security system disarmed after alarm"); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks fire alarm status if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char pushMessage[40] = "Security system fire alarm: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.fire[partition]) sendPush(pushMessage); - else sendPush("Security system fire alarm restored"); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } } @@ -185,95 +260,110 @@ void loop() { if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm - char pushMessage[24] = "Security zone alarm: "; + char messageContent[15] = "Zone alarm: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } else { - char pushMessage[33] = "Security zone alarm restored: "; + char messageContent[24] = "Zone alarm restored: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } } } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + // Checks for AC power status if (dsc.powerChanged) { dsc.powerChanged = false; // Resets the battery trouble status flag - if (dsc.powerTrouble) sendPush("Security system AC power trouble"); - else sendPush("Security system AC power restored"); + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); } // Checks for keypad fire alarm status if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendPush("Security system fire alarm button pressed"); + sendMessage("Keypad Fire alarm"); } // Checks for keypad aux auxiliary alarm status if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendPush("Security system aux alarm button pressed"); + sendMessage("Keypad Aux alarm"); } // Checks for keypad panic alarm status if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendPush("Security system panic alarm button pressed"); + sendMessage("Keypad Panic alarm"); } } } -bool sendPush(const char* pushMessage) { - - // Connects and sends the message as JSON - if (!pushClient.connect("api.pushbullet.com", 443)) return false; - pushClient.println(F("POST /v2/pushes HTTP/1.1")); - pushClient.println(F("Host: api.pushbullet.com")); - pushClient.println(F("User-Agent: ESP8266")); - pushClient.println(F("Accept: */*")); - pushClient.println(F("Content-Type: application/json")); - pushClient.print(F("Content-Length: ")); - pushClient.println(strlen(pushMessage) + 25); // Length including JSON data - pushClient.print(F("Access-Token: ")); - pushClient.println(pushbulletToken); - pushClient.println(); - pushClient.print(F("{\"body\":\"")); - pushClient.print(pushMessage); - pushClient.print(F("\",\"type\":\"note\"}")); +bool sendMessage(const char* messageContent) { + + // Connects and sends the message as a Pushbullet note-type push + if (!wifiClient.connect("api.pushbullet.com", 443)) return false; + wifiClient.println(F("POST /v2/pushes HTTP/1.1")); + wifiClient.println(F("Host: api.pushbullet.com")); + wifiClient.println(F("User-Agent: ESP8266")); + wifiClient.println(F("Accept: */*")); + wifiClient.println(F("Content-Type: application/json")); + wifiClient.print(F("Content-Length: ")); + wifiClient.println(strlen(messageContent) + strlen(messagePrefix) + 25); // Length including JSON data + wifiClient.print(F("Access-Token: ")); + wifiClient.println(pushbulletToken); + wifiClient.println(); + wifiClient.print(F("{\"body\":\"")); + wifiClient.print(messagePrefix); + wifiClient.print(messageContent); + wifiClient.print(F("\",\"type\":\"note\"}")); // Waits for a response unsigned long previousMillis = millis(); - while (!pushClient.available()) { + while (!wifiClient.available()) { dsc.loop(); if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - pushClient.stop(); + wifiClient.stop(); return false; } yield(); } // Reads the response until the first space - the next characters will be the HTTP status code - while (pushClient.available()) { - if (pushClient.read() == ' ') break; + while (wifiClient.available()) { + if (wifiClient.read() == ' ') break; } // Checks the first character of the HTTP status code - the message was sent successfully if the status code // begins with "2" - char statusCode = pushClient.read(); + char statusCode = wifiClient.read(); // Successful, reads the remaining response to clear the client buffer if (statusCode == '2') { - while (pushClient.available()) pushClient.read(); - pushClient.stop(); + while (wifiClient.available()) wifiClient.read(); + wifiClient.stop(); return true; } @@ -281,16 +371,16 @@ bool sendPush(const char* pushMessage) { else { Serial.println(F("Push notification error, response:")); Serial.print(statusCode); - while (pushClient.available()) Serial.print((char)pushClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - pushClient.stop(); + wifiClient.stop(); return false; } } -void appendPartition(byte sourceNumber, char* pushMessage) { +void appendPartition(byte sourceNumber, char* messageContent) { char partitionNumber[2]; itoa(sourceNumber + 1, partitionNumber, 10); - strcat(pushMessage, partitionNumber); + strcat(messageContent, partitionNumber); } diff --git a/examples/esp8266/Status/Status.ino b/examples/esp8266/Status/Status.ino index 036249a..558f6e5 100644 --- a/examples/esp8266/Status/Status.ino +++ b/examples/esp8266/Status/Status.ino @@ -54,13 +54,13 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -112,18 +112,21 @@ void loop() { // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.print(F(" armed")); - if (dsc.armedAway[partition]) Serial.println(F(" away")); - if (dsc.armedStay[partition]) Serial.println(F(" stay")); + Serial.print(F(": Armed ")); + + if (dsc.armedAway[partition]) Serial.print(F("away")); + else if (dsc.armedStay[partition]) Serial.print(F("stay")); + + if (dsc.noEntryDelay[partition]) Serial.println(F(" with no entry delay")); + else Serial.println(); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" disarmed")); + Serial.println(F(": Disarmed")); } } @@ -133,9 +136,15 @@ void loop() { if (dsc.alarm[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" in alarm")); + Serial.println(F(": Alarm")); + } + else if (!dsc.armedChanged[partition]) { + Serial.print(F("Partition ")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } + if (dsc.armedChanged[partition]) dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks exit delay status if (dsc.exitDelayChanged[partition]) { @@ -143,12 +152,12 @@ void loop() { if (dsc.exitDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" exit delay in progress")); + Serial.println(F(": Exit delay in progress")); } else if (!dsc.armed[partition]) { // Checks for disarm during exit delay Serial.print(F("Partition ")); - Serial.print(partition +1); - Serial.println(F(" disarmed")); + Serial.print(partition + 1); + Serial.println(F(": Disarmed")); } } @@ -158,7 +167,7 @@ void loop() { if (dsc.entryDelay[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" entry delay in progress")); + Serial.println(F(": Entry delay in progress")); } } @@ -168,12 +177,12 @@ void loop() { Serial.print(F("Partition ")); Serial.print(partition + 1); switch (dsc.accessCode[partition]) { - case 33: Serial.print(F(" duress")); break; - case 34: Serial.print(F(" duress")); break; - case 40: Serial.print(F(" master")); break; - case 41: Serial.print(F(" supervisor")); break; - case 42: Serial.print(F(" supervisor")); break; - default: Serial.print(F(" user")); break; + case 33: Serial.print(F(": Duress")); break; + case 34: Serial.print(F(": Duress")); break; + case 40: Serial.print(F(": Master")); break; + case 41: Serial.print(F(": Supervisor")); break; + case 42: Serial.print(F(": Supervisor")); break; + default: Serial.print(F(": User")); break; } Serial.print(F(" code ")); Serial.println(dsc.accessCode[partition]); @@ -185,12 +194,12 @@ void loop() { if (dsc.fire[partition]) { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm")); + Serial.println(F(": Fire alarm")); } else { Serial.print(F("Partition ")); Serial.print(partition + 1); - Serial.println(F(" fire alarm restored")); + Serial.println(F(": Fire alarm restored")); } } } @@ -312,19 +321,19 @@ void loop() { // Checks keypad fire alarm triggered if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - Serial.println(F("Keypad fire alarm")); + Serial.println(F("Keypad Fire alarm")); } // Checks keypad auxiliary alarm triggered if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - Serial.println(F("Keypad aux alarm")); + Serial.println(F("Keypad Aux alarm")); } // Checks keypad panic alarm triggered if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - Serial.println(F("Keypad panic alarm")); + Serial.println(F("Keypad Panic alarm")); } } } diff --git a/examples/esp8266/Telegram/Telegram.ino b/examples/esp8266/Telegram/Telegram.ino new file mode 100644 index 0000000..47a7ac0 --- /dev/null +++ b/examples/esp8266/Telegram/Telegram.ino @@ -0,0 +1,389 @@ +/* + * Telegram Bot 1.0 (esp8266) + * + * Processes the security system status and allows for control via a Telegram bot: https://www.telegram.org + * + * Setup: + * 1. Install the UniversalTelegramBot library from the Arduino IDE/PlatformIO library manager: + * https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot + * 2. Install the ArduinoJSON library from the Arduino IDE/PlatformIO library manager: + * https://github.com/bblanchon/ArduinoJson + * 3. Set the WiFi SSID and password in the sketch. + * 4. Set an access code in the sketch to manage the security system. + * 5. Send a message to BotFather: https://t.me/botfather + * 6. Create a new bot through BotFather: /newbot + * 7. Copy the bot token to the sketch in telegramBotToken. + * 8. Send a message to the newly created bot to start a conversation. + * 9. Send a message to @myidbot: https://telegram.me/myidbot + * 10. Get your user ID: /getid + * 11. Copy the user ID to the sketch in telegramUserID. + * 12. Upload the sketch. + * + * Usage: + * - Set the partition number to manage: /X (where X = 1-8) + * - Arm stay: /armstay + * - Arm away: /armaway + * - Arm night (no entry delay): /armnight + * - Disarm: /disarm + * + * Release notes: + * 1.0 - Initial release + * + * Wiring: + * DSC Aux(+) --- 5v voltage regulator --- esp8266 development board 5v pin (NodeMCU, Wemos) + * + * DSC Aux(-) --- esp8266 Ground + * + * +--- dscClockPin (esp8266: D1, D2, D8) + * DSC Yellow --- 33k ohm resistor ---| + * +--- 10k ohm resistor --- Ground + * + * +--- dscReadPin (esp8266: D1, D2, D8) + * DSC Green ---- 33k ohm resistor ---| + * +--- 10k ohm resistor --- Ground + * + * Virtual keypad (optional): + * DSC Green ---- NPN collector --\ + * |-- NPN base --- 1k ohm resistor --- dscWritePin (esp8266: D1, D2, D8) + * Ground --- NPN emitter --/ + * + * Virtual keypad uses an NPN transistor to pull the data line 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. + */ + +#include +#include +#include +#include +#include + +// Settings +const char* wifiSSID = ""; +const char* wifiPassword = ""; +const char* accessCode = ""; // An access code is required to disarm/night arm and may be required to arm (based on panel configuration) +const char* telegramBotToken = ""; // Set the Telegram bot access token +const char* telegramUserID = ""; // Set the Telegram chat user ID +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages + +// Configures the Keybus interface with the specified pins. +#define dscClockPin D1 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) +#define dscReadPin D2 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) +#define dscWritePin D8 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) + +// Initialize components +dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); +X509List telegramCert(TELEGRAM_CERTIFICATE_ROOT); +WiFiClientSecure wifiClient; +UniversalTelegramBot telegramBot(telegramBotToken, wifiClient); +const int telegramCheckInterval = 1000; +bool wifiConnected = true; + + +void setup() { + Serial.begin(115200); + delay(1000); + Serial.println(); + Serial.println(); + + Serial.print(F("WiFi")); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSSID, wifiPassword); + wifiClient.setTrustAnchors(&telegramCert); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); + + Serial.print(F("NTP time")); + configTime(0, 0, "pool.ntp.org"); + time_t now = time(nullptr); + while (now < 24 * 3600) + { + Serial.print("."); + delay(2000); + now = time(nullptr); + } + Serial.println(F("synchronized.")); + + // Sends a message on startup to verify connectivity + Serial.print(F("Telegram....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); + + // Starts the Keybus interface + dsc.begin(); + Serial.println(F("DSC Keybus Interface is online.")); +} + + +void loop() { + + // Updates status if WiFi drops and reconnects + if (!wifiConnected && WiFi.status() == WL_CONNECTED) { + Serial.println("WiFi reconnected"); + wifiConnected = true; + dsc.pauseStatus = false; + dsc.statusChanged = true; + } + else if (WiFi.status() != WL_CONNECTED && wifiConnected) { + Serial.println("WiFi disconnected"); + wifiConnected = false; + dsc.pauseStatus = true; + } + + // Checks for incoming Telegram messages + static unsigned long telegramPreviousTime; + if (millis() - telegramPreviousTime > telegramCheckInterval) { + byte telegramMessages = telegramBot.getUpdates(telegramBot.last_message_received + 1); + while (telegramMessages) { + handleTelegram(telegramMessages); + telegramMessages = telegramBot.getUpdates(telegramBot.last_message_received + 1); + } + telegramPreviousTime = millis(); + } + + dsc.loop(); + + if (dsc.statusChanged) { // Checks if the security system status has changed + dsc.statusChanged = false; // Reset the status tracking flag + + // If the Keybus data buffer is exceeded, the sketch is too busy to process all Keybus commands. Call + // loop() more often, or increase dscBufferSize in the library: src/dscKeybusInterface.h + if (dsc.bufferOverflow) { + Serial.println(F("Keybus buffer overflow")); + dsc.bufferOverflow = false; + } + + // Checks if the interface is connected to the Keybus + if (dsc.keybusChanged) { + dsc.keybusChanged = false; // Resets the Keybus data status flag + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); + } + + // Sends the access code when needed by the panel for arming + if (dsc.accessCodePrompt) { + dsc.accessCodePrompt = false; + dsc.write(accessCode); + } + + // Checks status per partition + for (byte partition = 0; partition < dscPartitions; partition++) { + + // Skips processing if the partition is disabled or in installer programming + if (dsc.disabled[partition]) continue; + + // Checks armed status + if (dsc.armedChanged[partition]) { + if (dsc.armed[partition]) { + char messageContent[25]; + + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks alarm triggered status + if (dsc.alarmChanged[partition]) { + dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag + + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag + + // Checks fire alarm status + if (dsc.fireChanged[partition]) { + dsc.fireChanged[partition] = false; // Resets the fire status flag + + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + } + + // Checks for zones in alarm + // Zone alarm status is stored in the alarmZones[] and alarmZonesChanged[] arrays using 1 bit per zone, up to 64 zones + // alarmZones[0] and alarmZonesChanged[0]: Bit 0 = Zone 1 ... Bit 7 = Zone 8 + // alarmZones[1] and alarmZonesChanged[1]: Bit 0 = Zone 9 ... Bit 7 = Zone 16 + // ... + // alarmZones[7] and alarmZonesChanged[7]: Bit 0 = Zone 57 ... Bit 7 = Zone 64 + if (dsc.alarmZonesStatusChanged) { + dsc.alarmZonesStatusChanged = false; // Resets the alarm zones status flag + for (byte zoneGroup = 0; zoneGroup < dscZones; zoneGroup++) { + for (byte zoneBit = 0; zoneBit < 8; zoneBit++) { + if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag + bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag + if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm + char messageContent[15] = "Zone alarm: "; + char zoneNumber[3]; + itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number + strcat(messageContent, zoneNumber); + sendMessage(messageContent); + } + else { + char messageContent[24] = "Zone alarm restored: "; + char zoneNumber[3]; + itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number + strcat(messageContent, zoneNumber); + sendMessage(messageContent); + } + } + } + } + } + + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + + // Checks for AC power status + if (dsc.powerChanged) { + dsc.powerChanged = false; // Resets the battery trouble status flag + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); + } + + // Checks for keypad fire alarm status + if (dsc.keypadFireAlarm) { + dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag + sendMessage("Keypad Fire alarm"); + } + + // Checks for keypad aux auxiliary alarm status + if (dsc.keypadAuxAlarm) { + dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag + sendMessage("Keypad Aux alarm"); + } + + // Checks for keypad panic alarm status + if (dsc.keypadPanicAlarm) { + dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag + sendMessage("Keypad Panic alarm"); + } + } +} + + +void handleTelegram(byte telegramMessages) { + static byte partition = 0; + + for (byte i = 0; i < telegramMessages; i++) { + + // Checks if a partition number 1-8 has been sent and sets the partition + if (telegramBot.messages[i].text[1] >= 0x31 && telegramBot.messages[i].text[1] <= 0x38) { + partition = telegramBot.messages[i].text[1] - 49; + char messageContent[17] = "Set: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + + // Resets status if attempting to change the armed mode while armed or not ready + if (telegramBot.messages[i].text != "/disarm" && !dsc.ready[partition]) { + dsc.armedChanged[partition] = true; + dsc.statusChanged = true; + return; + } + + // Arm stay + if (telegramBot.messages[i].text == "/armstay" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('s'); + } + + // Arm away + else if (telegramBot.messages[i].text == "/armaway" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('w'); + } + + // Arm night + else if (telegramBot.messages[i].text == "/armnight" && !dsc.armed[partition] && !dsc.exitDelay[partition]) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write('n'); + } + + // Disarm + else if (telegramBot.messages[i].text == "/disarm" && (dsc.armed[partition] || dsc.exitDelay[partition] || dsc.alarm[partition])) { + dsc.writePartition = partition + 1; // Sets writes to the partition number + dsc.write(accessCode); + } + } +} + + +bool sendMessage(const char* messageContent) { + byte messageLength = strlen(messagePrefix) + strlen(messageContent) + 1; + char message[messageLength]; + strcpy(message, messagePrefix); + strcat(message, messageContent); + if (telegramBot.sendMessage(telegramUserID, message, "")) return true; + else return false; +} + + +void appendPartition(byte sourceNumber, char* message) { + char partitionNumber[2]; + itoa(sourceNumber + 1, partitionNumber, 10); + strcat(message, partitionNumber); +} diff --git a/examples/esp8266/Twilio-SMS/Twilio-SMS.ino b/examples/esp8266/Twilio-SMS/Twilio-SMS.ino index 6e07988..572efe2 100644 --- a/examples/esp8266/Twilio-SMS/Twilio-SMS.ino +++ b/examples/esp8266/Twilio-SMS/Twilio-SMS.ino @@ -47,16 +47,15 @@ #include #include -// WiFi settings +// Settings const char* wifiSSID = ""; const char* wifiPassword = ""; - -// Twilio settings const char* AccountSID = ""; // Set the account SID from the Twilio Account Dashboard const char* AuthToken = ""; // Set the auth token from the Twilio Account Dashboard const char* Base64EncodedAuth = ""; // macOS/Linux terminal: $ echo -n "AccountSID:AuthToken" | base64 -w 0 const char* From = ""; // i.e. 16041234567 const char* To = ""; // i.e. 16041234567 +const char* messagePrefix = "[Security system] "; // Set a prefix for all messages // Configures the Keybus interface with the specified pins. #define dscClockPin D1 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) @@ -64,33 +63,34 @@ const char* To = ""; // i.e. 16041234567 // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin); -WiFiClientSecure pushClient; -bool wifiConnected = false; +WiFiClientSecure wifiClient; +bool wifiConnected = true; 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) delay(500); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); - - // Sets authentication method for BearSSL in esp8266 Arduino Core 2.5.0+ - #if HAS_ESP8266_VERSION_NUMERIC - if (esp8266::coreVersionNumeric() >= 20500000) pushClient.setInsecure(); - #endif + wifiClient.setInsecure(); // Sends a message on startup to verify connectivity - if (sendPush("Security system initializing")) Serial.println(F("Initialization SMS sent successfully.")); - else Serial.println(F("Initialization SMS failed to send.")); + Serial.print(F("Twilio....")); + if (sendMessage("Initializing")) Serial.println(F("connected.")); + else Serial.println(F("connection error.")); // Starts the Keybus interface dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -125,8 +125,8 @@ void loop() { // Checks if the interface is connected to the Keybus if (dsc.keybusChanged) { dsc.keybusChanged = false; // Resets the Keybus data status flag - if (dsc.keybusConnected) sendPush("Security system connected"); - else sendPush("Security system disconnected"); + if (dsc.keybusConnected) sendMessage("Connected"); + else sendMessage("Disconnected"); } // Checks status per partition @@ -137,24 +137,37 @@ void loop() { // Checks armed status if (dsc.armedChanged[partition]) { - dsc.armedChanged[partition] = false; // Resets the partition armed status flag if (dsc.armed[partition]) { + char messageContent[25]; - char pushMessage[40]; - if (dsc.armedAway[partition]) { - strcpy(pushMessage, "Security system armed away: partition "); - } - else if (dsc.armedStay[partition]) { - strcpy(pushMessage, "Security system armed stay: partition "); - } - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + if (dsc.armedAway[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedAway[partition]) strcpy(messageContent, "Armed away: Partition "); + else if (dsc.armedStay[partition] && dsc.noEntryDelay[partition]) strcpy(messageContent, "Armed night: Partition "); + else if (dsc.armedStay[partition]) strcpy(messageContent, "Armed stay: Partition "); + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } else { - char pushMessage[39] = "Security system disarmed: partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - sendPush(pushMessage); + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + } + + // Checks exit delay status + if (dsc.exitDelayChanged[partition]) { + dsc.exitDelayChanged[partition] = false; // Resets the exit delay status flag + + if (dsc.exitDelay[partition]) { + char messageContent[36] = "Exit delay in progress: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.exitDelay[partition] && !dsc.armed[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); } } @@ -162,22 +175,33 @@ void loop() { if (dsc.alarmChanged[partition]) { dsc.alarmChanged[partition] = false; // Resets the partition alarm status flag - char pushMessage[38] = "Security system in alarm: Partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.alarm[partition]) sendPush(pushMessage); - else sendPush("Security system disarmed after alarm"); + if (dsc.alarm[partition]) { + char messageContent[19] = "Alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else if (!dsc.armedChanged[partition]) { + char messageContent[22] = "Disarmed: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } + dsc.armedChanged[partition] = false; // Resets the partition armed status flag // Checks fire alarm status if (dsc.fireChanged[partition]) { dsc.fireChanged[partition] = false; // Resets the fire status flag - char pushMessage[40] = "Security system fire alarm: Partition "; - appendPartition(partition, pushMessage); // Appends the push message with the partition number - - if (dsc.fire[partition]) sendPush(pushMessage); - else sendPush("Security system fire alarm restored"); + if (dsc.fire[partition]) { + char messageContent[24] = "Fire alarm: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } + else { + char messageContent[33] = "Fire alarm restored: Partition "; + appendPartition(partition, messageContent); // Appends the message with the partition number + sendMessage(messageContent); + } } } @@ -194,101 +218,116 @@ void loop() { if (bitRead(dsc.alarmZonesChanged[zoneGroup], zoneBit)) { // Checks an individual alarm zone status flag bitWrite(dsc.alarmZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual alarm zone status flag if (bitRead(dsc.alarmZones[zoneGroup], zoneBit)) { // Zone alarm - char pushMessage[24] = "Security zone alarm: "; + char messageContent[15] = "Zone alarm: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } else { - char pushMessage[33] = "Security zone alarm restored: "; + char messageContent[24] = "Zone alarm restored: "; char zoneNumber[3]; itoa((zoneBit + 1 + (zoneGroup * 8)), zoneNumber, 10); // Determines the zone number - strcat(pushMessage, zoneNumber); - sendPush(pushMessage); + strcat(messageContent, zoneNumber); + sendMessage(messageContent); } } } } } + // Checks trouble status + if (dsc.troubleChanged) { + dsc.troubleChanged = false; // Resets the trouble status flag + if (dsc.trouble) sendMessage("Trouble status on"); + else sendMessage("Trouble status restored"); + } + // Checks for AC power status if (dsc.powerChanged) { dsc.powerChanged = false; // Resets the battery trouble status flag - if (dsc.powerTrouble) sendPush("Security system AC power trouble"); - else sendPush("Security system AC power restored"); + if (dsc.powerTrouble) sendMessage("AC power trouble"); + else sendMessage("AC power restored"); + } + + // Checks panel battery status + if (dsc.batteryChanged) { + dsc.batteryChanged = false; // Resets the battery trouble status flag + if (dsc.batteryTrouble) sendMessage("Panel battery trouble"); + else sendMessage("Panel battery restored"); } // Checks for keypad fire alarm status if (dsc.keypadFireAlarm) { dsc.keypadFireAlarm = false; // Resets the keypad fire alarm status flag - sendPush("Security system fire alarm button pressed"); + sendMessage("Keypad Fire alarm"); } // Checks for keypad aux auxiliary alarm status if (dsc.keypadAuxAlarm) { dsc.keypadAuxAlarm = false; // Resets the keypad auxiliary alarm status flag - sendPush("Security system aux alarm button pressed"); + sendMessage("Keypad Aux alarm"); } // Checks for keypad panic alarm status if (dsc.keypadPanicAlarm) { dsc.keypadPanicAlarm = false; // Resets the keypad panic alarm status flag - sendPush("Security system panic alarm button pressed"); + sendMessage("Keypad Panic alarm"); } } } -bool sendPush(const char* pushMessage) { +bool sendMessage(const char* messageContent) { // Connects and sends the message as x-www-form-urlencoded - if (!pushClient.connect("api.twilio.com", 443)) return false; - pushClient.print(F("POST https://api.twilio.com/2010-04-01/Accounts/")); - pushClient.print(AccountSID); - pushClient.println(F("/Messages.json HTTP/1.1")); - pushClient.print(F("Authorization: Basic ")); - pushClient.println(Base64EncodedAuth); - pushClient.println(F("Host: api.twilio.com")); - pushClient.println(F("User-Agent: ESP8266")); - pushClient.println(F("Accept: */*")); - pushClient.println(F("Content-Type: application/x-www-form-urlencoded")); - pushClient.print(F("Content-Length: ")); - pushClient.println(strlen(To) + strlen(From) + strlen(pushMessage) + 18); // Length including data - pushClient.println("Connection: Close"); - pushClient.println(); - pushClient.print(F("To=+")); - pushClient.print(To); - pushClient.print(F("&From=+")); - pushClient.print(From); - pushClient.print(F("&Body=")); - pushClient.println(pushMessage); + if (!wifiClient.connect("api.twilio.com", 443)) return false; + wifiClient.print(F("POST https://api.twilio.com/2010-04-01/Accounts/")); + wifiClient.print(AccountSID); + wifiClient.println(F("/Messages.json HTTP/1.1")); + wifiClient.print(F("Authorization: Basic ")); + wifiClient.println(Base64EncodedAuth); + wifiClient.println(F("Host: api.twilio.com")); + wifiClient.println(F("User-Agent: ESP8266")); + wifiClient.println(F("Accept: */*")); + wifiClient.println(F("Content-Type: application/x-www-form-urlencoded")); + wifiClient.print(F("Content-Length: ")); + wifiClient.println(strlen(To) + strlen(From) + strlen(messagePrefix) + strlen(messageContent) + 18); // Length including data + wifiClient.println("Connection: Close"); + wifiClient.println(); + wifiClient.print(F("To=+")); + wifiClient.print(To); + wifiClient.print(F("&From=+")); + wifiClient.print(From); + wifiClient.print(F("&Body=")); + wifiClient.print(messagePrefix); + wifiClient.println(messageContent); // Waits for a response unsigned long previousMillis = millis(); - while (!pushClient.available()) { + while (!wifiClient.available()) { dsc.loop(); if (millis() - previousMillis > 3000) { Serial.println(F("Connection timed out waiting for a response.")); - pushClient.stop(); + wifiClient.stop(); return false; } yield(); } // Reads the response until the first space - the next characters will be the HTTP status code - while (pushClient.available()) { - if (pushClient.read() == ' ') break; + while (wifiClient.available()) { + if (wifiClient.read() == ' ') break; } // Checks the first character of the HTTP status code - the message was sent successfully if the status code // begins with "2" - char statusCode = pushClient.read(); + char statusCode = wifiClient.read(); // Successful, reads the remaining response to clear the client buffer if (statusCode == '2') { - while (pushClient.available()) pushClient.read(); - pushClient.stop(); + while (wifiClient.available()) wifiClient.read(); + wifiClient.stop(); return true; } @@ -296,16 +335,16 @@ bool sendPush(const char* pushMessage) { else { Serial.println(F("SMS messaging error, response:")); Serial.print(statusCode); - while (pushClient.available()) Serial.print((char)pushClient.read()); + while (wifiClient.available()) Serial.print((char)wifiClient.read()); Serial.println(); - pushClient.stop(); + wifiClient.stop(); return false; } } -void appendPartition(byte sourceNumber, char* pushMessage) { +void appendPartition(byte sourceNumber, char* messageContent) { char partitionNumber[2]; itoa(sourceNumber + 1, partitionNumber, 10); - strcat(pushMessage, partitionNumber); + strcat(messageContent, partitionNumber); } diff --git a/examples/esp8266/Unlocker/Unlocker.ino b/examples/esp8266/Unlocker/Unlocker.ino index 9c2d37b..243946e 100644 --- a/examples/esp8266/Unlocker/Unlocker.ino +++ b/examples/esp8266/Unlocker/Unlocker.ino @@ -499,6 +499,7 @@ bool pauseTest = false; void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); diff --git a/examples/esp8266/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino b/examples/esp8266/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino index 9c2917d..d4f0e8e 100644 --- a/examples/esp8266/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino +++ b/examples/esp8266/VirtualKeypad-Blynk/VirtualKeypad-Blynk.ino @@ -11,6 +11,7 @@ * 3. Select "Refresh" to generate a new auth token. * 4. Go back to Project Settings, copy the auth token, and paste it in an email or message to yourself. * 5. Add the auth token to the sketch below. + * 6. Upload the sketch. * * Installing Blynk as a local server (https://github.com/blynkkk/blynk-server) is recommended to keep control of the * security system internal to your network. This also lets you use as many widgets as needed for free - local @@ -76,11 +77,9 @@ #define BLYNK_PRINT Serial -// WiFi settings +// Settings char wifiSSID[] = ""; char wifiPassword[] = ""; - -// Blynk settings char blynkAuthToken[] = ""; // Token generated from within the Blynk app char blynkServer[] = ""; // Blynk local server address int blynkPort = 8080; // Blynk local server port @@ -93,7 +92,7 @@ int blynkPort = 8080; // Blynk local server port // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); -bool wifiConnected = false; +bool wifiConnected = true; bool partitionChanged; byte viewPartition = 1; const char* lcdPartition = "Partition "; @@ -173,17 +172,24 @@ WidgetLED ledZone64(V124); void setup() { Serial.begin(115200); + delay(1000); Serial.println(); Serial.println(); + + Serial.print(F("Blynk...")); WiFi.mode(WIFI_STA); Blynk.begin(blynkAuthToken, wifiSSID, wifiPassword, blynkServer, blynkPort); - while (WiFi.status() != WL_CONNECTED) delay(100); - wifiConnected = true; + WiFi.begin(wifiSSID, wifiPassword); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); + Serial.println(WiFi.localIP()); // Starts the Keybus interface and optionally specifies how to print data. // begin() sets Serial by default and can accept a different stream: begin(Serial1), etc. dsc.begin(); - Serial.println(F("DSC Keybus Interface is online.")); } @@ -767,20 +773,21 @@ void setStatus(byte partition) { case 0x03: lcd.print(0,1, " "); lcd.print(0,1, "Zones open"); break; case 0x04: lcd.print(0,1, " "); lcd.print(0,1, "Armed stay"); break; case 0x05: lcd.print(0,1, " "); lcd.print(0,1, "Armed away"); break; + case 0x06: lcd.print(0,1, " "); lcd.print(0,1, "No entry delay"); break; case 0x07: lcd.print(0,1, " "); lcd.print(0,1, "Failed to arm"); break; case 0x08: lcd.print(0,1, " "); lcd.print(0,1, "Exit delay"); break; case 0x09: lcd.print(0,1, " "); lcd.print(0,1, "No entry delay"); break; case 0x0B: lcd.print(0,1, " "); lcd.print(0,1, "Quick exit"); break; case 0x0C: lcd.print(0,1, " "); lcd.print(0,1, "Entry delay"); break; case 0x0D: lcd.print(0,1, " "); lcd.print(0,1, "Alarm memory"); break; - case 0x0E: lcd.print(0,1, " "); lcd.print(0,1, "Not available"); break; + case 0x0E: lcd.print(0,1, " "); lcd.print(0,1, "Not available"); break; case 0x10: lcd.print(0,1, " "); lcd.print(0,1, "Keypad lockout"); break; case 0x11: lcd.print(0,1, " "); lcd.print(0,1, "Alarm"); break; - case 0x12: lcd.print(0,1, " "); lcd.print(0,1, "Battery check"); break; + case 0x12: lcd.print(0,1, " "); lcd.print(0,1, "Battery check"); break; case 0x14: lcd.print(0,1, " "); lcd.print(0,1, "Auto-arm"); break; case 0x15: lcd.print(0,1, " "); lcd.print(0,1, "Arm with bypass"); break; case 0x16: lcd.print(0,1, " "); lcd.print(0,1, "No entry delay"); break; - case 0x19: lcd.print(0,1, " "); lcd.print(0,1, "Alarm occured"); break; + case 0x19: lcd.print(0,1, " "); lcd.print(0,1, "Alarm occured"); break; case 0x22: lcd.print(0,1, " "); lcd.print(0,1, "Recent closing"); break; case 0x2F: lcd.print(0,1, " "); lcd.print(0,1, "LCD Pixel check"); break; case 0x33: lcd.print(0,1, " "); lcd.print(0,1, "Busy"); break; @@ -813,10 +820,10 @@ void setStatus(byte partition) { case 0xB8: lcd.print(0,1, " "); lcd.print(0,1, "Enter * code"); break; case 0xB9: lcd.print(0,1, " "); lcd.print(0,1, "Zone tamper"); break; case 0xBA: lcd.print(0,1, " "); lcd.print(0,1, "Zones low batt."); break; - case 0xBC: lcd.print(0,1, " "); lcd.print(0,1, "New 6-digit code"); break; + case 0xBC: lcd.print(0,1, " "); lcd.print(0,1, "New 6-digit code"); break; case 0xC6: lcd.print(0,1, " "); lcd.print(0,1, "Zone fault menu"); break; case 0xC8: lcd.print(0,1, " "); lcd.print(0,1, "Service required"); break; - case 0xCE: lcd.print(0,1, " "); lcd.print(0,1, "Active cam. mon."); break; + case 0xCE: lcd.print(0,1, " "); lcd.print(0,1, "Active cam. mon."); break; case 0xD0: lcd.print(0,1, " "); lcd.print(0,1, "Keypads low batt"); break; case 0xD1: lcd.print(0,1, " "); lcd.print(0,1, "Wireless low bat"); break; case 0xE4: lcd.print(0,1, " "); lcd.print(0,1, "Installer menu"); break; @@ -829,17 +836,17 @@ void setStatus(byte partition) { case 0xEC: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; case 0xED: lcd.print(0,1, " "); lcd.print(0,1, "Input: 32 digits"); break; case 0xEE: lcd.print(0,1, " "); lcd.print(0,1, "Input: option"); break; - case 0xEF: lcd.print(0,1, " "); lcd.print(0,1, "Supv. modules"); break; + case 0xEF: lcd.print(0,1, " "); lcd.print(0,1, "Supv. modules"); break; case 0xF0: lcd.print(0,1, " "); lcd.print(0,1, "Function key 1"); break; case 0xF1: lcd.print(0,1, " "); lcd.print(0,1, "Function key 2"); break; case 0xF2: lcd.print(0,1, " "); lcd.print(0,1, "Function key 3"); break; case 0xF3: lcd.print(0,1, " "); lcd.print(0,1, "Function key 4"); break; case 0xF4: lcd.print(0,1, " "); lcd.print(0,1, "Function key 5"); break; - case 0xF5: lcd.print(0,1, " "); lcd.print(0,1, "Wls. place. test"); break; - case 0xF6: lcd.print(0,1, " "); lcd.print(0,1, "Activate device"); break; + case 0xF5: lcd.print(0,1, " "); lcd.print(0,1, "Wls. place. test"); break; + case 0xF6: lcd.print(0,1, " "); lcd.print(0,1, "Activate device"); break; case 0xF7: lcd.print(0,1, " "); lcd.print(0,1, "*8PGM subsection"); break; case 0xF8: lcd.print(0,1, " "); lcd.print(0,1, "Keypad program"); break; - case 0xFA: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; + case 0xFA: lcd.print(0,1, " "); lcd.print(0,1, "Input: 6 digits"); break; default: return; } } diff --git a/examples/esp8266/VirtualKeypad-Web/VirtualKeypad-Web.ino b/examples/esp8266/VirtualKeypad-Web/VirtualKeypad-Web.ino index 6f7ad5c..ec114ef 100644 --- a/examples/esp8266/VirtualKeypad-Web/VirtualKeypad-Web.ino +++ b/examples/esp8266/VirtualKeypad-Web/VirtualKeypad-Web.ino @@ -66,15 +66,13 @@ #include #include #include -#include -#include #include #include // Settings char wifiSSID[] = ""; char wifiPassword[] = ""; -char dnsHostname[] = "dsc"; // Sets the domain name - if set to "dsc", access via: http://dsc.local +char dnsHostname[] = "dsc"; // Sets the host name - if set to "dsc", access via: http://dsc.local // Configures the Keybus interface with the specified pins - dscWritePin is // optional, leaving it out disables the virtual keypad @@ -82,7 +80,6 @@ char dnsHostname[] = "dsc"; // Sets the domain name - if set to "dsc", access v #define dscReadPin D2 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) #define dscWritePin D8 // esp8266: D1, D2, D8 (GPIO 5, 4, 15) - // Initialize components dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); AsyncWebServer server(80); @@ -100,13 +97,18 @@ const char* lcdPartition = "Partition "; 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) delay(100); - Serial.print(F("WiFi connected: ")); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + Serial.print(F("connected: ")); Serial.println(WiFi.localIP()); if (!MDNS.begin(dnsHostname)) { @@ -281,19 +283,20 @@ void setStatus(byte partition) { case 0x03: root["lcd_lower"] = "Zones open"; break; case 0x04: root["lcd_lower"] = "Armed stay"; break; case 0x05: root["lcd_lower"] = "Armed away"; break; + case 0x06: root["lcd_lower"] = "No entry delay"; break; case 0x07: root["lcd_lower"] = "Failed to arm"; break; case 0x08: root["lcd_lower"] = "Exit delay"; break; case 0x09: root["lcd_lower"] = "No entry delay"; break; case 0x0B: root["lcd_lower"] = "Quick exit"; break; case 0x0C: root["lcd_lower"] = "Entry delay"; break; case 0x0D: root["lcd_lower"] = "Alarm memory"; break; - case 0x0E: root["lcd_lower"] = "Not available"; break; + case 0x0E: root["lcd_lower"] = "Not available"; break; case 0x10: root["lcd_lower"] = "Keypad lockout"; break; case 0x11: root["lcd_lower"] = "Alarm"; break; - case 0x12: root["lcd_lower"] = "Battery check"; break; + case 0x12: root["lcd_lower"] = "Battery check"; break; case 0x14: root["lcd_lower"] = "Auto-arm"; break; case 0x16: root["lcd_lower"] = "No entry delay"; break; - case 0x19: root["lcd_lower"] = "Alarm occured"; break; + case 0x19: root["lcd_lower"] = "Alarm occured"; break; case 0x22: root["lcd_lower"] = "Recent closing"; break; case 0x2F: root["lcd_lower"] = "LCD Pixel check"; break; case 0x33: root["lcd_lower"] = "Busy"; break; @@ -326,8 +329,8 @@ void setStatus(byte partition) { case 0xB8: root["lcd_lower"] = "Enter * code"; break; case 0xB9: root["lcd_lower"] = "Zone tamper"; break; case 0xBA: root["lcd_lower"] = "Zones low batt."; break; - case 0xBC: root["lcd_lower"] = "New 6-digit code"; break; - case 0xCE: root["lcd_lower"] = "Active cam. mon."; break; + case 0xBC: root["lcd_lower"] = "New 6-digit code"; break; + case 0xCE: root["lcd_lower"] = "Active cam. mon."; break; case 0xC6: root["lcd_lower"] = "Zone fault menu"; break; case 0xC8: root["lcd_lower"] = "Service required"; break; case 0xD0: root["lcd_lower"] = "Keypads low batt"; break; @@ -342,7 +345,7 @@ void setStatus(byte partition) { case 0xEC: root["lcd_lower"] = "Input: 6 digits"; break; case 0xED: root["lcd_lower"] = "Input: 32 digits"; break; case 0xEE: root["lcd_lower"] = "Input: option"; break; - case 0xEF: root["lcd_lower"] = "Supv. modules"; break; + case 0xEF: root["lcd_lower"] = "Supv. modules"; break; case 0xF0: root["lcd_lower"] = "Function key 1"; break; case 0xF1: root["lcd_lower"] = "Function key 2"; break; case 0xF2: root["lcd_lower"] = "Function key 3"; break; @@ -350,9 +353,9 @@ void setStatus(byte partition) { case 0xF4: root["lcd_lower"] = "Function key 5"; break; case 0xF5: root["lcd_lower"] = "Wls. place. test"; break; case 0xF6: root["lcd_lower"] = "Activate device"; break; - case 0xF7: root["lcd_lower"] = "*8PGM subsection"; break; + case 0xF7: root["lcd_lower"] = "*8PGM subsection"; break; case 0xF8: root["lcd_lower"] = "Keypad program"; break; - case 0xFA: root["lcd_lower"] = "Input: 6 digits"; break; + case 0xFA: root["lcd_lower"] = "Input: 6 digits"; break; default: root["lcd_lower"] = dsc.status[partition]; } serializeJson(root, outas); diff --git a/src/dscKeybusInterface.h b/src/dscKeybusInterface.h index 65b10c7..38b1572 100644 --- a/src/dscKeybusInterface.h +++ b/src/dscKeybusInterface.h @@ -148,7 +148,7 @@ class dscKeybusInterface { static void dscDataInterrupt(); // Deprecated - bool handlePanel(); // Returns true if valid panel data is available. Relabeled to loop() + bool handlePanel(); // Returns true if valid panel data is available. Relabeled to loop() private: @@ -189,7 +189,7 @@ class dscKeybusInterface { void printPanelStatus14(byte panelByte); void printPanelStatus17(byte panelByte); void printPanelStatus18(byte panelByte); - void printPanelStatus1B(byte panelByte); + void printPanelStatus1B(byte panelByte); void printPanelMessages(byte panelByte); void printPanelLights(byte panelByte, bool printMessage = true); @@ -277,7 +277,7 @@ class dscKeybusInterface { bool printModule_Keys(); void printModule_KeyCodes(byte keyByte); void printModule_Expander(); - bool printModuleSlots(byte startCount, byte startByte, byte endByte, byte startMask, byte endMask, byte bitShift, byte matchValue); + bool printModuleSlots(byte startCount, byte startByte, byte endByte, byte startMask, byte endMask, byte bitShift, byte matchValue, bool reverse = false); bool validCRC(); void writeKeys(const char * writeKeysArray); diff --git a/src/dscKeybusPrintData.cpp b/src/dscKeybusPrintData.cpp index 3499216..70ffb27 100644 --- a/src/dscKeybusPrintData.cpp +++ b/src/dscKeybusPrintData.cpp @@ -97,7 +97,7 @@ void dscKeybusInterface::printPanelMessage() { case 0x7A: printPanel_0x7A(); return; // Tone, partition 2 | Structure: complete | Content: complete case 0x7F: printPanel_0x7F(); return; // Buzzer, partition 1 | Structure: complete | Content: complete case 0x82: printPanel_0x82(); return; // Buzzer, partition 2 | Structure: complete | Content: complete - case 0x87: printPanel_0x87(); return; // PGM outputs | Structure: *incomplete | Content: *incomplete + case 0x87: printPanel_0x87(); return; // PGM outputs | Structure: complete | Content: complete case 0x8D: printPanel_0x8D(); return; // User code programming key response, codes 17-32 | Structure: *incomplete | Content: *incomplete case 0x94: printPanel_0x94(); return; // Unknown - immediate after entering *5 programming | Structure: *incomplete | Content: *incomplete case 0x9E: printPanel_0x9E(); return; // DLS query | Structure: complete | Content: complete @@ -201,8 +201,9 @@ void dscKeybusInterface::printPanelMessages(byte panelByte) { case 0x01: stream->print(F("Partition ready")); break; case 0x02: stream->print(F("Stay zones open")); break; case 0x03: stream->print(F("Zones open")); break; - case 0x04: stream->print(F("Armed stay")); break; - case 0x05: stream->print(F("Armed away")); break; + case 0x04: stream->print(F("Armed: Stay")); break; + case 0x05: stream->print(F("Armed: Away")); break; + case 0x06: stream->print(F("Armed: No entry delay")); break; case 0x07: stream->print(F("Failed to arm")); break; case 0x08: stream->print(F("Exit delay in progress")); break; case 0x09: stream->print(F("Arming with no entry delay")); break; @@ -215,13 +216,13 @@ void dscKeybusInterface::printPanelMessages(byte panelByte) { case 0x12: stream->print(F("Battery check in progress")); break; case 0x14: stream->print(F("Auto-arm in progress")); break; case 0x15: stream->print(F("Arming with bypassed zones")); break; - case 0x16: stream->print(F("Armed with no entry delay")); break; + case 0x16: stream->print(F("Armed: No entry delay")); break; //case 0x17: break; // Observed in logs, unknown message - case 0x19: stream->print(F("Disarmed after alarm in memory")); break; + case 0x19: stream->print(F("Disarmed after alarm")); break; case 0x22: stream->print(F("Recent closing")); break; case 0x2F: stream->print(F("Keypad LCD test")); break; case 0x33: stream->print(F("Command output in progress")); break; - case 0x3D: stream->print(F("Disarmed after alarm in memory")); break; + case 0x3D: stream->print(F("Disarmed after alarm")); break; case 0x3E: stream->print(F("Partition disarmed")); break; case 0x40: stream->print(F("Keypad blanking")); break; case 0x8A: stream->print(F("Activate stay/away zones")); break; @@ -237,7 +238,7 @@ void dscKeybusInterface::printPanelMessages(byte panelByte) { case 0xA4: stream->print(F("Door chime disabled")); break; case 0xA5: stream->print(F("Enter master code")); break; case 0xA6: stream->print(F("*5: Access codes")); break; - case 0xA7: stream->print(F("*5: Enter new 4-digit code")); break; + case 0xA7: stream->print(F("*5: Enter 4-digit code")); break; case 0xA9: stream->print(F("*6: User functions")); break; case 0xAA: stream->print(F("*6: Time and date")); break; case 0xAB: stream->print(F("*6: Auto-arm time")); break; @@ -250,13 +251,13 @@ void dscKeybusInterface::printPanelMessages(byte panelByte) { case 0xB8: stream->print(F("Key * while armed")); break; case 0xB9: stream->print(F("*2: Zone tamper menu")); break; case 0xBA: stream->print(F("*2: Zones with low batteries")); break; - case 0xBC: stream->print(F("*5: Enter new 6-digit code")); break; + case 0xBC: stream->print(F("*5: Enter 6-digit code")); break; case 0xC6: stream->print(F("*2: Zone fault menu")); break; case 0xC8: stream->print(F("*2: Service required menu")); break; - case 0xCE: stream->print(F("Active camera monitor selection")); break; + case 0xCE: stream->print(F("Active camera monitor selection")); break; // case 0xCD: Enter DLS (?) case 0xD0: stream->print(F("*2: Keypads with low batteries")); break; - case 0xD1: stream->print(F("*2: Keysfobs with low batteries")); break; + case 0xD1: stream->print(F("*2: Keyfobs with low batteries")); break; case 0xE4: stream->print(F("*8: Installer programming")); break; case 0xE5: stream->print(F("Keypad slot assignment")); break; case 0xE6: stream->print(F("Input 2 digits")); break; @@ -277,7 +278,7 @@ void dscKeybusInterface::printPanelMessages(byte panelByte) { case 0xF6: stream->print(F("Activate device for test")); break; case 0xF7: stream->print(F("Installer programming subsection")); break; case 0xF8: stream->print(F("Keypad programming")); break; - case 0xFA: stream->print(F("Input 6 digits")); break; + case 0xFA: stream->print(F("Input 6 digits")); break; default: printUnknownData(); stream->print(F(": 0x")); @@ -349,10 +350,10 @@ void dscKeybusInterface::printPanelStatus0(byte panelByte) { // 0x56 - 0x75: Zone tamper, zones 1-32 // 0x76 - 0x95: Zone tamper restored, zones 1-32 case 0x98: stream->print(F("Keypad lockout")); break; - // 0x99 - 0xBD: Armed: Access code + // 0x99 - 0xBD: Armed: Access codes 1-34, 40-42 case 0xBE: stream->print(F("Armed partial: Zones bypassed")); break; case 0xBF: stream->print(F("Armed special: quick-arm/auto-arm/keyswitch/wireless key/DLS")); break; - // 0xC0 - 0xE4: Disarmed: Access code + // 0xC0 - 0xE4: Disarmed: Access codes 1-34, 40-42 case 0xE5: stream->print(F("Auto-arm cancelled")); break; case 0xE6: stream->print(F("Disarmed special: Keyswitch/wireless key/DLS")); break; case 0xE7: stream->print(F("Panel battery trouble")); break; @@ -369,7 +370,7 @@ void dscKeybusInterface::printPanelStatus0(byte panelByte) { case 0xF4: stream->print(F("Telephone line restored")); break; case 0xF7: stream->print(F("Phone 1 FTC")); break; case 0xF8: stream->print(F("Phone 2 FTC")); break; - case 0xF9: stream->print(F("Event buffer treshold")); break; //75% full since last DLS upload + case 0xF9: stream->print(F("Event buffer threshold")); break; //75% full since last DLS upload case 0xFA: stream->print(F("DLS lead-in")); break; case 0xFB: stream->print(F("DLS lead-out")); break; case 0xFE: stream->print(F("Periodic test transmission")); break; @@ -438,11 +439,11 @@ void dscKeybusInterface::printPanelStatus0(byte panelByte) { } /* - * Armed by access code + * Armed by access codes 1-34, 40-42 * * Command YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC - * 10100101 0 00011000 01001101 00001000 10010000 10011001 11111111 00111010 [0xA5] 2018.03.08 08:36 | Partition 1 | Armed by user code 1 - * 10100101 0 00011000 01001101 00001000 10111100 10111011 11111111 10001000 [0xA5] 2018.03.08 08:47 | Partition 1 | Armed by master code 40 + * 10100101 0 00011000 01001101 00001000 10010000 10011001 11111111 00111010 [0xA5] 2018.03.08 08:36 | Partition 1 | Armed: User code 1 + * 10100101 0 00011000 01001101 00001000 10111100 10111011 11111111 10001000 [0xA5] 2018.03.08 08:47 | Partition 1 | Armed: Master code 40 * Byte 0 1 2 3 4 5 6 7 8 */ if (panelData[panelByte] >= 0x99 && panelData[panelByte] <= 0xBD) { @@ -453,11 +454,11 @@ void dscKeybusInterface::printPanelStatus0(byte panelByte) { } /* - * Disarmed by access code + * Disarmed by access codes 1-34, 40-42 * * Command YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC - * 10100101 0 00011000 01001101 00001000 11101100 11000000 11111111 10111101 [0xA5] 2018.03.08 08:59 | Partition 1 | Disarmed by user code 1 - * 10100101 0 00011000 01001101 00001000 10110100 11100010 11111111 10100111 [0xA5] 2018.03.08 08:45 | Partition 1 | Disarmed by master code 40 + * 10100101 0 00011000 01001101 00001000 11101100 11000000 11111111 10111101 [0xA5] 2018.03.08 08:59 | Partition 1 | Disarmed: User code 1 + * 10100101 0 00011000 01001101 00001000 10110100 11100010 11111111 10100111 [0xA5] 2018.03.08 08:45 | Partition 1 | Disarmed: Master code 40 * Byte 0 1 2 3 4 5 6 7 8 */ if (panelData[panelByte] >= 0xC0 && panelData[panelByte] <= 0xE4) { @@ -498,9 +499,9 @@ void dscKeybusInterface::printPanelStatus1(byte panelByte) { * 10100101 0 00010110 01010110 00101011 11010001 11010010 00000000 11011111 [0xA5] 2016.05.17 11:52 | Partition 1 | Armed with no entry delay cancelled * Byte 0 1 2 3 4 5 6 7 8 */ - case 0x03: stream->print(F("Police code")); return; + case 0x03: stream->print(F("Cross zone alarm")); return; case 0x04: stream->print(F("Delinquency alarm")); return; - // 0x24 - 0x28: Access code + // 0x24 - 0x28: Access codes 33-34, 40-42 case 0x29: stream->print(F("Downloading forced answer")); return; case 0x2B: stream->print(F("Armed: Auto-arm")); return; // 0x2C - 0x4B: Zone battery restored, zones 1-32 @@ -515,11 +516,11 @@ void dscKeybusInterface::printPanelStatus1(byte panelByte) { case 0xD0: stream->print(F("Command output 4")); return; case 0xD1: stream->print(F("Exit fault pre-alert")); return; case 0xD2: stream->print(F("Armed with no entry delay cancelled")); return; - case 0xD3: stream->print(F("Downlook remote trigger")); return; + case 0xD3: stream->print(F("Download remote trigger")); return; } /* - * Access code + * Access codes 33-34, 40-42 * * Command YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC * 10100101 0 00010001 01101101 01100000 10101001 00100100 00000000 01010000 [0xA5] 2011.11.11 00:42 | Partition 1 | Duress code 33 @@ -654,10 +655,10 @@ void dscKeybusInterface::printPanelStatus2(byte panelByte) { case 0x9A: stream->print(F("Armed: Stay")); return; case 0x9B: stream->print(F("Armed: Away")); return; case 0x9C: stream->print(F("Armed: No entry delay")); return; - // 0x9E - 0xC2: *1: access code - // 0xC3 - 0xC5: *5: access code 40-42 - // 0xC6 - 0xE5: User code - // 0xE6 - 0xE8: *6: access code 40-42 + // 0x9E - 0xC2: *1: Access codes 1-34, 40-42 + // 0xC3 - 0xC5: *5: Access codes 40-42 + // 0xC6 - 0xE5: Access codes 1-34, 40-42 + // 0xE6 - 0xE8: *6: Access codes 40-42 // 0xE9 - 0xF0: Keypad restored: Slots 1-8 // 0xF1 - 0xF8: Keypad trouble: Slots 1-8 // 0xF9 - 0xFE: Zone expander restored: 1-6 @@ -665,7 +666,7 @@ void dscKeybusInterface::printPanelStatus2(byte panelByte) { } /* - * *1: Access code + * *1: Access codes 1-34, 40-42 */ if (panelData[panelByte] >= 0x9E && panelData[panelByte] <= 0xC2) { byte dscCode = panelData[panelByte] - 0x9D; @@ -675,28 +676,17 @@ void dscKeybusInterface::printPanelStatus2(byte panelByte) { } /* - * *5: Access code + * *5: Access codes 40-42 */ if (panelData[panelByte] >= 0xC3 && panelData[panelByte] <= 0xC5) { byte dscCode = panelData[panelByte] - 0xA0; - stream->print(F("*5 programming: ")); - printPanelAccessCode(dscCode); - return; - } - - - /* - * *6: Access code - */ - if (panelData[panelByte] >= 0xE6 && panelData[panelByte] <= 0xE8) { - byte dscCode = panelData[panelByte] - 0xC3; - stream->print(F("*6 programming: ")); + stream->print(F("*5: ")); printPanelAccessCode(dscCode); return; } /* - * User code + * Access codes 1-32 * * Command YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC * 10100101 0 00010001 01101101 01100000 00111110 11000110 00000000 10000111 [0xA5] 2011.11.11 00:15 | Partition 1 | User code 1 @@ -709,6 +699,16 @@ void dscKeybusInterface::printPanelStatus2(byte panelByte) { return; } + /* + * *6: Access codes 40-42 + */ + if (panelData[panelByte] >= 0xE6 && panelData[panelByte] <= 0xE8) { + byte dscCode = panelData[panelByte] - 0xC3; + stream->print(F("*6: ")); + printPanelAccessCode(dscCode); + return; + } + /* * Keypad restored: Slots 1-8 * @@ -844,7 +844,7 @@ void dscKeybusInterface::printPanelStatus3(byte panelByte) { * the numerical list of messages. * * These commands use 1 byte for the status message, and appear to use the preceding byte to select - * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1X. + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. * * Command Partition YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC * 11101011 0 00000001 00011000 00011000 10001111 00101000 00000100 00000000 10010001 01101000 [0xEB] 2018.06.04 15:10 | Partition 1 | Zone alarm: 33 @@ -885,28 +885,21 @@ void dscKeybusInterface::printPanelStatus4(byte panelByte) { /* - * Status messages set 0x14, 0x17, 0x1B for panel commands: 0xEB, 0xEC + * Status messages set 0x05 for panel commands: 0xEB, 0xEC * Structure decoding: complete * Content decoding: likely incomplete - observed messages from logs have been decoded, but there are gaps in * the numerical list of messages. * * These commands use 1 byte for the status message, and appear to use the preceding byte to select - * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1X. - * - * Command Partition YYY1YYY2 MMMMDD DDDHHHHH MMMMMM Status CRC - * 11101011 0 00000000 00000001 00000100 01100000 01001000 00010100 01100000 10000001 10001101 [0xEB] 2001.01.03 00:18 | Zone fault: 33 - * 11101011 0 00000000 00000001 00000100 01100000 01001100 00010100 01000000 11111111 11101111 [0xEB] 2001.01.03 00:19 | Zone fault restored: 33 - * 11101011 0 00000000 00000001 00000100 01100000 00001100 00010100 01011111 11111111 11001110 [0xEB] 2001.01.03 00:03 | Zone fault restored: 64 - * Byte 0 1 2 3 4 5 6 7 8 9 10 + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. */ - void dscKeybusInterface::printPanelStatus5(byte panelByte) { -/* - * Armed by access code - * 0x00 ... 0x04 user 35..39 - * 0x05 ... 0x39 user 43..95 - */ + /* + * Armed by access codes 35-95 + * 0x00 - 0x04: Access codes 35-39 + * 0x05 - 0x39: Access codes 43-95 + */ if (panelData[panelByte] >= 0x00 && panelData[panelByte] <= 0x39) { byte dscCode = panelData[panelByte] + 0x23; stream->print(F("Armed: ")); @@ -915,9 +908,9 @@ void dscKeybusInterface::printPanelStatus5(byte panelByte) { } /* - * Disarmed by access code - * 0x3A ... 0x3E user 35..39 - * 0x3F ... 0x73 user 43..95 + * Disarmed by access codes 35-95 + * 0x3A - 0x3E: Access codes 35-39 + * 0x3F - 0x73: Access codes 43-95 */ if (panelData[panelByte] >= 0x3A && panelData[panelByte] <= 0x73) { byte dscCode = panelData[panelByte] - 0x17; @@ -925,10 +918,20 @@ void dscKeybusInterface::printPanelStatus5(byte panelByte) { printPanelAccessCode(dscCode, false); return; } - + printUnknownData(); } + +/* + * Status messages set 0x14 for panel commands: 0xEB, 0xEC + * Structure decoding: complete + * Content decoding: likely incomplete - observed messages from logs have been decoded, but there are gaps in + * the numerical list of messages. + * + * These commands use 1 byte for the status message, and appear to use the preceding byte to select + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. + */ void dscKeybusInterface::printPanelStatus14(byte panelByte) { switch (panelData[panelByte]) { case 0xC0: stream->print(F("TLink com fault")); return; @@ -936,13 +939,25 @@ void dscKeybusInterface::printPanelStatus14(byte panelByte) { case 0xC4: stream->print(F("TLink receiver trouble")); return; case 0xC5: stream->print(F("TLink receiver restored")); return; } + printUnknownData(); } + +/* + * Status messages set 0x17 for panel commands: 0xEB, 0xEC + * Structure decoding: complete + * Content decoding: likely incomplete - observed messages from logs have been decoded, but there are gaps in + * the numerical list of messages. + * + * These commands use 1 byte for the status message, and appear to use the preceding byte to select + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. + */ void dscKeybusInterface::printPanelStatus17(byte panelByte) { + /* - * *1: Access code - * 0x4A - 0x83: *1: User codes 35..39 and 43..95 + * *1: Access codes 35-95 + * 0x4A - 0x83: *1: Access codes 35-39, 43-95 */ if (panelData[panelByte] >= 0x4A && panelData[panelByte] <= 0x83) { byte dscCode = panelData[panelByte] - 0x27; @@ -952,9 +967,9 @@ void dscKeybusInterface::printPanelStatus17(byte panelByte) { } /* - * *2: Access code - * 0x00 - 0x24: *2: Access code 1-32, 40, 41, 42 - * 0x84 - 0xBD: *2: User codes 35..39 and 43..95 + * *2: Access codes 1-95 + * 0x00 - 0x24: *2: Access code 1-32, 40-42 + * 0x84 - 0xBD: *2: Access codes 35-39, 43-95 */ if (panelData[panelByte] >= 0 && panelData[panelByte] <= 0x24) { byte dscCode = panelData[panelByte] + 1; @@ -962,6 +977,7 @@ void dscKeybusInterface::printPanelStatus17(byte panelByte) { printPanelAccessCode(dscCode); return; } + if (panelData[panelByte] >= 0x84 && panelData[panelByte] <= 0xBD) { byte dscCode = panelData[panelByte] - 0x61; stream->print(F("*2: ")); @@ -970,9 +986,9 @@ void dscKeybusInterface::printPanelStatus17(byte panelByte) { } /* - * *3: Access code - * 0x25 - 0x49: *3: Access code 1-32, 40, 41, 42 - * 0xBE - 0xF7: *3: User codes 35..39 and 43..95 + * *3: Access codes 1-95 + * 0x25 - 0x49: *3: Access code 1-32, 40-42 + * 0xBE - 0xF7: *3: Access codes 35-39, 43-95 */ if (panelData[panelByte] >= 0x25 && panelData[panelByte] <= 0x49) { byte dscCode = panelData[panelByte] - 0x24; @@ -980,35 +996,46 @@ void dscKeybusInterface::printPanelStatus17(byte panelByte) { printPanelAccessCode(dscCode); return; } + if (panelData[panelByte] >= 0xBE && panelData[panelByte] <= 0xF7) { byte dscCode = panelData[panelByte] - 0x9B; stream->print(F("*3: ")); printPanelAccessCode(dscCode, false); return; } - + printUnknownData(); } + +/* + * Status messages set 0x18 for panel commands: 0xEB, 0xEC + * Structure decoding: complete + * Content decoding: likely incomplete - observed messages from logs have been decoded, but there are gaps in + * the numerical list of messages. + * + * These commands use 1 byte for the status message, and appear to use the preceding byte to select + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. + */ void dscKeybusInterface::printPanelStatus18(byte panelByte) { + /* - * *7/User/Auto-arm cancel by Access code - * - * 0x00 - 0x04: *7/*User codes 35..39 - * 0x05 - 0x39: *7/*User codes 43..95 + * *7/User/Auto-arm cancel by access codes 35-95 + * + * 0x00 - 0x04: *7/*Access codes 35-39 + * 0x05 - 0x39: *7/*Access codes 43-95 */ if (panelData[panelByte] >= 0x00 && panelData[panelByte] <= 0x39) { byte dscCode = panelData[panelByte] + 0x23; - stream->print(F("User code: ")); printPanelAccessCode(dscCode, false); return; } /* - * *5: Access code - * - * 0x3A - 0x60: *5: User codes 1..39 - * 0x61 - 0x95: *5: User codes 43..95 + * *5: Access codes 1-95 + * + * 0x3A - 0x60: *5: Access codes 1-39 + * 0x61 - 0x95: *5: Access codes 43-95 */ if (panelData[panelByte] >= 0x3A && panelData[panelByte] <= 0x95) { byte dscCode = panelData[panelByte] - 0x39; @@ -1018,10 +1045,10 @@ void dscKeybusInterface::printPanelStatus18(byte panelByte) { } /* - * *6: Access code - * - * 0x96 - 0xBC: *6: User codes 1..39 - * 0xBD - 0xF1: *6: User codes 43..95 + * *6: Access codes 1-95 + * + * 0x96 - 0xBC: *6: Access codes 1-39 + * 0xBD - 0xF1: *6: Access codes 43-95 */ if (panelData[panelByte] >= 0x96 && panelData[panelByte] <= 0xF1) { byte dscCode = panelData[panelByte] - 0x95; @@ -1033,6 +1060,16 @@ void dscKeybusInterface::printPanelStatus18(byte panelByte) { printUnknownData(); } + +/* + * Status messages set 0x18 for panel commands: 0xEB, 0xEC + * Structure decoding: complete + * Content decoding: likely incomplete - observed messages from logs have been decoded, but there are gaps in + * the numerical list of messages. + * + * These commands use 1 byte for the status message, and appear to use the preceding byte to select + * from multiple sets of status messages, split into printPanelStatus4...printPanelStatus1B. + */ void dscKeybusInterface::printPanelStatus1B(byte panelByte) { switch (panelData[panelByte]) { case 0xF1: stream->print(F("System reset transmission")); return; @@ -1041,6 +1078,7 @@ void dscKeybusInterface::printPanelStatus1B(byte panelByte) { printUnknownData(); } + /* * 0x05: Status - partitions 1-4 * Interval: constant @@ -1709,8 +1747,8 @@ void dscKeybusInterface::printPanel_0x82() { /* * 0x87: PGM outputs * CRC: yes - * Structure decoding: *incomplete - * Content decoding: *incomplete + * Structure decoding: complete + * Content decoding: complete * * Byte 2 bit 0: PGM 3 * Byte 2 bit 1: PGM 4 @@ -1743,14 +1781,15 @@ void dscKeybusInterface::printPanel_0x82() { */ void dscKeybusInterface::printPanel_0x87() { stream->print(F("PGM outputs enabled: ")); - if (panelData[3] & 0x04) stream->print(F("Midnight | ")); - if (panelData[3] & 0x08) stream->print(F("Battery check | ")); - if (panelData[2] == 0 && panelData[3] == 0) stream->print(F("none")); + if (panelData[2] == 0 && panelData[3] == 0) stream->print(F("none ")); else { printPanelBitNumbers(3, 1, 0, 1, false); printPanelBitNumbers(2, 3, 0, 7, false); printPanelBitNumbers(3, 11, 4, 7, false); } + + if (panelData[3] & 0x04) stream->print(F("| Midnight ")); + if (panelData[3] & 0x08) stream->print(F("| Battery check")); } @@ -2803,7 +2842,8 @@ void dscKeybusInterface::printModule_0xDD() { * * Later generation panels: * Byte 6: Unknown - * Byte 7 bit 0-3: Unknown + * Byte 7 bit 0-2: Unknown + * Byte 7 bit 3: Keypad idle notification, panel response: 0x1B, keypad responds on byte 4 with its partition number * Byte 7 bit 4: Zone expander 7 notification, panel response: 0xE6.0E Zone expander 7 query * Byte 7 bit 5: Zone expander 6 notification, panel response: 0xE6.0C Zone expander 6 query * Byte 7 bit 6: Zone expander 5 notification, panel response: 0xE6.0A Zone expander 5 query @@ -2842,35 +2882,45 @@ void dscKeybusInterface::printModule_0xDD() { */ void dscKeybusInterface::printModule_Status() { bool printedMessage = false; + static bool keypadIdle = false; // Keypad keys if (printModule_Keys()) printedMessage = true; - // Zone expander notification - if ((moduleData[4] & 0xF0) != 0xF0 || (moduleByteCount > 6 && (moduleData[7] & 0xF0) != 0xF0)) { - if (printedMessage) stream->print("| "); - stream->print(F("Zone expander notification: ")); + // Keypad idle partition + if (keypadIdle) { + stream->print(F("Keypad partition: ")); + printModuleSlots(1, 4, 4, 0x80, 0, 1, 0, true); + keypadIdle = false; printedMessage = true; - - printModuleSlots(0, 4, 4, 0x80, 0x10, 1, 0); - if (moduleByteCount > 6) { - printModuleSlots(4, 7, 7, 0x80, 0x20, 1, 0); - if ((moduleData[7] & 0x10) == 0) printNumberSpace(7); - } } + else { + // Zone expander notification + if ((moduleData[4] & 0xF0) != 0xF0 || (moduleByteCount > 6 && (moduleData[7] & 0xF0) != 0xF0)) { + if (printedMessage) stream->print("| "); + stream->print(F("Zone expander notification: ")); + printedMessage = true; + + printModuleSlots(0, 4, 4, 0x80, 0x10, 1, 0); + if (moduleByteCount > 6) { + printModuleSlots(4, 7, 7, 0x80, 0x20, 1, 0); + if ((moduleData[7] & 0x10) == 0) printNumberSpace(7); + } + } - // Module tamper notification, panel responds with 0x4C query - if ((moduleData[4] & 0x01) == 0) { - if (printedMessage) stream->print("| "); - stream->print(F("Module tamper notification ")); - printedMessage = true; - } + // Module tamper notification, panel responds with 0x4C query + if ((moduleData[4] & 0x01) == 0) { + if (printedMessage) stream->print("| "); + stream->print(F("Module tamper notification ")); + printedMessage = true; + } - // Wireless module battery notification - if ((moduleData[4] & 0x08) == 0) { - if (printedMessage) stream->print("| "); - stream->print(F("Wireless module battery notification ")); - printedMessage = true; + // Wireless module battery notification + if ((moduleData[4] & 0x08) == 0) { + if (printedMessage) stream->print("| "); + stream->print(F("Wireless module battery notification ")); + printedMessage = true; + } } //Keypad zone notification, panel responds with 0xD5 query @@ -2898,6 +2948,7 @@ void dscKeybusInterface::printModule_Status() { if (moduleByteCount > 6 && (moduleData[7] & 0x08) == 0) { if (printedMessage) stream->print("| "); stream->print(F("Keypad idle notification ")); + keypadIdle = true; printedMessage = true; } @@ -3195,16 +3246,16 @@ void dscKeybusInterface::printModule_0x4C() { void dscKeybusInterface::printModule_0x57() { bool printedMessage = false; - if (printModuleSlots(255, 2, 5, 0xC0, 0, 2, 0x02)) { + if (printModuleSlots(255, 2, 5, 0xC0, 0, 2, 0x02, true)) { stream->print(F("Wireless key low battery: ")); - printModuleSlots(1, 2, 5, 0xC0, 0, 2, 0x02); + printModuleSlots(1, 2, 5, 0xC0, 0, 2, 0x02, true); printedMessage = true; } - if (printModuleSlots(255, 2, 5, 0xC0, 0, 2, 0x01)) { + if (printModuleSlots(255, 2, 5, 0xC0, 0, 2, 0x01, true)) { if (printedMessage) stream->print(F("| ")); stream->print(F("Wireless key battery restored: ")); - printModuleSlots(1, 2, 5, 0xC0, 0, 2, 0x01); + printModuleSlots(1, 2, 5, 0xC0, 0, 2, 0x01, true); } } @@ -3510,7 +3561,7 @@ void dscKeybusInterface::printModule_Expander() { * If outputNumber is set to 0, printModuleSlots() will return 'true' if any of the specified * bytes contains data - this is used to selectively print a label only if data is present. */ -bool dscKeybusInterface::printModuleSlots(byte outputNumber, byte startByte, byte endByte, byte startMask, byte endMask, byte bitShift, byte matchValue) { +bool dscKeybusInterface::printModuleSlots(byte outputNumber, byte startByte, byte endByte, byte startMask, byte endMask, byte bitShift, byte matchValue, bool reverse) { for (byte testByte = startByte; testByte <= endByte; testByte++) { byte matchShift = 8 - bitShift; for (byte testMask = startMask; testMask != 0; testMask >>= bitShift) { @@ -3518,8 +3569,8 @@ bool dscKeybusInterface::printModuleSlots(byte outputNumber, byte startByte, byt byte testData = moduleData[testByte]; - // Reverses the bit order of 0x57 wireless key data - if (currentCmd == 0x57) { + // Reverses the bit order + if (reverse) { testData = 0; for (byte i = 0; i < 8; i++) testData |= ((moduleData[testByte] >> i) & 1) << (7 - i); } @@ -3593,19 +3644,19 @@ void dscKeybusInterface::printPanelTime(byte panelByte) { /* - * Prints access codes for printPanelStatus0()...printPanelStatus1X() status messages + * Prints access codes for printPanelStatus0()...printPanelStatus1B() status messages * Structure decoding: complete * Content decoding: complete */ void dscKeybusInterface::printPanelAccessCode(byte dscCode, bool accessCodeIncrease) { - + if (accessCodeIncrease) { if (dscCode >= 35) dscCode += 5; - } + } else { if (dscCode >= 40) dscCode += 3; } - + switch (dscCode) { case 33: stream->print(F("Duress ")); break; diff --git a/src/dscKeybusProcessData.cpp b/src/dscKeybusProcessData.cpp index 5b3ebd2..ac243aa 100644 --- a/src/dscKeybusProcessData.cpp +++ b/src/dscKeybusProcessData.cpp @@ -98,7 +98,7 @@ bool dscKeybusInterface::setTime(unsigned int year, byte month, byte day, byte h void dscKeybusInterface::processPanelStatus() { // Trouble status - if (panelData[3] <= 0x05) { // Ignores trouble light status in intermittent states + if (panelData[3] <= 0x06) { // Ignores trouble light status in intermittent states if (bitRead(panelData[2],4)) trouble = true; else trouble = false; if (trouble != previousTrouble) { @@ -273,6 +273,7 @@ void dscKeybusInterface::processPanelStatus() { } // Partition armed with no entry delay + case 0x06: case 0x16: { noEntryDelay[partitionIndex] = true; @@ -376,7 +377,7 @@ void dscKeybusInterface::processPanel_0x27() { } // Armed with no entry delay - else if (panelData[messageByte] == 0x16) { + else if (panelData[messageByte] == 0x06 || panelData[messageByte] == 0x16) { noEntryDelay[partitionIndex] = true; // Sets an armed mode if not already set, used if interface is initialized while the panel is armed