Sure!
***************************************************************************
**
** Light switch v1.0 with additional relay/power supply daughter board
**
** Scraped together by D Hille. MySensors library by Henrik Ekblad et al.
** Button lib.: github.com/JChristensen/Button
** PinChangeInterrupt lib.: github.com/NicoHood/PinChangeInterrupt
** FastLED lib.: github.com/FastLED
**
***************************************************************************/
// MySensors definitions
#define MY_DEBUG // Comment out after finishing sketch (saves over 6kB flash)
//#define MY_DEBUG_VERBOSE_SIGNING // Comment out when no signing is used or when everything is OK
#define MY_BAUD_RATE 57600 // Lower as max because of 8 MHz Arduino
#define MY_RADIO_RFM69
#define MY_IS_RFM69HW
#define MY_NODE_ID 85 // Delete to use automatic ID assignment
#define MY_TRANSPORT_WAIT_READY_MS 100 //Start the node even if no connection to the GW could be made
//#define MY_TRANSPORT_STATE_RETRIES 1
//#define MY_TRANSPORT_MAX_TSM_FAILURES (2u) //Uncomment these three when usung battery powered node
//#define MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE (5*60*1000ul)
/**************************************************************************/
// Security
/**************************************************************************/
//#define MY_SIGNING_SOFT
//#define MY_SIGNING_SOFT_RANDOMSEED_PIN 7
//#define MY_SIGNING_REQUEST_SIGNATURES
//#define MY_RFM69_ENABLE_ENCRYPTION
/**************************************************************************/
// Switch definitions
/**************************************************************************/
#define BATTERY_POWERED
#define battUpdater 50 // Number of TX actions to count before a new battery status update is sent.
#define LED_FUNCTION 2
// LED functions:
// 0.: no LED present
// 1.: ACK (LED will flash briefly when ACK is received)
// 2.: Notification (RGB-LED wil display a function of the controller, or flash white when ACK is received)
#define NODE_CHAN_A_FUNCTION 1
#define NODE_CHAN_B_FUNCTION 3
#define NODE_CHAN_C_FUNCTION 0
#define NODE_CHAN_D_FUNCTION 0
// Node functions:
// 0.: channel off
// 1.: button only - momentary ("ON" while button pushed, "OFF" when released)
// 2.: button only - toggle (every button push toggles state)
// 3.: normal (relay controlled via RF and local)
// 4.: local relay (no RF control, only button)
//#define SAFETY_BUTTON_A // When defined button needs to be pressed for a set time to activate the button.
//#define SAFETY_BUTTON_B // Comment out to deactivate.
//#define SAFETY_BUTTON_C
//#define SAFETY_BUTTON_D
#define SAFETY_PRESS_TIME 1 // time in seconds the button needs to be pressed.
/**************************************************************************/
// Debug and interrupt definitions
/**************************************************************************/
#define LOCAL_DEBUG // Comment out to switch all debug messages off (saves 1kb flash)
/*#ifdef MY_DEBUG // Differentiate between global debug including radio and local debug
#define LOCAL_DEBUG // for the sketch alone.
#endif*/
#ifdef LOCAL_DEBUG
#define Sprint(a) (Serial.print(a)) // Macro as substitute for serial debug. Will be an empty macro when
#define Sprintln(a) (Serial.println(a)) // debug is switched off
#else
#define Sprint(a)
#define Sprintln(a)
#endif
#define EI_NOTEXTERNAL
#define EI_NOTPORTB
#define EI_NOTPORTD
#define EI_ARDUINO_INTERRUPTED_PIN
/**************************************************************************/
#include <Button.h>
#include <EnableInterrupt.h>
#include <MySensors.h>
#include <FastLED.h>
#include <Vcc.h>
#define BMEaddr 0x76
#define Max44099Addr 0x4A
#define sketchName "switchNode(Hal)"
#define sketchVer "1.0"
#define buttApin 14 // Arduino Digital I/O pin numbers for connected buttons
#define buttBpin 15 // when connected to '`official' PCB.
#define buttCpin 8
#define buttDpin 9
#define relayApin 16
#define relayBpin 17
#define LEDpinACK 5
#define LEDpinWS 6
#define WS_Power 9
#define ChanA 0
#define ChanB 1
#define ChanC 2
#define ChanD 3
#define PULLUP true
#define INVERT true
#define bounceTime 20 // A debounce time of 20 milliseconds usually works well for tactile button switches.
#define sleepWait 2000 // Time to wait in ms before node sleeps (to be able to receive notification messages).
volatile int pinChanged = 255;
int indexButton = 255;
int currDim = 255; // Bright value of notificator at startup.
int newDim = 0;
int currLED = 30; // Hue value of notificator at startup.
int newLED = 0;
bool buttonPressed = false;
bool changeLED = false;
bool flashLED = false;
int flashNumber = 1;
bool flashWait = false;
bool dimLED = false;
bool flashOn = false;
bool battPower = true;
int inputPin[5] = {0, 0, 0, 0, 0}; // State machine registers
int actuatorPin[2] = {0, 0};
int function[4] = {0, 0, 0, 0};
bool safetyBttn[4] = {0, 0, 0, 0};
bool reqAck[4] = {0, 0, 0, 0};
bool inState[5] = {0, 0, 0, 0, 0};
bool longPr[5] = {0, 0, 0, 0, 0};
bool outState[4] = {0, 0, 0, 0};
bool longPress = false;
unsigned long lastTimeButton; // Timer registers
unsigned long flashTime;
unsigned long lightsOut;
unsigned long buttonTime;
unsigned long longPressTime;
bool startUp = true;
bool transportDown = false;
bool metric = true;
int battStatCounter = 0;
const float VccMin = 0.0; // Minimum expected Vcc level, in Volts.
const float VccMax = 5.0; // Maximum expected Vcc level, in Volts.
const float VccCorrection = 3.41/3.35; // Measured Vcc by multimeter divided by reported Vcc
/**************************************************************************/
// Library declarations
/**************************************************************************/
Button BtnA(buttApin, PULLUP, INVERT, bounceTime);
Button BtnB(buttBpin, PULLUP, INVERT, bounceTime);
Button BtnC(buttCpin, PULLUP, INVERT, bounceTime);
Button BtnD(buttDpin, PULLUP, INVERT, bounceTime);
CRGB notifier[1]; // define notification LED
MyMessage msgButton(0, V_TRIPPED);
MyMessage msgToggle(0, V_STATUS);
MyMessage msgLED(LEDpinWS, V_RGB);
Vcc vcc(VccCorrection);
/**************************************************************************/
// Error messages
/**************************************************************************/
#if (NODE_CHAN_A_FUNCTION == 0)
#error Channel A cannot be turned off. For single channel use turn off channel B.
#endif
#if (NODE_CHAN_C_FUNCTION >= 3 || NODE_CHAN_D_FUNCTION >= 3)
#error Channels C and D do not have relays attached, can only be used as buttons.
#endif
/**************************************************************************/
void before(void)
{
Sprintln("\nStarting up...");
Sprintln("\nReading config...");
// Fill state machine registers
inputPin[ChanA] = buttApin;
#ifdef SAFETY_BUTTON_A
safetyBttn[ChanA] = true;
#endif
function[ChanA] = NODE_CHAN_A_FUNCTION;
enableInterrupt(inputPin[ChanA], ChanA_ISR, CHANGE);
if (function[ChanA] <= 2 && LED_FUNCTION >= 1) {
reqAck[ChanA] = true;
}
else {
actuatorPin[ChanA] = relayApin;
}
if (NODE_CHAN_B_FUNCTION >= 1) {
inputPin[ChanB] = buttBpin;
#ifdef SAFETY_BUTTON_B
safetyBttn[ChanB] = true;
#endif
function[ChanB] = NODE_CHAN_B_FUNCTION;
enableInterrupt(inputPin[ChanB], ChanB_ISR, CHANGE);
if (function[ChanB] <= 2 && LED_FUNCTION >= 1) {
reqAck[ChanB] = true;
}
else {
actuatorPin[ChanB] = relayBpin;
}
}
if (NODE_CHAN_C_FUNCTION >= 1) {
inputPin[ChanC] = buttCpin;
#ifdef SAFETY_BUTTON_C
safetyBttn[ChanC] = true;
#endif
function[ChanC] = NODE_CHAN_C_FUNCTION;
enableInterrupt(inputPin[ChanC], ChanC_ISR, CHANGE);
if (function[ChanC] <= 2 && LED_FUNCTION >= 1) {
reqAck[ChanC] = true;
}
}
if (NODE_CHAN_D_FUNCTION >= 1) {
inputPin[ChanD] = buttDpin;
#ifdef SAFETY_BUTTON_D
safetyBttn[ChanD] = true;
#endif
function[ChanD] = NODE_CHAN_D_FUNCTION;
enableInterrupt(inputPin[ChanD], ChanD_ISR, CHANGE);
if (function[ChanD] <= 2 && LED_FUNCTION >= 1) {
reqAck[ChanD] = true;
}
}
longPressTime = SAFETY_PRESS_TIME * 1000;
}
/**************************************************************************/
void presentation()
{
Sprintln("Start radio and sensors");
Sprintln("Send node info. \n");
sendSketchInfo(sketchName, sketchVer);
for (int i = ChanA; i < ChanD+1; i++) {
Sprint("\nPresent channel "); Sprint(i); Sprint(", function: "); Sprint(function[i]); Sprint(", safety: "); Sprintln(safetyBttn[i]);
if (function[i] == 1) {
present(i, S_BINARY);
}
else if (function[i] >= 2) {
present(i, S_LIGHT);
}
}
if (LED_FUNCTION == 2) {
Sprintln("\nPresent notifier.");
present(LEDpinWS, S_RGB_LIGHT);
}
synchronizeChannels();
}
/**************************************************************************/
void setup(void)
{
#ifndef BATTERY_POWERED
battPower = false;
#endif
pinMode(LEDpinACK, OUTPUT);
if (LED_FUNCTION == 2) {
pinMode(WS_Power, OUTPUT);
digitalWrite(WS_Power, HIGH);
FastLED.addLeds<WS2812B, LEDpinWS, GRB>(notifier, 1);
wait(30);
notifier[0] = CHSV(currLED, 255, currDim); // give LED initial color
FastLED.show();
}
if (function[ChanA] >= 3) {
pinMode(actuatorPin[ChanA], OUTPUT);
}
if (function[ChanB] >= 3) {
pinMode(actuatorPin[ChanB], OUTPUT);
}
Sprintln("\nDone. \nStarting program.");
lastTimeButton = millis();
checkTransportStatus();
}
/**************************************************************************/
void loop(void)
{
if (indexButton <= 25) { // Measure time when safety feature is enabled.
if (millis() >= buttonTime + longPressTime) {
Sprint("\nChannel "); Sprint(indexButton); Sprintln(" long-pressed");
flashLED = true;
pinChanged = indexButton;
longPr[indexButton] = true;
indexButton = 255;
wait(30);
}
}
//if (buttonPressed) {
// readInputs();
//}
switch (function[pinChanged]) {
case 1:
Sprint("\nChannel "); Sprint(pinChanged);
if (inState[pinChanged] && !buttonPressed) {
Sprintln(" pressed.");
if ((!safetyBttn[pinChanged]) || (longPr[pinChanged])){
if (!transportDown) {
send(msgButton.setSensor(pinChanged).set(true));
}
}
else {
indexButton = pinChanged;
buttonTime = millis();
}
pinChanged = 255;
//buttonPressed = true;
wait(30);
break;
}
else if (!inState[pinChanged]) {
lastTimeButton = millis();
Sprintln(" released.");
if ((!safetyBttn[pinChanged]) || (longPr[pinChanged])) {
if (!transportDown) {
send(msgButton.setSensor(pinChanged).set(false),reqAck[pinChanged]);
}
else {
flashLED = true;
flashNumber = 3;
}
}
longPr[pinChanged] = false;
indexButton = 255;
pinChanged = 255;
wait(30);
battStatCounter++;
}
break;
case 2:
case 3:
case 4:
lastTimeButton = millis();
if (inState[pinChanged]) {
if ((!safetyBttn[pinChanged]) || (longPr[pinChanged])) {
Sprint("\nChannel "); Sprint(pinChanged); Sprint(" toggled");
outState[pinChanged] = !outState[pinChanged];
if (function[pinChanged] >= 3) {
digitalWrite(actuatorPin[pinChanged], outState[pinChanged]);
Sprint(", relay changed");
}
if (function[pinChanged] == 2 || function[pinChanged] == 3) {
if (!transportDown) {
send(msgToggle.setSensor(pinChanged).set(outState[pinChanged]),true);
Sprint(", sent");
}
else {
flashLED = true;
flashNumber = 3;
}
}
Sprint(". State: "); Sprintln(outState[pinChanged]);
longPr[pinChanged] = false;
}
else {
buttonTime = millis();
indexButton = pinChanged;
}
pinChanged = 255;
battStatCounter++;
}
else {
pinChanged = 255;
}
break;
}
if (battPower && (battStatCounter >= battUpdater)) {
batteryStats();
battStatCounter = 0;
}
checkTransportStatus();
if (pinChanged > 25) {
if ((battPower) && (millis() >= lastTimeButton + sleepWait)) {
if (!dimLED) {
newDim = 0;
}
dimLED = true;
if (currDim <= 0) {
lightsOUT();
newDim = 255;
dimLED = true;
}
}
}
updateLED();
}
/**************************************************************************/
void ChanA_ISR()
{
pinChanged = ChanA;
inState[ChanA] = BtnA.read();
}
void ChanB_ISR()
{
pinChanged = ChanB;
inState[ChanB] = BtnB.read();
}
void ChanC_ISR()
{
pinChanged = ChanC;
inState[ChanC] = BtnC.read();
}
void ChanD_ISR()
{
pinChanged = ChanD;
inState[ChanD] = BtnD.read();
}
/**************************************************************************/
void readInputs()
{
inState[ChanA] = BtnA.read();
inState[ChanB] = BtnB.read();
inState[ChanC] = BtnC.read();
inState[ChanD] = BtnD.read();
}
/**************************************************************************/
void receive(const MyMessage &message)
{
Sprintln("\nMessage received");
if (message.isAck()) {
Sprintln("ACK\n");
flashLED = true;
}
if (message.type == V_VAR1)
{
Sprint("Notifier value; ");
if (message.sensor == LEDpinWS) {
newLED = message.getInt();
Sprint("received hue-value: "); Sprint(newLED); Sprintln("\n");
changeLED = true;
}
}
if (message.type == V_STATUS) {
outState[message.sensor] = message.getBool();
Sprint("Channel "); Sprint(message.sensor); Sprint(" switched to "); Sprintln(outState[message.sensor]); Sprintln("\n");
digitalWrite(actuatorPin[message.sensor], outState[message.sensor]);
}
}
/**************************************************************************/
void lightsOUT()
{
Sprintln("\nSleep\n");
digitalWrite(WS_Power, HIGH);
pinMode(WS_Power, INPUT);
wait(30);
int testInt = sleep(0xff, 0x00, 0xff, 0x00, 0);
pinMode(WS_Power, OUTPUT);
digitalWrite(WS_Power, HIGH);
Sprintln(testInt);
wait(30);
}
/**************************************************************************/
void updateLED()
{
if ((millis() >= flashTime + 100) && flashWait) {
flashWait = false;
}
if (flashLED) { // Flash the LED white (or whatever color)as confirmation.
if (!flashOn && !flashWait) {
flashTime = millis(); // Use millis() to prevent blocking the code with wait() or delay().
flashOn = true;
if (LED_FUNCTION == 1) {
digitalWrite(LEDpinACK, HIGH);
}
else if (LED_FUNCTION == 2) {
notifier[0] = CHSV(currLED, 0, currDim); // Set the saturation to 0 (no color, just light -> white).
}
}
else if ((millis() >= flashTime + 50) && !flashWait) {
if (LED_FUNCTION == 1) {
digitalWrite(LEDpinACK, LOW);
}
else if (LED_FUNCTION == 2) {
notifier[0] = CHSV(currLED, 255, currDim); // Set the saturation back to original.
}
flashOn = false;
flashWait = true;
flashNumber--;
if (flashNumber <= 0) {
flashNumber = 1;
flashLED = false;
flashWait = false;
}
}
}
else if (changeLED) { // Morph the next color in the current for the notification LED.
int deltaHSV = currLED - newLED;
if (currLED != newLED) {
if (deltaHSV < 0) {
currLED += 1;
}
else if (deltaHSV > 0) {
currLED -= 1;
}
notifier[0] = CHSV(currLED, 255, currDim);
Sprintln(currLED);
}
else {
changeLED = false;
}
}
else if (dimLED) { // Dim the notification LED to 0 when battery powered.
if (currDim != newDim) {
if (currDim >= newDim) {
currDim -= 1; // Slowly dim the indicator before sleep time and...
}
else if (currDim <= newDim) {
currDim += 5; // ...bring it back up in a hurry.
}
notifier[0] = CHSV(currLED, 255, currDim);
}
else {
dimLED = false;
}
}
FastLED.show();
}
/**************************************************************************/
void checkTransportStatus()
{
/*if (isTransportSearchingParent()) {
if (!transportDown); {
transportDown = true;
}
}
else if (transportDown) {
transportDown = false;
synchronizeChannels();
}*/
}
/**************************************************************************/
void synchronizeChannels()
{
for (int i = ChanA; i < ChanD+1; i++) { // Syncing switch states to controller.
if ((function[i] == 2) || (function[i] == 3)) {
Sprint("\nChannel "); Sprint(i); Sprintln(" sync.");
send(msgToggle.setSensor(i).set(false));
}
}
}
/**************************************************************************/
void indication(const indication_t)
{
}
/**************************************************************************/
void batteryStats()
{
if (battPower) {
float battPct = vcc.Read_Perc();
float battVolt = vcc.Read_Volts();
wait(50);
sendBatteryLevel(battPct);
Sprint("Battery level: "); Sprintln(battVolt); Sprintln("V.\n");
}
}
0 MCO:BGN:INIT NODE,CP=RRNNA--,VER=2.1.1
6 MCO:BGN:BFR
Starting up...
Reading config...
10 TSM:INIT
16 TSF:WUR:MS=100
22 TSM:INIT:TSP OK
26 TSM:INIT:STATID=85
28 TSF:SID:OK,ID=85
32 TSM:FPAR
165 TSF:MSG:SEND,85-85-255-255,s=255,c=3,t=7,pt=0,l=0,sg=0,ft=0,st=OK:
178 MCO:BGN:STP
Done.
Starting program.
210 MCO:BGN:INIT OK,TSP=0
985 TSF:MSG:READ,0-0-85,s=255,c=3,t=8,pt=1,l=1,sg=0:0
993 TSF:MSG:FPAR OK,ID=0,D=1
2179 TSM:FPAR:OK
2181 TSM:ID
2185 TSM:ID:OK
2187 TSM:UPL
2197 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=24,pt=1,l=1,sg=0,ft=0,st=OK:1
2246 TSF:MSG:READ,0-0-85,s=255,c=3,t=25,pt=1,l=1,sg=0:1
2256 TSF:MSG:PONG RECV,HP=1
2263 TSM:UPL:OK
2265 TSM:READY:ID=85,PAR=0,DIS=1
2277 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=15,pt=6,l=2,sg=0,ft=0,st=OK:0100
2326 TSF:MSG:READ,0-0-85,s=255,c=3,t=15,pt=6,l=2,sg=0:0100
2344 TSF:MSG:SEND,85-85-0-0,s=255,c=0,t=17,pt=0,l=5,sg=0,ft=0,st=OK:2.1.1
2367 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=6,pt=1,l=1,sg=0,ft=0,st=OK:0
2422 TSF:MSG:READ,0-0-85,s=255,c=3,t=6,pt=0,l=1,sg=0:M
Start radio and sensors
Send node info.
2443 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=11,pt=0,l=15,sg=0,ft=0,st=OK:switchNode(Hal)
2465 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=12,pt=0,l=3,sg=0,ft=0,st=OK:1.0
Present channel 0, function: 1, safety: 0
2488 TSF:MSG:SEND,85-85-0-0,s=0,c=0,t=3,pt=0,l=0,sg=0,ft=0,st=OK:
Present channel 1, function: 3, safety: 0
2508 TSF:MSG:SEND,85-85-0-0,s=1,c=0,t=3,pt=0,l=0,sg=0,ft=0,st=OK:
Present channel 2, function: 0, safety: 0
Present channel 3, function: 0, safety: 0
Present notifier.
2535 TSF:MSG:SEND,85-85-0-0,s=6,c=0,t=26,pt=0,l=0,sg=0,ft=0,st=OK:
Channel 1 sync.
2549 !MCO:SND:NODE NOT REG
2557 MCO:REG:REQ
2568 TSF:MSG:SEND,85-85-0-0,s=255,c=3,t=26,pt=1,l=1,sg=0,ft=0,st=OK:2
2617 TSF:MSG:READ,0-0-85,s=255,c=3,t=27,pt=1,l=1,sg=0:1
2627 MCO:PIM:NODE REG=1
Sleep
3268 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
3276 MCO:SLP:TPD
3278 MCO:SLP:WUP=-1
-1
Sleep
4112 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
4120 MCO:SLP:TPD
4122 MCO:SLP:WUP=-1
-1
Sleep
4956 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
4964 MCO:SLP:TPD
4966 MCO:SLP:WUP=-1
-1
Sleep
5799 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
5808 MCO:SLP:TPD
5810 MCO:SLP:WUP=-1
-1
Sleep
6643 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
6651 MCO:SLP:TPD
6653 MCO:SLP:WUP=-1
-1
Sleep
7487 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
7495 MCO:SLP:TPD
7497 MCO:SLP:WUP=-1
-1
Sleep
8331 MCO:SLP:MS=0,SMS=0,I1=255,M1=0,I2=255,M2=0
8339 MCO:SLP:TPD
8341 MCO:SLP:WUP=-1
-1
And so on...
The only funny thing I see is at 2549 !MCO:SND:NODE NOT REG
.
( I actually just noticed that the setup
is run before the presentation
. I thought that had been changed)
edit: I forgot to mention my hardware!
328p-AU on a custom board on the 8MHz internal clock. The interrupt pins are connected via a RC network (debounce) to the pins with the internal pullups. I used the button.h
library because I'm lazy it also keeps the code a bit neater to look at.