For those eager to built something more I am posting a new version, with included sensors for temperature and Himidity DHT22 (Pin D4), MQ135 (Pin AI 3), KY-026 IR Flame Sensor (Pin AI 1) it could also be a light sensor LM393, Led (Pin D3), I used LM2596 for 5V which I have taken from 12V power strip. 3.3 V I have taken from arduino UNO. I got some problems with arduino nano, when dealing with bigger powers for led like 50W so that is why I used arduino Uno, it works fine. The problems was that it freezes (I think it was because the quality of power for 3.3V). In the lower powers like 10W there was no problems for arduino nano.
/****************************************************************
Title: MySensors enabled, gesture controlled lamp.
V1.1 March 2016 by Theo
This lamp can be turned on/off, and dimmed by gestures. And it can be controlled by any Home Automation
system, that can talk with MySensors.
Supported gesture:
1. Left to right stroke: turns lamp on
2. Right to left stroke: turns lamp off
3. Down to up stroke: increases brightness (When brightness is already at max, the lamp will blink - duration and blink count can be changed in the sketch)
4. Up to down stroke: decreases brgihtness (When brightness level is at 0 the lamp is off)
The gesture sensor ues in this Sketch is an APDS-9960 RGB and Gesture Sensor, sold by SparkFun. They can be found on eBay and Aliexpress as well.
See Sparkfuns hookup guide for pin lay-out ( https://learn.sparkfun.com/tutorials/apds-9960-rgb-and-gesture-sensor-hookup-guide )
IMPORTANT: The APDS-9960 can only accept 3.3V! Use bi direction level converter when using another Arduino than a Pro Mini 3.3V
This Sketch is based upon the GestureTest exampl developped by,
Shawn Hymel @ SparkFun Electronics on May 30, 2014. See https://github.com/sparkfun/APDS-9960_RGB_and_Gesture_Sensor
The lamp itself is a white LED strip, controlled by an N channel MOSFET.
Fore more details on the used hardware and libraries (see https://www.openhardware.io/view/50/Gesture-controlled-MySensors-Floor-lamp )
Developed and test with MySensors release 1.5.4
Revision history:
3-07-2017 Changed for tests on Mysensors 2.0
- The change was not made by the original author of the program
20-03-2016 Version 1.1:
- set the brightness level to defined BRIGHTNESS_INCREMENT, when lamp is being turned on be a gesture and the current brightness level is 0.
This was a little bug in previous version. It sometimes seemed that the APDS didn't work, but in some cases I had to increase brightness after
I turned the lamp on.
- cleaned up the code a bit
19-03-2016 Version 1.0
****************************************************************/
// Import libraries used by the Sketch.
// added some of my radio definitions (must be before #includes)
#define MY_DEBUG
#define MY_RADIO_NRF24
#define MY_RF24_PA_LEVEL RF24_PA_MAX
#define MY_NODE_ID 10
// Enable repeater functionality for this node
#define MY_REPEATER_FEATURE
#include <SPI.h>
#include <MySensors.h>
#include <Wire.h>
#include <SparkFun_APDS9960.h>
#include <DHT.h>
// Constants declaration(s)
#define APDS9960_INT 2 // Needs to be an interrupt pin. We should be able to use the Arduino's pin 3 as well.
#define LED_PIN 3 // The led PWM pin, that drives the LED strip. This is the pin thats attached to the mosfet.
#define MAXDIMLEVELS 100 // The maximum number of dim levels. Domoticz supports 0-100 (we'll transalate thim in an array)
#define BRIGHTNESS_INCREMENT 15 // Dimmer increment for gesture(s). Play with this value yourself. The amount of gesture controlled
// dim levels is MAXDIMLEVELS / BRIGHTNESS_INCREMENT having 8 to 5 levels feels more natural to me.
#define MAX_LEVEL_REACHED_DELAY 150 // Short blinking delay when increasing the dimmer while the dimmer is already at max
// Had some troubles in the past with tha acurateness of the gw.wait on pro mini's. But that'll probably
// not be noticeable with this short delay. Delay is in milliseconds
#define MAX_LEVEL_REACHED_SIGNAL_COUNT 2 // The amount of blinks, when the max dim level already has been reached. It's just a way to let the user know
// that the lamp can not be more brighter than the current brightness
#define CHILD_ID_LIGHT 1 // The child id of the Node. Only one Node on this sensor though. See MySensors documentation
#define LIGHT_OFF 0 // Constant indicating the lamp on state
#define LIGHT_ON 1 // Constant indicationg light off state
//#define SN "Gesture controlled lamp" // Description of this sketch.
//#define SV "1.1" // The version of the Sketch
#define DHT_DATA_PIN 4
//#define CHILD_ID_AIQ 0
#define AIQ_SENSOR_ANALOG_PIN 3 //bylo 6
// Set this offset if the sensor has a permanent small offset to the real temperatures
#define SENSOR_TEMP_OFFSET 0
#define MQ135_DEFAULTPPM 399 //default ppm of CO2 for calibration
//#define MQ135_DEFAULTRO 68550 //default Ro for MQ135_DEFAULTPPM ppm of CO2
#define MQ135_DEFAULTRO 229550 //default Ro for MQ135_DEFAULTPPM ppm of CO2
#define MQ135_SCALINGFACTOR 116.6020682 //CO2 gas value
#define MQ135_EXPONENT -2.769034857 //CO2 gas value
#define MQ135_MAXRSRO 2.428 //for CO2
#define MQ135_MINRSRO 0.358 //for CO2
#define CHILD_ID_HUM 0
#define CHILD_ID_TEMP 1
#define CHILD_ID_LIGHTpoz 2
#define LIGHT_SENSOR_ANALOG_PIN 0 //bylo 7
//VARIABLES
double mq135_ro = 10000; // this has to be tuned 10K Ohm
double val = 0; // variable to store the value coming from the sensor
double valAIQ =0;
double lastAIQ =0;
// Sleep time between sensor updates (in milliseconds)
// Must be >1000ms for DHT22 and >2000ms for DHT11
static const uint64_t UPDATE_INTERVAL = 30000;
// Force sending an update of the temperature after n sensor reads, so a controller showing the
// timestamp of the last update doesn't show something like 3 hours in the unlikely case, that
// the value didn't change since;
// i.e. the sensor would force sending an update every UPDATE_INTERVAL*FORCE_UPDATE_N_READS [ms]
static const uint8_t FORCE_UPDATE_N_READS = 10;
unsigned long lastRefreshTime = 0; // Use this to implement a non-blocking delay function
float lastTemp;
float lastHum;
float lastLightLevel;
uint8_t nNoUpdatesTemp;
uint8_t nNoUpdatesHum;
uint8_t nNoUpdatesLight;
float lastLightLevelpoz;
uint8_t nNoUpdatesLightpoz;
bool metric = true;
uint8_t nNoUpdatesvalr;
// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960(); // Initialize a SparkFun_APDS9960 object. This does all the magic incl. i2c communication with the sensor.
int isr_flag = 0; // interrupt flag, triggered when a gesture has been dectected. Used to detect gesture detection in the interrupt handler
// the actual handling is done in the main loop. This allows is the keep the interrupt handler is lightweight is possible
// which is a good practice. Otherwise you'll get some behaviour you don't expect.
int LastLightState=LIGHT_OFF; // The current lamp state. Wel'll turn it off for first time useage. The Sketch will query your Home Automation System for
// the last value when it's being reboot.
int LastDimValue=MAXDIMLEVELS; // last known dim value (Only initial value. Value is retrieved from your HA system)
/**
* Values could be calculated. But the sketch is small and storing in an Array is just faster. Otherwise we have to do calculations that incl Floats.
* Floats take up a lot of your arduino memory
*/
int dimlevels[ MAXDIMLEVELS ] = // PWM values used for translating home automation dimmer levels. This gives smoother transations
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 35, 39, 42, 46, 49, 52, 56,
59, 62, 66, 69, 73, 76, 79, 83, 86, 89,
93, 96, 100, 103, 106, 110, 113, 116, 120, 123,
126, 130, 133, 137, 140, 144, 147, 150, 154, 157,
160, 164, 167, 171, 174, 177, 181, 184, 187, 191,
194, 197, 201, 204, 208, 211, 215, 218, 221, 225,
228, 231, 235, 238, 242, 245, 246, 250, 251, 255 };
/**
* Variables needed for setting up and communication with MySensors. These variables will handle all of the MySensors magic for you.
*/
//MySensor gw;
MyMessage lightMsg(CHILD_ID_LIGHT, V_LIGHT);
MyMessage dimmerMsg(CHILD_ID_LIGHT, V_DIMMER);
MyMessage msg(AIQ_SENSOR_ANALOG_PIN, V_LEVEL);
MyMessage msgHum(CHILD_ID_HUM, V_HUM);
MyMessage msgTemp(CHILD_ID_TEMP, V_TEMP );
MyMessage msgLight(CHILD_ID_LIGHTpoz, V_LIGHT_LEVEL);
DHT dht;
/**
* Initializing code. Basicly we'll setup MySensors communication and test if we can communicate with the gesture sensor. See comment in setup method for more details
*/
// interrupt handler. Is being triggered by the gesture sensor whenever a gesture has been detected. We've setup this up in the setup.
void interruptRoutine() {
isr_flag = 1;
}
void presentation()
{
// Send the sketch version information to the gateway
sendSketchInfo("Led Kuchnia Lampa", "2.0");
// Register all sensors to gw (they will be created as child devices)
present(CHILD_ID_HUM, S_HUM);
present(CHILD_ID_TEMP, S_TEMP);
present(AIQ_SENSOR_ANALOG_PIN, S_AIR_QUALITY);
present(CHILD_ID_LIGHTpoz, S_LIGHT_LEVEL);
present(CHILD_ID_LIGHT, S_DIMMER);
metric = getControllerConfig().isMetric;
}
void setup() {
// Initialize NFR24L01+ radio for enabling MySensors );
//gw.begin(incomingMessage, AUTO, false);
// Send the Sketch Version Information to the Gateway. Domoticz seems to ignore this. Don't know about the other HA systems.
// gw.sendSketchInfo(SN, SV);
///sendSketchInfo("Led Kuchnia Lampa", "2.0");
// Present the dimmable lamp is a child node to the HA system. We'll enable ACK communication.
// gw.present(CHILD_ID_LIGHT, S_DIMMER, "floor lamp", true );
// present(CHILD_ID_LIGHT, S_DIMMER);
// declare output pin for PWM control of the MOSFET
dht.setup(DHT_DATA_PIN); // set data pin of DHT sensor
if (UPDATE_INTERVAL <= dht.getMinimumSamplingPeriod()) {
Serial.println("Warning: UPDATE_INTERVAL is smaller than supported by the sensor!");
}
pinMode( LED_PIN, OUTPUT );
// APDS Initialization code
pinMode(APDS9960_INT, INPUT); // Set interrupt pin as input. @@Note: this should be handled my the library. But it isn't
attachInterrupt(0, interruptRoutine, FALLING); // Initialize interrupt service routine. Basicly it'll call our interrupt routine
if ( apds.init() ) { // Initialize APDS-9960 (configure I2C and initial values)
// @@NOTE: original value is two. But it looks like the modern gesture sensor or more sensitive. 1 does it for me
apds.setGestureGain( 1 );
}
if ( apds.enableGestureSensor(true) ) { // Start running the APDS-9960 gesture sensor engine. Sensor support more functions than gesture detection only.
analogWrite( LED_PIN, 0 ); // Turn the lamp off.
//gw.request( CHILD_ID_LIGHT, V_PERCENTAGE ); // Request current dimvalue from HA. The current value is being handled in the incomming method.
request( CHILD_ID_LIGHT, V_PERCENTAGE ); // Request current dimvalue from HA. The current value is being handled in the incomming method.
//gw.request( CHILD_ID_LIGHT, V_STATUS ); // Request current lamp state from HA. The current value is being handled in the incomming method.
request( CHILD_ID_LIGHT, V_STATUS ); // Request current lamp state from HA. The current value is being handled in the incomming method.
}
}
/*
* get the calibrated ro based upon read resistance, and a know ppm
*/
long mq135_getro(long resvalue, double ppm) {
return (long)(resvalue * exp( log(MQ135_SCALINGFACTOR/ppm) / MQ135_EXPONENT ));
}
/*
* get the ppm concentration
*/
double mq135_getppm(long resvalue, long ro) {
double ret = 0;
double validinterval = 0;
validinterval = resvalue/(double)ro;
if(validinterval<MQ135_MAXRSRO && validinterval>MQ135_MINRSRO) {
ret = (double)((double)MQ135_SCALINGFACTOR * pow( ((double)resvalue/ro), MQ135_EXPONENT));
}
return ret;
}
// }
// Main loop. Does twoe things. Handle gestures if detected by the interrupt routine. And give the MySensors lib the oppurtunity to handle incomming messages.
void loop()
{
Serial.print ( "test 1" );
boolean needRefresh = (millis() - lastRefreshTime) > UPDATE_INTERVAL;
Serial.print ( "test 2 ");
if (needRefresh)
{
lastRefreshTime = millis();
Serial.print ( "test 3 ");
// Force reading sensor, so it works also after sleep()
dht.readSensor(true);
// Get temperature from DHT library
float temperature = dht.getTemperature();
if (isnan(temperature)) {
Serial.println("Failed reading temperature from DHT!");
} else if (temperature != lastTemp || nNoUpdatesTemp == FORCE_UPDATE_N_READS) {
// Only send temperature if it changed since the last measurement or if we didn't send an update for n times
lastTemp = temperature;
if (!metric) {
temperature = dht.toFahrenheit(temperature);
}
// Reset no updates counter
nNoUpdatesTemp = 0;
temperature += SENSOR_TEMP_OFFSET;
send(msgTemp.set(temperature, 1));
#ifdef MY_DEBUG
Serial.print("T: ");
Serial.println(temperature);
#endif
} else {
// Increase no update counter if the temperature stayed the same
nNoUpdatesTemp++;
Serial.print ( "test 4 ");
}
// Get humidity from DHT library
float humidity = dht.getHumidity();
if (isnan(humidity)) {
Serial.println("Failed reading humidity from DHT");
} else if (humidity != lastHum || nNoUpdatesHum == FORCE_UPDATE_N_READS) {
// Only send humidity if it changed since the last measurement or if we didn't send an update for n times
lastHum = humidity;
// Reset no updates counter
nNoUpdatesHum = 0;
send(msgHum.set(humidity, 1));
#ifdef MY_DEBUG
Serial.print("H: ");
Serial.println(humidity);
#endif
} else {
// Increase no update counter if the humidity stayed the same
nNoUpdatesHum++;
Serial.print ( "test 5 ");
}
// Tu wrzucam cala reszte ktora potrzebuje sleep
//TU Wrzucilem czujnik MQ135
Serial.print ( "test 6 ");
double valr = analogRead(AIQ_SENSOR_ANALOG_PIN);// Get AIQ value
Serial.print ( "test 7 odczytano sensor MQ135 ");
Serial.println(val);
if (isnan(valr)) {
Serial.println("Failed reading MQ135");
double val = ((float)22000*(1023-valr)/valr);
//during clean air calibration, read the Ro value and replace MQ135_DEFAULTRO value with it, you can even deactivate following function call.
mq135_ro = mq135_getro(val, MQ135_DEFAULTPPM);
//convert to ppm (using default ro)
valAIQ = mq135_getppm(val, MQ135_DEFAULTRO);
Serial.print ( "Val / Ro / value:");
Serial.print ( val);
Serial.print ( " / ");
Serial.print ( mq135_ro);
Serial.print ( " / ");
Serial.print ( valAIQ);
} else if (valAIQ != lastAIQ || nNoUpdatesvalr == FORCE_UPDATE_N_READS) {
Serial.print (" test myk zaczal ");
////tu zaczalem myk
double valr = analogRead(AIQ_SENSOR_ANALOG_PIN);// Get AIQ value
Serial.println(val);
Serial.print (" mysk w trakcie ");
double val = ((float)22000*(1023-valr)/valr);
//during clean air calibration, read the Ro value and replace MQ135_DEFAULTRO value with it, you can even deactivate following function call.
mq135_ro = mq135_getro(val, MQ135_DEFAULTPPM);
//convert to ppm (using default ro)
valAIQ = mq135_getppm(val, MQ135_DEFAULTRO);
Serial.print ( "Val / Ro / value:");
Serial.print ( val);
Serial.print ( " / ");
Serial.print ( mq135_ro);
Serial.print ( " / ");
Serial.print ( valAIQ);
// tu skonczylem myk
send(msg.set(MQ135_DEFAULTPPM+(int)ceil(valAIQ)));
lastAIQ = ceil(valAIQ);
nNoUpdatesvalr = 0;
Serial.print (" test 7 ");
} else {
// Increase no update counter if the lightlevel stayed the same
nNoUpdatesvalr++;
}
///// czujnik pozarowy
int16_t lightLevel = (1023-analogRead(LIGHT_SENSOR_ANALOG_PIN))/10.23;
if (isnan(lightLevel)) {
Serial.println(lightLevel);
Serial.println("Failed reading Light");
} else if (lightLevel != lastLightLevelpoz || nNoUpdatesLightpoz == FORCE_UPDATE_N_READS) {
lastLightLevelpoz = lightLevel;
nNoUpdatesLightpoz = 0;
send(msgLight.set(lightLevel, 1));
#ifdef MY_DEBUG
Serial.print("Lux: ");
Serial.println(lightLevel);
#endif
} else {
// Increase no update counter if the lightlevel stayed the same
nNoUpdatesLightpoz++;
}
}
// Sleep for a while to save energy
// sleep(UPDATE_INTERVAL);
// Tu wrzucam cala reszte ktora nie potrzebuje sleep
if( isr_flag == 1 ) {
detachInterrupt(0);
handleGesture();
isr_flag = 0;
attachInterrupt(0, interruptRoutine, FALLING);
}
}
/***************************** MQGetPercentage **********************************
Input: rs_ro_ratio - Rs divided by Ro
pcurve - pointer to the curve of the target gas
Output: ppm of the target gas
Remarks: By using the slope and a point of the line. The x(logarithmic value of ppm)
of the line could be derived if y(rs_ro_ratio) is provided. As it is a
logarithmic coordinate, power of 10 is used to convert the result to non-logarithmic
value.
************************************************************************************/
int MQGetPercentage(float rs_ro_ratio, float ro, float *pcurve)
{
return (double)(pcurve[0] * pow(((double)rs_ro_ratio/ro), pcurve[1]));
}
//here originaly was
// interrupt handler. Is being triggered by the gesture sensor whenever a gesture has been detected. We've setup this up in the setup.
//void interruptRoutine() {
// isr_flag = 1;
//}
// Determine gesture and handle accordingly.We'll ignore FAR and NEAR gestures. Do didn't feel natural to me anyway.
void handleGesture() {
if ( apds.isGestureAvailable() ) { // Check if there's a gesture available. Which should be, because the interrupt handler told us so.
switch ( apds.readGesture() ) { // Get the gesture type.
case DIR_UP: // Handle up stroke (Brightness increasing)
if ( LastDimValue + BRIGHTNESS_INCREMENT > ( MAXDIMLEVELS - 1 ) ) {
for ( int i = 0; i < MAX_LEVEL_REACHED_SIGNAL_COUNT; i++ ) {
analogWrite( LED_PIN, dimlevels[ 0 ] );
//gw.wait( MAX_LEVEL_REACHED_DELAY );
wait( MAX_LEVEL_REACHED_DELAY );
analogWrite( LED_PIN, dimlevels[ MAXDIMLEVELS - 1 ] );
//gw.wait(MAX_LEVEL_REACHED_DELAY );
wait(MAX_LEVEL_REACHED_DELAY );
}
LastDimValue = ( MAXDIMLEVELS - 1 );
}
else {
LastDimValue += BRIGHTNESS_INCREMENT;
}
LastLightState = LIGHT_ON;
SetCurrentState2Hardware();
break;
case DIR_DOWN: // Handle down stroke (Brightness decreasing)
if ( LastDimValue - BRIGHTNESS_INCREMENT <= 0 ) {
LastDimValue = 0;
}
else {
LastDimValue -= BRIGHTNESS_INCREMENT;
}
if ( LastDimValue == 0 ) {
LastLightState = LIGHT_OFF;
}
else {
LastLightState = LIGHT_ON;
}
SetCurrentState2Hardware();
break;
case DIR_LEFT: // Handle left stroke. Turn lamp off
LastLightState = LIGHT_OFF;
SetCurrentState2Hardware();
break;
case DIR_RIGHT: // Handle right stroke. Turn lamp on
LastLightState = LIGHT_ON;
if ( LastDimValue == 0 ) { // @@Version 1.1 addition Slight modification. When the dimValue was 0, the light stayed off when turned on.
LastDimValue = BRIGHTNESS_INCREMENT; // Just used the first Gesture dimming stage (2 times would be better for my taste though. Might change that in the future)
}
SetCurrentState2Hardware();
break;
}
}
}
/**
* Handler for message send by the MySensor gateway.
*/
void receive(const MyMessage &message) {
if ( message.type == V_LIGHT ) { // Gateway reports a new Light state
int lstate= atoi( message.data );
if ( ( lstate < 0 ) || ( lstate > 1 ) ) {
return;
}
LastLightState=lstate;
if ( ( LastLightState == LIGHT_ON ) &&( LastDimValue == 0 ) ) {
//In the case that the Light State = On, but the dimmer value is zero, then something (probably the controller) did something wrong,
//for the Dim value to 100%
LastDimValue = 100;
}
//When receiving a V_LIGHT command we switch the light between OFF and the last received dimmer value
//This means if you previously set the lights dimmer value to 50%, and turn the light ON
//it will do so at 50%
}
else if (message.type == V_DIMMER) { // Gateway reports new dimmer level. We'l adjust it to the new value
int dimvalue= atoi( message.data );
if ( ( dimvalue < 0 ) || ( dimvalue > 100 ) ) {
return;
}
if ( dimvalue == 0 ) {
LastLightState = LIGHT_OFF;
}
else {
LastLightState = LIGHT_ON;
LastDimValue = dimvalue;
}
}
//Here we'll set the actual light state/level
SetCurrentState2Hardware();
}
// Send current values to the PWM controlled MOSFET
void SetCurrentState2Hardware() {
if (LastLightState==LIGHT_OFF) {
analogWrite( LED_PIN, dimlevels[0] );
}
else {
analogWrite( LED_PIN, dimlevels[ LastDimValue - 1 ] );
}
//Send current state to the controller
SendCurrentState2Controller();
}
// Report new values to the Gateway.
void SendCurrentState2Controller()
{
if ((LastLightState==LIGHT_OFF)||(LastDimValue==0)) {
//gw.send(dimmerMsg.set(0));
send(dimmerMsg.set(0));
}
else {
//gw.send(dimmerMsg.set(LastDimValue));
send(dimmerMsg.set(LastDimValue));
}
}