diff --git a/CHANGELOG.md b/CHANGELOG.md index 691cbf9..46ac5a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,20 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added +- MQTT Topic `panel-time` to get/set the alarm panel time +- MQTT Topic `partition-N-access-code` the access code is published whenever the alarm was armed/disarmed + +### Changed +- Updated library dependency to use Homie-esp8266 v3.0.1 +- Updated library dependency to use dscKeybusInterface v1.3 (develop branch) + +### Fixed +- Writing to `keypad` topic no longer causes an unexpected behaviour due to a bug in the dscKeybusInterface library. ## [1.0.1] 2019-06-05 ### Added -- MQTT Topic `maintenance` - See README.md. Added the ability to stop and start the dsc interface, needed to avoid crashing during config update +- MQTT Topic `maintenance` - See (README.md). Added the ability to stop and start the dsc interface, needed to avoid crashing during config update ### Removed - MQTT Topic `reboot` has been removed. Its functionality has been incorporated into the new `maintenance` topic diff --git a/README.md b/README.md index 15a313c..3e2c3bb 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,24 @@ homie/device-id/alarm/ - `trouble` - `power-trouble` - `battery-trouble` -- `fire-alarm-keypad` +- `fire-alarm-keypad` - `aux-alarm-keypad` - `panic-alarm-keypad` +- `panel-time` provides the date/time stored in the alarm system formatted as YYYY-MM-DD HH:mm ### Partitions: - `partition-1-away`: 0 (disarmed) | 1 (armed away) - `partition-1-stay`: 0 (disarmed) | 1 (armed stay) - `partition-1-alarm`: 0 (no alarm) | 1 (triggered) - `partition-1-fire`: 0 (no alarm) | 1 (fire alarm) +- `partition-1-access-code`: The access code used to arm/disarm - ... - `partition-N-xxxx` as above +### To arm/disarm a partition, publish to: +- `partition-N-away/set`: 0 (disarm) | 1 (arm partition - away mode) +- `partition-N-stay/set`: 0 (disarm) | 1 (arm partition - stay mode) + ### Zones: - `openzone-1`: 0 (no movement) | 1 (movement detected) - ... @@ -51,6 +57,9 @@ homie/device-id/alarm/ ### To request status refresh, publish to - `refresh-status/set`: 1 +### To set the panel date/time, publish to +- `panel-time/set`: "YYYY-MM-DD HH:mm" + ### To reboot the device, publish to - `maintenance/set`: "reboot" to reboot - `maintenance/set`: "dsc-stop" to stop dsc keybus interrupts @@ -60,11 +69,6 @@ The DSC Keybus interrupts seem to interfere with the OTA and Config update opera So before sending a config update, stop the DSC interrupts by publishing to `maintenance/set`: "dsc-stop". Once the config update has been made, restart the DSC interface using `maintenance/set`: "dsc-start" or sending a reboot request. - -### To arm/disarm a partition, publish to: -- `partition-N-away/set`: 0 (disarm) | 1 (arm partition - away mode) -- `partition-N-stay/set`: 0 (disarm) | 1 (arm partition - stay mode) - ## Initial Setup Homie needs to be configured before it can connect to your Wifi / MQTT server. Standard Homie configuration methods are of course supported. However, the easiest is to follow these steps: @@ -115,4 +119,6 @@ Note - The last step may be unnecessary because the ESP8266 seems to crash and restart, but the config gets updated. - You can update the wifi / mqtt connection details in the same manner - +## Library Dependencies +- [Homie-esp8266 v3.x](https://github.com/homieiot/homie-esp8266.git#develop-v3) +- [dscKeybusReader v1.3](https://github.com/taligentx/dscKeybusInterface.git#develop) \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 8213980..241f3da 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,10 +8,15 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html +;[env:nodemcu] [env:d1_mini_pro] platform = espressif8266 +;board = nodemcu board = d1_mini_pro framework = arduino -lib_deps = dscKeybusInterface, Homie +lib_deps = + https://github.com/taligentx/dscKeybusInterface.git#develop + https://github.com/homieiot/homie-esp8266.git#develop-v3 build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -monitor_speed = 115200 \ No newline at end of file +monitor_speed = 115200 + diff --git a/src/main.cpp b/src/main.cpp index 1ad2375..5a9e726 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,32 +9,8 @@ * Processes the security system status and implement the Homie convention. * * See README.md - * - * Wiring: - * DSC Aux(+) ---+--- esp8266 NodeMCU Vin pin - * | - * +--- 5v voltage regulator --- esp8266 Wemos D1 Mini 5v pin - * - * DSC Aux(-) --- esp8266 Ground - * - * +--- dscClockPin (esp8266: D1, D2, D8) - * DSC Yellow --- 15k ohm resistor ---| - * +--- 10k ohm resistor --- Ground - * - * +--- dscReadPin (esp8266: D1, D2, D8) - * DSC Green ---- 15k 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 (EBC) - * -- BC547, BC548, BC549 - * + * + * For details on the wiring, please see: * https://github.com/taligentx/dscKeybusInterface * * @@ -42,8 +18,9 @@ #include #include +#include -#define SOFTWARE_VERSION "1.0.1" +#define SOFTWARE_VERSION "1.1.0 Build 11" // Configures the Keybus interface with the specified pins // dscWritePin is optional, leaving it out disables the virtual keypad. @@ -54,51 +31,27 @@ dscKeybusInterface dsc(dscClockPin, dscReadPin, dscWritePin); enum ArmType { arm_away, arm_stay }; -bool homieNodeInputHandler(const String& property, const HomieRange& range, const String& value); +bool homieNodeInputHandler(const HomieRange& range, const String& property, const String& value); -HomieNode homieNode("alarm", "alarm", homieNodeInputHandler); +HomieNode homieNode("alarm", "alarm", "alarm", false, 0, 0, homieNodeInputHandler); HomieSetting dscAccessCode("access-code", "Alarm access code"); unsigned long dscStopTime = 0; // the time (in millis) when the dsc-stop was last requested -void resetDscStatus() { - dsc.statusChanged = true; - dsc.keybusChanged = true; - dsc.troubleChanged = true; - dsc.powerChanged = true; - dsc.batteryChanged = true; - for (byte partition = 0; partition < dscPartitions; partition++) { - dsc.armedChanged[partition] = true; - dsc.alarmChanged[partition] = true; - dsc.exitDelayChanged[partition] = true; - dsc.entryDelayChanged[partition] = true; - dsc.fireChanged[partition] = true; - } - dsc.openZonesStatusChanged = true; - dsc.alarmZonesStatusChanged = true; - for (byte zoneGroup = 0; zoneGroup < dscZones; zoneGroup++) { - dsc.openZonesChanged[zoneGroup] = dsc.alarmZonesChanged[zoneGroup] = 0xFF; - } -} - -void dscEnd() { - timer1_detachInterrupt(); - detachInterrupt(digitalPinToInterrupt(dscClockPin)); -} - void setupHandler() { dsc.begin(); } void loopHandler() { - // autmatically restart dsc after 5 minutes if it was stopped + // automatically restart dsc after 5 minutes if it was stopped if (dscStopTime > 0 && (millis() - dscStopTime) > 5*60*1000) { dsc.begin(); dscStopTime = 0; homieNode.setProperty("message").setRetained(false).send("DSC Interface restarted automatically"); } - if (!(dsc.handlePanel() && dsc.statusChanged)) { + dsc.loop(); + if (!dsc.statusChanged) { return; } dsc.statusChanged = false; @@ -111,7 +64,7 @@ void loopHandler() { } // Sends the access code when needed by the panel for arming - if (dsc.accessCodePrompt && dsc.writeReady) { + if (dsc.accessCodePrompt) { dsc.accessCodePrompt = false; dsc.write(dscAccessCode.get()); } @@ -146,6 +99,15 @@ void loopHandler() { homieNode.setProperty("panic-alarm-keypad").send("1"); } + if (dsc.timestampChanged) { + dsc.timestampChanged = false; + if (!(dsc.year < 0 || dsc.year > 9999 || dsc.month < 1 || dsc.month > 12 || dsc.day < 1 || dsc.day > 31 || dsc.hour < 0 || dsc.hour > 23 || dsc.minute < 0 || dsc.minute > 59)) { + char panelTime[17]; // YYYY-MM-DD HH:mm + sprintf(panelTime, "%04d-%02d-%02d %02d:%02d", dsc.year, dsc.month, dsc.day, dsc.hour, dsc.minute); + homieNode.setProperty("panel-time").setRetained(false).send(panelTime); + } + } + String property; // loop through partitions for (byte partition = 0; partition < dscPartitions; partition++) { @@ -175,6 +137,12 @@ void loopHandler() { dsc.fireChanged[partition] = false; homieNode.setProperty(property + "-fire").send(dsc.fire[partition] ? "1" : "0"); } + + // Publish access code + if (dsc.accessCodeChanged[partition]) { + dsc.accessCodeChanged[partition] = false; + homieNode.setProperty(property + "-access-code").setRetained(false).send(String(dsc.accessCode[partition], DEC).c_str()); + } } // Publish zones 1-64 status @@ -188,11 +156,11 @@ void loopHandler() { for (byte zoneGroup = 0; zoneGroup < dscZones; zoneGroup++) { for (byte zoneBit = 0; zoneBit < 8; zoneBit++) { if (bitRead(dsc.openZonesChanged[zoneGroup], zoneBit)) { // Checks an individual open zone status flag - bitWrite(dsc.openZonesChanged[zoneGroup], zoneBit, 0); // Resets the individual open zone status flag property = "openzone-" + String(zoneBit + 1 + (zoneGroup * 8)); homieNode.setProperty(property).send(bitRead(dsc.openZones[zoneGroup], zoneBit) ? "1" : "0"); } } + dsc.openZonesChanged[zoneGroup] = 0; } } @@ -207,18 +175,24 @@ void loopHandler() { 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 property = "alarmzone-" + String(zoneBit + 1 + (zoneGroup * 8)); homieNode.setProperty(property).send(bitRead(dsc.alarmZones[zoneGroup], zoneBit) ? "1" : "0"); } } + dsc.alarmZonesChanged[zoneGroup] = 0; } } } bool onKeypad(const HomieRange& range, const String& command) { - dsc.write(command.c_str()); - return true; + static char commandBuffer[256]; + if (command.length() > sizeof(commandBuffer)/sizeof(commandBuffer[0])) { + homieNode.setProperty("message").setRetained(false).send("Keypad data is too long"); + } else { + strcpy(commandBuffer, command.c_str()); + dsc.write(commandBuffer); + } + return true; } // Arm - the partition argument is 0 based @@ -253,7 +227,8 @@ byte extractPrefixDigits(String &str) { return prefix; } -bool homieNodeInputHandler(const String& property, const HomieRange& range, const String& value) { +bool homieNodeInputHandler(const HomieRange& range, const String& property, const String& value) { +// bool homieNodeInputHandler(const String& property, const HomieRange& range, const String& value) { if (!property.startsWith("partition-")) { return false; } @@ -281,44 +256,60 @@ bool homieNodeInputHandler(const String& property, const HomieRange& range, cons } bool onRefreshStatus(const HomieRange& range, const String& command) { - resetDscStatus(); + dsc.resetStatus(); homieNode.setProperty("refresh-status").setRetained(false).send("OK"); return true; } -bool onDscActive(const HomieRange& range, const String& command) { - if (command == "1") { - dsc.begin(); - } else { - dscEnd(); - } - return true; -} +// bool onDscActive(const HomieRange& range, const String& command) { +// if (command == "1") { +// dsc.begin(); +// } else { +// dsc.stop(); +// } +// return true; +// } bool onMaintenance(const HomieRange& range, const String& command) { if (command == "reboot") { - homieNode.setProperty("maintenance").setRetained(false).send("Rebooting..."); + homieNode.setProperty("message").setRetained(false).send("Rebooting..."); Homie.reboot(); } else if (command == "dsc-stop") { - dscEnd(); - homieNode.setProperty("maintenance").setRetained(false).send("DSC Interface stopped"); + dsc.stop(); + homieNode.setProperty("message").setRetained(false).send("DSC Interface stopped"); dscStopTime = millis(); if (dscStopTime == 0) dscStopTime = 1; // just in case millis() returned 0 } else if (command == "dsc-start") { dscStopTime = 0; dsc.begin(); - homieNode.setProperty("maintenance").setRetained(false).send("DSC Interface started"); + homieNode.setProperty("message").setRetained(false).send("DSC Interface started"); } else { - homieNode.setProperty("maintenance").setRetained(false).send("Unknown maintenance command"); + homieNode.setProperty("message").setRetained(false).send("Unknown maintenance command"); return false; } return true; } +// parse the provided time and set the dsc panel time +bool onPanelTime(const HomieRange& range, const String& command) { + if (dsc.keybusConnected) { + struct tm tm; // note tm_year is year since 1900, and tm_mon is month since January. Add offsets accordingly + strptime(command.c_str(), "%Y-%m-%d %H:%M", &tm); + dsc.setTime(1900 + tm.tm_year, 1 + tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, dscAccessCode.get()); + // homieNode.setProperty("message").setRetained(false).send("OK"); + homieNode.setProperty("message").setRetained(false).send("OK: " + + String(tm.tm_year + 1900, DEC) + "-" + String(1+tm.tm_mon, DEC) + "-" + String(tm.tm_mday) + " " + + String(tm.tm_hour, DEC) + ":" + String(tm.tm_min, DEC)); + } else { + homieNode.setProperty("message").setRetained(false).send("ERROR: DSC Keybus is not connected"); + } + return true; +} + void onHomieEvent(const HomieEvent& event) { switch (event.type) { - case HomieEventType::MQTT_READY: resetDscStatus(); break; - case HomieEventType::OTA_STARTED: dscEnd(); break; + case HomieEventType::MQTT_READY: dsc.resetStatus(); break; + case HomieEventType::OTA_STARTED: dsc.stop(); break; case HomieEventType::OTA_SUCCESSFUL: case HomieEventType::OTA_FAILED: dsc.begin(); break; } @@ -341,6 +332,7 @@ void setup() { homieNode.advertise("fire-alarm-keypad"); homieNode.advertise("aux-alarm-keypad"); homieNode.advertise("panic-alarm-keypad"); + homieNode.advertise("panel-time").settable(onPanelTime); for (byte i = 1; i <= dscPartitions; i++) { String pName = "partition-" + String(i) + "-"; @@ -349,6 +341,7 @@ void setup() { homieNode.advertise(String(pName + "exit-delay").c_str()); homieNode.advertise(String(pName + "alarm").c_str()); homieNode.advertise(String(pName + "fire").c_str()); + // homieNode.advertise(String(pName + "access-code").c_str()); //## BUG: It seems that the mqtt library cannot handle large messages } Homie.setup();