This is my first real effort at making a sensor... It is also my first time programming Arduinos - I learnt BASIC at school 30yrs ago and use VBA at work but that is it so be kind please... I am grateful for any suggestions on how to improve it and cut out any lines I don't need
I already use RFLink and Domoticz to do some controlling around my house but with MySensors I thought I could add to that. The aim is to create one lot of code that can then be used on about 10 separate idenitcal nodes around the house. That way I can debug the code before deployment rather than having to debug every node.
I decided I needed temp, humidity, a couple of relays, a PIR, and (on a whim) a gesture sensor. I used a dht11 for the temp/hum and apds-9960 for the gestures. I wanted lots of possibilities for sending command to Domoticz which will then handle the logic e.g. using the gesture sensor, if I swipe up I'll get Domoticz to switch on a relay.
I've also made it so that there is a separate switch that can deactivate motion sensing from the controller (don't know why, just seemed like a good idea. In addition I made a temperature adjustment child by declaring a dimmer switch then using the value to calculate an adjustment where 50% dimmed is 0 degrees offset. 0% = -5 degrees, 25% = -2.5 degrees, 50% = no offset, 75% = +2.5 degrees, 76% = +2.6 degrees etc.
Fianlly I thought that dealing with the status of the relays, the PIR, and temp offset might be different depending on the sensor (e.g. I might want a relay connected to a fridge to default to "On" if the node rebooted for any reason, but e.g. the light in the garage to default to off, I might also want the sensor to get the last value from eeprom as in the relay example sketch or even query Domoticz to find the current state according to it).
To do this I added a StoreXState variable that can be 1,2, or 3 and after a power cycle, it decides if the "Motion Enable/disable", Relays, & Temp Offset states are either default, got from EEPROM, or requested from Domoticz.
I'm pretty pleased with it as a first attempt, so thought I'd share the code. A few things:
- there are loads of Serial Prints so I could see what was happening
- if debug is defined then it uses loads of memory (like 99%) on a pro mini and I got really odd behaviour from the node
- I've borrowed heavily from other people's code, thank you to them
- it is currently a jumble of wires on my desk so no pics yet
- massive thanks to the MySensors team
/**
The MySensors Arduino library handles the wireless radio link and protocol
between your home built sensors/actuators and HA controller of choice.
The sensors forms a self healing radio network with optional repeaters. Each
repeater and gateway builds a routing tables in EEPROM which keeps track of the
network topology allowing messages to be routed to nodes.
Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
Copyright (C) 2013-2015 Sensnology AB
Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
Documentation: http://www.mysensors.org
Support Forum: http://forum.mysensors.org
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
*******************************
Hardware Connections:
IMPORTANT: The APDS-9960 can only accept 3.3V!
Arduino Pin APDS-9960 Board Function
3.3V VCC Power
GND GND Ground
A4 SDA I2C Data
A5 SCL I2C Clock
2 INT Interrupt
*/
// Enable debug prints to serial monitor
//#define MY_DEBUG
// Enable and select radio type attached
#define MY_RADIO_NRF24
//#define MY_RADIO_RFM69
// Enable repeater functionality for this node
#define MY_REPEATER_FEATURE
#define MY_NODE_ID 103
#include <Wire.h>
#include <SparkFun_APDS9960.h>
#include <SPI.h>
#include <DHT.h>
#include <MySensors.h>
#define RELAY_1_PIN 5 // Arduino Digital I/O pin number for first relay
#define RELAY_2_PIN 6 // Arduino Digital I/O pin number for second relay
#define RELAY_ON 0 // GPIO value to write to turn on attached relay - domoticz needs 0 for on, 1 for off
#define RELAY_OFF 1 // GPIO value to write to turn off attached relay
#define CHILD_ID_RELAY1 1
#define CHILD_ID_RELAY2 2
#define CHILD_ID_HUM 3
#define CHILD_ID_TEMP 4
#define CHILD_ID_MOT 5 // Id of the sensor child
#define CHILD_ID_TEMP_OFFSET 20
#define CHILD_ID_MotionOnOff 21
#define CHILD_ID_GESTUREUD 30 //up down gesture
#define CHILD_ID_GESTURELR 31 //left right gesture
#define CHILD_ID_GESTURENF 32 //near far gesture
#define GestureUp 1 //command to send on gesture, 0 = off, 1 = on
#define GestureDown 0 //command to send on gesture, 0 = off, 1 = on
#define GestureLeft 0 //command to send on gesture, 0 = off, 1 = on
#define GestureRight 1 //command to send on gesture, 0 = off, 1 = on
#define GestureNear 0 //command to send on gesture, 0 = off, 1 = on
#define GestureFar 1 //command to send on gesture, 0 = off, 1 = on
#define GestureSensitivity 0 // value between 0 & 3, 0 most sensitive, 3 least
#define HUMIDITY_SENSOR_DIGITAL_PIN 7
#define APDS9960_INT 2 // for the APDS-9960 - Needs to be an interrupt pin
#define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your motion sensor. (Only 2 and 3 generates interrupt!)
#define INTERRUPT DIGITAL_INPUT_SENSOR-2 // for the PIR - Usually the interrupt = pin -2 (on uno/nano anyway)
SparkFun_APDS9960 apds = SparkFun_APDS9960();
volatile int isr_flag = 0; //handles the interrupt process for gesture sensor
unsigned long SLEEP_TIME = 120000; // Sleep time between reads of temp/hum (in milliseconds)
unsigned long lastRefreshTime = 0; // Use this to implement a non-blocking delay function
unsigned long RefreshCyclesUntilForcedUpdate = 5; // use to force a temp/hum reading after 'n' SLEEP_TIME cycles even if value hasn't changed, set v.high (e.g. 10,000) to effectively disable
unsigned long ForceUpdate = RefreshCyclesUntilForcedUpdate * SLEEP_TIME; // Force update read after this amount of time
unsigned long lastForceUpdateTime = 0; // Use this to implement a non-blocking delay function that forces update even if temp hasn't changed
DHT dht;
float lastTemp;
float lastHum;
boolean metric = true;
bool lastTripped = 0; // Used to store last motion sensor value
boolean FirstBoot = true;
// the below allow you decide if the default state of certain sensors is stored in EEPROM or Controller or always start in a certain state
// 1 = EEPROM 2 = Controller (e.g. Domoticz), 3 or any other number then they start as with default values
// If option 1 or 3 is selected then we will also send a message to the controller to tell it the states
// unsigned long WaitBetweenSendingMessages = 150; //how long to wait between sending messages and presenting nodes
int StoreTempOffset = 2;
int StoreMotionOnOff = 2;
int StoreRelayState = 2;
// below are the default states that you can set to be loaded at boot each time
// these will be reverted to in EEPROM values are corrupted
bool MotionON = 0; //enable/disable the PIR reports (1=enabled 0=disabled)
bool RelayState = 0; // default state for relays (1 = on, 0 = off)
int TempOffset = 50; // a value of 50 means that initial offset is zero when first loaded. Tempoffset can be between 0-100
volatile float ActualTempOffset = (TempOffset / 10.0) - 5.0; //Adjust temp offset to a decimal between +/- 5 degrees
MyMessage msgHum(CHILD_ID_HUM, V_HUM);
MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP);
MyMessage msgMot(CHILD_ID_MOT, V_TRIPPED);
MyMessage msgTempOffset(CHILD_ID_TEMP_OFFSET, V_PERCENTAGE);
MyMessage msgMotOnOff(CHILD_ID_MotionOnOff, V_STATUS);
MyMessage msgRelay1(1, V_STATUS);
MyMessage msgRelay2(2, V_STATUS);
MyMessage msgGestureUpDown(CHILD_ID_GESTUREUD, V_STATUS);
MyMessage msgGestureLeftRight(CHILD_ID_GESTURELR, V_STATUS);
MyMessage msgGestureNearFar(CHILD_ID_GESTURENF, V_STATUS);
void before()
{
// Then set relay pins in output mode
pinMode(RELAY_1_PIN , OUTPUT);
pinMode(RELAY_2_PIN , OUTPUT);
pinMode(DIGITAL_INPUT_SENSOR, INPUT); // sets the motion sensor digital pin as input
}
void setup()
{
// Set gesture sensor interrupt pin as input
pinMode(APDS9960_INT, INPUT);
//define sensitivity of gesture sensor
apds.setGestureGain( GestureSensitivity );
dht.setup(HUMIDITY_SENSOR_DIGITAL_PIN);
// metric = getConfig().isMetric;
// Initialize interrupt service routine for gestures
attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
// Initialize APDS-9960 (configure I2C and initial values)
if ( apds.init() ) {
Serial.println(F("APDS-9960 initialization complete"));
} else {
Serial.println(F("Something went wrong during APDS-9960 init!"));
}
// Start running the APDS-9960 gesture sensor engine
if ( apds.enableGestureSensor(true) ) {
Serial.println(F("Gesture sensor is now running"));
} else {
Serial.println(F("Something went wrong during gesture sensor init!"));
}
}
void presentation()
{
// Send the sketch version information to the gateway and Controller
sendSketchInfo("MyMultiSensor", "1.4");
// Register all sensors to gw (they will be created as child devices)
present(CHILD_ID_RELAY1, S_BINARY, "Relay 1");
wait(100);
present(CHILD_ID_RELAY2, S_BINARY, "Relay 2");
wait(100);
present(CHILD_ID_HUM, S_HUM, "Humidity");
wait(100);
present(CHILD_ID_TEMP, S_TEMP, "Temperature");
wait(100);
present(CHILD_ID_MOT, S_MOTION, "PIR");
wait(100);
present(CHILD_ID_TEMP_OFFSET, S_DIMMER, "Temp Calibration");
wait(100);
present(CHILD_ID_MotionOnOff, S_BINARY, "PIR Enable/Disable");
wait(100);
present(CHILD_ID_GESTUREUD, S_BINARY, "Gesture Up/Down");
wait(100);
present(CHILD_ID_GESTURELR, S_BINARY, "Gesture Left/Right");
wait(100);
present(CHILD_ID_GESTURENF, S_BINARY, "Gesture Near/Far");
wait(100);
}
void firstboot()
{
}
void loop()
{
if(FirstBoot)
{
Serial.println("FirstBoot sequence started");
// Set relay to correct state (using eeprom/default/domoticz value)
switch (StoreRelayState) {
case 1:
if (loadState(CHILD_ID_RELAY1) == 1) {
digitalWrite(RELAY_1_PIN , RELAY_ON);
}
else
{
digitalWrite(RELAY_1_PIN , RELAY_OFF);
}
if (loadState(CHILD_ID_RELAY2) == 1) {
digitalWrite(RELAY_2_PIN , RELAY_ON);
}
else
{
digitalWrite(RELAY_2_PIN , RELAY_OFF);
}
Serial.print("Relay 1 loadstate from EEPROM: ");
Serial.println(loadState(CHILD_ID_RELAY1));
Serial.print("Relay 2 loadstate from EEPROM: ");
Serial.println(loadState(CHILD_ID_RELAY2));
//best let domoticz know
send(msgRelay1.set(RelayState));
wait(100);
send(msgRelay2.set(RelayState));
wait(100);
Serial.print("Default Relay state sent to Domoticz: ");
Serial.println(RelayState);
break;
case 2:
//get Relay Statuses from Domoticz
Serial.println("Relay status requested from Domoticz");
request(CHILD_ID_RELAY1, V_STATUS);
wait(1000);
request(CHILD_ID_RELAY2, V_STATUS);
wait(1000);
break;
default:
digitalWrite(RELAY_1_PIN , RelayState ? RELAY_ON : RELAY_OFF);
digitalWrite(RELAY_2_PIN , RelayState ? RELAY_ON : RELAY_OFF);
Serial.print("Relays set to default boot state: ");
Serial.println(RelayState);
//
send(msgRelay1.set(RelayState));
wait(100);
send(msgRelay2.set(RelayState));
wait(100);
Serial.print("Default Relay state sent to Domoticz: ");
Serial.println(RelayState);
break;
}
// Set tempoffset to correct state (using eeprom/default/domoticz value) - needs to be 0 - 100 otherwise set to no offset
switch (StoreTempOffset) {
case 1:
if (loadState(CHILD_ID_TEMP_OFFSET) >100) {
ActualTempOffset = 5.0;
}
else
{
TempOffset = loadState(CHILD_ID_TEMP_OFFSET);
ActualTempOffset = (TempOffset / 10.0) - 5.0;
}
Serial.print("Temp Offset retrieved from eeprom: ");
Serial.println(ActualTempOffset);
//let Domoticz know the value
send(msgTempOffset.set(TempOffset, 1));
wait(100);
Serial.print("tempOffset from sensor sent to Domoticz: ");
Serial.println(TempOffset);
break;
case 2:
//get TempOffSet from Domoticz
Serial.println("TempOffset value requested from Domoticz");
request(CHILD_ID_TEMP_OFFSET, V_PERCENTAGE);
wait(1000);
break;
default:
Serial.print("Default boot temperature offset is: ");
Serial.println(ActualTempOffset);
//let Domoticz know the value
send(msgTempOffset.set(TempOffset, 1));
wait(100);
Serial.print("tempOffset from sensor sent to Domoticz: ");
Serial.println(TempOffset);
break;
}
// Set PIR enable/disable (using eeprom/default/domoticz value)
switch(StoreMotionOnOff) {
case 1:
if (loadState(CHILD_ID_MotionOnOff) == 1 || loadState(CHILD_ID_MotionOnOff) == 0) { //check for odd values in EEPROM
MotionON = loadState(CHILD_ID_MotionOnOff);
}
else {
Serial.print("Error in PIR Enable/Disable Value in EEPROM");
Serial.println("Default value used instead");
}
//Let Domoticz know
send(msgMotOnOff.set(MotionON, 1));
wait(100);
Serial.print("PIR ENABLE status from sensor sent to Domoticz: ");
Serial.println(MotionON);
break;
case 2:
//get PIR enable status from Domoticz
Serial.println("MotionOnOff status requested from Domoticz");
request(CHILD_ID_MotionOnOff, V_STATUS);
wait(1000);
break;
default:
send(msgMotOnOff.set(MotionON, 1));
wait(100);
Serial.print("Default PIR ENABLE status used and sent to Domoticz: ");
Serial.println(MotionON);
break;
}
wait(2000);
Serial.println("First boot checks completed");
FirstBoot = false;
}
//check if gesture sensor interrupt is triggered
if( isr_flag == 1 ) {
detachInterrupt(digitalPinToInterrupt(APDS9960_INT));
handleGesture();
isr_flag = 0;
attachInterrupt(digitalPinToInterrupt(APDS9960_INT), interruptRoutine, FALLING);
}
// Read digital motion value
if (MotionON) {
bool tripped = digitalRead(DIGITAL_INPUT_SENSOR) == HIGH;
if (lastTripped != tripped) {
Serial.print("New Motion State: ");
Serial.println(tripped);
// Send tripped value to gw
send(msgMot.set(tripped ? "1" : "0"));
lastTripped = tripped;
}
}
boolean needRefresh = (millis() - lastRefreshTime) > SLEEP_TIME;
if (needRefresh)
{
Serial.println("I'm alive");
lastRefreshTime = millis();
float temperature = dht.getTemperature() + ActualTempOffset;
if (isnan(temperature)) {
Serial.println("Failed reading temperature from DHT");
}
else if (temperature != lastTemp ) {
lastTemp = temperature;
if (!metric) {
temperature = dht.toFahrenheit(temperature);
}
send(msgTemp.set(temperature, 1));
Serial.print("T: ");
Serial.println(temperature);
}
else if (millis() - lastForceUpdateTime > ForceUpdate) {
lastTemp = temperature;
lastForceUpdateTime = millis();
if (!metric) {
temperature = dht.toFahrenheit(temperature);
}
send(msgTemp.set(temperature, 1));
Serial.print("T: ");
Serial.println(temperature);
}
float humidity = dht.getHumidity();
if (isnan(humidity)) {
Serial.println("Failed reading humidity from DHT");
}
else if (humidity != lastHum) {
lastHum = humidity;
send(msgHum.set(humidity, 1));
Serial.print("H: ");
Serial.println(humidity);
}
else if (millis() - lastForceUpdateTime > ForceUpdate) {
lastHum = humidity;
lastForceUpdateTime = millis();
send(msgHum.set(humidity, 1));
Serial.print("H: ");
Serial.println(humidity);
}
}
}
void interruptRoutine() {
//Serial.println("Interrupt Routine started");
isr_flag = 1;
}
void handleGesture() {
if ( apds.isGestureAvailable() ) {
switch ( apds.readGesture() ) {
case DIR_UP:
Serial.println("UP");
send(msgGestureUpDown.set(GestureUp));
break;
case DIR_DOWN:
Serial.println("DOWN");
send(msgGestureUpDown.set(GestureDown));
break;
case DIR_LEFT:
Serial.println("LEFT");
send(msgGestureLeftRight.set(GestureLeft));
break;
case DIR_RIGHT:
Serial.println("RIGHT");
send(msgGestureLeftRight.set(GestureRight));
break;
case DIR_NEAR:
Serial.println("NEAR");
send(msgGestureNearFar.set(GestureNear));
break;
case DIR_FAR:
Serial.println("FAR");
send(msgGestureNearFar.set(GestureFar));
break;
default:
Serial.println("NONE");
}
}
}
void receive(const MyMessage &message)
{
//For debugging just checking why controller sent a msg e.g. was it in response to a request by node
switch (message.getCommand()) { // message.getCommand will give us the command type of the incomming message
case C_SET:
Serial.println("msg sent by controller");
break;
case C_REQ:
Serial.println("msg state sent by controller in response to request");
break;
default:
Serial.println("msg isn't C_SET or C_REQ so what you gonna do?");
break;
}
// We only expect V_STATUS (for relays) or V_Percentage (for temp offset) message from controller. But we better check anyway.
if (message.type == V_STATUS) {
// Change relay state
if (message.sensor == 1 ) {
digitalWrite(RELAY_1_PIN , message.getBool() ? RELAY_ON : RELAY_OFF);
// Store state in eeprom
saveState(message.sensor, message.getBool());
// Write some debug info
Serial.print("Incoming Relay 1 State: ");
Serial.println(message.getBool());
}
else if (message.sensor == 2) {
digitalWrite(RELAY_2_PIN , message.getBool() ? RELAY_ON : RELAY_OFF);
// Store state in eeprom
saveState(message.sensor, message.getBool());
// Write some debug info
Serial.print("Incoming Relay 2 State: ");
Serial.println(message.getBool());
}
else if (message.sensor == CHILD_ID_MotionOnOff) {
if (message.getBool() == 1) {
MotionON = 1;
saveState(CHILD_ID_MotionOnOff, MotionON);
}
else
{
MotionON = 0;
}
Serial.print("Motion Sensor on/off state: ");
Serial.println(MotionON);
saveState(CHILD_ID_MotionOnOff, MotionON);
}
}
else if (message.type == V_PERCENTAGE) {
int TempOffset = atoi(message.data);
ActualTempOffset = (TempOffset / 10.0) - 5.0;
Serial.print("Temp Offset value: ");
Serial.println(ActualTempOffset);
saveState(CHILD_ID_TEMP_OFFSET, TempOffset);
}
}