From ae9f175dad729aa0dad5f72c66fccadd8ae5763e Mon Sep 17 00:00:00 2001 From: Ard Kuijpers Date: Wed, 8 Apr 2020 22:44:37 +0200 Subject: [PATCH] Updated with reed relais and debouncing button --- include/Heartbeater.hpp | 57 +++++++++++++ lib/HomieNodes/ButtonNode.cpp | 139 ++++++++++++++++++++++++++++++ lib/HomieNodes/ButtonNode.hpp | 52 +++++++++++ lib/HomieNodes/ContactNode.cpp | 111 ++++++++++++++++++++++++ lib/HomieNodes/ContactNode.hpp | 47 ++++++++++ lib/HomieNodes/PingNode.cpp | 152 --------------------------------- lib/HomieNodes/PingNode.hpp | 71 --------------- platformio.ini | 1 - src/main.cpp | 80 ++++++++--------- 9 files changed, 441 insertions(+), 269 deletions(-) create mode 100644 include/Heartbeater.hpp create mode 100644 lib/HomieNodes/ButtonNode.cpp create mode 100644 lib/HomieNodes/ButtonNode.hpp create mode 100644 lib/HomieNodes/ContactNode.cpp create mode 100644 lib/HomieNodes/ContactNode.hpp delete mode 100644 lib/HomieNodes/PingNode.cpp delete mode 100644 lib/HomieNodes/PingNode.hpp diff --git a/include/Heartbeater.hpp b/include/Heartbeater.hpp new file mode 100644 index 0000000..a3eaf49 --- /dev/null +++ b/include/Heartbeater.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +class Heartbeater +{ +private: + int _pin; + uint8_t _onState; + uint8_t _ledState; + uint8_t _inBeatCount; + unsigned long _beatInterval; + unsigned long _onInterval = 30; + unsigned long _offInterval = 150; + unsigned long _inBeatInterval = 0; + unsigned long _lastBeat; + unsigned long _lastInBeat; + unsigned long _beatCount = 0; +public: + explicit Heartbeater(const int pin, uint8_t onState = HIGH, unsigned long beatIntervalMillis = 2000UL) + : _pin(pin), _onState(onState), _beatInterval(beatIntervalMillis) + { + _ledState = !_onState; + pinMode(_pin, OUTPUT); + } + + void loop() + { + digitalWrite(_pin, _ledState); // each iteration of loop() will set the IO pins, even if they don't change, that's okay + + // Toggle back and forth between the two LEDs + if ((millis() - _lastBeat) >= _beatInterval) + { + // time is up! + _inBeatCount = 0; + _lastBeat += _beatInterval; + _beatCount++; + } + + // Create the heartbeat effect + if ((millis() - _lastInBeat) >= _inBeatInterval) + { + _lastInBeat = millis(); + if (_inBeatCount < 2) + { + _ledState = !_ledState; + if (_ledState == _onState) { + _inBeatInterval = _onInterval; + } + else { + _inBeatCount++; + _inBeatInterval = _offInterval; + } + } + } + } +}; \ No newline at end of file diff --git a/lib/HomieNodes/ButtonNode.cpp b/lib/HomieNodes/ButtonNode.cpp new file mode 100644 index 0000000..d8c9b94 --- /dev/null +++ b/lib/HomieNodes/ButtonNode.cpp @@ -0,0 +1,139 @@ +/* + * ButtonNode.cpp + * Homie Node for a button with optional callback function + * + * Version: 1.0 + * Author: Lübbe Onken (http://github.com/luebbe) + */ + +#include "ButtonNode.hpp" + +ButtonNode::ButtonNode(const char *name, + const int buttonPin, + TButtonPressCallback buttonPressCallback, + TButtonChangeCallback buttonChangeCallback) + : HomieNode(name, "ButtonNode", "sensor") +{ + _buttonPin = buttonPin; + _buttonPressCallback = buttonPressCallback; + _buttonChangeCallback = buttonChangeCallback; +} + +void ButtonNode::handleButtonPress(unsigned long dt) +{ + if (Homie.isConnected()) + { + setProperty("duration").send(String(dt)); + } + + printCaption(); + Homie.getLogger() << cIndent << "pressed: " << dt << " ms" << endl; + + if (_buttonPressCallback) + { + _buttonPressCallback(); + } +} + +void ButtonNode::handleButtonChange(bool down) { + if (Homie.isConnected()) + { + setProperty("down").send(down ? "true" : "false"); + } + + printCaption(); + Homie.getLogger() << cIndent << (down ? "down" : "up") << endl; + + if (_buttonChangeCallback) + { + _buttonChangeCallback(down); + } +} + +void ButtonNode::onPress(TButtonPressCallback buttonCallback) +{ + _buttonPressCallback = buttonCallback; +} + +void ButtonNode::onChange(TButtonChangeCallback buttonCallback) +{ + _buttonChangeCallback = buttonCallback; +} + +void ButtonNode::setMinButtonDownTime(unsigned short downTime) +{ + _minButtonDownTime = downTime; +} + +void ButtonNode::setMaxButtonDownTime(unsigned short downTime) +{ + _maxButtonDownTime = downTime; +} + +void ButtonNode::printCaption() +{ + Homie.getLogger() << cCaption << endl; +} + +void ButtonNode::loop() +{ + // Detect a single press between 90ms and 900ms + // This could be improved to detect multiple quick or long presses + // and: + // a) report them via MQTT + // b) react by calling different callbacks + if (_buttonPin > DEFAULTPIN) + { + byte reading = digitalRead(_buttonPin); + + if (reading != _lastReading) { + // reset the debouncing timer + _lastDebounceTime = millis(); + } + + if ((millis() - _lastDebounceTime) > _minButtonDownTime) { + // whatever the reading is at, it's been there for longer than the debounce + // delay, so take it as the actual current state: + if (reading != _buttonState) + { + _buttonState = reading; + + if (_buttonState == LOW) + { + handleButtonChange(true); + _buttonChangeHandled = true; + _buttonDownTime = millis(); + _buttonPressHandled = false; + } + else + { + handleButtonChange(false); + _buttonChangeHandled = true; + + unsigned long dt = millis() - _buttonDownTime; + if (dt >= _minButtonDownTime && dt <= _maxButtonDownTime && !_buttonPressHandled) + { + handleButtonPress(dt); + _buttonPressHandled = true; + } + } + } + } + _lastReading = reading; + } +} + +void ButtonNode::setup() +{ + advertise("down").setDatatype("boolean"); + advertise("duration").setDatatype("integer").setUnit("ms"); + + printCaption(); + Homie.getLogger() << cIndent << "Pin: " << _buttonPin << endl; + + if (_buttonPin > DEFAULTPIN) + { + pinMode(_buttonPin, INPUT_PULLUP); + } +} + diff --git a/lib/HomieNodes/ButtonNode.hpp b/lib/HomieNodes/ButtonNode.hpp new file mode 100644 index 0000000..ef7d627 --- /dev/null +++ b/lib/HomieNodes/ButtonNode.hpp @@ -0,0 +1,52 @@ +/* + * ButtonNode.hpp + * Homie Node for a button with optional callback function + * + * Version: 1.0 + * Author: Lübbe Onken (http://github.com/luebbe) + */ + +#pragma once + +#include + +#define DEFAULTPIN -1 + +class ButtonNode : public HomieNode +{ +public: + typedef std::function TButtonPressCallback; + typedef std::function TButtonChangeCallback; + + +private: + const char *cCaption = "• Button:"; + const char *cIndent = " ◦ "; + + TButtonPressCallback _buttonPressCallback; + TButtonChangeCallback _buttonChangeCallback; + int _buttonPin; + byte _lastReading = HIGH; + byte _buttonState = HIGH; + bool _buttonPressHandled = 0; + bool _buttonChangeHandled = 0; + unsigned long _buttonDownTime = 0; + unsigned long _minButtonDownTime = 90; + unsigned long _maxButtonDownTime = 2000; + unsigned long _lastDebounceTime = 0; // the last time the button pin was toggled + + void handleButtonPress(unsigned long dt); + void handleButtonChange(bool down); + void printCaption(); + +protected: + virtual void loop() override; + virtual void setup() override; + +public: + explicit ButtonNode(const char *name, const int buttonPin = DEFAULTPIN, TButtonPressCallback buttonPressedCallback = NULL, TButtonChangeCallback buttonChangedCallback = NULL); + void onPress(TButtonPressCallback buttonCallback); + void onChange(TButtonChangeCallback buttonCallback); + void setMinButtonDownTime(unsigned short downTime); + void setMaxButtonDownTime(unsigned short downTime); +}; diff --git a/lib/HomieNodes/ContactNode.cpp b/lib/HomieNodes/ContactNode.cpp new file mode 100644 index 0000000..fadd978 --- /dev/null +++ b/lib/HomieNodes/ContactNode.cpp @@ -0,0 +1,111 @@ +/* + * ContactNode.cpp + * Homie Node for a Contact switch + * + * Version: 1.0 + * Author: Lübbe Onken (http://github.com/luebbe) + */ + +#include "ContactNode.hpp" + +ContactNode::ContactNode(const char *name, + const int contactPin, + TContactCallback contactCallback) + : HomieNode(name, "ContactNode", "sensor") +{ + _contactPin = contactPin; + _contactCallback = contactCallback; +} + +int ContactNode::getContactPin() +{ + return _contactPin; +} + +byte ContactNode::readPin() +{ + return digitalRead(_contactPin); +} + +// Debounce input pin. +bool ContactNode::debouncePin(void) +{ + byte inputState = readPin(); + if (inputState != _lastInputState) + { + _stateChangedTime = millis(); + _stateChangeHandled = false; + _lastInputState = inputState; +#ifdef DEBUG + Homie.getLogger() << "State Changed to " << inputState << endl; +#endif + } + else + { + unsigned long dt = millis() - _stateChangedTime; + if (dt >= DEBOUNCE_TIME && !_stateChangeHandled) + { +#ifdef DEBUG + Homie.getLogger() << "State Stable for " << dt << "ms" << endl; +#endif + _stateChangeHandled = true; + return true; + } + } + return false; +} + +void ContactNode::handleStateChange(bool open) +{ + if (Homie.isConnected()) + { + setProperty("open").send(open ? "true" : "false"); + } + if (_contactCallback) + { + _contactCallback(open); + } + + printCaption(); + Homie.getLogger() << cIndent << "is " << (open ? "open" : "closed") << endl; +} + +void ContactNode::onChange(TContactCallback contactCallback) +{ + _contactCallback = contactCallback; +} + +void ContactNode::printCaption() +{ + Homie.getLogger() << cCaption << endl; +} + +void ContactNode::loop() +{ + if (_contactPin > DEFAULTPIN) + { + if (debouncePin() && (_lastSentState != _lastInputState)) + { + handleStateChange(_lastInputState == HIGH); + _lastSentState = _lastInputState; + } + } +} + +void ContactNode::setupPin() +{ + pinMode(_contactPin, INPUT_PULLUP); +} + +void ContactNode::setup() +{ + advertise("open"); + + printCaption(); + Homie.getLogger() << cIndent << "Pin: " << _contactPin << endl; + + if (_contactPin > DEFAULTPIN) + { + setupPin(); + } +} diff --git a/lib/HomieNodes/ContactNode.hpp b/lib/HomieNodes/ContactNode.hpp new file mode 100644 index 0000000..4125870 --- /dev/null +++ b/lib/HomieNodes/ContactNode.hpp @@ -0,0 +1,47 @@ +/* + * ContactNode.hpp + * Homie Node for a Contact switch + * + * Version: 1.0 + * Author: Lübbe Onken (http://github.com/luebbe) + */ + +#pragma once + +#include + +#define DEFAULTPIN -1 +#define DEBOUNCE_TIME 200 + +class ContactNode : public HomieNode +{ +public: + typedef std::function TContactCallback; + +private: + const char *cCaption = "• Contact:"; + const char *cIndent = " ◦ "; + + TContactCallback _contactCallback; + int _contactPin; + // Use invalid values for last states to force sending initial state... + int _lastInputState = -1; // Input pin state. + int _lastSentState = -1; // Last pin state sent + bool _stateChangeHandled = false; + unsigned long _stateChangedTime = 0; + + bool debouncePin(void); + void handleStateChange(bool open); + void printCaption(); + +protected: + int getContactPin(); + virtual void loop() override; + virtual void setup() override; + virtual void setupPin(); + virtual byte readPin(); + +public: + explicit ContactNode(const char *name, const int contactPin = DEFAULTPIN, TContactCallback contactCallback = NULL); + void onChange(TContactCallback contactCallback); +}; diff --git a/lib/HomieNodes/PingNode.cpp b/lib/HomieNodes/PingNode.cpp deleted file mode 100644 index 9ba1861..0000000 --- a/lib/HomieNodes/PingNode.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * BME280Node.cpp - * Homie Node for BME280 sensors using Adafruit BME280 library. - * - * Version: 1.1 - * Author: Lübbe Onken (http://github.com/luebbe) - * Author: Markus Haack (http://github.com/mhaack) - */ - -#include "PingNode.hpp" -#include - -bool checkBounds(float value, float min, float max) { - return !isnan(value) && value >= min && value <= max; -} - -PingNode::PingNode(const char *name, const int triggerPin, const int echoPin, - const int measurementInterval, const int publishInterval) - : SensorNode(name, "RCW-0001"), - _triggerPin(triggerPin), _echoPin(echoPin),_lastMeasurement(0), _lastPublish(0) -{ - _measurementInterval = (measurementInterval > MIN_INTERVAL) ? measurementInterval : MIN_INTERVAL; - _publishInterval = (publishInterval > int(_measurementInterval)) ? publishInterval : _measurementInterval; - setMicrosecondsToMetersFactor(20); - - if (_triggerPin > DEFAULTPIN && _echoPin > DEFAULTPIN) { - sonar = new NewPing(_triggerPin,_echoPin,cMaxDistance*100.0); - } - - advertise(cDistanceTopic) - .setDatatype("float") - .setFormat("0:3") - .setUnit(cUnitMeter); - advertise(cPingTopic) - .setDatatype("float") - .setUnit(cUnitMicrosecond); - advertise(cStatusTopic) - .setDatatype("enum") - .setFormat("error, ok"); - advertise(cChangedTopic) - .setName("Obstacle changed") - .setDatatype("boolean"); -} - -void PingNode::printCaption() -{ - Homie.getLogger() << cCaption << " triggerpin[" << _triggerPin << "], echopin[" << _echoPin << "]:" << endl; -} - -void PingNode::send() -{ - printCaption(); - Homie.getLogger() << cIndent << "Ping: " << _ping_us << " " << cUnitMicrosecond << endl; - Homie.getLogger() << cIndent << "Distance: " << _distance << " " << cUnitMeter << endl; - bool valid = _distance > 0; - Homie.getLogger() << cIndent << "Status: " << (valid ? "ok" : "error") << endl; - bool changed = signalChange(_distance, _lastDistance); - Homie.getLogger() << cIndent << "Changed: " << (changed ? "true" : "false") << " " << endl; - if (Homie.isConnected()) - { - setProperty(cStatusTopic).send(valid ? "ok" : "error"); - if (valid) { - setProperty(cDistanceTopic).send(String(_distance)); - setProperty(cPingTopic).send(String(_ping_us)); - setProperty(cChangedTopic).send(changed ? "true": "false"); - } - } - if (changed) - { - _changeHandler(); - } - if (valid) { - _lastDistance = _distance; - _distance = -1; - } - -} - -void PingNode::loop() -{ - if (sonar) { - if (millis() - _lastMeasurement >= _measurementInterval * 1000UL || _lastMeasurement == 0) - { - float ping_us = sonar->ping_median(); - // Calculating the distance @ 10 °C from d = t_ping /2 * c => t_ping /2 * 337 [m/s] => t_ping_us / 1e-6 * 1/2 * 337 - float newDistance = ping_us*_microseconds2meter; - fixRange(&newDistance, cMinDistance, cMaxDistance); - if (newDistance > 0) { - _ping_us = ping_us; - _distance = newDistance; - } - _lastMeasurement = millis(); - } - - if (millis() - _lastPublish >= _publishInterval * 1000UL || _lastPublish == 0) - if (_distance > 0) { - send(); - _lastPublish = millis(); - _distance = 0; - } - } -} - -void PingNode::onReadyToOperate() -{ - if (Homie.isConnected()) - { - setProperty(cStatusTopic).send("ok"); - } -}; - -void PingNode::setup() -{ - printCaption(); - Homie.getLogger() << cIndent << "Reading interval: " << _measurementInterval << " s" << endl; - Homie.getLogger() << cIndent << "Publish interval: " << _publishInterval << " s" << endl; -} - -void PingNode::setMicrosecondsToMetersFactor(float temperatureCelcius) -{ - //float soundSpeed = 337.0; // @ 10°C - float soundSpeed = 331.4 + 0.6*temperatureCelcius; - printCaption(); - Homie.getLogger() << cIndent - << "SpeedOfSound: " << soundSpeed << " " << cUnitMetersPerSecond - << " at " << temperatureCelcius << " " << cUnitDegrees << endl; - // Calculating the distance from d = t_ping /2 * c => t_ping /2 * 337 [m/s] => t_ping_us / 1e-6 * 1/2 * 337 - _microseconds2meter = 0.5e-6 * soundSpeed; -} - -float PingNode::getRawEchoTime() { - // Clears the trigPin - digitalWrite(_triggerPin, LOW); - delayMicroseconds(2); - - // Sets the trigPin on HIGH state for 10 micro seconds - digitalWrite(_triggerPin, HIGH); - delayMicroseconds(10); - digitalWrite(_triggerPin, LOW); - - // Reads the echoPin, returns the sound wave travel time in microseconds - return pulseIn(_echoPin, HIGH); -} - -bool PingNode::signalChange(float distance, float lastDistance) { - return fabs(distance - lastDistance) > cMinimumChange; -} - -PingNode& PingNode::setChangeHandler(const ChangeHandler& changeHandler) { - _changeHandler = changeHandler; - return *this; -} diff --git a/lib/HomieNodes/PingNode.hpp b/lib/HomieNodes/PingNode.hpp deleted file mode 100644 index 9d3b152..0000000 --- a/lib/HomieNodes/PingNode.hpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * BME280Node.h - * Homie Node for BME280 sensors using Adafruit BME280 library. - * - * Version: 1.1 - * Author: Lübbe Onken (http://github.com/luebbe) - * Author: Markus Haack (http://github.com/mhaack) - */ - -#pragma once - -#include "NewPing.h" - -#include "SensorNode.hpp" -#include "constants.hpp" - -#define DEFAULTPIN -1 - -class PingNode : public SensorNode -{ -public: - typedef std::function ChangeHandler; -private: - const float cMinDistance = 0.0; - const float cMaxDistance = 3.0; - const float cMinimumChange = 0.2; - - static const int MIN_INTERVAL = 1; // in seconds - const char *cCaption = "• RCW-0001 sensor"; - const char *cIndent = " ◦ "; - - int _triggerPin; - int _echoPin; - float _microseconds2meter; - - unsigned long _measurementInterval; - unsigned long _lastMeasurement; - unsigned long _publishInterval; - unsigned long _lastPublish; - - NewPing* sonar; - float _distance = NAN; - int _ping_us = 0; - float _lastDistance = 0; - ChangeHandler _changeHandler = [](){}; - - float getRawEchoTime(); - void setMicrosecondsToMetersFactor(float temperatureCelcius); - bool signalChange(float distance, float lastDistance); - void printCaption(); - void send(); - -protected: - virtual void setup() override; - virtual void loop() override; - virtual void onReadyToOperate() override; - static const int DEFAULT_MEASUREMENT_INTERVAL = 1; - static const int DEFAULT_PUBLISH_INTERVAL = 5; - - -public: - explicit PingNode(const char *name, - const int triggerPin = DEFAULTPIN, - const int echoPin = DEFAULTPIN, - const int measurementInterval = DEFAULT_MEASUREMENT_INTERVAL, - const int publishInterval = DEFAULT_PUBLISH_INTERVAL); - - float getDistance() const { return _distance; } - void setTemperature(float temperatureCelcius) { setMicrosecondsToMetersFactor(temperatureCelcius); } - PingNode& setChangeHandler(const ChangeHandler& changeHandler); -}; diff --git a/platformio.ini b/platformio.ini index 8b7fc79..98cf086 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,6 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 lib_deps = - NewPing Homie DHT sensor library Adafruit Unified Sensor diff --git a/src/main.cpp b/src/main.cpp index d7a7185..a6e80f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,44 +1,48 @@ #include -#include "PingNode.hpp" -//#include "RelayNode.hpp" +#include "RelayNode.hpp" #include "DHT22Node.hpp" - -#define TURN_ON LOW -#define TURN_OFF HIGH - -const int trigPin = D1; -const int echoPin = D2; -const int relayPin = D5; +#include "ButtonNode.hpp" +#include "ContactNode.hpp" +#include "Heartbeater.hpp" +// #include "PingNode.hpp" +//const int trigPin = D1; +//const int echoPin = D2; +const int relayPin = D1; +const int contactPin = D2; +const int buttonPin = D5; const int dhtPin = D7; ; -const int ledPin = LED_BUILTIN; +const int ledPin = LED_BUILTIN; +const int heartbeatPin = LED_BUILTIN; -unsigned long HEARTBEAT_INTERVAL = 5; -unsigned long lastHeartbeat = 0; unsigned long TEMPERATURE_INTERVAL = 120; unsigned long lastTemperatureUpdate = 0; +bool relayInverse = true; - -PingNode obstacleNode("obstacle",trigPin,echoPin); +//PingNode obstacleNode("obstacle",trigPin,echoPin); DHT22Node airNode("air",dhtPin,TEMPERATURE_INTERVAL); -//RelayNode relayNode("relay",relayPin,ledPin); +RelayNode relayNode("relay",relayPin,ledPin,relayInverse); +ButtonNode buttonNode("button",buttonPin); +ContactNode contactNode("contact",contactPin); +Heartbeater heartbeater(heartbeatPin, heartbeatPin == LED_BUILTIN ? LOW : HIGH); void signal_led(bool on = true); -void heartbeat_led(int times = 2); void changeHandler() { signal_led(); } -void loopHandler() { - if (millis() - lastHeartbeat > HEARTBEAT_INTERVAL * 1000UL || lastHeartbeat == 0) { - heartbeat_led(); - lastHeartbeat = millis(); - } - if (millis() - lastTemperatureUpdate > TEMPERATURE_INTERVAL * 1000UL || lastTemperatureUpdate == 0) { - obstacleNode.setTemperature(airNode.getTemperature()); - lastTemperatureUpdate = millis(); - } +void buttonChangeHandler(bool down) { + Homie.getLogger() << "Button changing relay to " << (down ? "on" : "off") << endl; + relayNode.setRelay(down); +} + +void loopHandler() { + heartbeater.loop(); + // if (millis() - lastTemperatureUpdate > TEMPERATURE_INTERVAL * 1000UL || lastTemperatureUpdate == 0) { + // obstacleNode.setTemperature(airNode.getTemperature()); + // lastTemperatureUpdate = millis(); + // } } void setup() { @@ -48,9 +52,10 @@ void setup() { pinMode(ledPin, OUTPUT); Homie_setBrand("EtxeanIoT"); - Homie_setFirmware("etxean-garagesensor", "1.0.5"); + Homie_setFirmware("etxean-garagesensor", "1.2.3"); Homie.setLoopFunction(loopHandler); - obstacleNode.setChangeHandler(changeHandler); + // obstacleNode.setChangeHandler(changeHandler); + buttonNode.onChange(buttonChangeHandler); Homie.setup(); } @@ -58,25 +63,10 @@ void loop() { Homie.loop(); } +const uint8_t TURN_ON = ledPin == LED_BUILTIN ? LOW : HIGH; +const uint8_t TURN_OFF = ledPin == LED_BUILTIN ? HIGH : LOW; + void signal_led(bool on) { digitalWrite(ledPin, on ? TURN_ON : TURN_OFF); } - -void heartbeat_led(int times) -{ - for (int blink=0; blink