Updated with reed relais and debouncing button

This commit is contained in:
Ard Kuijpers
2020-04-08 22:44:37 +02:00
parent d02b9a63f4
commit ae9f175dad
9 changed files with 441 additions and 269 deletions

57
include/Heartbeater.hpp Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <Arduino.h>
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;
}
}
}
}
};

View File

@@ -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);
}
}

View File

@@ -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 <Homie.hpp>
#define DEFAULTPIN -1
class ButtonNode : public HomieNode
{
public:
typedef std::function<void(void)> TButtonPressCallback;
typedef std::function<void(bool)> 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);
};

View File

@@ -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();
}
}

View File

@@ -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 <Homie.hpp>
#define DEFAULTPIN -1
#define DEBOUNCE_TIME 200
class ContactNode : public HomieNode
{
public:
typedef std::function<void(bool)> 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);
};

View File

@@ -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 <Homie.h>
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;
}

View File

@@ -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<void()> 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);
};

View File

@@ -15,7 +15,6 @@ framework = arduino
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
lib_deps = lib_deps =
NewPing
Homie Homie
DHT sensor library DHT sensor library
Adafruit Unified Sensor Adafruit Unified Sensor

View File

@@ -1,44 +1,48 @@
#include <Homie.h> #include <Homie.h>
#include "PingNode.hpp" #include "RelayNode.hpp"
//#include "RelayNode.hpp"
#include "DHT22Node.hpp" #include "DHT22Node.hpp"
#include "ButtonNode.hpp"
#define TURN_ON LOW #include "ContactNode.hpp"
#define TURN_OFF HIGH #include "Heartbeater.hpp"
// #include "PingNode.hpp"
const int trigPin = D1; //const int trigPin = D1;
const int echoPin = D2; //const int echoPin = D2;
const int relayPin = D5; const int relayPin = D1;
const int contactPin = D2;
const int buttonPin = D5;
const int dhtPin = D7; ; 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 TEMPERATURE_INTERVAL = 120;
unsigned long lastTemperatureUpdate = 0; unsigned long lastTemperatureUpdate = 0;
bool relayInverse = true;
//PingNode obstacleNode("obstacle",trigPin,echoPin);
PingNode obstacleNode("obstacle",trigPin,echoPin);
DHT22Node airNode("air",dhtPin,TEMPERATURE_INTERVAL); 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 signal_led(bool on = true);
void heartbeat_led(int times = 2);
void changeHandler() { void changeHandler() {
signal_led(); signal_led();
} }
void loopHandler() { void buttonChangeHandler(bool down) {
if (millis() - lastHeartbeat > HEARTBEAT_INTERVAL * 1000UL || lastHeartbeat == 0) { Homie.getLogger() << "Button changing relay to " << (down ? "on" : "off") << endl;
heartbeat_led(); relayNode.setRelay(down);
lastHeartbeat = millis(); }
}
if (millis() - lastTemperatureUpdate > TEMPERATURE_INTERVAL * 1000UL || lastTemperatureUpdate == 0) { void loopHandler() {
obstacleNode.setTemperature(airNode.getTemperature()); heartbeater.loop();
lastTemperatureUpdate = millis(); // if (millis() - lastTemperatureUpdate > TEMPERATURE_INTERVAL * 1000UL || lastTemperatureUpdate == 0) {
} // obstacleNode.setTemperature(airNode.getTemperature());
// lastTemperatureUpdate = millis();
// }
} }
void setup() { void setup() {
@@ -48,9 +52,10 @@ void setup() {
pinMode(ledPin, OUTPUT); pinMode(ledPin, OUTPUT);
Homie_setBrand("EtxeanIoT"); Homie_setBrand("EtxeanIoT");
Homie_setFirmware("etxean-garagesensor", "1.0.5"); Homie_setFirmware("etxean-garagesensor", "1.2.3");
Homie.setLoopFunction(loopHandler); Homie.setLoopFunction(loopHandler);
obstacleNode.setChangeHandler(changeHandler); // obstacleNode.setChangeHandler(changeHandler);
buttonNode.onChange(buttonChangeHandler);
Homie.setup(); Homie.setup();
} }
@@ -58,25 +63,10 @@ void loop() {
Homie.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) void signal_led(bool on)
{ {
digitalWrite(ledPin, on ? TURN_ON : TURN_OFF); digitalWrite(ledPin, on ? TURN_ON : TURN_OFF);
} }
void heartbeat_led(int times)
{
for (int blink=0; blink<times; blink++)
{
signal_led(true);
delay(10);
signal_led(false);
delay(200);
}
}
void longbeat_led()
{
signal_led(true);
delay(500);
signal_led(false);
}