├── Alarm_clock └── Alarm_clock.ino ├── Anemone └── Anemone.ino ├── Candle_cleaner └── Candle_cleaner.ino ├── Candle_maker_tool └── Candle_maker_tool.ino ├── Candle_receiver └── Candle_receiver.ino ├── Carbon_sensor └── Carbon_sensor.ino ├── Dust_sensor └── Dust_sensor.ino ├── Energy_use_meter └── Energy_use_meter.ino ├── Example └── Example.ino ├── LICENSE ├── Plant_health └── Plant_health.ino ├── README.md ├── Signal-hub └── Signal-hub.ino ├── Smart_lock ├── Smart_lock.ino ├── SoftwareSerial.cpp └── SoftwareSerial.h ├── Temperature └── Temperature.ino └── Temperature_and_more └── Temperature_and_more.ino /Anemone/Anemone.ino: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * DESCRIPTION 5 | * 6 | * The Anemone can disconnect your home from the internet when an internet connect is not required. For example, when you are sleeping, away or on holiday. 7 | * 8 | * If your home is not physically connected to the internet, it becomes impossible for non-targetted hacking attempts to affect you. 9 | * 10 | * SETTINGS */ 11 | 12 | #define CHECK_CONNECTION // Fail safe. If the Anemone loses its connection to the Candle controller, should it re-enable the internet connection? 13 | 14 | //#define MY_REPEATER_FEATURE // Act as signal repeater. Should this device act as a repeater for your other Candle devices? This can help the signal spread further. 15 | 16 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 17 | 18 | 19 | /* END OF SETTINGS 20 | * 21 | * 22 | * TODO 23 | * - Use desiredState and only toggle in one position, once per second. 24 | * - Check if the last toggle came from the controller or from the button, and depending on that let it override to either position or not. So the button can turn it on and off during a non-safe situation, and then the security value will respect that. 25 | * 26 | * OTHER ADVANCED SETTINGS 27 | */ 28 | 29 | 30 | #define JESSE // If this is Jesse's Candle prototype 31 | 32 | //#define DEBUG // Show general debugging data from the device 33 | //#define MY_DEBUG // Debug the MySensors network activity 34 | 35 | 36 | // MySensors radio type 37 | #define MY_RADIO_NRF24 // A 2.4Ghz transmitter and receiver, often used with MySensors. The RF-Nano has this built in. 38 | //#define MY_RADIO_RFM69 // 433Mhz transmitter and reveiver. 39 | //#define MY_RADIO_NRF5_ESB // NRF5 devices 40 | //#define MY_RADIO_RFM95 41 | 42 | // MySensors: Choose your desired radio power level. High and max power can cause issues on cheap Chinese NRF24 radios. 43 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 44 | #define MY_RF24_PA_LEVEL RF24_PA_LOW 45 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 46 | //#define MY_RF24_PA_LEVEL RF24_PA_MAX 47 | 48 | // Mysensors advanced settings 49 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 50 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 51 | #define MY_RF24_DATARATE RF24_1MBPS // The RF-Nano requires a 1Mbps datarate. 52 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 53 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 54 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 55 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 56 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 57 | 58 | // MySensors security 59 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" 60 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" 61 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup noise makes signing more secure. 62 | 63 | 64 | // PINS 65 | #define MAIN_PUSH_BUTTON_PIN 4 66 | #define RELAY_PIN 5 // The pin where the relay is connected 67 | #define STATUS_LED 6 // The pin where an optional status LED is connected. It will light up if the connection is allowed. 68 | 69 | #ifdef JESSE 70 | int top_switch_pin = 2; 71 | int bottom_switch_pin = 3; 72 | int motor_forward_pin = 7; 73 | int motor_backward_pin = 8; 74 | #endif 75 | 76 | #ifdef RF_NANO 77 | // If you are using an RF-Nano, you have to switch CE and CS pins. 78 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 79 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 80 | #endif 81 | 82 | 83 | 84 | 85 | // STATES 86 | #define NORMAL 0 87 | #define LOST_CONNECTION 11 88 | 89 | byte state = NORMAL; 90 | 91 | 92 | // VARIABLES 93 | #define RADIO_DELAY 150 94 | #define MINILOOP_DURATION 1000 // Every few seconds we do the main loop. Don't make this bigger than 7900 (almost 8 seconds), since it resets the watchdog. 95 | #define SAFETY_CHECK_ONCE_EVERY_X_LOOPS 60 // Once a minute (12 x 5 seconds = 60 seconds) we do a safety check. 96 | #define MAXIMUM_CONNECTION_LOSS_COUNT 5 // If fail-safe is active, and no connection to the controller could be made for 5 minutes, then reconnect the network. 97 | 98 | unsigned long currentMillis = 0; // The millisecond clock in the main loop. 99 | unsigned long lastMiniLoop = 0; // Used to remember the time of the last temperature measurement. 100 | 101 | byte connection_loss_counter = 0; 102 | byte mini_loop_counter = 0; 103 | boolean relay_state = true; 104 | boolean desired_relay_state = true; 105 | boolean send_all_values = true; 106 | boolean is_there_danger = false; 107 | boolean button_pressed = false; 108 | 109 | // MySensors child ID's 110 | #define ACTIVATED_CHILD_ID 1 // Is the thermostat on or off? 111 | #define STATUS_CHILD_ID 2 // for MySensors. Within this node each sensortype should have its own ID number. 112 | 113 | 114 | //RELAY VARIABLES 115 | #define RELAY_ON 1 // GPIO value to write to turn on attached relay 116 | #define RELAY_OFF 0 // GPIO value to write to turn off attached relay 117 | 118 | 119 | // LIBRARIES 120 | #include 121 | 122 | 123 | // Mysensors settings 124 | MyMessage txtmsg(STATUS_CHILD_ID, V_TEXT); // Sets up the message format that we'll be sending to the MySensors gateway later. The first part is the ID of the specific sensor module on this node. The second part tells the gateway what kind of data to expect. 125 | MyMessage relaymsg(ACTIVATED_CHILD_ID, V_STATUS); 126 | 127 | 128 | // Variable for Jesse's candle prototype 129 | #ifdef JESSE 130 | int motorSpeed = 100; 131 | #endif 132 | 133 | 134 | void before() 135 | { 136 | 137 | // BUTTON 138 | pinMode(MAIN_PUSH_BUTTON_PIN, INPUT_PULLUP); // Set another digital pin as power input 139 | 140 | // LED 141 | pinMode(STATUS_LED, OUTPUT); // Set the LED pin as output. 142 | 143 | // RELAY 144 | pinMode(RELAY_PIN, OUTPUT); 145 | digitalWrite(RELAY_PIN, RELAY_OFF); // Start with the relay off 146 | 147 | if( loadState(ACTIVATED_CHILD_ID) != 255 ){ 148 | relay_state = (boolean)loadState(ACTIVATED_CHILD_ID); 149 | Serial.print(F("Loaded relay_state from eeprom: ")); Serial.println(relay_state); 150 | } 151 | } 152 | 153 | 154 | void presentation() 155 | { 156 | sendSketchInfo(F("Anemone"), F("1.0")); // Send the sketch version information to the gateway and Controller 157 | present(ACTIVATED_CHILD_ID, S_BINARY, F("Internet connection")); 158 | present(STATUS_CHILD_ID, S_INFO, F("Status")); 159 | 160 | send_all_values = true; 161 | } 162 | 163 | 164 | void send_values() 165 | { 166 | send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(relay_state)); wait(RADIO_DELAY); 167 | send(txtmsg.setSensor(STATUS_CHILD_ID).set(F("Hello"))); wait(RADIO_DELAY); 168 | } 169 | 170 | 171 | void setup() 172 | { 173 | Serial.begin(115200); // For serial debugging. 174 | wait(100); 175 | Serial.println(F("Hello world")); 176 | 177 | if( isTransportReady() ){ 178 | Serial.println(F("Connected to gateway!")); 179 | } 180 | else { 181 | Serial.println(F("! NO CONNECTION")); 182 | } 183 | 184 | #ifdef JESSE 185 | pinMode(motor_forward_pin, OUTPUT); 186 | pinMode(motor_backward_pin, OUTPUT); 187 | pinMode(top_switch_pin, INPUT_PULLUP); 188 | pinMode(bottom_switch_pin, INPUT_PULLUP); 189 | #endif 190 | 191 | wdt_enable(WDTO_8S); // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart. 192 | } 193 | 194 | 195 | 196 | void loop() 197 | { 198 | // Send everything to the controller. This will initialise things there. 199 | if( send_all_values ){ 200 | //Serial.println(F("Sending all values")); 201 | send_all_values = false; 202 | send_values(); 203 | } 204 | 205 | currentMillis = millis(); // The time since the sensor started, counted in milliseconds. This script tries to avoid using the Sleep function, so that it could at the same time be a MySensors repeater. 206 | 207 | 208 | wait(20); 209 | 210 | // ON DEVICE TOGGLE BUTTON 211 | if( digitalRead(MAIN_PUSH_BUTTON_PIN) == LOW ){ 212 | button_pressed = true; 213 | #ifdef DEBUG 214 | Serial.print(F("button pushed")); 215 | #endif 216 | } 217 | 218 | // Every few seconds... 219 | if( currentMillis - lastMiniLoop >= MINILOOP_DURATION ){ 220 | lastMiniLoop = currentMillis; 221 | wdt_reset(); 222 | 223 | if( button_pressed == true ){ 224 | button_pressed = false; 225 | desired_relay_state = !relay_state; // Switch the device to the other position. 226 | Serial.print(F("-Button was pressed.")); 227 | send(txtmsg.setSensor(STATUS_CHILD_ID).set(F("BUTTON PRESSED"))); 228 | } 229 | 230 | if( desired_relay_state != relay_state ){ 231 | relay_state = desired_relay_state; 232 | saveState(ACTIVATED_CHILD_ID, relay_state); 233 | send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(relay_state)); // We acknowledge to the controller that we are now in the new state. 234 | Serial.print(F("-New relay state: ")); Serial.println(relay_state); 235 | 236 | #ifdef JESSE 237 | if( relay_state = true ){ 238 | switch_on(); 239 | } 240 | else { 241 | switch_off(); 242 | } 243 | } 244 | #endif 245 | 246 | mini_loop_counter++; 247 | #ifdef DEBUG 248 | Serial.println(mini_loop_counter); 249 | #endif 250 | 251 | // Do a safety check - are we still connected to the controller? 252 | if( mini_loop_counter >= SAFETY_CHECK_ONCE_EVERY_X_LOOPS ){ 253 | #ifdef DEBUG 254 | Serial.println(F("Checking connection")); 255 | #endif 256 | mini_loop_counter = 0; 257 | 258 | #ifdef CHECK_CONNECTION 259 | connection_loss_counter++; 260 | #endif 261 | send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(relay_state),1); wait(RADIO_DELAY); 262 | 263 | is_there_danger = safety_check(); 264 | } 265 | 266 | 267 | 268 | // If everything is ok, the relay may be toggled. Otherwise return to the fail-safe position. 269 | if( is_there_danger == false ){ 270 | // Set the device LED to show the current state of the internet connection. 271 | digitalWrite(STATUS_LED, relay_state); 272 | 273 | digitalWrite(RELAY_PIN, relay_state?RELAY_ON:RELAY_OFF); 274 | } 275 | else{ 276 | digitalWrite(STATUS_LED, LOW); // Go to fail-safe position. 277 | digitalWrite(STATUS_LED, RELAY_OFF); // Go to fail-safe position. 278 | } 279 | 280 | } 281 | } 282 | 283 | 284 | 285 | boolean safety_check() 286 | { 287 | 288 | // CHECKING - Is everything ok? 289 | 290 | // Is the controller up? 291 | if(connection_loss_counter > MAXIMUM_CONNECTION_LOSS_COUNT){ 292 | Serial.println(F("LOST CONNECTION TO SERVER")); 293 | if(state != LOST_CONNECTION){ 294 | send(txtmsg.setSensor(STATUS_CHILD_ID).set(F("CONNECTION LOST"))); 295 | } 296 | state = LOST_CONNECTION; 297 | return true; // Danger spotted! 298 | } 299 | 300 | return false; // No issue spotted. 301 | } 302 | 303 | 304 | void receive(const MyMessage &message) 305 | { 306 | Serial.println(F(">> receiving message")); 307 | 308 | connection_loss_counter = 0; 309 | 310 | if (message.isAck()) { 311 | Serial.println(F("-Got ACK")); 312 | return; 313 | } 314 | 315 | if (message.type == V_STATUS && message.sensor == ACTIVATED_CHILD_ID ){ 316 | desired_relay_state = message.getBool(); //?RELAY_ON:RELAY_OFF; 317 | //send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(relay_state)); 318 | Serial.print(F("-New desired relay state: ")); Serial.println(desired_relay_state); 319 | } 320 | } 321 | 322 | 323 | #ifdef JESSE 324 | void switch_on() { 325 | analogWrite(motor_forward_pin, motorSpeed); 326 | digitalWrite(motor_backward_pin, LOW); 327 | while (digitalRead(top_switch_pin) == 1); 328 | stop(); 329 | } 330 | 331 | void switch_off() { 332 | digitalWrite(motor_forward_pin, LOW); 333 | analogWrite(motor_backward_pin, motorSpeed); 334 | while (digitalRead(bottom_switch_pin) == 1); 335 | stop(); 336 | } 337 | 338 | void stop() { 339 | digitalWrite(motor_forward_pin, LOW); 340 | digitalWrite(motor_backward_pin, LOW); 341 | } 342 | #endif 343 | 344 | 345 | 346 | /** 347 | * 348 | * The MySensors Arduino library handles the wireless radio link and protocol 349 | * between your home built sensors/actuators and HA controller of choice. 350 | * The sensors forms a self healing radio network with optional repeaters. Each 351 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 352 | * network topology allowing messages to be routed to nodes. 353 | * 354 | * Created by Henrik Ekblad 355 | * Copyright (C) 2013-2015 Sensnology AB 356 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 357 | * 358 | * Documentation: http://www.mysensors.org 359 | * Support Forum: http://forum.mysensors.org 360 | * 361 | * This program is free software; you can redistribute it and/or 362 | * modify it under the terms of the GNU General Public License 363 | * version 2 as published by the Free Software Foundation. 364 | * 365 | */ 366 | -------------------------------------------------------------------------------- /Candle_cleaner/Candle_cleaner.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Candle cleaner 3 | * 4 | * This script is required by the Candle Manager add-on for the Mozilla WebThings Gateway, as it is used during the 'test' phase. 5 | * 6 | * It functions as a 'factory reset. It clears the memory (eeprom) of the Arduino that you upload this code to. Afterwards it simply blinks its LED. 7 | * 8 | * Wiping the memory removes old security/encryption settings, as well as any old network ID that the device may have had. 9 | * 10 | * SETTINGS */ 11 | 12 | int blink_seconds = 2; // Blink duration. How many seconds should a LED blink take? 13 | 14 | /* END OF SETTINGS 15 | * 16 | * 17 | */ 18 | 19 | #include // Import the required library to work with the EEPROM (storage memory) of the Arduino 20 | 21 | 22 | // The setup function runs once when you press reset or power the board 23 | void setup() 24 | { 25 | Serial.begin(115200); // Allows for serial debugging over USB. 26 | Serial.println("Hello"); 27 | 28 | Serial.println("Starting memory wipe."); 29 | for (int i = 0 ; i < EEPROM.length() ; i++) { 30 | EEPROM.write(i, 255); 31 | } 32 | Serial.println("My memory (eeprom) has been wiped."); 33 | 34 | if(blink_seconds <= 0){ // Blink time should not be set to 0 (or less). 35 | blink_seconds = 1; 36 | } 37 | pinMode(LED_BUILTIN, OUTPUT); // Set the LED pin to output mode. 38 | } 39 | 40 | 41 | //The main loop function runs over and over again, forever. 42 | void loop() { 43 | 44 | digitalWrite(LED_BUILTIN, HIGH); // Turn the LED on (HIGH is the voltage level). 45 | delay( blink_seconds*1000 ); // Wait before we continue. 46 | 47 | digitalWrite(LED_BUILTIN, LOW); // Turn the LED off by making the voltage LOW. 48 | delay( blink_seconds*1000 ); // Wait before we continue. 49 | 50 | Serial.println(F("I just blinked my LED")); 51 | } 52 | -------------------------------------------------------------------------------- /Candle_maker_tool/Candle_maker_tool.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The Candle Maker Tool allows you to connect a wide range of electronic parts to your smart home. It tries to be as universal as possible, and makes it easy to connect: 4 | * 5 | * Input: 6 | * - Two binary inputs. For example: a simple on/off switch. 7 | * - Two analog inputs. For example: rotating dials or sensors that have an analog output. 8 | * 9 | * Output: 10 | * - Two servos. 11 | * - Two on/off parts, such as a relay or an LED. 12 | * - Two analog outputs. 13 | * - An OLED display. Use it to display any value you like. 14 | * 15 | * The fun starts when you incorporate these parts with your home automations. For example: 16 | * - Let a servo rotate depending on the temperature in your home. 17 | * - Turn on an LED on your desk when the dishwasher is done. 18 | * - Get the value of any sensor with an analog output. There is a wide range available, including gas sensors, light sensors, bending sensors, noise sensors, and so much more. 19 | * - Show the latest SMS your smart lock received on the OLED screen. 20 | * 21 | * Use your imagination, and have fun! 22 | * 23 | * 24 | * SETTINGS */ 25 | 26 | // You can enable and disable the settings below by adding or removing double slashes ( // ) in front of a line. 27 | 28 | //#define BINARY_SENSOR1_CONNECTED // Binary sensor 1. Did you connect an on/off input on pin 2? For example, this could be a button or a motion sensor. As long as it only output "on" and "off", you can connect it. 29 | //#define BINARY_SENSOR2_CONNECTED // Binary sensor 2. Did you connect an on/off input on pin 3? For example, this could be a button or a motion sensor. As long as it only output "on" and "off", you can connect it. 30 | 31 | //#define ANALOG_SENSOR1_CONNECTED // Analog sensor 1. Did you connect an analog sensor on A0? 32 | //#define ANALOG_SENSOR2_CONNECTED // Analog sensor 2. Did you connect an analog sensor on A1? 33 | 34 | //#define BINARY_ACTUATOR1_CONNECTED // Digital output 1. Did you connect an LED, relay or other binary 'actuator' part to pin 4? 35 | //#define BINARY_ACTUATOR1_SELF_LOCKING // Turn digital output 1 off automatically? If you have connected something on the first digital output, should that part be set back to 'off' after a little while? 36 | 37 | //#define BINARY_ACTUATOR2_CONNECTED // Digital output 2. Did you connect an LED, relay or other binary 'actuator' part to pin 5? 38 | //#define BINARY_ACTUATOR2_SELF_LOCKING // Turn digital output 2 off automatically? Should digital output 2 turn itself off after a little while? 39 | 40 | #define SELF_LOCKING_DELAY 3 // Turn off delay. If you want a digital output to turn itself off again after a short amount of time, how many seconds should pass before this happens? 41 | 42 | //#define PWM_ACTUATOR1_CONNECTED // Servo 1. Did you connect a servo on pin 6? 43 | //#define PWM_ACTUATOR2_CONNECTED // Servo 2. Did you connect a servo on pin 7? 44 | 45 | //#define ANALOG_ACTUATOR1_CONNECTED // Analog output 1. Did you connect an analog actuator on pin A2? 46 | //#define ANALOG_ACTUATOR2_CONNECTED // Analog output 2. Did you connect an analog actuator on pin A3? 47 | 48 | 49 | //#define HAS_DISPLAY // Did you connect an OLED display? If you connect a display you can send text messages to display on it. 50 | //#define TWO_LINES // Two lines on display. Adds an extra output for the display, so you can send two strings to it instead of one. 51 | 52 | //#define MY_REPEATER_FEATURE // Act as signal repeater. Should this sensor act as a repeater for your other devices? This can help the signal spread further. 53 | 54 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 55 | 56 | 57 | /* END OF SETTINGS 58 | * 59 | * 60 | * TODO / IDEAS 61 | * - Store and load strings using eeprom. 62 | * - Add 1 Basic digital PWM output? 63 | * - Add stepper motor? 64 | * - Add rotational encoder? 65 | * 66 | */ 67 | 68 | 69 | //#define DEBUG // General debug option, give extra information via the serial output when enabled. 70 | //#define MY_DEBUG // Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok. 71 | 72 | 73 | // Enable and select the attached radio type 74 | #define MY_RADIO_RF24 // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi. 75 | //#define MY_RADIO_NRF5_ESB // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet. 76 | //#define MY_RADIO_RFM69 // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect. 77 | //#define MY_RADIO_RFM95 // This is a LoRaWan radio, which can have a range of 10km. 78 | 79 | // MySensors: Choose your desired radio power level. High power can cause issues on cheap Chinese NRF24 radio's. 80 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 81 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 82 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 83 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 84 | 85 | // Mysensors advanced security 86 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 87 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 88 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 89 | 90 | // Mysensors advanced settings 91 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 92 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 93 | #define MY_RF24_DATARATE RF24_1MBPS // Slower datarate makes the network more stable? 94 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 95 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 96 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 97 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 98 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 99 | 100 | #define RADIO_DELAY 50 // Gives the radio some space to catch a breath 101 | 102 | 103 | // PINS 104 | #define BINARY_SENSOR1_PIN 2 // A push button or switch, for example. 105 | #define BINARY_SENSOR2_PIN 3 // A switch that can be in an of or off state, for example. 106 | #define BINARY_ACTUATOR1_PIN 4 // A LED or relay, for example. 107 | #define BINARY_ACTUATOR2_PIN 5 108 | #define PWM_ACTUATOR1_PIN 6 // A servo, for example. 109 | #define PWM_ACTUATOR2_PIN 7 110 | #define ANALOG_SENSOR1_PIN A0 111 | #define ANALOG_SENSOR2_PIN A1 112 | #define ANALOG_ACTUATOR1_PIN A2 113 | #define ANALOG_ACTUATOR2_PIN A3 114 | 115 | #ifdef RF_NANO 116 | // If you are using an RF-Nano, you have to switch CE and CS pins. 117 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 118 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 119 | #endif 120 | 121 | 122 | 123 | 124 | 125 | 126 | // REQUIRED LIBRARIES 127 | #include // The MySensors library. Hurray! 128 | #include // The watch dog timer resets the device if it becomes unresponsive. 129 | 130 | #if defined(PWM_ACTUATOR1_CONNECTED) || defined(PWM_ACTUATOR2_CONNECTED) 131 | #include // Used to connect with servos. Make sure you only attach very small servos - an Arduino can't handle big ones. 132 | #endif 133 | 134 | 135 | 136 | // CHILDREN IDs 137 | 138 | #define BINARY_SENSOR1_CHILD_ID 1 139 | #define BINARY_SENSOR2_CHILD_ID 2 140 | #define ANALOG_SENSOR1_CHILD_ID 3 141 | #define ANALOG_SENSOR2_CHILD_ID 4 142 | #define BINARY_ACTUATOR1_CHILD_ID 5 143 | #define BINARY_ACTUATOR2_CHILD_ID 6 144 | #define PWM_ACTUATOR1_CHILD_ID 7 145 | #define PWM_ACTUATOR2_CHILD_ID 8 146 | #define ANALOG_ACTUATOR1_CHILD_ID 9 147 | #define ANALOG_ACTUATOR2_CHILD_ID 10 148 | 149 | #define TEXT_STRING1_CHILD_ID 25 150 | #define TEXT_STRING2_CHILD_ID 26 151 | 152 | // MySensors messages 153 | #ifdef BINARY_SENSOR1_CONNECTED 154 | MyMessage binary_sensor_message(BINARY_SENSOR1_CHILD_ID, V_TRIPPED); 155 | #elif defined(BINARY_SENSOR2_CONNECTED) and !defined BINARY_SENSOR1_CONNECTED 156 | MyMessage binary_sensor_message(BINARY_SENSOR2_CHILD_ID, V_TRIPPED); 157 | #endif 158 | 159 | #ifdef BINARY_ACTUATOR1_CONNECTED 160 | MyMessage binary_actuator_message(BINARY_ACTUATOR1_CHILD_ID, V_STATUS); 161 | #elif defined(BINARY_ACTUATOR2_CONNECTED) and !defined BINARY_ACTUATOR1_CONNECTED 162 | MyMessage binary_actuator_message(BINARY_ACTUATOR2_CHILD_ID, V_STATUS); 163 | #endif 164 | 165 | #ifdef PWM_ACTUATOR1_CONNECTED 166 | MyMessage percentage_message(PWM_ACTUATOR1_CHILD_ID, V_PERCENTAGE); 167 | #elif defined(PWM_ACTUATOR2_CONNECTED) and !defined PWM_ACTUATOR1_CONNECTED 168 | MyMessage percentage_message(PWM_ACTUATOR2_CHILD_ID, V_PERCENTAGE); 169 | #endif 170 | 171 | #ifdef ANALOG_SENSOR1_CONNECTED 172 | MyMessage analog_sensor_message(ANALOG_SENSOR1_CHILD_ID, V_CUSTOM); 173 | #elif defined(ANALOG_SENSOR2_CONNECTED) and !defined ANALOG_SENSOR1_CONNECTED 174 | MyMessage analog_sensor_message(ANALOG_SENSOR2_CHILD_ID, V_CUSTOM); 175 | #endif 176 | 177 | #if defined ANALOG_ACTUATOR1_CONNECTED and !defined PWM_ACTUATOR1_CONNECTED and !defined PWM_ACTUATOR2_CONNECTED 178 | MyMessage percentage_message(ANALOG_ACTUATOR1_CHILD_ID, V_PERCENTAGE); 179 | #elif defined(ANALOG_ACTUATOR2_CONNECTED) and !defined ANALOG_ACTUATOR1_CONNECTED and !defined PWM_ACTUATOR1_CONNECTED and !defined PWM_ACTUATOR2_CONNECTED 180 | MyMessage percentage_message(ANALOG_ACTUATOR2_CHILD_ID, V_PERCENTAGE); 181 | #endif 182 | 183 | #ifdef HAS_DISPLAY 184 | MyMessage text_message(TEXT_STRING1_CHILD_ID, V_TEXT); // Sets up the message format that we'll be sending to the MySensors gateway later. The first part is the ID of the specific sensor module on this node. The second part tells the gateway what kind of data to expect. 185 | #endif 186 | 187 | 188 | 189 | // VARIABLES 190 | boolean desired_binary_actuator1_state = 0; // 0 or 1 191 | boolean desired_binary_actuator2_state = 0; // 0 or 1 192 | boolean binary_actuator1_state = 0; // 0 or 1 193 | boolean binary_actuator2_state = 0; // 0 or 1 194 | int desired_binary_actuator1_delay = SELF_LOCKING_DELAY; 195 | int desired_binary_actuator2_delay = SELF_LOCKING_DELAY; 196 | 197 | int desired_pwm_actuator1_state = 0; // Between 0 and 100 198 | int desired_pwm_actuator2_state = 0; // Between 0 and 100 199 | int pwm_actuator1_state = 0; // Between 0 and 100 200 | int pwm_actuator2_state = 0; // Between 0 and 100 201 | 202 | int desired_analog_actuator1_state = 0; // Between 0 and 100 203 | int desired_analog_actuator2_state = 0; // Between 0 and 100 204 | int analog_actuator1_state = 0; // Between 0 and 100 205 | int analog_actuator2_state = 0; // Between 0 and 100 206 | 207 | boolean new_binary_sensor1_state = 0; // 0 or 1 208 | boolean new_binary_sensor2_state = 0; // 0 or 1 209 | boolean binary_sensor1_state = 0; // 0 or 1 210 | boolean binary_sensor2_state = 0; // 0 or 1 211 | 212 | int new_analog_sensor1_state = 0; // Between 0 and 1000 213 | int new_analog_sensor2_state = 0; // Between 0 and 1000 214 | int analog_sensor1_state = 0; // Between 0 and 1000 215 | int analog_sensor2_state = 0; // Between 0 and 1000 216 | 217 | 218 | // SERVOS 219 | #if defined(PWM_ACTUATOR1_CONNECTED) || defined(PWM_ACTUATOR2_CONNECTED) 220 | Servo servo1; // Create servo object to control a servo 221 | Servo servo2; // Create servo object to control a servo 222 | byte servo1_maximum_degrees = 180; // Servo type. Does your first servo go to 180 or to 270 degrees? 223 | byte servo2_maximum_degrees = 180; // Servo type. Does your second servo go to 180 or to 270 degrees? 224 | int servo_slow_step = 1024; 225 | #endif 226 | 227 | // OLED DISPLAY 228 | 229 | #ifdef HAS_DISPLAY 230 | #define INCLUDE_SCROLLING 0 // Text scrolling for the OLED screen is not necessary 231 | #define TEXT_STRING_LENGTH 26 // How long the strings are that store the text to display on the screen 232 | #define OLED_I2C_ADDRESS 0x3C // A technical requirement. All I2C OLED screens have an 'address' 233 | #include // Simple drivers for the screen. 234 | #include // "SSD1306Ascii" 235 | SSD1306AsciiAvrI2c oled; // Creating the display object 236 | byte screen_vertical_position = 3; // Used to always show both CO and CO2 levels at the top of the screen. 237 | char string1[26] = "HELLO WORLD"; // Initial text to show on display. 238 | #endif 239 | 240 | #if defined HAS_DISPLAY && defined TWO_LINES 241 | char string2[26] = ""; // Initial text line 2 to show on display. 242 | #endif 243 | 244 | 245 | // Timer 246 | #define MAIN_LOOP_DURATION 1000 // Main loop delay in milliseconds. 1000 milliseconds = 1 second. 247 | static unsigned long lastLoopTime = 0; // Holds the last time the main loop ran. 248 | 249 | 250 | // Other 251 | boolean send_all_values = true; // When this is true, all current values will be (re)-sent to the controller. 252 | 253 | 254 | void presentation() 255 | { 256 | sendSketchInfo(F("Candle maker tool"), F("1.0")); // Send the name and version to the controller 257 | #ifdef BINARY_SENSOR1_CONNECTED 258 | present(BINARY_SENSOR1_CHILD_ID, S_MOTION, F("Binary sensor 1")); 259 | #endif 260 | #ifdef BINARY_SENSOR2_CONNECTED 261 | present(BINARY_SENSOR2_CHILD_ID, S_MOTION, F("Binary sensor 2")); 262 | #endif 263 | 264 | #ifdef ANALOG_SENSOR1_CONNECTED 265 | present(ANALOG_SENSOR1_CHILD_ID, S_CUSTOM, F("Analog sensor 1")); 266 | #endif 267 | #ifdef ANALOG_SENSOR2_CONNECTED 268 | present(ANALOG_SENSOR2_CHILD_ID, S_CUSTOM, F("Analog sensor 2")); 269 | #endif 270 | 271 | #ifdef BINARY_ACTUATOR1_CONNECTED 272 | present(BINARY_ACTUATOR1_CHILD_ID, S_BINARY, F("Button 1")); 273 | #endif 274 | #ifdef BINARY_ACTUATOR2_CONNECTED 275 | present(BINARY_ACTUATOR2_CHILD_ID, S_BINARY, F("Button 2")); 276 | #endif 277 | 278 | #ifdef PWM_ACTUATOR1_CONNECTED 279 | present(PWM_ACTUATOR1_CHILD_ID, S_DIMMER, F("Servo 1")); 280 | #endif 281 | #ifdef PWM_ACTUATOR2_CONNECTED 282 | present(PWM_ACTUATOR2_CHILD_ID, S_DIMMER, F("Servo 2")); 283 | #endif 284 | 285 | #ifdef ANALOG_ACTUATOR1_CONNECTED 286 | present(ANALOG_ACTUATOR1_CHILD_ID, S_DIMMER, F("Analog control 1")); 287 | #endif 288 | #ifdef ANALOG_ACTUATOR2_CONNECTED 289 | present(ANALOG_ACTUATOR2_CHILD_ID, S_DIMMER, F("Analog control 2")); 290 | #endif 291 | 292 | #ifdef HAS_DISPLAY 293 | present(TEXT_STRING1_CHILD_ID, S_INFO, F("Text 1")); 294 | #endif 295 | 296 | #if defined HAS_DISPLAY && defined TWO_LINES 297 | present(TEXT_STRING2_CHILD_ID, S_INFO, F("Text 2")); 298 | #endif 299 | 300 | send_all_values = true; 301 | } 302 | 303 | void setup() 304 | { 305 | Serial.begin(115200); 306 | Serial.println(F("Hello")); 307 | 308 | #ifdef RF_NANO 309 | Serial.println(F("I am using RF_NANO settings")); 310 | #endif 311 | 312 | 313 | #ifdef HAS_DISPLAY 314 | // Initiate the display 315 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 316 | oled.setFont(Adafruit5x7); 317 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 318 | oled.setScroll(false); 319 | oled.setCursor(0,0); 320 | oled.set2X(); 321 | oled.print(string1); 322 | #endif 323 | 324 | 325 | if(isTransportReady()){ 326 | Serial.println(F("Connected to gateway")); 327 | }else{ 328 | Serial.println(F("! NOT CONNECTED TO GATEWAY")); 329 | } 330 | 331 | 332 | // Set initial pin states 333 | 334 | #ifdef BINARY_SENSOR1_CONNECTED 335 | pinMode(BINARY_SENSOR1_PIN, INPUT_PULLUP); 336 | Serial.println(F("I have a binary sensor 1")); 337 | #endif 338 | #ifdef BINARY_SENSOR2_CONNECTED 339 | pinMode(BINARY_SENSOR2_PIN, INPUT_PULLUP); 340 | Serial.println(F("I have a binary sensor 2")); 341 | #endif 342 | 343 | #ifdef ANALOG_SENSOR1_CONNECTED 344 | pinMode(ANALOG_SENSOR1_PIN, INPUT_PULLUP); 345 | Serial.println(F("I have an analog sensor 1")); 346 | #endif 347 | #ifdef ANALOG_SENSOR2_CONNECTED 348 | pinMode(ANALOG_SENSOR2_PIN, INPUT_PULLUP); 349 | Serial.println(F("I have an analog sensor 2")); 350 | #endif 351 | 352 | 353 | // Load previous states from EEPROM. 354 | 355 | // Set the initial position of the binary output (relay/LED). 356 | #ifdef BINARY_ACTUATOR1_CONNECTED 357 | if( loadState(BINARY_ACTUATOR1_CHILD_ID) != 255 ){ 358 | boolean original_state = loadState(BINARY_ACTUATOR1_CHILD_ID); 359 | desired_binary_actuator1_state = original_state; 360 | binary_actuator1_state = original_state; 361 | } 362 | pinMode(BINARY_ACTUATOR1_PIN, OUTPUT); 363 | digitalWrite(BINARY_ACTUATOR1_PIN, binary_actuator1_state); 364 | Serial.println(F("I have a binary actuator 1")); 365 | #endif 366 | 367 | #ifdef BINARY_ACTUATOR2_CONNECTED 368 | if( loadState(BINARY_ACTUATOR2_CHILD_ID) != 255 ){ 369 | boolean original_state = loadState(BINARY_ACTUATOR2_CHILD_ID); 370 | desired_binary_actuator2_state = original_state; 371 | binary_actuator2_state = original_state; 372 | } 373 | pinMode(BINARY_ACTUATOR2_PIN, OUTPUT); 374 | digitalWrite(BINARY_ACTUATOR2_PIN, binary_actuator2_state); 375 | Serial.println(F("I have a binary actuator 2")); 376 | #endif 377 | 378 | 379 | // Set the initial position of the PWM output (servo) 380 | 381 | 382 | #ifdef PWM_ACTUATOR1_CONNECTED 383 | if( loadState(PWM_ACTUATOR1_CHILD_ID) < 101 ){ 384 | desired_pwm_actuator1_state = int(loadState(PWM_ACTUATOR1_CHILD_ID)); 385 | pwm_actuator1_state = int(loadState(PWM_ACTUATOR1_CHILD_ID)); 386 | #ifdef DEBUG 387 | Serial.println("loaded pwm_actuator1 state from eeprom"); 388 | #endif 389 | } 390 | pinMode(PWM_ACTUATOR1_PIN, OUTPUT); 391 | servo1.attach(PWM_ACTUATOR1_PIN); 392 | servo1.write(percentage_to_servo(pwm_actuator1_state, servo1_maximum_degrees)); 393 | Serial.println(F("I have a servo 1")); 394 | #endif 395 | 396 | #ifdef PWM_ACTUATOR2_CONNECTED 397 | if( loadState(PWM_ACTUATOR2_CHILD_ID) < 101 ){ 398 | desired_pwm_actuator2_state = int(loadState(PWM_ACTUATOR2_CHILD_ID)); 399 | pwm_actuator2_state = int(loadState(PWM_ACTUATOR2_CHILD_ID)); 400 | #ifdef DEBUG 401 | Serial.println("loaded pwm_actuator2 state from eeprom"); 402 | #endif 403 | } 404 | pinMode(PWM_ACTUATOR2_PIN, OUTPUT); 405 | servo2.attach(PWM_ACTUATOR2_PIN); 406 | servo2.write(percentage_to_servo(pwm_actuator2_state, servo2_maximum_degrees)); 407 | Serial.println(F("I have a servo 2")); 408 | #endif 409 | 410 | 411 | 412 | // Set the initial position of the binary output (relay/LED). 413 | #ifdef ANALOG_ACTUATOR1_CONNECTED 414 | if( loadState(BINARY_ACTUATOR1_CHILD_ID) != 255 ){ 415 | int original_state = loadState(ANALOG_ACTUATOR1_CHILD_ID); 416 | desired_analog_actuator1_state = original_state; 417 | analog_actuator1_state = original_state; 418 | } 419 | pinMode(ANALOG_ACTUATOR1_PIN, OUTPUT); 420 | digitalWrite(ANALOG_ACTUATOR1_PIN, percentage_to_servo(analog_actuator1_state,255)); //map(analogRead(ANALOG_SENSOR2_PIN), 0, 100, 0, 255)); //int(analog_actuator1_state * 2.55)); 421 | Serial.println(F("I have an analog actuator 1")); 422 | #endif 423 | 424 | #ifdef ANALOG_ACTUATOR2_CONNECTED 425 | if( loadState(ANALOG_ACTUATOR2_CHILD_ID) != 255 ){ 426 | int original_state = loadState(ANALOG_ACTUATOR2_CHILD_ID); 427 | desired_analog_actuator2_state = original_state; 428 | analog_actuator2_state = original_state; 429 | } 430 | pinMode(ANALOG_ACTUATOR2_PIN, OUTPUT); 431 | digitalWrite(ANALOG_ACTUATOR2_PIN, percentage_to_servo(analog_actuator2_state,255)); //map(analogRead(ANALOG_SENSOR2_PIN), 0, 100, 0, 255)); //int(analog_actuator2_state * 2.55)); 432 | Serial.println(F("I have an analog actuator 2")); 433 | #endif 434 | 435 | 436 | #ifdef HAS_DISPLAY 437 | Serial.println(F("I have an OLED screen")); 438 | #endif 439 | 440 | #if defined HAS_DISPLAY && defined TWO_LINES 441 | Serial.println(F("My screen has two lines")); 442 | #endif 443 | 444 | wdt_enable(WDTO_8S); // Starts the watchdog timer. If it is not reset once every 2 seconds, then the entire device will automatically restart. 445 | } 446 | 447 | void send_values() 448 | { 449 | 450 | // Binary sensor 451 | #ifdef BINARY_SENSOR1_CONNECTED 452 | send(binary_sensor_message.setSensor(BINARY_SENSOR1_CHILD_ID).set(binary_sensor1_state)); wait(RADIO_DELAY); 453 | #endif 454 | #ifdef BINARY_SENSOR2_CONNECTED 455 | send(binary_sensor_message.setSensor(BINARY_SENSOR2_CHILD_ID).set(binary_sensor2_state)); wait(RADIO_DELAY); 456 | #endif 457 | 458 | // Analog sensor 459 | #ifdef ANALOG_SENSOR1_CONNECTED 460 | send(analog_sensor_message.setSensor(ANALOG_SENSOR1_CHILD_ID).set(analog_sensor1_state)); wait(RADIO_DELAY); 461 | #endif 462 | #ifdef ANALOG_SENSOR2_CONNECTED 463 | send(analog_sensor_message.setSensor(ANALOG_SENSOR2_CHILD_ID).set(analog_sensor2_state)); wait(RADIO_DELAY); 464 | #endif 465 | 466 | // Binary actuator 467 | #ifdef BINARY_ACTUATOR1_CONNECTED 468 | send(binary_actuator_message.setSensor(BINARY_ACTUATOR1_CHILD_ID).set(binary_actuator1_state)); wait(RADIO_DELAY); 469 | #endif 470 | #ifdef BINARY_ACTUATOR2_CONNECTED 471 | send(binary_actuator_message.setSensor(BINARY_ACTUATOR2_CHILD_ID).set(binary_actuator2_state)); wait(RADIO_DELAY); 472 | #endif 473 | 474 | // PWM actuator (servo) 475 | #ifdef PWM_ACTUATOR1_CONNECTED 476 | send(percentage_message.setSensor(PWM_ACTUATOR1_CHILD_ID).set(pwm_actuator1_state)); wait(RADIO_DELAY); 477 | #endif 478 | #ifdef PWM_ACTUATOR2_CONNECTED 479 | send(percentage_message.setSensor(PWM_ACTUATOR2_CHILD_ID).set(pwm_actuator2_state)); wait(RADIO_DELAY); 480 | #endif 481 | 482 | // Analog actuator 483 | #ifdef ANALOG_ACTUATOR1_CONNECTED 484 | send(percentage_message.setSensor(ANALOG_ACTUATOR1_CHILD_ID).set(analog_actuator1_state)); wait(RADIO_DELAY); 485 | #endif 486 | #ifdef ANALOG_ACTUATOR2_CONNECTED 487 | send(percentage_message.setSensor(ANALOG_ACTUATOR2_CHILD_ID).set(analog_actuator2_state)); wait(RADIO_DELAY); 488 | #endif 489 | 490 | 491 | #ifdef HAS_DISPLAY 492 | send(text_message.setSensor(TEXT_STRING1_CHILD_ID).set(F("Change me"))); wait(RADIO_DELAY); 493 | #endif 494 | 495 | #if defined HAS_DISPLAY && defined TWO_LINES 496 | send(text_message.setSensor(TEXT_STRING2_CHILD_ID).set(F("Change me too"))); wait(RADIO_DELAY); 497 | #endif 498 | 499 | } 500 | 501 | 502 | void loop() 503 | { 504 | // Send all the child states to the controller. This will initialise things there. 505 | if( send_all_values ){ 506 | #ifdef DEBUG 507 | Serial.println(F("Sending all values")); 508 | #endif 509 | send_all_values = false; 510 | send_values(); 511 | } 512 | 513 | 514 | // UPDATING EVERYTHING 515 | 516 | #ifdef PWM_ACTUATOR1_CONNECTED 517 | if( pwm_actuator1_state != desired_pwm_actuator1_state ){ 518 | signed int difference = desired_pwm_actuator1_state - pwm_actuator1_state; 519 | pwm_actuator1_state = desired_pwm_actuator1_state; 520 | send(percentage_message.setSensor(PWM_ACTUATOR1_CHILD_ID).set(pwm_actuator1_state)); wait(RADIO_DELAY); 521 | Serial.print(F("percentage to servo: ")); Serial.println(percentage_to_servo(pwm_actuator1_state, servo1_maximum_degrees)); 522 | servo1.write(percentage_to_servo(pwm_actuator1_state, servo1_maximum_degrees)); 523 | } 524 | #endif 525 | #ifdef PWM_ACTUATOR2_CONNECTED 526 | if( pwm_actuator2_state != desired_pwm_actuator2_state ){ 527 | signed int difference = desired_pwm_actuator2_state - pwm_actuator2_state; 528 | pwm_actuator2_state = desired_pwm_actuator2_state; 529 | send(percentage_message.setSensor(PWM_ACTUATOR2_CHILD_ID).set(pwm_actuator2_state)); wait(RADIO_DELAY); 530 | Serial.print(F("percentage to servo: ")); Serial.println(percentage_to_servo(pwm_actuator2_state, servo2_maximum_degrees)); 531 | servo2.write(percentage_to_servo(pwm_actuator2_state, servo2_maximum_degrees)); 532 | } 533 | #endif 534 | 535 | // Update binary actuator 1 if necessary 536 | #ifdef BINARY_ACTUATOR1_CONNECTED 537 | if( desired_binary_actuator1_state != binary_actuator1_state ){ 538 | Serial.println(F("BUTTON 1 ACTIVE")); 539 | binary_actuator1_state = desired_binary_actuator1_state; 540 | digitalWrite(BINARY_ACTUATOR1_PIN, binary_actuator1_state); 541 | desired_binary_actuator1_delay = SELF_LOCKING_DELAY; 542 | send(binary_actuator_message.setSensor(BINARY_SENSOR1_CHILD_ID).set(binary_sensor1_state)); wait(RADIO_DELAY); 543 | } 544 | #endif 545 | #ifdef BINARY_ACTUATOR2_CONNECTED 546 | // Update binary actuator 2 if necessary 547 | if( desired_binary_actuator2_state != binary_actuator2_state ){ 548 | Serial.println(F("BUTTON 2 ACTIVE")); 549 | binary_actuator2_state = desired_binary_actuator2_state; 550 | digitalWrite(BINARY_ACTUATOR2_PIN, binary_actuator2_state); 551 | desired_binary_actuator2_delay = SELF_LOCKING_DELAY; 552 | send(binary_actuator_message.setSensor(BINARY_SENSOR2_CHILD_ID).set(binary_sensor2_state)); wait(RADIO_DELAY); 553 | } 554 | #endif 555 | 556 | 557 | #ifdef BINARY_SENSOR1_CONNECTED 558 | boolean new_binary_sensor1_state = digitalRead(BINARY_SENSOR1_PIN); 559 | if(new_binary_sensor1_state != binary_sensor1_state){ 560 | binary_sensor1_state = new_binary_sensor1_state; 561 | send(binary_sensor_message.setSensor(BINARY_SENSOR1_CHILD_ID).set(binary_sensor1_state)); wait(RADIO_DELAY); 562 | } 563 | #endif 564 | #ifdef BINARY_SENSOR2_CONNECTED 565 | boolean new_binary_sensor2_state = digitalRead(BINARY_SENSOR2_PIN); 566 | if(new_binary_sensor2_state != binary_sensor2_state){ 567 | binary_sensor2_state = new_binary_sensor2_state; 568 | send(binary_sensor_message.setSensor(BINARY_SENSOR2_CHILD_ID).set(binary_sensor2_state)); wait(RADIO_DELAY); 569 | } 570 | #endif 571 | 572 | 573 | wait(20); // Give the system some calm moment before reading the analog value. 574 | 575 | // Analog sensor 1 576 | #ifdef ANALOG_SENSOR1_CONNECTED 577 | new_analog_sensor1_state = map(analogRead(ANALOG_SENSOR1_PIN), 0, 1023, 0, 100); 578 | if( new_analog_sensor1_state != analog_sensor1_state ){ 579 | analog_sensor1_state = new_analog_sensor1_state; 580 | Serial.print(F("Analog sensor 1 changed to: ")); Serial.println(analog_sensor1_state); 581 | send(analog_sensor_message.setSensor(ANALOG_SENSOR1_CHILD_ID).set(analog_sensor1_state)); wait(RADIO_DELAY); 582 | } 583 | #endif 584 | 585 | #ifdef ANALOG_SENSOR2_CONNECTED 586 | // Analog sensor 2 587 | new_analog_sensor2_state = map(analogRead(ANALOG_SENSOR2_PIN), 0, 1023, 0, 100); 588 | if( new_analog_sensor2_state != analog_sensor2_state ){ 589 | analog_sensor2_state = new_analog_sensor2_state; 590 | Serial.println(F("Analog sensor 2 changed to: ")); Serial.println(analog_sensor2_state); 591 | send(analog_sensor_message.setSensor(ANALOG_SENSOR1_CHILD_ID).set(analog_sensor2_state)); wait(RADIO_DELAY); 592 | } 593 | #endif 594 | 595 | // The watchdog makes sure that if the device freezes it will reboot itself. Here we tell it that everything is still running ok. 596 | if( millis() - lastLoopTime > MAIN_LOOP_DURATION ){ 597 | lastLoopTime = millis(); 598 | wdt_reset(); 599 | 600 | #if defined(BINARY_ACTUATOR1_CONNECTED) && defined(BINARY_ACTUATOR1_SELF_LOCKING) 601 | if( desired_binary_actuator1_delay > 0 ){ 602 | if( desired_binary_actuator1_delay == 1 ){ 603 | desired_binary_actuator1_state == 0; 604 | } 605 | desired_binary_actuator1_delay--; 606 | } 607 | #endif 608 | #if defined(BINARY_ACTUATOR2_CONNECTED) && defined(BINARY_ACTUATOR2_SELF_LOCKING) 609 | if( desired_binary_actuator2_delay > 0 ){ 610 | if( desired_binary_actuator2_delay == 1 ){ 611 | desired_binary_actuator2_state == 0; 612 | } 613 | desired_binary_actuator2_delay--; 614 | } 615 | #endif 616 | } 617 | } 618 | 619 | // A small helper function which makes it easier to attach servos with different degrees of range. 620 | int percentage_to_servo(byte percentage, byte maximum){ 621 | return map(percentage, 0, 100, 0, maximum); 622 | } 623 | 624 | 625 | void receive(const MyMessage &message) 626 | { 627 | Serial.println(F(">> receiving message")); 628 | 629 | if (message.isAck()) { 630 | Serial.println(F("-Got ACK")); 631 | return; 632 | } 633 | 634 | 635 | // Binary actuators 636 | #ifdef BINARY_ACTUATOR1_CONNECTED 637 | if (message.type == V_STATUS && message.sensor == BINARY_ACTUATOR1_CHILD_ID ){ 638 | desired_binary_actuator1_state = message.getBool(); //?RELAY_ON:RELAY_OFF; 639 | #if !defined(BINARY_ACTUATOR1_SELF_LOCKING) 640 | saveState(BINARY_ACTUATOR1_CHILD_ID, desired_binary_actuator1_state); 641 | #endif 642 | Serial.print(F("-New desired actuator 1 state: ")); Serial.println(desired_binary_actuator1_state); 643 | } 644 | #endif 645 | #ifdef BINARY_ACTUATOR2_CONNECTED 646 | if (message.type == V_STATUS && message.sensor == BINARY_ACTUATOR2_CHILD_ID ){ 647 | desired_binary_actuator2_state = message.getBool(); //?RELAY_ON:RELAY_OFF; 648 | #if !defined(BINARY_ACTUATOR2_SELF_LOCKING) 649 | saveState(BINARY_ACTUATOR2_CHILD_ID, desired_binary_actuator2_state); 650 | #endif 651 | Serial.print(F("-New desired actuator 2 state: ")); Serial.println(desired_binary_actuator2_state); 652 | } 653 | #endif 654 | 655 | 656 | // Servo motors 657 | #ifdef PWM_ACTUATOR1_CONNECTED 658 | if (message.type == V_DIMMER && message.sensor == PWM_ACTUATOR1_CHILD_ID ){ 659 | desired_pwm_actuator1_state = atoi( message.data ); 660 | desired_pwm_actuator1_state = desired_pwm_actuator1_state > 100 ? 100 : desired_pwm_actuator1_state; // Clip incoming level to valid range of 0 to 100 661 | desired_pwm_actuator1_state = desired_pwm_actuator1_state < 0 ? 0 : desired_pwm_actuator1_state; 662 | saveState(PWM_ACTUATOR1_CHILD_ID, desired_pwm_actuator1_state); 663 | Serial.print(F("-New desired PWM output 1 state: ")); Serial.println(desired_pwm_actuator1_state); 664 | } 665 | #endif 666 | #ifdef PWM_ACTUATOR2_CONNECTED 667 | if (message.type == V_DIMMER && message.sensor == PWM_ACTUATOR2_CHILD_ID ){ 668 | desired_pwm_actuator2_state = atoi( message.data ); 669 | desired_pwm_actuator2_state = desired_pwm_actuator2_state > 100 ? 100 : desired_pwm_actuator2_state; // Clip incoming level to valid range of 0 to 100 670 | desired_pwm_actuator2_state = desired_pwm_actuator2_state < 0 ? 0 : desired_pwm_actuator2_state; 671 | saveState(PWM_ACTUATOR2_CHILD_ID, desired_pwm_actuator2_state); 672 | Serial.print(F("-New desired PWM output 2 state: ")); Serial.println(desired_pwm_actuator2_state); 673 | } 674 | #endif 675 | 676 | 677 | // analog actuators 678 | #ifdef ANALOG_ACTUATOR1_CONNECTED 679 | if (message.type == V_DIMMER && message.sensor == ANALOG_ACTUATOR1_CHILD_ID ){ 680 | desired_analog_actuator1_state = atoi( message.data ); 681 | desired_analog_actuator1_state = desired_analog_actuator1_state > 100 ? 100 : desired_analog_actuator1_state; // Clip incoming level to valid range of 0 to 100 682 | desired_analog_actuator1_state = desired_analog_actuator1_state < 0 ? 0 : desired_analog_actuator1_state; 683 | saveState(ANALOG_ACTUATOR1_CHILD_ID, desired_analog_actuator1_state); 684 | Serial.print(F("-New desired analog output 1 state: ")); Serial.println(desired_analog_actuator1_state); 685 | } 686 | #endif 687 | #ifdef ANALOG_ACTUATOR2_CONNECTED 688 | if (message.type == V_DIMMER && message.sensor == PWM_ACTUATOR2_CHILD_ID ){ 689 | desired_analog_actuator2_state = atoi( message.data ); 690 | desired_analog_actuator2_state = desired_analog_actuator2_state > 100 ? 100 : desired_analog_actuator2_state; // Clip incoming level to valid range of 0 to 100 691 | desired_analog_actuator2_state = desired_analog_actuator2_state < 0 ? 0 : desired_analog_actuator2_state; 692 | saveState(ANALOG_ACTUATOR2_CHILD_ID, desired_analog_actuator2_state); 693 | Serial.print(F("-New desired analog output 2 state: ")); Serial.println(desired_analog_actuator2_state); 694 | } 695 | #endif 696 | 697 | 698 | // Text output to display 699 | #ifdef HAS_DISPLAY 700 | if (message.type == V_TEXT && message.sensor == TEXT_STRING1_CHILD_ID ){ 701 | memset(string1, 0, sizeof(string1)); // Clear the array that holds the string 702 | strcpy(string1, message.getString()); 703 | Serial.print(F("-New string 1: ")); Serial.println(string1); 704 | oled.setCursor(0,0); 705 | oled.print(string1); 706 | oled.clearToEOL(); 707 | } 708 | #endif 709 | #if defined HAS_DISPLAY && defined TWO_LINES 710 | if (message.type == V_TEXT && message.sensor == TEXT_STRING2_CHILD_ID ){ 711 | memset(string2, 0, sizeof(string2)); // Clear the array that holds the string 712 | strcpy(string2, message.getString()); 713 | Serial.print(F("-New string 2: ")); Serial.println(string2); 714 | oled.setCursor(0,2); 715 | oled.print(string2); 716 | oled.clearToEOL(); 717 | } 718 | #endif 719 | } 720 | 721 | 722 | 723 | 724 | /** 725 | * 726 | * The MySensors Arduino library handles the wireless radio link and protocol 727 | * between your home built sensors/actuators and HA controller of choice. 728 | * The sensors forms a self healing radio network with optional repeaters. Each 729 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 730 | * network topology allowing messages to be routed to nodes. 731 | * 732 | * Created by Henrik Ekblad 733 | * Copyright (C) 2013-2015 Sensnology AB 734 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 735 | * 736 | * Documentation: http://www.mysensors.org 737 | * Support Forum: http://forum.mysensors.org 738 | * 739 | * This program is free software; you can redistribute it and/or 740 | * modify it under the terms of the GNU General Public License 741 | * version 2 as published by the Free Software Foundation. 742 | * 743 | */ 744 | -------------------------------------------------------------------------------- /Candle_receiver/Candle_receiver.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The Candle receiver acts as the bridge between the Candle devices and the Candle Controller. 4 | * 5 | * It only allows communication with other Candle devices that use the same encryption password as it uses itself. 6 | * When you install the Candle Manager, a random password is generated for you. If you ever want to change the encryption password used by your network, this can be done in the Candle Manager settings. 7 | * Be warned that you will have to re-create this receiver as well as all your devices, since they will all need to have new code with the new password in it. 8 | * 9 | * If you have already installed the MySensors add-on, please temporarily disable it before creating this receiver. Otherwise the MySensors add-on may try to connect to it during the creation process, and thus disrupt it. 10 | * 11 | * 12 | * SETTINGS */ 13 | 14 | // You can enable and disable the settings below by adding or removing double slashes ( // ) in front of a line. 15 | 16 | #define SHOW_TRANSMISSION_DETAILS // Show transmission details. If you enable this, the receiver will shown as a separate device. It will have details about how many succesful and how many failed transmission are made. These values are updated every 5 minutes. 17 | 18 | #define RF_NANO // RF-Nano. Enable this if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 19 | 20 | 21 | /* END OF SETTINGS 22 | * 23 | * 24 | * 25 | */ 26 | 27 | 28 | // Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok. 29 | //#define MY_DEBUG 30 | 31 | #ifdef RF_NANO 32 | // If you are using an RF-Nano, you have to switch CE and CS pins. 33 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 34 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 35 | #endif 36 | 37 | // Enable and select radio type attached 38 | #define MY_RADIO_RF24 // MySensors supports multiple radio modules. Candle uses NRF24. 39 | //#define MY_RADIO_NRF5_ESB 40 | //#define MY_RADIO_RFM69 41 | //#define MY_RADIO_RFM95 42 | 43 | // Set LOW transmit power level as default, if you have an amplified NRF-module and 44 | // power your radio separately with a good regulator you can turn up PA level. 45 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 46 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 47 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 48 | #define MY_RF24_PA_LEVEL RF24_PA_MAX // Sets the radio to transmit at maximum power, for optimum range. 49 | 50 | // Mysensors advanced security 51 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // The Candle Manager add-on will change this into the actual password your network uses. 52 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 53 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 54 | 55 | // Mysensors advanced settings 56 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. You can even try 115. 57 | //#define MY_RF24_DATARATE RF24_250KBPS // Slower datarate increases the range, but the RF-Nano does not support this slow speed. 58 | #define MY_RF24_DATARATE RF24_1MBPS // This datarate is supported by pretty much all NRF24 radios, including the RF-Nano. 59 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 60 | 61 | // Enable serial gateway 62 | #define MY_GATEWAY_SERIAL // This is the main function of this code. It tells the MySensors library to turn this device into a gateway/receiver for MySensors network. 63 | 64 | 65 | #ifdef SHOW_TRANSMISSION_DETAILS 66 | // Report how often there are wireless communication issues 67 | #define MY_INDICATION_HANDLER 68 | unsigned int txOK = 0; // Good transmissions counter 69 | unsigned int txERR = 0; // Failed transmissions counter 70 | #define REPORT_INTERVAL 300000 // Report every 5 minutes 71 | #define CHILD_ID_TX_OK 1 // Child id for the counter of OK transmissions 72 | #define CHILD_ID_TX_ERR 2 // Child id for the counter of failed transmissions 73 | #endif 74 | 75 | 76 | #include // The MySensors library, which takes care of creating the wireless network. 77 | #include // The watchdog timer - if the device becomes unresponsive and doesn't periodically reset the timer, then it will automatically reset once the timer reaches 0. 78 | 79 | // Clock for the watchdog 80 | #define INTERVAL 500 // Every second we reset the watchdog timer. If the device freezes, the watchdog will not be reset, and the device will reboot. 81 | #define RADIO_DELAY 100 // Milliseconds of delay between sending transmissions, this keeps the radio module happy. 82 | 83 | 84 | unsigned long current_time = 0; 85 | byte loops_passed = 0; // Used to schedule things on a timeline. 86 | 87 | 88 | #ifdef SHOW_TRANSMISSION_DETAILS 89 | MyMessage transmission_quality_message(CHILD_ID_TX_OK, V_CUSTOM); 90 | 91 | void indication(indication_t ind) 92 | { 93 | switch (ind) 94 | { 95 | case INDICATION_TX: 96 | txOK++; 97 | break; 98 | case INDICATION_ERR_TX: 99 | txERR++; 100 | break; 101 | } 102 | } 103 | #endif 104 | 105 | 106 | 107 | void presentation() 108 | { 109 | #ifdef SHOW_TRANSMISSION_DETAILS 110 | sendSketchInfo(F("Candle receiver"), F("1.0")); 111 | present(CHILD_ID_TX_OK, S_CUSTOM, F("Good transmissions")); // Tell the controller about this property 112 | present(CHILD_ID_TX_ERR, S_CUSTOM,F("Bad transmissions")); // Tell the controller about this property 113 | #endif 114 | } 115 | 116 | 117 | 118 | void setup() 119 | { 120 | //Serial.println(F("Hello, I am a Candle receiver")); 121 | #ifdef SHOW_TRANSMISSION_DETAILS 122 | send(transmission_quality_message.setSensor(CHILD_ID_TX_OK).set(txOK)); // Send the good transmissions value 123 | send(transmission_quality_message.setSensor(CHILD_ID_TX_ERR).set(txERR)); // Send the failed transmissions value 124 | #endif 125 | wdt_enable(WDTO_2S); // Starts the watchdog timer. If it is not reset at least once every 2 seconds, then the entire device will automatically restart. 126 | } 127 | 128 | 129 | 130 | void loop() 131 | { 132 | static unsigned long previous_millis = 0; // Used to run the internal clock 133 | current_time = millis(); 134 | 135 | if( current_time - previous_millis >= INTERVAL ){ // Main loop, runs every second. 136 | previous_millis = current_time; // Store the current time as the previous measurement start time. 137 | wdt_reset(); // Reset the watchdog timer 138 | 139 | loops_passed++; 140 | if( loops_passed > 120 ){ // If a minute has passed, send a heartbeat. 141 | sendHeartbeat(); // Tells the controller we're still connected. 142 | loops_passed = 0; 143 | } 144 | } 145 | 146 | #ifdef SHOW_TRANSMISSION_DETAILS 147 | static unsigned long last_send = 0; 148 | if( current_time - last_send >= REPORT_INTERVAL ){ 149 | send(transmission_quality_message.setSensor(CHILD_ID_TX_OK).set(txOK)); // Send the good transmissions value 150 | send(transmission_quality_message.setSensor(CHILD_ID_TX_ERR).set(txERR)); // Send the failed transmissions value 151 | txOK = 0; // Reset the good transmissions counter back to 0 152 | txERR = 0; // Reset the error transmissions counter back to 0 153 | last_send = current_time; // Remember the time when the transmissions was done. 154 | } 155 | #endif 156 | 157 | } 158 | 159 | 160 | /** 161 | * The MySensors Arduino library handles the wireless radio link and protocol 162 | * between your home built sensors/actuators and HA controller of choice. 163 | * The sensors forms a self healing radio network with optional repeaters. Each 164 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 165 | * network topology allowing messages to be routed to nodes. 166 | * 167 | * Created by Henrik Ekblad 168 | * Copyright (C) 2013-2018 Sensnology AB 169 | * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors 170 | * 171 | * Documentation: http://www.mysensors.org 172 | * Support Forum: http://forum.mysensors.org 173 | * 174 | * This program is free software; you can redistribute it and/or 175 | * modify it under the terms of the GNU General Public License 176 | * version 2 as published by the Free Software Foundation. 177 | */ 178 | -------------------------------------------------------------------------------- /Carbon_sensor/Carbon_sensor.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This is a CO2 sensor from the Candle project. 4 | * 5 | * You can attach both a carbon monoxide and a carbon dioxide sensor. 6 | * 7 | * Carbon monoxide is a dangerous, poisonous gas which is completely odourless. It is often formed when something is burning, but doesn't burn with enough oxygen. 8 | * 9 | * Carbon dioxide is what we breathe out. Plants consume carbon dioxide to grow. High levels of carbon dioxide can influence how you feel. 10 | * 11 | * Do not use this device as your sole carbon monoxide sensor! Use it as a support to your main carbon monoxide sensor only. 12 | * 13 | * SETTINGS */ 14 | 15 | 16 | 17 | //#define HAS_CO_SENSOR // Have you attached a CO sensor? 18 | 19 | #define HAS_CO2_SENSOR // Have you attached a CO2 sensor? 20 | 21 | #define MEASUREMENT_INTERVAL 60 // How many seconds do you want between each measurement? The minimum is 10 seconds. 22 | 23 | #define HAS_DISPLAY // Does the sensor have a little OLED display attached? 24 | 25 | #define ALLOW_CONNECTING_TO_NETWORK // Connect wirelessly. Is this device allowed to connect to the local Candle network? For privacy or security reasons you may prefer a stand-alone device. 26 | 27 | #define ALLOW_FAKE_DATA // Allow fake data? This feature is designed to make the sensor less intrusive in some social situations. If enabled, you can toggle this ability by pressing a button that you will need to connect as well. When fake data is being generated, a small F icon is visible on the display. 28 | 29 | //#define MY_REPEATER_FEATURE // Act as a repeater? The devices can pass along messages to each other to increase the range of your network. 30 | 31 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 32 | 33 | /* END OF SETTINGS 34 | * 35 | */ 36 | 37 | //#define DEBUG 38 | //#define MY_DEBUG // MySensors debugging. Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok. 39 | 40 | // Enable and select the attached radio type 41 | #define MY_RADIO_RF24 // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi. 42 | //#define MY_RADIO_NRF5_ESB // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet. 43 | //#define MY_RADIO_RFM69 // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect. 44 | //#define MY_RADIO_RFM95 // This is a LoRaWan radio, which can have a range of 10km. 45 | 46 | // MySensors: Choose your desired radio power level. High power can cause issues on cheap Chinese NRF24 radio's. 47 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 48 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 49 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 50 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 51 | 52 | // Mysensors security 53 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 54 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 55 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 56 | 57 | // Mysensors advanced settings 58 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 59 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 60 | #define MY_RF24_DATARATE RF24_1MBPS // Slower datarate makes the network more stable? 61 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 62 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 63 | 64 | 65 | 66 | // PINS 67 | // Be aware, on the RF-Nano pins 9 through 13 are used by the radio. 68 | #define CO_RX_PIN 3 // The RX (receive) pin for the CO sensor. This should be connected to the TX (transmit) pin on the sensor module. 69 | #define CO_TX_PIN 4 // The TX (transmit) pin for the CO sensor. This should be connected to the RX (receive) pin on the sensor module. 70 | #define CO2_RX_PIN 5 // The RX (receive) pin for the CO2 sensor. This should be connected to the TX (transmit) pin on the sensor module. 71 | #define CO2_TX_PIN 6 // The TX (transmit) pin for the CO2 sensor. This should be connected to the RX (receive) pin on the sensor module. 72 | 73 | 74 | #define HORIZONTAL_START_POSITION 0 // Pixels from the left of the screen 75 | #define DATA_TRANSMISSION_BUTTON_PIN 8 76 | #define TOGGLE_FAKE_DATA_PIN 7 77 | 78 | 79 | #ifdef RF_NANO 80 | // If you are using an RF-Nano, you have to switch CE and CS pins. 81 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 82 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 83 | #endif 84 | 85 | 86 | 87 | 88 | // LIBRARIES (in the Arduino IDE go to Sketch -> Include Library -> Manage Libraries to add these if you don't have them installed yet.) 89 | #include // MySensors library 90 | #include // Serial data connection to the sensor 91 | 92 | 93 | #ifdef HAS_DISPLAY 94 | #define OLED_I2C_ADDRESS 0x3C 95 | #include // Simple drivers for the screen. 96 | #include // "SSD1306Ascii". 97 | SSD1306AsciiAvrI2c oled; 98 | byte screen_vertical_position = 3; // Used to always show both CO and CO2 levels at the top of the screen. 99 | #define F_POSITION 66 // Horizontal position of the "F" icon, indicating it is allowed to generate fake data. 100 | #define T_POSITION 72 // Horizontal position of the "T" icon, indicating it is allowed to transmit data. 101 | #define W_POSITION 80 // Horizontal position of the "W" icon, indicating a wireless connection. 102 | #endif 103 | 104 | 105 | #ifdef HAS_CO_SENSOR 106 | // CO sensor variables 107 | SoftwareSerial co_sensor(CO_RX_PIN, CO_TX_PIN); // Receive (RX) and transmit (TX) pins. RX should always connect to TX on the opposite device. 108 | int COValue = 0; 109 | #endif 110 | 111 | 112 | #ifdef HAS_CO2_SENSOR 113 | // CO2 sensor variables 114 | SoftwareSerial co2_sensor(CO2_RX_PIN, CO2_TX_PIN); // Receive (RX) and transmit (TX) pins. RX should always connect to TX on the opposite device. 115 | int co2_value = 0; 116 | float average_co2_value = 400; 117 | #endif 118 | 119 | 120 | // Mysensors settings 121 | #define CO_CHILD_ID 1 // The child ID of the sensor that will be presented to the controller. 122 | #define CO2_CHILD_ID 2 // The child ID of the sensor that will be presented to the controller. 123 | #define DATA_TRANSMISSION_CHILD_ID 3 // The child ID of the data transmission switch. 124 | #define CO_OPINION_CHILD_ID 4 // The child ID of the human readable opinion about the carbon monoxide level. 125 | #define CO2_OPINION_CHILD_ID 5 // The child ID of the human readable opinion about the carbon dioxide level. 126 | 127 | const byte RADIO_DELAY = 100; // A few milliseconds delay between sending makes the radio happy. 128 | 129 | MyMessage relaymsg(DATA_TRANSMISSION_CHILD_ID, V_STATUS); // To toggle data transmission on or off remotely. 130 | 131 | MyMessage prefix_message(CO_CHILD_ID, V_UNIT_PREFIX); // Tell the controller what to display along with the value. 132 | 133 | 134 | #ifdef HAS_CO_SENSOR 135 | MyMessage CO_message(CO_CHILD_ID, V_LEVEL); // Sets up the message format that we'll be sending to the MySensors gateway later. 136 | #endif 137 | 138 | #ifdef HAS_CO2_SENSOR 139 | MyMessage CO2_message(CO2_CHILD_ID, V_LEVEL); // Sets up the message format that we'll be sending to the controller. 140 | MyMessage info_message(CO2_OPINION_CHILD_ID,V_TEXT); // Sets up the message format that we'll be sending to the controller. The first part is the ID of the specific sensor module on this node. The second part describes what kind of data to expect. 141 | 142 | #endif 143 | 144 | 145 | 146 | #ifdef ALLOW_FAKE_DATA 147 | // Fake data feature 148 | #define AMOUNT_OF_MEASUREMENTS_TO_AVERAGE 5 // How many old measurements to remember. This is used to determine a good fake data range. 149 | boolean sending_fake_data = false; // Experimental. Will allow a user to send fake data for a while. Useful in some social situations. 150 | boolean desired_sending_fake_data = false; // If the user wants to change the state of sending fake data. 151 | float fake_co2_value = 0; // Holds the meandering fake value 152 | float co2_minimum_found = 400; 153 | float co2_maximum_found = 410; 154 | float last_co2_minimum_found = 400; 155 | float last_co2_maximum_found = 410; 156 | 157 | 158 | float co2_fakeness_range = 0; // How far of the last average the value can meander. 159 | float fake_co2_jitter = 0; // Holds how much will actually be deviated from the average when generating a fake value. 160 | float co2_fake_data_movement_factor = -0.5; // The odds that a new fake value will be above or below the current fake value. 161 | float past_measurements[AMOUNT_OF_MEASUREMENTS_TO_AVERAGE]; // An array that holds previous measurements. Used to generate fake data. 162 | byte measurements_fakeness_range_counter = AMOUNT_OF_MEASUREMENTS_TO_AVERAGE; // Current position in the array that holds a few of the last real measurements. 163 | #endif 164 | 165 | // Connection toggle feature 166 | boolean connected_to_network = false; 167 | boolean transmission_state = true; 168 | boolean previous_transmission_state = true; 169 | // Other 170 | #define LOOPDURATION 1000 // The main loop runs every x milliseconds. Normally this sensor has a 'heartbeat' of once every second. 171 | boolean send_all_values = true; // If the controller asks the devive to re-present itself, then this is used to also resend all the current sensor values. 172 | 173 | 174 | 175 | void presentation() 176 | { 177 | #ifdef ALLOW_CONNECTING_TO_NETWORK 178 | // Send the sketch version information to the gateway and Controller 179 | sendSketchInfo(F("Carbon sensor"), F("1.0")); wait(RADIO_DELAY); 180 | 181 | // Register all sensors to gateway: 182 | present(DATA_TRANSMISSION_CHILD_ID, S_BINARY, F("Data transmission")); wait(RADIO_DELAY); 183 | 184 | #ifdef HAS_CO_SENSOR 185 | present(CO_CHILD_ID, S_AIR_QUALITY, F("Carbon monoxide")); wait(RADIO_DELAY); 186 | #endif 187 | 188 | #ifdef HAS_CO2_SENSOR 189 | present(CO2_CHILD_ID, S_AIR_QUALITY, F("Carbon dioxide")); wait(RADIO_DELAY); 190 | #ifdef HAS_CO_SENSOR 191 | present(CO2_OPINION_CHILD_ID, S_INFO, F("Carbon dioxide opinion")); wait(RADIO_DELAY); // The opinion about the CO2 level. If a CO sensor is also present, the name of this property helps distinguish the two opinions. 192 | #else 193 | present(CO2_OPINION_CHILD_ID, S_INFO, F("Opinion")); wait(RADIO_DELAY); // The opinion about the CO2 level. 194 | #endif 195 | #endif 196 | 197 | send_all_values = true; 198 | #endif 199 | } 200 | 201 | 202 | void setup() 203 | { 204 | Serial.begin(115200); 205 | wait(200); 206 | 207 | Serial.println(F("Hello, I am a carbon sensor")); 208 | 209 | transmission_state = loadState(DATA_TRANSMISSION_CHILD_ID); 210 | 211 | pinMode(CO2_RX_PIN, INPUT); 212 | pinMode(CO2_TX_PIN, OUTPUT); 213 | 214 | pinMode(DATA_TRANSMISSION_BUTTON_PIN, INPUT_PULLUP); // Attach a push button to this pin to toggle whether or not the device is allowed to transmit data to the controller. 215 | 216 | 217 | #ifdef ALLOW_FAKE_DATA 218 | pinMode(TOGGLE_FAKE_DATA_PIN, INPUT_PULLUP); 219 | //Serial.print(F("Toggle fake-data-mode using a button on pin ")); Serial.println(TOGGLE_FAKE_DATA_PIN); 220 | #endif 221 | 222 | 223 | #ifdef HAS_DISPLAY 224 | // Initiate the display 225 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 226 | oled.setFont(Adafruit5x7); 227 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 228 | oled.setScroll(false); 229 | oled.setCursor(HORIZONTAL_START_POSITION,0); 230 | oled.print(F("CARBON")); 231 | #endif 232 | 233 | 234 | #ifdef ALLOW_CONNECTING_TO_NETWORK 235 | if( isTransportReady() ){ // Check if a network connection has been established 236 | Serial.println(F("Connected to gateway!")); 237 | connected_to_network = true; 238 | } 239 | else { 240 | Serial.println(F("! NO CONNECTION")); 241 | } 242 | #endif 243 | 244 | 245 | #ifdef HAS_CO_SENSOR 246 | co_sensor.begin(9600); 247 | #ifdef HAS_DISPLAY 248 | oled.setCursor(0,screen_vertical_position - 1); // The labels are shown slightly above the values. 249 | oled.print(F("CO PPM:")); 250 | screen_vertical_position = screen_vertical_position + 3; 251 | #endif 252 | #endif 253 | 254 | 255 | #ifdef HAS_CO2_SENSOR 256 | wait(8000); // Give the sensor some time to boot up 257 | co2_sensor.begin(9600); 258 | //wait(4000); // Give the sensor some time to boot up 259 | #ifdef HAS_DISPLAY 260 | oled.setCursor(0,screen_vertical_position - 1); // The labels are shown slightly above the values. 261 | oled.print(F("CO2 PPM:")); 262 | #endif 263 | #endif 264 | 265 | #if not defined(HAS_CO_SENSOR) && not defined(HAS_CO2_SENSOR) 266 | Serial.println(F("Please enable at least one sensor!")); 267 | #ifdef HAS_DISPLAY 268 | oled.setCursor(0,3); 269 | oled.print(F("NO SENSORS ENABLED")); 270 | #endif 271 | while(1); 272 | #endif 273 | 274 | wdt_enable(WDTO_8S); // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart. 275 | } 276 | 277 | 278 | #ifdef ALLOW_CONNECTING_TO_NETWORK 279 | void send_values() 280 | { 281 | send(relaymsg.setSensor(DATA_TRANSMISSION_CHILD_ID).set(transmission_state)); wait(RADIO_DELAY); 282 | #ifdef HAS_CO_SENSOR 283 | send(prefix_message.setSensor(CO_CHILD_ID).set( F("ppm") )); delay(RADIO_DELAY); // Carbon values are always transmitted. They are not a large privacy risk, while being very important to safety. 284 | if( COValue != 0 ){ 285 | send(CO_message.setSensor(CO_CHILD_ID).set(COValue),1); 286 | } 287 | #endif 288 | if(transmission_state){ 289 | #ifdef HAS_CO2_SENSOR 290 | send(prefix_message.setSensor(CO2_CHILD_ID).set( F("ppm") )); delay(RADIO_DELAY); 291 | if(co2_value > 350){ 292 | send(CO2_message.setSensor(CO2_CHILD_ID).set(co2_value),1); 293 | } 294 | #endif 295 | } 296 | } 297 | #endif 298 | 299 | 300 | void loop() 301 | { 302 | #ifdef ALLOW_CONNECTING_TO_NETWORK 303 | if( send_all_values ){ 304 | #ifdef DEBUG 305 | Serial.println(F("RESENDING VALUES")); 306 | #endif 307 | send_all_values = 0; 308 | send_values(); 309 | } 310 | #endif 311 | 312 | 313 | #ifdef ALLOW_FAKE_DATA 314 | boolean fake_data_pin_state = digitalRead(TOGGLE_FAKE_DATA_PIN); 315 | if( fake_data_pin_state == 0 ){ // If the button is being pressed 316 | desired_sending_fake_data = !desired_sending_fake_data; // Switch the setting to its opposive value. 317 | Serial.print(F("FAKE DATA TOGGLED TO ")); Serial.println(desired_sending_fake_data); 318 | wait(500); // Wait a while to allow the button to be released 319 | } 320 | #endif 321 | 322 | 323 | boolean transmission_button_state = digitalRead(DATA_TRANSMISSION_BUTTON_PIN); 324 | if( transmission_button_state == 0 ){ // If the button is being pressed 325 | transmission_state = !transmission_state; // Switch the setting to its opposive value. 326 | saveState(DATA_TRANSMISSION_CHILD_ID, transmission_state); // Store the new preference, so that is the device is rebooted, it will still be correct. 327 | Serial.println(F("Data transmission button pressed ")); 328 | wait(500); // Wait a while to allow the button to be released 329 | #ifdef ALLOW_CONNECTING_TO_NETWORK 330 | 331 | send(relaymsg.setSensor(DATA_TRANSMISSION_CHILD_ID).set(transmission_state)); 332 | #endif 333 | } 334 | 335 | if ( transmission_state != previous_transmission_state ){ 336 | previous_transmission_state = transmission_state; 337 | saveState(DATA_TRANSMISSION_CHILD_ID, transmission_state); 338 | Serial.print(F("Sending new data transmission state: ")); Serial.println(transmission_state); 339 | send(relaymsg.setSensor(DATA_TRANSMISSION_CHILD_ID).set(transmission_state)); 340 | } 341 | 342 | 343 | // 344 | // HEARTBEAT LOOP 345 | // runs every second (or as long as you want). By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory. 346 | // The maximum time that can be scheduled is 255 * the time that one loop takes. So usually 255 seconds. 347 | // 348 | 349 | static unsigned long lastLoopTime = 0; // Holds the last time the main loop ran. 350 | static int loopCounter = 0; // Count how many loops have passed (reset to 0 after at most 254 loops). 351 | unsigned long currentMillis = millis(); 352 | 353 | if( currentMillis - lastLoopTime > LOOPDURATION ){ 354 | lastLoopTime = currentMillis; 355 | loopCounter++; 356 | #ifdef DEBUG 357 | Serial.print("loopcounter:"); Serial.println(loopCounter); 358 | #endif 359 | if(loopCounter >= MEASUREMENT_INTERVAL){ 360 | Serial.println(); Serial.println(F("__starting__")); 361 | loopCounter = 0; 362 | } 363 | 364 | wdt_reset(); // Reset the watchdog timer 365 | 366 | /* 367 | // Used during development 368 | if(measurements_counter == 10 && desired_sending_fake_data == false){ 369 | Serial.println(); Serial.println(F("INITIATING FAKENESS----------------------------------------")); 370 | desired_sending_fake_data = true; 371 | } 372 | */ 373 | 374 | #ifdef HAS_DISPLAY 375 | // Show second counter 376 | oled.set1X(); 377 | oled.setCursor(100,0); 378 | oled.print(MEASUREMENT_INTERVAL - loopCounter); 379 | oled.clearToEOL(); 380 | 381 | screen_vertical_position = 3; // If there is one sensor attached, then new data should be shown at line 3 of the screen. If there are two, then data is shown on line 3 and line 6. 382 | 383 | #endif 384 | 385 | 386 | // schedule 387 | if( loopCounter == 1 ){ 388 | 389 | // CARBON MONIXODE 390 | #ifdef HAS_CO_SENSOR 391 | COValue = readCOValue(); // Get carbon monoxide level from sensor module 392 | #ifdef HAS_DISPLAY 393 | // Show CO level on the screen 394 | //oled.set1X(); 395 | oled.setCursor(HORIZONTAL_START_POSITION,screen_vertical_position); 396 | 397 | if (COValue == -1){ // -1 value means sensor probably not connected 398 | oled.print(F("CHECK WIRE")); 399 | oled.clearToEOL(); 400 | break; 401 | } 402 | else if (COValue == -2){ 403 | oled.print(F("DATA ERROR")); // -2 value means we got data form the sensor module was was not a CO2 level. For example, a response to some other command. 404 | oled.clearToEOL(); 405 | } 406 | else{ 407 | // Display CO value. 408 | oled.print(COValue); 409 | oled.clearToEOL(); 410 | 411 | // Show quality opinion the screen. 412 | oled.setCursor(60,screen_vertical_position); 413 | if (COValue > 0 && COValue < 450){ oled.print(F("GREAT"));} 414 | else if (COValue < 700){ oled.print(F("GOOD"));oled.clearToEOL();} 415 | else if (COValue < 1000){ oled.print(F("OK")); oled.clearToEOL();} 416 | else if (COValue < 2000){ oled.print(F("POOR"));oled.clearToEOL();} 417 | else if (COValue < 4500){ oled.print(F("BAD")); oled.clearToEOL();} 418 | else { 419 | oled.print(F("Wait..")); 420 | oled.clearToEOL(); 421 | } 422 | } 423 | screen_vertical_position = screen_vertical_position + 3; // If a CO sensor is attached, it's value will be displayed on top. The Co2 value will then be shown 3 lines below it. 424 | #endif 425 | #endif 426 | 427 | // CARBON DIOXIDE 428 | #ifdef HAS_CO2_SENSOR 429 | //int new_co2_value = readco2_value(); // Get carbon dioxide level from sensor module 430 | co2_value = read_co2_value(); // Get carbon dioxide level from sensor module 431 | Serial.print(F("fresh co2 value: ")); Serial.println(co2_value); 432 | #ifdef DEBUG 433 | if( co2_value == -1 || co2_value == -2 ){ 434 | Serial.println(F("SENSOR ERROR -> GENERATING RANDOM DATA")); // Used during development to test even though no actual sensor is attached. 435 | co2_value = random(500,600); 436 | } 437 | #endif 438 | 439 | #ifdef ALLOW_FAKE_DATA 440 | if( co2_value > 350 && sending_fake_data == false){ // While fake data is not being created, we analyse the real data to look for the range it displays. 441 | measurements_fakeness_range_counter++; 442 | Serial.print(F("measurements_fakeness_range_counter = ")); Serial.println(measurements_fakeness_range_counter); 443 | if( measurements_fakeness_range_counter >= AMOUNT_OF_MEASUREMENTS_TO_AVERAGE){ 444 | Serial.print(F("Restarting min-max analysis around co2 value of ")); Serial.println(co2_value); 445 | measurements_fakeness_range_counter = 0; 446 | if( co2_maximum_found - co2_minimum_found != 0 && co2_maximum_found - co2_minimum_found < 30 ){ 447 | co2_fakeness_range = co2_maximum_found - co2_minimum_found; // What is the difference between the highest and lowest co2 value we spotted recently. 448 | last_co2_minimum_found = co2_minimum_found; 449 | last_co2_maximum_found = co2_maximum_found; 450 | } 451 | co2_minimum_found = co2_value; 452 | co2_maximum_found = co2_value; 453 | } 454 | 455 | if(co2_value < co2_minimum_found){ 456 | co2_minimum_found = co2_value; 457 | Serial.println(F("new co2 value was smaller than minimum found.")); 458 | } 459 | else if(co2_value > co2_maximum_found){ 460 | co2_maximum_found = co2_value; 461 | Serial.println(F("new co2 value was bigger than maximum found.")); 462 | } 463 | else{ 464 | Serial.println(F("new co2 values was not bigger or smaller than recent measurements.")); 465 | } 466 | Serial.print(F("potential min-max range: ")); Serial.println(co2_maximum_found - co2_minimum_found); 467 | Serial.print(F("actual min-max range: ")); Serial.println(co2_fakeness_range); 468 | } 469 | 470 | 471 | if( desired_sending_fake_data == true ){ 472 | Serial.println(F("User wants to generate fake data.")); 473 | if( sending_fake_data == false ){ // On the first run of fake data, we try and figure out what the range to fake in is. 474 | Serial.println(F("initiating fake data")); 475 | sending_fake_data = true; 476 | if(co2_fakeness_range == 0){ 477 | co2_fakeness_range = 1; // The minimum to actually make some fake jitter 478 | } 479 | co2_fake_data_movement_factor = -0.5; 480 | fake_co2_value = co2_value; // The initial fake value; 481 | } 482 | } 483 | else{ 484 | // The user no longer wants to generate fake data. 485 | if( sending_fake_data == true ){ // If we were generating fake data, we should slowly move the fake value towards the real value. Only then can we stop generating the fake value. 486 | last_co2_minimum_found = co2_value - co2_fakeness_range; 487 | last_co2_maximum_found = co2_value + co2_fakeness_range; 488 | 489 | if(co2_value > fake_co2_value){ 490 | co2_fake_data_movement_factor = -0.1; // By modifiying this factor to favour one direction, the fake data will move towards the real co2 value. 491 | Serial.println(F("stopping faking, movement factor set to 0,9: ")); 492 | } 493 | else if(co2_value < fake_co2_value){ 494 | Serial.println(F("stopping faking, movement factor set to -0,9: ")); 495 | co2_fake_data_movement_factor = -0.9; 496 | } 497 | if( abs(fake_co2_value - co2_value) < co2_fakeness_range ){ // When the fake value is very close to the real value, the real value can take over again. 498 | Serial.println(F("Faking has ended")); 499 | sending_fake_data = false; 500 | } 501 | } 502 | } 503 | 504 | if( sending_fake_data == true ){ 505 | // We are now sending fake data. 506 | fake_co2_jitter = (float)random( (co2_fakeness_range) * 10000) / 10000; 507 | Serial.print(F("fake CO2 addition: ")); Serial.println(fake_co2_jitter); 508 | 509 | float flipped_coin = random(2); // This will be 0 or 1. 510 | Serial.print(F("flipped coin: ")); Serial.println(flipped_coin); 511 | Serial.print(F("co2_fake_data_movement_factor: ")); Serial.println(co2_fake_data_movement_factor); 512 | float factor = flipped_coin + co2_fake_data_movement_factor; 513 | Serial.print(F("actual movement factor: ")); Serial.println(factor); 514 | fake_co2_jitter = fake_co2_jitter * factor; // The addition is now multiplied by -0,5 or +0,5. 515 | Serial.print(F("fake CO2 jitter after movement factor: ")); Serial.println(fake_co2_jitter); 516 | 517 | Serial.print(F("last_min: ")); Serial.println(last_co2_minimum_found); 518 | Serial.print(F("last_max: ")); Serial.println(last_co2_maximum_found); 519 | if( fake_co2_jitter > 0 && fake_co2_value + fake_co2_jitter > last_co2_maximum_found){ // If the new addition (which can be negative) moves the fake data value ourside of the allowed range, then adjust it. 520 | fake_co2_jitter = -fake_co2_jitter; 521 | Serial.println("A"); 522 | } 523 | else if( fake_co2_jitter < 0 && fake_co2_value + fake_co2_jitter < last_co2_minimum_found){ // If the new addition (which can be negative) moves the fake data value ourside of the allowed range, then adjust it. 524 | fake_co2_jitter = -fake_co2_jitter; 525 | Serial.println("B"); 526 | } 527 | else{ 528 | Serial.println("CC"); 529 | } 530 | Serial.print(F("fake CO2 addition after bounds check: ")); Serial.println(fake_co2_jitter); 531 | 532 | fake_co2_value = fake_co2_value + fake_co2_jitter; 533 | co2_value = int(fake_co2_value); 534 | /* 535 | // Create meandering data effect 536 | if( flipped_coin == 0 && fake_co2_value + fake_co2_jitter > average_co2_value + co2_fakeness_range ){ // Check if there is head room to make the fake data value change in the random direction. 537 | co2_value = fake_co2_value - fake_co2_jitter; // There is no room to go up, the fake value should go down. 538 | } 539 | else if( flipped_coin == 1 && fake_co2_value - fake_co2_jitter < average_co2_value - co2_fakeness_range ){ 540 | co2_value = fake_co2_value + fake_co2_jitter; // There is no room to go down, the fake value should go up. 541 | } 542 | else{ 543 | if( flipped_coin ){ fake_co2_jitter = -fake_co2_jitter; } // If we have not reached the maximum high or low fake data value, then randomly add or subtract the addition. 544 | fake_co2_value = fake_co2_value + fake_co2_jitter; 545 | } 546 | */ 547 | Serial.print(F(" {}{}{} Fake CO2 value: ")); Serial.println(co2_value); 548 | 549 | } 550 | #endif // End of allow fake data 551 | 552 | 553 | 554 | #ifdef HAS_DISPLAY 555 | // Show CO2 level on the screen 556 | oled.set2X(); 557 | oled.setCursor(HORIZONTAL_START_POSITION,screen_vertical_position); 558 | 559 | if (co2_value == -1){ // -1 value means sensor probably not connected 560 | oled.print(F("CHECK WIRE")); 561 | oled.clearToEOL(); 562 | } 563 | else if (co2_value == -2){ 564 | oled.print(F("DATA ERROR")); // -2 value means we got data form the sensor module was was not a CO2 level. For example, a response to some other command. 565 | oled.clearToEOL(); 566 | } 567 | else if( co2_value > 350 && co2_value < 5001){ 568 | // Display CO2 value. 569 | oled.print(co2_value); 570 | oled.clearToEOL(); 571 | 572 | // Show quality opinion the screen. 573 | oled.setCursor(60,screen_vertical_position); 574 | 575 | if (co2_value < 500){ oled.print(F("GREAT"));} 576 | else if (co2_value < 700){ oled.print(F("GOOD"));} 577 | else if (co2_value < 1000){ oled.print(F("OK"));} 578 | else if (co2_value < 2000){ oled.print(F("POOR"));} 579 | else if (co2_value < 5001){ oled.print(F("BAD"));} 580 | else { 581 | oled.print(F("Wait..")); 582 | } 583 | oled.clearToEOL(); 584 | } 585 | #ifdef DEBUG 586 | else{ 587 | Serial.println(F("CO2 value was out of bounds")); 588 | } 589 | #endif 590 | 591 | 592 | #endif // End of has_display 593 | #endif // End of hasCO2senor 594 | } 595 | 596 | 597 | else if( loopCounter == 2 ){ // Send the data 598 | 599 | #ifdef HAS_CO_SENSOR 600 | if( COValue > 0 && COValue < 4500 ){ // Avoid sending erroneous values 601 | #ifdef ALLOW_CONNECTING_TO_NETWORK 602 | connected_to_network = false; 603 | Serial.println(F("Sending CO to controller")); 604 | send(CO_message.setSensor(CO_CHILD_ID).set(COValue),1); // We ask the controller to acknowledge that it has received the data. 605 | #endif // end of allow connecting to network 606 | } 607 | #endif // end of has CO sensor 608 | 609 | 610 | #ifdef HAS_CO2_SENSOR 611 | if( co2_value > 300 && co2_value < 4500 ){ // Avoid sending erroneous values 612 | #ifdef ALLOW_CONNECTING_TO_NETWORK 613 | if( transmission_state ){ 614 | connected_to_network = false; // If the network connection is ok, then this will be immediately set back to true. 615 | Serial.print(F("Sending CO2 value: ")); Serial.println(co2_value); 616 | connected_to_network = false; // If the network connection is ok, then this will be immediately set back to true: 617 | send(CO2_message.setSensor(CO2_CHILD_ID).set(co2_value),1); // We send the data, and ask the controller to acknowledge that it has received the data. 618 | wait(RADIO_DELAY); 619 | 620 | // Also send the human readable opinion 621 | if (co2_value < 450){ send(info_message.setSensor(CO2_OPINION_CHILD_ID).set( F("Great") )); } 622 | else if (co2_value < 700){ send(info_message.setSensor(CO2_OPINION_CHILD_ID).set( F("Good") )); } 623 | else if (co2_value < 1000){ send(info_message.setSensor(CO2_OPINION_CHILD_ID).set( F("OK") )); } 624 | else if (co2_value < 2000){ send(info_message.setSensor(CO2_OPINION_CHILD_ID).set( F("Poor") )); } 625 | else if (co2_value < 5000){ send(info_message.setSensor(CO2_OPINION_CHILD_ID).set( F("Bad") )); } 626 | 627 | } 628 | else{ 629 | Serial.println(F("Not allowed to send the CO2 data")); 630 | } 631 | #endif // end of allow connecting to network 632 | } 633 | #endif // end of has CO2 sensor 634 | } 635 | 636 | 637 | #ifdef HAS_DISPLAY 638 | else if( loopCounter == 3 ){ // Show the various states on the display 639 | oled.set1X(); 640 | oled.setCursor(W_POSITION,0); 641 | if( connected_to_network ){ // Add W icon to the top right corner of the screen, indicating a wireless connection. 642 | oled.print(F("W")); 643 | }else { 644 | oled.print(F(".")); // Remove W icon 645 | } 646 | } 647 | 648 | 649 | // The following two are updated every second. 650 | oled.set1X(); 651 | 652 | #ifdef ALLOW_FAKE_DATA 653 | oled.setCursor(F_POSITION,0); 654 | if( desired_sending_fake_data && sending_fake_data){ // We are sending fake data 655 | oled.print(F("F")); 656 | } 657 | else if(desired_sending_fake_data != sending_fake_data){ // In the transition between real and fake data 658 | oled.print(F("f")); 659 | } 660 | else{ // No fake data is being generated 661 | oled.print(F(".")); 662 | } 663 | #endif 664 | 665 | #ifdef ALLOW_CONNECTING_TO_NETWORK 666 | oled.setCursor(T_POSITION,0); 667 | if( transmission_state ){ 668 | oled.print(F("T")); 669 | } 670 | else{ 671 | oled.print(F(".")); 672 | } 673 | #endif 674 | 675 | #endif // end of has display 676 | } 677 | } 678 | 679 | 680 | 681 | #ifdef HAS_CO_SENSOR 682 | int readCOValue() 683 | { 684 | 685 | while (co_sensor.read()!=-1) {}; // Clear serial buffer 686 | 687 | char response[9]; // Holds response from sensor 688 | byte requestReading[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}; 689 | 690 | Serial.println(F("Requesting data from CO sensor module")); 691 | co_sensor.write(requestReading, 9); // Request PPM CO 692 | co_sensor.readBytes(response, 9); 693 | 694 | // Do some checks on the response: 695 | if (byte(response[0]) != 0xFF){ 696 | Serial.println(F("! Sensor not connected?")); 697 | while (co_sensor.read()!=-1) {}; // Empty the serial buffer, for a fresh start, just in case. 698 | return -1; 699 | } 700 | if (byte(response[1]) != 0x86){ 701 | Serial.println(F("! Sensor did not send CO data")); 702 | return -2; 703 | } 704 | // Did the data get damaged along the way? 705 | char check = getCheckSum(response); 706 | if (response[8] != check) { 707 | Serial.println(F("ERROR: checksum did not match")); 708 | return -2; 709 | } 710 | 711 | int high = response[2]; 712 | int low = response[3]; 713 | return high * 256 + low; 714 | } 715 | #endif 716 | 717 | 718 | #ifdef HAS_CO2_SENSOR 719 | int read_co2_value() 720 | { 721 | 722 | while (co2_sensor.read()!=-1) {}; // Clear serial buffer 723 | 724 | char response[9]; // Holds response from sensor 725 | byte requestReading[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}; 726 | 727 | Serial.println(F("Requesting data from CO2 sensor module")); 728 | co2_sensor.write(requestReading, 9); // Request data from sensor module. 729 | co2_sensor.readBytes(response, 9); 730 | 731 | // Do some checks on the response: 732 | if (byte(response[0]) != 0xFF){ 733 | Serial.println(F("! Is the CO2 sensor connected?")); 734 | return -1; 735 | } 736 | if (byte(response[1]) != 0x86){ 737 | Serial.println(F("! Non-sensor data")); 738 | #ifdef DEBUG 739 | Serial.println(response[1]); 740 | #endif 741 | return -2; 742 | } 743 | // Did the data get damaged along the way? 744 | char check = getCheckSum(response); 745 | if (response[8] != check) { 746 | Serial.println(F("! Corrupted data")); 747 | return -2; 748 | } 749 | 750 | int high = response[2]; 751 | int low = response[3]; 752 | return high * 256 + low; 753 | } 754 | #endif 755 | 756 | 757 | #ifdef ALLOW_CONNECTING_TO_NETWORK 758 | void receive(const MyMessage &message) 759 | { 760 | Serial.println(F(">> receiving message")); 761 | connected_to_network = true; 762 | 763 | if( message.isAck() ){ 764 | Serial.println(F("-Got echo")); 765 | return; 766 | } 767 | 768 | if (message.type == V_STATUS && message.sensor == DATA_TRANSMISSION_CHILD_ID ){ 769 | transmission_state = message.getBool(); //?RELAY_ON:RELAY_OFF; 770 | Serial.print(F("-New desired transmission state: ")); Serial.println(transmission_state); 771 | } 772 | } 773 | #endif 774 | 775 | // A helper function to check the integrity of a received sensor message. 776 | byte getCheckSum(byte* packet) 777 | { 778 | byte i; 779 | unsigned char checksum = 0; 780 | for (i = 1; i < 8; i++) { 781 | checksum += packet[i]; 782 | } 783 | checksum = 0xff - checksum; 784 | checksum += 1; 785 | return checksum; 786 | } 787 | 788 | 789 | 790 | /* 791 | * 792 | * This code makes use of the MySensors library: 793 | * 794 | * The MySensors Arduino library handles the wireless radio link and protocol 795 | * between your home built sensors/actuators and HA controller of choice. 796 | * The sensors forms a self healing radio network with optional repeaters. Each 797 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 798 | * network topology allowing messages to be routed to nodes. 799 | * 800 | * Created by Henrik Ekblad 801 | * Copyright (C) 2013b-2015 Sensnology AB 802 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 803 | * 804 | * Documentation: http://www.mysensors.org 805 | * Support Forum: http://forum.mysensors.org 806 | * 807 | * This program is free software; you can redistribute it and/or 808 | * modify it under the terms of the GNU General Public License 809 | * version 2 as published by the Free Software Foundation. 810 | * 811 | ******************************* 812 | * 813 | */ 814 | -------------------------------------------------------------------------------- /Dust_sensor/Dust_sensor.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Candle fine dust sensor 3 | * 4 | * Often the air in our homes is much dirtier than the air outside. This fine dust sensor can help you gain insight into the quality of he air inside your home. 5 | * 6 | * It is built around the SDS011 fine dust sensor. It's used here because we can directly connect to it without needing to solder anything onto it. 7 | * 8 | * Sensors like can output 'particle count per cubic meter' or 'micrograms per cubic meter. This sensor does the latter. 9 | * 10 | * The most popular particle size to measure is 2,5 micrometers. These particles can enter deep inside your lungs. Some dust sensors can also mearure other 'bins', such as 1, 5 or 10 micrometers. This sensors can also give us the weight of the 10 micrometer particles. This weight includes the weight of the smaller sizes particles.. 11 | * 12 | * 13 | * SETTINGS */ 14 | 15 | 16 | #define MEASUREMENT_INTERVAL 60 // Seconds between measurements. How many seconds between measurements? Recommended is at least 120 seconds. The minimum is 15 seconds. 17 | 18 | #define HAS_DISPLAY // Did you connect an OLED display? If you have connected a small OLED dislay it will show the latest measurements. 19 | 20 | //#define SHOW_DATAVIZ // Show a basic datavizualisation on the display? This vizualisation is experimental and far from perfect, but might be fun to try. 21 | 22 | #define ALLOW_CONNECTING_TO_NETWORK // Connect wirelessly. Is this device allowed to connect to the network? For privacy or security reasons you may prefer a stand-alone device. If you do allow the device to connect, you can connect a toggle switch to pin 6 to change the connection state at any time. 23 | 24 | //#define ALLOW_FAKE_DATA // Allow fake data to be sent? This is an experimental feature, and requires you to attach a switch to pin 5. It's designed to make the sensor less intrusive in some social situations, allowing you to pretend you are not smoking/cooking. 25 | 26 | //#define MY_REPEATER_FEATURE // Act as signal repeater. Should this sensor act as a repeater for your other devices? This can help the signal spread further. 27 | 28 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 29 | 30 | 31 | /* END OF SETTINGS 32 | * 33 | * 34 | * 35 | */ 36 | 37 | //#define MY_DEBUG // Enable MySensors debug output to the serial monitor? This will help you check if the radio is working ok. 38 | 39 | 40 | #define AVERAGE_HOURS 1 // Averaging period. Over how many hours should the average be calculated? If the device has been on shorter than this period, it will show the average until then. 41 | 42 | 43 | // PINS 44 | #define DUST_SENSOR_RX_PIN 3 // Dust sensor RX pin. Connect this to the TX pin on the sensor. 45 | #define DUST_SENSOR_TX_PIN 4 // Dust sensor TX pin. Connect this to the RX pin on the sensor. 46 | #define TOGGLE_FAKE_DATA_PIN 5 // Pin where the toggle switch to send fake data is connected. 47 | #define CONNECT_TO_NETWORK_PIN 6 // Pin where the toggle switch to allow connecting to the network is connected. 48 | #define RANDOM_SEED_PIN A7 // Pin to use to create more random variables. 49 | 50 | #ifdef RF_NANO 51 | // If you are using an RF-Nano, you have to switch CE and CS pins. 52 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 53 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 54 | #endif 55 | 56 | 57 | // Enable and select the attached radio type 58 | #define MY_RADIO_RF24 // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi. 59 | //#define MY_RADIO_NRF5_ESB // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet. 60 | //#define MY_RADIO_RFM69 // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect. 61 | //#define MY_RADIO_RFM95 // This is a LoRaWan radio, which can have a range of 10km. 62 | 63 | // MySensors: Choose your desired radio power level. High power can cause issues on cheap Chinese NRF24 radio's. 64 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 65 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 66 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 67 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 68 | 69 | // Mysensors advanced security 70 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 71 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 72 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 73 | 74 | // Mysensors advanced settings 75 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 76 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 77 | #define MY_RF24_DATARATE RF24_1MBPS // Slower datarate makes the network more stable? 78 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 79 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 80 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 81 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 82 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 83 | 84 | 85 | 86 | #define RADIO_DELAY 150 // Milliseconds between sensing radio signals during the presentation phase. Gives the radio some time to breathe in between working, and time to listen to a response. 87 | #define LOOPDURATION 1000 // The main loop runs every x milliseconds. It's like a second counter on a clock. 88 | 89 | 90 | // LIBRARIES (in the Arduino IDE go to Sketch -> Include Library -> Manage Libraries to add these if you don't have them installed yet.) 91 | #include // MySensors library 92 | #include // "SDS011 sensor Library". Makes it easy to talk to the fine dust sensor. 93 | #include // Watchdog library. Resets the device if it becomes unresponsive. 94 | 95 | 96 | #ifdef HAS_DISPLAY 97 | #define INCLUDE_SCROLLING 0 98 | #define OLED_I2C_ADDRESS 0x3C 99 | #include // Simple drivers for the screen. 100 | #include // "SSD1306Ascii". Simple drivers for the screen. 101 | SSD1306AsciiAvrI2c oled; 102 | #endif 103 | 104 | 105 | // SDS011 dust sensor details 106 | float p10 = 0; 107 | float p25 = 0; 108 | float average_p10 = 0; 109 | float average_p25 = 0; 110 | 111 | SDS011 my_sds; 112 | 113 | 114 | // Mysensors settings. 115 | #define CHILD_ID_DUST_PM10 0 116 | #define CHILD_ID_DUST_PM25 1 117 | #define ACTIVATED_CHILD_ID 2 118 | MyMessage message_dust(CHILD_ID_DUST_PM10, V_LEVEL); // Sets up the message format for actual dust messages. 119 | MyMessage message_prefix(CHILD_ID_DUST_PM10, V_UNIT_PREFIX); // Sets up the MySensors prefix message 120 | MyMessage relaymsg(ACTIVATED_CHILD_ID, V_STATUS); // Toggle message 121 | 122 | 123 | 124 | // Other 125 | unsigned long lastLoopTime = 0; // Holds the last time the main loop ran. 126 | int loopCounter = MEASUREMENT_INTERVAL; // Count how many loops have passed. 127 | int measurements_counter = 0; // Used by averaging function. 128 | byte vizPosition = 30; // Used by the experimenal data vizualisation option. 129 | boolean send_all_values = true; // If the controller asks the devive to re-present itself, then this is used to also resend all the current sensor values. 130 | boolean received_echo = false; // If we get a response from the controller, then this is set to true. 131 | 132 | // Fake data feature 133 | boolean sending_fake_data = false; // Experimental. Will allow a user to send fake data for a while. Useful in some social situations. 134 | boolean desired_sending_fake_data = false; // If the user wants to change the state of sending fake data. 135 | float p25_fakeness_range = 0; // How far of the last average the value can meander. 136 | float p10_fakeness_range = 0; // How far of the last average the value can meander. 137 | float p25_addition = 0; // Holds how much will actually be deviated from the average when generating a fake value. 138 | float fakeness_proportion = 0; // How these two values relate. if one goes up, the other should also go up, but in proportion to it's own fakeness range. 139 | 140 | // Connection toggle feature 141 | boolean desired_connecting_to_network = false; 142 | boolean connecting_to_network = true; 143 | boolean may_send_data = true; 144 | 145 | void presentation() 146 | { 147 | #ifdef ALLOW_CONNECTING_TO_NETWORK 148 | // Send the sketch version information to the gateway and Controller 149 | sendSketchInfo(F("Fine dust sensor"), F("1.1")); 150 | 151 | // Register all child sensors with the gateway 152 | present(CHILD_ID_DUST_PM10, S_DUST, F("10 micrometers & smaller")); delay(RADIO_DELAY); 153 | present(CHILD_ID_DUST_PM25, S_DUST, F("2.5 micrometers")); delay(RADIO_DELAY); 154 | present(ACTIVATED_CHILD_ID, S_BINARY, F("data transmission")); 155 | 156 | send_all_values = true; 157 | #endif 158 | } 159 | 160 | 161 | void setup() 162 | { 163 | my_sds.begin(DUST_SENSOR_RX_PIN, DUST_SENSOR_TX_PIN); 164 | Serial.begin(115200); 165 | Serial.println(F("Hello, I am a dust sensor")); 166 | 167 | #ifdef ALLOW_FAKE_DATA 168 | pinMode(TOGGLE_FAKE_DATA_PIN, INPUT_PULLUP); 169 | Serial.print(F("Toggle fake-data-mode using a switch on pin ")); Serial.println(TOGGLE_FAKE_DATA_PIN); 170 | #endif 171 | 172 | 173 | #ifdef HAS_DISPLAY 174 | // Start the display (if there is one) 175 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 176 | oled.setFont(Adafruit5x7); 177 | 178 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 179 | oled.setScroll(false); 180 | oled.setCursor(0,0); 181 | oled.print(F("FINE DUST")); 182 | //delay(1000); 183 | #endif 184 | 185 | 186 | #ifdef ALLOW_CONNECTING_TO_NETWORK 187 | // Check if there is a network connection 188 | if(isTransportReady()){ 189 | Serial.println(F("Connected to gateway!")); 190 | 191 | #ifdef HAS_DISPLAY 192 | // Show connection icon on the display 193 | oled.setCursor(80,0); 194 | oled.print(F("W")); 195 | #endif 196 | 197 | }else{ 198 | Serial.println(F("! NO CONNECTION")); 199 | } 200 | 201 | #ifdef HAS_DISPLAY 202 | // Show data transmission icon on the display 203 | oled.setCursor(70,0); 204 | if(may_send_data){ // A small "T" icon on the screen reflects that data transmission is currently allowed. 205 | oled.print(F("T")); 206 | } 207 | else{ 208 | oled.print(F(" ")); 209 | } 210 | #endif 211 | 212 | 213 | #else 214 | Serial.println("This device will not connect to the network.") 215 | #endif 216 | 217 | // Place labels on the screen 218 | #ifdef HAS_DISPLAY 219 | oled.setCursor(0,2); 220 | oled.print(F("2.5:")); 221 | oled.setCursor(0,5); 222 | oled.print(F("10.0:")); 223 | #endif 224 | 225 | wdt_enable(WDTO_8S); // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart. 226 | 227 | //my_sds.wakeup(); 228 | } 229 | 230 | 231 | void send_values() 232 | { 233 | #ifdef ALLOW_CONNECTING_TO_NETWORK 234 | 235 | send(message_prefix.setSensor(CHILD_ID_DUST_PM10).set( F("ug/m3") ),0); delay(RADIO_DELAY); 236 | if(may_send_data){ 237 | send(message_dust.setSensor(CHILD_ID_DUST_PM10).set(p10,1),0); delay(RADIO_DELAY); 238 | } 239 | send(message_prefix.setSensor(CHILD_ID_DUST_PM25).set( F("ug/m3") ),0); delay(RADIO_DELAY); 240 | if(may_send_data){ 241 | send(message_dust.setSensor(CHILD_ID_DUST_PM25).set(p25,1),0); delay(RADIO_DELAY); 242 | } 243 | send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(may_send_data)); wait(RADIO_DELAY); 244 | 245 | #endif 246 | } 247 | 248 | 249 | void loop() { 250 | // Send all the child states to the controller. This will initialise things there. 251 | if( send_all_values ){ 252 | #ifdef DEBUG 253 | Serial.println(F("Sending all values")); 254 | #endif 255 | send_all_values = false; 256 | send_values(); 257 | } 258 | 259 | 260 | 261 | 262 | #ifdef ALLOW_FAKE_DATA 263 | boolean fake_data_pin_state = digitalRead(TOGGLE_FAKE_DATA_PIN); 264 | if( fake_data_pin_state != desired_sending_fake_data ){ 265 | desired_sending_fake_data = fake_data_pin_state; 266 | Serial.print(F("FAKE DATA TOGGLED TO ")); Serial.println(desired_sending_fake_data); 267 | #ifdef HAS_DISPLAY 268 | oled.set1X(); 269 | oled.setCursor(72,0); 270 | if( desired_sending_fake_data ){ 271 | oled.print(F("F")); 272 | } 273 | else{ 274 | oled.print(F(" ")); 275 | } 276 | #endif 277 | wait(20); 278 | } 279 | #endif 280 | 281 | 282 | #ifdef ALLOW_CONNECTING_TO_NETWORK 283 | if( digitalRead(CONNECT_TO_NETWORK_PIN) != connecting_to_network ){ 284 | 285 | //connecting_to_network = !desired_connecting_to_network; 286 | wait(10); 287 | connecting_to_network = digitalRead(CONNECT_TO_NETWORK_PIN); 288 | Serial.print(F("NETWORK TOGGLED TO ")); Serial.println(connecting_to_network); 289 | 290 | #ifdef HAS_DISPLAY 291 | if(!connecting_to_network){ // If we should not connect to the network, remove the W icon. 292 | oled.set1X(); 293 | oled.setCursor(80,0); 294 | oled.print(F(" ")); 295 | } 296 | #endif 297 | wait(10); // Avoid bounce 298 | } 299 | #endif 300 | 301 | 302 | // 303 | // HEARTBEAT LOOP 304 | // runs every second (or as long as you want). By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory. 305 | // The maximum time that can be scheduled is 255 * the time that one loop takes. So usually 255 seconds. 306 | // 307 | unsigned long currentMillis = millis(); 308 | 309 | if (currentMillis - lastLoopTime > LOOPDURATION) { 310 | lastLoopTime = currentMillis; 311 | loopCounter++; 312 | Serial.print("loopcounter:"); Serial.println(loopCounter); 313 | if(loopCounter > MEASUREMENT_INTERVAL){ 314 | Serial.println(); Serial.println(F("__starting__")); 315 | loopCounter = 0; 316 | } 317 | wdt_reset(); 318 | 319 | #ifdef HAS_DISPLAY 320 | // Update the second countdown on the display. 321 | oled.set1X(); 322 | oled.setCursor(100,0); 323 | oled.print(MEASUREMENT_INTERVAL - loopCounter); 324 | oled.clearToEOL(); 325 | #endif 326 | 327 | 328 | // schedule 329 | switch (loopCounter) { 330 | 331 | case 1: // On the first second 332 | //if(MEASUREMENT_INTERVAL > 29){ // Only uses the sleep-and-wake functionality if there is at least 30 seconds between measurements. 333 | Serial.println(F("Sensor waking up")); 334 | my_sds.wakeup(); 335 | //} 336 | break; 337 | 338 | case 10: 339 | Serial.println(F("Asking for fresh data")); 340 | my_sds.read(&p25, &p10); 341 | break; 342 | 343 | case 11: // On the 11th second (after the fan has been spinning for 10 seconds) 344 | Serial.println(F("Asking for fresh data again")); 345 | while (!my_sds.read(&p25, &p10)) 346 | { 347 | Serial.println(F("Waiting for data")); 348 | delay(10); 349 | } 350 | 351 | #ifdef HAS_DISPLAY 352 | // update the display 353 | 354 | oled.set2X(); 355 | oled.setCursor(0,3); 356 | oled.print(p25); oled.println(F(" ")); 357 | oled.setCursor(0,6); 358 | oled.print(p10); oled.println(F(" ")); 359 | 360 | 361 | 362 | // PM2.5 levels based on opinions of Dutch scientists and the World Health Organization. Keep your yearly average below 10. 363 | oled.setCursor(70,3); 364 | if(sending_fake_data){oled.print(F("FAKED"));} 365 | else if (p25 == 0){ oled.clearToEOL(); } 366 | else if (p25 <= 3){ oled.print(F("GREAT"));} 367 | else if (p25 <= 5){ oled.print(F("GOOD "));} 368 | else if (p25 <= 8){ oled.print(F("OK "));} 369 | else if (p25 <= 14){ oled.print(F("POOR "));} 370 | else if (p25 > 20){ oled.print(F("BAD "));} 371 | 372 | // PM10 levels based on opinions of Dutch scientists and the World Health Organization. Keep your yearly average below 20. 373 | oled.setCursor(70,6); 374 | if(sending_fake_data){oled.print(F("FAKED"));} 375 | else if (p10 == 0){ oled.clearToEOL(); } 376 | else if (p10 <= 5){ oled.print(F("GREAT"));} 377 | else if (p10 <= 10){ oled.print(F("GOOD "));} 378 | else if (p10 <= 20){ oled.print(F("OK "));} 379 | else if (p10 <= 30){ oled.print(F("POOR "));} 380 | else if (p10 > 30){ oled.print(F("BAD "));} 381 | #endif 382 | 383 | if( MEASUREMENT_INTERVAL > 29 ){ // Only goes to sleep if there is a long enough interval between desired measurements. 384 | Serial.println(F("Sensor going to sleep.")); 385 | my_sds.sleep(); 386 | } 387 | break; 388 | 389 | case 12: // On the 12th second we send the first bit of data 390 | received_echo = false; // If all goes well this will be reset to 'true' when the controller acknowledges that it has received the first message. 391 | #ifdef ALLOW_CONNECTING_TO_NETWORK 392 | 393 | if( desired_sending_fake_data == true && sending_fake_data == false ){ 394 | 395 | if( average_p25 != 0 && p25 != average_p25 && average_p10 != 0 && p10 != average_p10 ){ 396 | // We have good enough measurements to create fake data. 397 | 398 | // Determine the fakeness range: how far from the last average newly generated values may meander. 399 | /*if( p25 > average_p25){ 400 | p25_fakeness_range = p25 - average_p25; 401 | } 402 | else if ( p25 < average_p25){ 403 | p25_fakeness_range = average_p25 - p25; 404 | } 405 | 406 | if( p10 > average_p10){ 407 | p10_fakeness_range = p10 - average_p10; 408 | } 409 | else if ( p10 < average_p10){ 410 | p10_fakeness_range = average_p25 - p25; 411 | } 412 | */ 413 | p25_fakeness_range = p25 - average_p25; 414 | p10_fakeness_range = p10 - average_p10; 415 | 416 | if( p25_fakeness_range < 0 ){ 417 | p25_fakeness_range = -p25_fakeness_range; 418 | } 419 | if( p10_fakeness_range < 0 ){ 420 | p10_fakeness_range = -p10_fakeness_range; 421 | } 422 | //fakeness_proportion = (float) p10_fakeness_range / p25_fakeness_range; // Usually the p10 values will be slightly bigger. 423 | fakeness_proportion = (float) p10 / p25; // Usually the p10 values will be slightly bigger. 424 | 425 | randomSeed(analogRead(RANDOM_SEED_PIN)); // Creates better random values. 426 | Serial.print(F("Starting sending fake data. \n-P25 range: ")); Serial.println(p25_fakeness_range); 427 | Serial.print(F("-p10 range: ")); Serial.println(p10_fakeness_range); 428 | Serial.print(F("-proportion: ")); Serial.println(fakeness_proportion); 429 | if(p25_fakeness_range != 0){ 430 | sending_fake_data = true; 431 | } 432 | } 433 | #ifdef DEBUG 434 | else{ 435 | Serial.println(F("Measurement not useful for intiating fake data.")); 436 | } 437 | #endif 438 | } 439 | if( desired_sending_fake_data == false && sending_fake_data == true ){ 440 | Serial.print(F("Will send real data again.")); 441 | sending_fake_data = false; 442 | } 443 | 444 | if(sending_fake_data){ 445 | p25_addition = (float)random( p25_fakeness_range * 100000) / 100000; 446 | if( random(2) ){ p25_addition = -p25_addition;} 447 | Serial.print(F("2.5 Addition: ")); Serial.println(p25_addition); 448 | p25_addition = average_p25 + p25_addition; 449 | Serial.print(F("<< sending FAKE 2.5: ")); Serial.println(p25_addition); 450 | send(message_dust.setSensor(CHILD_ID_DUST_PM25).set(p25_addition,1),1); // This message asks the controller to send and acknowledgement it was received. 451 | } 452 | else{ 453 | Serial.print(F("<< sending 2.5: ")); Serial.println(p25); 454 | send(message_dust.setSensor(CHILD_ID_DUST_PM25).set(p25,1),1); // This message asks the controller to send and acknowledgement it was received. 455 | } 456 | #endif 457 | break; 458 | 459 | case 13: // On the 13th second we send the second bit of data, and check the network connection. 460 | #ifdef ALLOW_CONNECTING_TO_NETWORK 461 | if(sending_fake_data){ // Now creating fake data for P10. It should generally move in the same direction as the p25. 462 | float p10_addition = (float) (p10_fakeness_range * fakeness_proportion); // + (p10_fakeness_range / random(4,8)); 463 | //float addition = (float)random( p25_fakeness_range * 100000) / 100000; 464 | //if( random(2) ){ p10_addition = p10_addition ; } 465 | Serial.print(F("p10_addition = ")); Serial.println(p10_addition); 466 | p10_addition = p10_addition + average_p10; 467 | Serial.print(F("<< sending FAKE 10.0: ")); Serial.println(p10_addition); 468 | if(may_send_data){ 469 | send(message_dust.setSensor(CHILD_ID_DUST_PM25).set(p10_addition,1),1); // This message asks the controller to send and acknowledgement it was received. 470 | } 471 | } 472 | else{ 473 | Serial.print(F("-> sending 10.0: ")); Serial.println(p10); 474 | if(may_send_data){ 475 | send(message_dust.setSensor(CHILD_ID_DUST_PM10).set(p10,1),0); 476 | } 477 | } 478 | #endif 479 | 480 | #ifdef HAS_DISPLAY 481 | oled.set1X(); 482 | oled.setCursor(80,0); 483 | #endif 484 | 485 | if( received_echo == true ){ 486 | Serial.println(F("Connection to controller is ok.")); 487 | #ifdef HAS_DISPLAY 488 | oled.print(F("W")); // Add W icon 489 | #endif 490 | } 491 | else { 492 | Serial.println(F("No connection to controller!")); 493 | #ifdef HAS_DISPLAY 494 | oled.print(F(" ")); // Remove W icon 495 | #endif 496 | } 497 | 498 | 499 | break; 500 | 501 | case 14: // Calculating averages 502 | // Still thinking how to optimally deal with outliers and/or early mis-measurements: 503 | if(measurements_counter * MEASUREMENT_INTERVAL < AVERAGE_HOURS * 3600){ // This limits how strongly the old values influence the new average. 504 | measurements_counter++; 505 | } 506 | 507 | if(!sending_fake_data){ 508 | average_p25 = average(measurements_counter, average_p25, p25); 509 | average_p10 = average(measurements_counter, average_p10, p10); 510 | } 511 | else{ 512 | Serial.print(F("Sending fake data, so average is not changing.")); 513 | } 514 | 515 | Serial.print(F("Average p2.5: ")); Serial.println(average_p25); 516 | Serial.print(F("Average p10: ")); Serial.println(average_p10); 517 | 518 | // Data vizualisation experiment 519 | #if defined(HAS_DISPLAY) && defined(SHOW_DATAVIZ) 520 | // This is an experimental way to show a basic datavizualisation using only the characters available in this display library. 521 | if(vizPosition > 60 ){vizPosition = 30;} 522 | //Serial.print(F("Dataviz x position: ")); Serial.println(vizPosition); 523 | oled.set1X(); 524 | oled.setCursor(vizPosition,1); 525 | if(p25 < 1 || vizPosition == 1){oled.print(F(" "));} 526 | else if(p25 < 2){oled.print(F("_"));} 527 | else if(p25 < 3){oled.print(F("/"));} 528 | else if(p25 < 4){oled.print(F("4"));} // This one isn't perfect.. 529 | else if(p25 < 5){oled.print(F("+"));} 530 | else if(p25 < 6){oled.print(F("t"));} 531 | else if(p25 < 7){oled.print(F("~"));} 532 | else {oled.print(F("'"));} 533 | vizPosition++; 534 | oled.setCursor(vizPosition,1); 535 | oled.print(F(" ")); 536 | #endif 537 | break; 538 | } 539 | } 540 | } 541 | 542 | 543 | float average(int measurements, float old_average, float new_value){ 544 | if(measurements < 2){ 545 | return new_value; 546 | } 547 | else if(measurements < 4){ 548 | return (old_average + new_value) / 2; 549 | } 550 | else if(measurements >= 4){ 551 | float totally = (measurements - 1) * old_average; 552 | return (totally + new_value) / measurements; 553 | } 554 | } 555 | 556 | void receive(const MyMessage &message) 557 | { 558 | #ifdef ALLOW_CONNECTING_TO_NETWORK 559 | 560 | 561 | if (message.isAck()) { 562 | Serial.println(F(">> Received acknowledgement")); 563 | received_echo = true; 564 | } 565 | 566 | if (message.type == V_STATUS && message.sensor == ACTIVATED_CHILD_ID ){ 567 | may_send_data = message.getBool(); //?RELAY_ON:RELAY_OFF; 568 | send(relaymsg.setSensor(ACTIVATED_CHILD_ID).set(may_send_data)); // We echo the new state to the controller, to say "we got the message". 569 | Serial.print(F("-New may_send_data state: ")); Serial.println(may_send_data); 570 | #ifdef HAS_DISPLAY 571 | // Show connection icon on the display 572 | oled.setCursor(70,0); 573 | if(may_send_data){ // A small "T" icon on the screen reflects that data transmission is currently allowed. 574 | oled.print(F("T")); 575 | } 576 | else{ 577 | oled.print(F(" ")); 578 | } 579 | #endif 580 | 581 | } 582 | 583 | #endif 584 | } 585 | 586 | 587 | 588 | /** This device uses the MySensors library: 589 | * 590 | * The MySensors Arduino library handles the wireless radio link and protocol 591 | * between your home built sensors/actuators and HA controller of choice. 592 | * The sensors forms a self healing radio network with optional repeaters. Each 593 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 594 | * network topology allowing messages to be routed to nodes. 595 | * 596 | * Created by Henrik Ekblad 597 | * Copyright (C) 2013-2015 Sensnology AB 598 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 599 | * 600 | * Documentation: http://www.mysensors.org 601 | * Support Forum: http://forum.mysensors.org 602 | * 603 | * This program is free software; you can redistribute it and/or 604 | * modify it under the terms of the GNU General Public License 605 | * version 2 as published by the Free Software Foundation. 606 | * 607 | ******************************* 608 | */ 609 | -------------------------------------------------------------------------------- /Energy_use_meter/Energy_use_meter.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Energy use sensor 3 | * 4 | * Your home probably has a central electricity meter. On it, you may find a blinking LED. How frequently it blinks indicates how much power you are using. If you use more, it will blink faster. By measuring how often it blinks, this device can calculate how much electricity your home is currently using (in Watt). It also calculates how many electricity (in KiloWattHour) your home is consuming over time. It will give you live values, as well as the averages of the past hour and past day. If you disable live data transmission, it will only transmit these hourly and daily averages. You can always still see the live values on the display. 5 | * 6 | * 7 | * 8 | * SETTINGS */ 9 | 10 | //#define HAS_DISPLAY // Did you connect an OLED display on pins A4 (SDA) and A5 (SCK)? 11 | 12 | #define PULSE_FACTOR 1000 // LED flashes per Kwh. Set the number of LED flashes that corresponds to the consumption of one Khw. In most cases this is 1000. 13 | 14 | #define ALLOW_CONNECTING_TO_NETWORK // Connect wirelessly. Is this device allowed to connect to the network? For privacy or security reasons you may prefer a stand-alone device. 15 | 16 | //#define MY_REPEATER_FEATURE // Act as signal repeater. Should this sensor act as a repeater for your other devices? This can help your network reach further. 17 | 18 | //#define RF_NANO // RF-Nano Classic. The Candle project recommends using the RF-Nano, which is an Arduino Nano with a built in NRF24 radio module. There are two versions of the Nano. Both have a built in antenna, but one also has an antenna-connector plug. Enable this option if you are using the RF Nano variant WITHOUT the antenna connector. 19 | 20 | 21 | /* END OF SETTINGS 22 | * 23 | * 24 | */ 25 | 26 | 27 | //#define DEBUG // General debug option, give extra information via the serial output when enabled. 28 | //#define TEST 29 | //#define MY_DEBUG // Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok. 30 | 31 | 32 | // Enable and select the attached radio type 33 | #define MY_RADIO_RF24 // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi. 34 | //#define MY_RADIO_NRF5_ESB // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet. 35 | //#define MY_RADIO_RFM69 // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect. 36 | //#define MY_RADIO_RFM95 // This is a LoRaWan radio, which can have a range of 10km. 37 | 38 | // MySensors: Choose your desired radio power level. High power can cause issues on cheap Chinese NRF24 radio's. 39 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 40 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 41 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 42 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 43 | 44 | // Mysensors advanced security 45 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 46 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 47 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 48 | 49 | // Mysensors advanced settings 50 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 51 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 52 | #define MY_RF24_DATARATE RF24_1MBPS // Slower datarate makes the network more stable? 53 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 54 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 55 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 56 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 57 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 58 | 59 | #define RADIO_DELAY 100 // Gives the radio some space to catch breath 60 | 61 | 62 | // PINS 63 | #define LIGHT_PULSE_SENSOR_PIN 2 // The digital input you attached your light sensor. (Only 2 and 3 generate interrupt) 64 | //#define TRANSMISSION_TOGGLE_PIN 8 // A psychical switch to disable data transmission. This feature is currently disabled. In future, a small push button might be used to toggle data transmission state instead. 65 | 66 | 67 | #ifdef RF_NANO 68 | // If you are using an RF-Nano, you have to switch CE and CS pins. 69 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 70 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 71 | #endif 72 | 73 | 74 | // Enable and select radio type attached 75 | #define MY_RADIO_RF24 76 | //#define MY_RADIO_NRF5_ESB 77 | //#define MY_RADIO_RFM69 78 | //#define MY_RADIO_RFM95 79 | 80 | 81 | // VARIABLES 82 | #define SLEEP_MODE false // You could run this device on battery power, which means "sleep mode" is enabled. Watt value can only be reported when sleep mode is false. 83 | #define MAX_WATT 10000 // Max watt value to report. This filters outliers. 84 | 85 | uint32_t SEND_FREQUENCY = 10000; // Minimum time between send (in milliseconds). We don't want to spam the gateway. 86 | float ppwh = ((float)PULSE_FACTOR)/1000; // Pulses per watt hour 87 | boolean pulse_count_received = false; 88 | boolean transmission_state = true; 89 | boolean previous_transmission_state = true; 90 | volatile boolean connected_to_network = false; // Whether the device has actually been able to succesfully connect to the network. 91 | volatile boolean waiting_for_first_pulse = true; 92 | volatile uint32_t pulse_count = 0; // uint32_t = unsigned long. 93 | volatile uint32_t last_blink = 0; // Used to accurately measure the time between pulses in microseconds. 94 | volatile uint32_t watt = 0; 95 | uint32_t previous_pulse_count = 0; 96 | uint32_t old_watt = 0; 97 | float kwh = 0; // the total kwh so far during this day 98 | float kwh_hour = 0; // The total kwh so far during this hour 99 | float previous_kwh_hour_total = 0; // Stores what the KWH value was up to when the previous hour ended. It's used to calculate how much was used in the current hour. 100 | float kwh_hour_total = 0; // How much electricity was used in the previous hour. 101 | 102 | 103 | unsigned long seconds_left_in_the_day = 86400; // When this counts down to 60, it will automatically trigger a re-request of the current time from the controller. 104 | unsigned int seconds_left_in_the_hour = 0; 105 | unsigned long epoch_time = 0; // Holds the universal epoch time, which is requested from the gateway once in a while. 106 | 107 | unsigned long lastLoopTime = 0; // Holds the last time the main loop ran. 108 | 109 | #define millis_period = 3600000; // By default, the Kwh measurement works per day. 110 | 111 | // REQUIRED LIBRARIES 112 | #ifdef ALLOW_CONNECTING_TO_NETWORK 113 | #include // The MySensors library. Hurray! 114 | #endif 115 | #include // The watch dog timer resets the device if it becomes unresponsive. 116 | 117 | #ifdef HAS_DISPLAY 118 | #define INCLUDE_SCROLLING 0 // Text scrolling for the OLED screen is not necessary 119 | #define TEXT_STRING_LENGTH 26 // How long the strings are that store the text to display on the screen 120 | #define OLED_I2C_ADDRESS 0x3C // A technical requirement. All I2C OLED screens have an 'address' 121 | #include // Simple drivers for the screen. 122 | #include // "SSD1306Ascii" 123 | SSD1306AsciiAvrI2c oled; // Creating the display object 124 | byte screen_vertical_position = 3; // Used to always show both output at the top of the screen. 125 | //#define F_POSITION 66 // Horizontal position of the "F" icon, indicating it is allowed to generate fake data. 126 | #define T_POSITION 72 // Horizontal position of the "T" icon, indicating it is allowed to transmit data. 127 | #define W_POSITION 80 // Horizontal position of the "W" icon, indicating a wireless connection. 128 | #endif // End of has display 129 | 130 | 131 | 132 | #ifdef ALLOW_CONNECTING_TO_NETWORK 133 | 134 | // MySensors 135 | #define WATT_CHILD_ID 1 // The current wattage being used. 136 | #define KWH_PER_DAY_CHILD_ID 2 // Every hour we update how much total electricity is being used as the day progresses. 137 | #define KWH_PER_HOUR_CHILD_ID 3 // Every hour we update how much electricity was used in the past hour. 138 | #define DATA_TRANSMISSION_CHILD_ID 4 // The child ID of the data transmission switch. 139 | #define KWH_PER_DAY_TOTAL_CHILD_ID 5 // Every hour we update how much total electricity is being used as the day progresses. 140 | #define KWH_PER_HOUR_TOTAL_CHILD_ID 6 // Every hour we update how much electricity was used in the past hour. 141 | 142 | MyMessage relay_message(DATA_TRANSMISSION_CHILD_ID, V_STATUS); // A generic boolean state message. 143 | MyMessage watt_message(WATT_CHILD_ID,V_WATT); 144 | MyMessage kwh_message(KWH_PER_HOUR_CHILD_ID,V_KWH); 145 | //MyMessage pulse_count_message(WATT_PULSES_CHILD_ID,V_VAR1); // This is currently not used, but in future could be used to ask the controller to remember the total amount of pulses to far. This value could then be requested back whenever this device reboots. 146 | 147 | 148 | // Other 149 | boolean send_all_values = true; // When this is true, all current values will be (re)-sent to the controller. 150 | 151 | void presentation() 152 | { 153 | // Send the sketch version information to the gateway and Controller 154 | sendSketchInfo(F("Energy meter"), F("1.0")); wait(RADIO_DELAY); 155 | 156 | // Register this device with the controller 157 | present(WATT_CHILD_ID, S_POWER,F("Wattage")); wait(RADIO_DELAY); 158 | present(KWH_PER_DAY_CHILD_ID, S_POWER,F("Daily use")); wait(RADIO_DELAY); 159 | present(KWH_PER_DAY_TOTAL_CHILD_ID, S_POWER,F("Yesterday")); wait(RADIO_DELAY); 160 | present(KWH_PER_HOUR_CHILD_ID, S_POWER,F("Hourly use")); wait(RADIO_DELAY); 161 | present(KWH_PER_HOUR_TOTAL_CHILD_ID, S_POWER,F("Last hourly total")); wait(RADIO_DELAY); 162 | present(DATA_TRANSMISSION_CHILD_ID, S_BINARY, F("Data transmission")); wait(RADIO_DELAY); 163 | 164 | send_all_values = true; 165 | } 166 | #endif // End of allow connecting to network 167 | 168 | 169 | void setup() 170 | { 171 | Serial.begin(115200); 172 | Serial.println(F("Hello, I am an energy use meter")); 173 | 174 | 175 | // Use the internal pullup to be able to hook up this sketch directly to an energy meter with S0 output 176 | // If no pullup is used, the reported usage will be too high because of the floating pin 177 | pinMode(LIGHT_PULSE_SENSOR_PIN, INPUT_PULLUP); 178 | //pinMode(TRANSMISSION_TOGGLE_PIN, INPUT_PULLUP); 179 | 180 | 181 | #ifdef ALLOW_CONNECTING_TO_NETWORK 182 | transmission_state = loadState(DATA_TRANSMISSION_CHILD_ID); 183 | 184 | #ifdef DEBUG 185 | Serial.print(F("transmission_state loaded from eeprom: ")); 186 | Serial.println(transmission_state); 187 | #endif 188 | 189 | requestTime(); // Request the current time from the controller. 190 | wait(1000); 191 | 192 | //connecting_to_network = !digitalRead(TRANSMISSION_TOGGLE_PIN); 193 | 194 | if(isTransportReady()){ 195 | Serial.println(F("Connected to gateway")); 196 | //connected_to_network = true; // connected_to_network is only set to true once a watt reading exists, to avoid sending a zero value to the controller. 197 | }else{ 198 | Serial.println(F("! NOT CONNECTED TO GATEWAY")); 199 | } 200 | #endif // End of allow connecting to network 201 | 202 | #ifdef HAS_DISPLAY 203 | // Start the display (if there is one) 204 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 205 | oled.setFont(Adafruit5x7); 206 | 207 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 208 | oled.setScrollMode(false); 209 | oled.setCursor(0,0); 210 | oled.print(F("ENERGY USE")); 211 | 212 | oled.setCursor(0,2); 213 | oled.print(F("Watt:")); 214 | oled.setCursor(0,5); 215 | oled.print(F("Kwh:")); 216 | 217 | #ifdef DEBUG 218 | Serial.println(F("I have a display")); 219 | #endif 220 | 221 | #endif // End of has display 222 | 223 | wdt_enable(WDTO_2S); // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart. 224 | 225 | attachInterrupt(digitalPinToInterrupt(LIGHT_PULSE_SENSOR_PIN), onPulse, RISING); // Start listening to light pulses 226 | } 227 | 228 | #ifdef ALLOW_CONNECTING_TO_NETWORK 229 | void send_values() 230 | { 231 | send(relay_message.setSensor(DATA_TRANSMISSION_CHILD_ID).set(transmission_state)); 232 | 233 | if(waiting_for_first_pulse == false){ 234 | if( transmission_state ){ 235 | send(watt_message.setSensor(WATT_CHILD_ID).set(watt,0),1); wait(RADIO_DELAY); 236 | send(kwh_message.setSensor(KWH_PER_HOUR_CHILD_ID).set(kwh_hour, 4),0); // Send kwh value to gateway 237 | send(kwh_message.setSensor(KWH_PER_DAY_CHILD_ID).set(kwh, 4),0); // Send kwh value to gateway 238 | 239 | 240 | } 241 | /* 242 | if(previous_kwh_hour_total != 0){ 243 | send(kwh_message.setSensor(KWH_PER_HOUR_CHILD_ID).set(previous_kwh_hour_total, 4),0); // Send previous kwh value from the previous hour to the controller 244 | } 245 | if(previous_kwh_day_total != 0){ 246 | send(kwh_message.setSensor(KWH_PER_HOUR_CHILD_ID).set(previous_kwh_day_total, 4),0); // Send previous kwh value from the previous hour to the controller 247 | } 248 | */ 249 | } 250 | } 251 | #endif 252 | 253 | 254 | 255 | void loop() 256 | { 257 | #ifdef ALLOW_CONNECTING_TO_NETWORK 258 | // Send all the child states to the controller. This will initialise things there. 259 | if( send_all_values ){ 260 | #ifdef DEBUG 261 | Serial.println(F("Sending all values")); 262 | #endif 263 | send_all_values = false; 264 | send_values(); 265 | } 266 | 267 | 268 | if( transmission_state != previous_transmission_state ){ 269 | previous_transmission_state = transmission_state; 270 | saveState(DATA_TRANSMISSION_CHILD_ID, transmission_state); 271 | Serial.print(F("Sending new data transmission state: ")); Serial.println(transmission_state); 272 | send(relay_message.setSensor(DATA_TRANSMISSION_CHILD_ID).set(transmission_state),0); 273 | } 274 | #endif // End of allow connecting to network 275 | 276 | unsigned long currentMillis = millis(); 277 | 278 | #ifdef DEBUG 279 | wait(200); 280 | Serial.print("."); 281 | #endif 282 | 283 | 284 | #ifdef TEST 285 | // Creates fake pulses, used for testing. 286 | static int nextPulseDelay = 2000; 287 | static long lastTestPulseTime = 0; 288 | if (currentMillis - lastTestPulseTime > nextPulseDelay) { 289 | Serial.println(F("Test pulse")); 290 | lastTestPulseTime = currentMillis; 291 | nextPulseDelay = 500 + random(3000); 292 | watt = 3510 - nextPulseDelay; 293 | pulse_count++; 294 | if(waiting_for_first_pulse){ 295 | waiting_for_first_pulse = false; 296 | } 297 | } 298 | #endif 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | if (currentMillis - lastLoopTime > 1000) { 310 | lastLoopTime = currentMillis; 311 | 312 | wdt_reset(); 313 | 314 | seconds_left_in_the_day--; 315 | seconds_left_in_the_hour = seconds_left_in_the_day % 3600; 316 | Serial.print(F("Seconds left in the hour: ")); Serial.println(seconds_left_in_the_hour); 317 | 318 | 319 | // 320 | // CHECK IF A PULSE WAS RECEIVED 321 | // 322 | 323 | if( waiting_for_first_pulse == false ){ 324 | if( watt != old_watt ){ 325 | Serial.print(F("Watt changed to: ")); 326 | Serial.println(watt); 327 | if (watt<((uint32_t)MAX_WATT)) { // Check that we don't get unreasonable large watt value. This could happen when long wraps or false interrupt triggered. 328 | Serial.println(F("Watt is reasonable")); 329 | old_watt = watt; 330 | #ifdef ALLOW_CONNECTING_TO_NETWORK 331 | if(transmission_state){ // If it is allowed to update the live values, do so. 332 | Serial.println(F("Transmitting Watt value")); 333 | connected_to_network = false; // Will be set back to 'true' if a response is received from the controller. 334 | send(watt_message.setSensor(WATT_CHILD_ID).set(watt),1); wait(RADIO_DELAY); 335 | } 336 | #endif 337 | } 338 | } 339 | 340 | 341 | // Pulse count value has changed. 342 | if( pulse_count != previous_pulse_count ){ 343 | digitalWrite(LED_BUILTIN, true); 344 | 345 | #ifdef HAS_DISPLAY 346 | oled.set1X(); 347 | oled.setCursor(0,1); 348 | oled.print(F("pulses:")); 349 | oled.print(pulse_count); 350 | oled.clearToEOL(); 351 | #endif 352 | Serial.print(F("Pulse count: ")); 353 | Serial.println(pulse_count); 354 | //send(pulse_count_message.set(pulse_count)); // Send pulse count value to gateway 355 | kwh = ((float)pulse_count/((float)PULSE_FACTOR)); // Get the live kwh consumption so far this day 356 | previous_pulse_count = pulse_count; 357 | 358 | Serial.print(F("Kwh so far today: ")); 359 | Serial.println(kwh); 360 | 361 | kwh_hour = kwh - previous_kwh_hour_total; // Get the live kwh consumption so far this hour 362 | 363 | Serial.print(F("Kwh so far this hour: ")); 364 | Serial.println(kwh_hour); 365 | #ifdef ALLOW_CONNECTING_TO_NETWORK 366 | if(transmission_state){ // If it is allowed to update the live values, do so. 367 | send(kwh_message.setSensor(KWH_PER_HOUR_CHILD_ID).set(kwh_hour, 4),0); // Hour 368 | #endif 369 | wait(100); // Allows the built-in LED to blink in unison with the electricity meter. Also gives the radio some time to recover before retransmitting. 370 | digitalWrite(LED_BUILTIN, false); 371 | #ifdef ALLOW_CONNECTING_TO_NETWORK 372 | send(kwh_message.setSensor(KWH_PER_DAY_CHILD_ID).set(kwh, 4),0); // Day 373 | } 374 | #endif 375 | } 376 | 377 | } 378 | else{ 379 | Serial.println("Still waiting to receive the first light pulse"); 380 | } 381 | 382 | 383 | 384 | // 385 | // UPDATING THE DISPLAY 386 | // 387 | 388 | #ifdef HAS_DISPLAY 389 | oled.set1X(); 390 | oled.setCursor(100,0); 391 | oled.print(seconds_left_in_the_hour); 392 | oled.clearToEOL(); 393 | 394 | oled.set2X(); 395 | oled.setCursor(50,2); 396 | oled.print(watt); 397 | oled.clearToEOL(); 398 | oled.setCursor(50,5); 399 | oled.print(kwh); 400 | oled.clearToEOL(); 401 | #endif 402 | 403 | 404 | 405 | #ifdef ALLOW_CONNECTING_TO_NETWORK 406 | if( seconds_left_in_the_day % 10 == 9){ // A second later than the above. For example if there are 39 seconds left. 407 | // We asked the server to acknowledge that it has received the data. If it hasn't, remove the connection icon. 408 | if( connected_to_network ){ 409 | Serial.println(F("Connection is ok")); 410 | // add W icon 411 | #ifdef HAS_DISPLAY 412 | oled.set1X(); 413 | oled.setCursor(W_POSITION,0); 414 | oled.print(F("W")); 415 | #endif 416 | } 417 | else { 418 | Serial.println(F("No connection")); 419 | #ifdef HAS_DISPLAY 420 | // remove W icon 421 | oled.set1X(); 422 | oled.setCursor(W_POSITION,0); 423 | oled.print(F(".")); 424 | #endif 425 | } 426 | } 427 | 428 | // Set whether data transmission is allowed 429 | #ifdef HAS_DISPLAY 430 | oled.setCursor(T_POSITION,0); 431 | oled.set1X(); 432 | if( transmission_state ){ 433 | oled.print(F("T")); 434 | } 435 | else{ 436 | oled.print(F(".")); 437 | } 438 | #endif 439 | 440 | #endif // end of allow connecting to network 441 | 442 | 443 | 444 | 445 | // 446 | // UPDATING THE AVERAGE TOTAL VALUES 447 | // 448 | 449 | if( seconds_left_in_the_hour == 0){ 450 | Serial.println(F("")); 451 | // Send total kwh used in the previous hour. 452 | kwh_hour_total = kwh - previous_kwh_hour_total; 453 | Serial.print(F("A new hour is starting. Kwh used over the last hour: ")); 454 | Serial.println(kwh_hour_total); 455 | 456 | #ifdef ALLOW_CONNECTING_TO_NETWORK 457 | send(kwh_message.setSensor(KWH_PER_HOUR_TOTAL_CHILD_ID).set(kwh_hour_total, 4),0); wait(RADIO_DELAY);// Send kwh value for the past hour to the controller 458 | #endif 459 | previous_kwh_hour_total = kwh; 460 | } 461 | 462 | // If a full day has passed, reset the kwh counting. 463 | if( seconds_left_in_the_day == 0 ){ 464 | Serial.print(F("A new day is starting. Kwh used over the last day: ")); 465 | Serial.println(kwh); 466 | 467 | #ifdef ALLOW_CONNECTING_TO_NETWORK 468 | send(kwh_message.setSensor(KWH_PER_DAY_TOTAL_CHILD_ID).set(kwh, 4),0); wait(RADIO_DELAY);// Send kwh value for the past day to the controller 469 | requestTime(); // Try to synchronize the current time with the controller. 470 | #endif 471 | seconds_left_in_the_day = 86400; 472 | pulse_count = 0; 473 | kwh = 0; 474 | previous_kwh_hour_total = 0; 475 | } 476 | 477 | 478 | } // End of main loop that runs every second 479 | } 480 | 481 | 482 | // 483 | // HANDLE INCOMING MESSAGES 484 | // 485 | 486 | #ifdef ALLOW_CONNECTING_TO_NETWORK 487 | void receive(const MyMessage &message) 488 | { 489 | Serial.println(F(">> receiving message")); 490 | connected_to_network = true; 491 | 492 | if( message.isAck() ){ 493 | Serial.println(F("-Got echo")); 494 | return; 495 | } 496 | if (message.type == V_STATUS && message.sensor == DATA_TRANSMISSION_CHILD_ID ){ 497 | transmission_state = message.getBool(); //?RELAY_ON:RELAY_OFF; 498 | Serial.print(F("-New desired transmission state: ")); Serial.println(transmission_state); 499 | } 500 | 501 | /* 502 | else if (message.type==V_VAR1) { 503 | pulse_count = previous_pulse_count = message.getLong(); 504 | Serial.print("Received last pulse count from gateway:"); 505 | Serial.println(pulse_count); 506 | pulse_count_received = true; 507 | } 508 | */ 509 | } 510 | 511 | void receiveTime(unsigned long controller_time) { 512 | Serial.print(F("Received time: ")); Serial.println(controller_time); 513 | epoch_time = controller_time; 514 | seconds_left_in_the_day = 86400 - (controller_time % 86400); 515 | Serial.print("Second left in the day: "); 516 | Serial.println(seconds_left_in_the_day); 517 | connected_to_network = true; 518 | } 519 | #endif // End of allow connecting to network 520 | 521 | 522 | 523 | 524 | // 525 | // HANDLE LED PULSE 526 | // 527 | 528 | void onPulse() 529 | { 530 | if (!SLEEP_MODE) { 531 | uint32_t new_blink = micros(); 532 | uint32_t interval = new_blink - last_blink; 533 | if (interval<300000L) { // Sometimes we get interrupt on RISING 534 | return; 535 | } 536 | watt = (3600000000.0 /interval) / ppwh; 537 | last_blink = new_blink; 538 | pulse_count++; 539 | if(waiting_for_first_pulse){ 540 | waiting_for_first_pulse = false; 541 | } 542 | } 543 | #ifdef DEBUG 544 | Serial.println("x"); 545 | #endif 546 | } 547 | 548 | 549 | 550 | /* This code builds on the MySensors project. 551 | * 552 | * The MySensors Arduino library handles the wireless radio link and protocol 553 | * between your home built sensors/actuators and HA controller of choice. 554 | * The sensors forms a self healing radio network with optional repeaters. Each 555 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 556 | * network topology allowing messages to be routed to nodes. 557 | * 558 | * Created by Henrik Ekblad 559 | * Copyright (C) 2013-2018 Sensnology AB 560 | * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors 561 | * 562 | * Documentation: http://www.mysensors.org 563 | * Support Forum: http://forum.mysensors.org 564 | * 565 | * This program is free software; you can redistribute it and/or 566 | * modify it under the terms of the GNU General Public License 567 | * version 2 as published by the Free Software Foundation. 568 | * 569 | ******************************* 570 | * 571 | */ 572 | -------------------------------------------------------------------------------- /Example/Example.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Candle Example 3 | * 4 | * This is an example Arduino Sketch that shows how you can integrate your Arduino code with the Candle Controller. 5 | * 6 | * This commented out part at the top of the sketch, which you are reading now, will be shown in the 'explanation' section of the Candle Manager interface. 7 | * You can use it to explain in more detail how the device works, and that the settings do. 8 | * 9 | * If you upload this code to an Arduino it will simply blink its LED. 10 | * 11 | * Learn more at https://github.com/createcandle/Candle-manager-addon or https://www.createcandle.com. 12 | * 13 | * 14 | * 15 | * 16 | * SETTINGS */ 17 | 18 | // When users create new code from a template like this, the settings in this SETTINGS area are turned into an easy to use interface. 19 | // it will only do this for settings in between the "SETTINGS */" and "/* END OF SETTINGS" lines. 20 | 21 | // The first sentence of the comment will become the title, while the second and later sentences are turned into the comment text beneath the input field. 22 | 23 | int blinkSeconds = 2; // Blink delay. How many seconds should a LED blink on-off take? 24 | 25 | char character_array_example[14] = "+31123456789"; // Phone number. This is an example of a string (character array) variable. 26 | 27 | #define STRING_EXAMPLE "passwordje" // Define a value. Defines are also possible. They are turned into a settings interface automatically too. 28 | 29 | #define NUMBER_EXAMPLE 5 // This first sentence will become the title. This second sentence will become the explanation comment underneath the input field. Try it. 30 | 31 | #define BOOLEAN_EXAMPLE // A boolean value. A basic #define without a value is turned into a checkbox. This will toggle whether or not the define is commented out. 32 | 33 | 34 | /* END OF SETTINGS 35 | * 36 | * 37 | */ 38 | 39 | 40 | 41 | // The Candle Manager will also try to download any required Arduino libraries: 42 | 43 | // This is a simple example. Here the name that the Candle manager will search for is taken from the include 44 | // value. In this case it will search for and try to install "SoftwareSerial". The name will be taken from inbetween < > or " ". 45 | #include 46 | 47 | // This is a more complex example. Often the filename and the library name will differ. 48 | // To tell the Candle Manager specificall which library should be downloaded you can add the proper name 49 | // as a comment, in between two quotation marks: 50 | #include "Seeed_BME280.h" // "Grove - Barometer Sensor BME280". A relatively new library (as of 2018), works well with cheap BME280 sensors from China. 51 | 52 | 53 | // Finally, libraries with "avr/" at the beginning of their name will be ignored. 54 | #include // The watchdog timer - if the device becomes unresponsive and doesn't periodically reset the timer, then it will automatically reset once the timer reaches 0. 55 | 56 | 57 | 58 | // The setup function runs once when you press reset or power the board 59 | void setup() 60 | { 61 | Serial.begin(115200); // For serial debugging over USB. Always use 115200 baud. 62 | Serial.println("Hello"); 63 | 64 | pinMode(LED_BUILTIN, OUTPUT); 65 | 66 | if(blinkSeconds <= 0){ // This should not be set to 0 (or less). 67 | blinkSeconds = 1; 68 | } 69 | } 70 | 71 | 72 | // The loop function runs over and over again forever 73 | void loop() { 74 | 75 | digitalWrite(LED_BUILTIN, HIGH); // Turn the LED on (HIGH is the voltage level). 76 | delay( (blinkSeconds / 2)*1000 ); // Wait before we continue. 77 | 78 | digitalWrite(LED_BUILTIN, LOW); // Turn the LED off by making the voltage LOW. 79 | delay( (blinkSeconds / 2)*1000 ); // Wait before we continue. 80 | 81 | Serial.println("I just blinked my LED"); 82 | 83 | } 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Create Candle - the privacy friendly smart home 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Plant_health/Plant_health.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * DESCRIPTION 4 | * 5 | * This device can measure the moisture level of five different plants. It uses the cheap 'capacitive analog moisture sensor' that you can get for about a dollar a piece online. 6 | * 7 | * Each plant's moisture value can also be reacted to. If the moisture level goes below a set threshold, then a pin on the device is set to 'on'. This can be used to turn on a LED or, if you want, per-plant automated irrigation by connecting a little water pump or solenoid. 8 | * 9 | * SETTINGS */ 10 | 11 | #define NUMBER_OF_SENSORS 3 // Sensor count. How many moisture sensors have you connected? The maximum is five. 12 | 13 | //#define HAS_DISPLAY // Has OLED display. Did you attach an OLED display? 14 | 15 | #define WATERING // Thirsty triggering. Enabling this will allow you to set the minimum moisture level or each plant. When the moisture level drops to this level, a pin on the device will be turned on. You can use this to turn on a LED for each plant when that plant is thirsty. You can also use this to automatically trigger irrigation, for example by turning on a small pump or opening a valve to water the thirsty plant. 16 | 17 | #define MEASUREMENT_INTERVAL 3600 // Measurement interval. How many seconds should pass between checking on the plants and sending the data? Don't make this less than 15. The default is 3600 (one hour) 18 | 19 | //#define MY_REPEATER_FEATURE // Act as repeater. Do you want this node to also be act as repeater for other devices? 20 | 21 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 22 | 23 | 24 | /* END OF SETTINGS 25 | * 26 | * 27 | * 28 | */ 29 | 30 | 31 | //#define DEBUG 32 | //#define MY_DEBUG // Enable MySensors debug prints to serial monitor 33 | 34 | 35 | // Enable and select radio type attached 36 | #define MY_RADIO_RF24 37 | //#define MY_RADIO_NRF5_ESB 38 | //#define MY_RADIO_RFM69 39 | //#define MY_RADIO_RFM95 40 | 41 | // Set LOW transmit power level as default, if you have an amplified NRF-module and power your radio separately with a good regulator you can turn up PA level. Choose one: 42 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 43 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 44 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 45 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 46 | 47 | // Mysensors advanced settings 48 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 49 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 50 | #define MY_RF24_DATARATE RF24_1MBPS // The RF-Nano requires a 1Mbps datarate. 51 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 52 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 53 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 54 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 55 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 56 | 57 | // MySensors security 58 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" 59 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" 60 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup noise makes signing more secure. 61 | 62 | 63 | // PINS 64 | #ifdef RF_NANO 65 | // If you are using an RF-Nano, you have to switch CE and CS pins. 66 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 67 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 68 | #endif 69 | 70 | 71 | 72 | // 73 | // FURTHER SETTINGS 74 | // 75 | 76 | #define IRRIGATION_RELAYS 0 // How many irrigation relays are connected? 77 | #define LOOPDURATION 1000 // The main loop runs every x milliseconds. This main loop starts the modem, and from then on periodically requests the password. 78 | 79 | #define RADIO_DELAY 100 // Milliseconds betweeen radio signals during the presentation phase. 80 | 81 | #define ALLOW_CONNECTING_TO_NETWORK 82 | 83 | 84 | // 85 | // Do not change below this line 86 | // 87 | 88 | 89 | #ifdef HAS_DISPLAY 90 | #define OLED_I2C_ADDRESS 0x3C 91 | #include // Simple drivers for the screen. 92 | #include // "SSD1306Ascii". 93 | SSD1306AsciiAvrI2c oled; 94 | #endif 95 | 96 | unsigned long lastLoopTime = 0; 97 | unsigned long loopCounter = 0; // Counts the loops until the MEASUREMENT_INTERVAL value has been reached. Then new data is sent to the controller. 98 | boolean send_all_values = true; 99 | boolean may_transmit = true; 100 | boolean previous_may_transmit = true; 101 | boolean may_water = false; 102 | boolean previous_may_water = false; 103 | boolean actually_connected = false; 104 | boolean first_run = true; 105 | 106 | static const uint8_t analog_pins[] = {A0,A1,A2,A3,A6,A7}; // The pins to which moisture sensors are connected 107 | static const uint8_t trigger_pins[] = {2,3,4,5,6,7}; // The pins used to trigger an output, such as an LED or a water pump. Pins 0 and 1 are reserved for the USB serial connection, so they are skipped. 108 | byte moistureLevels[6] = {50, 50, 50, 50, 50, 50}; // Initial moisture levels, before any actual sensor values replace these. 109 | byte moistureThresholds[6] = {35, 35, 35, 35, 35, 35}; // for each plant we can have a unique moisture level to compare against. 110 | 111 | 112 | #define TRANSMIT_CHILD_ID 100 // Is the device allowed to transmit data? 113 | #define WATERING_CHILD_ID 101 // Is the device allowed to automatically trigger things? (LED or water pump). Sometimes you may want to disable this. 114 | 115 | #ifdef ALLOW_CONNECTING_TO_NETWORK 116 | 117 | #include 118 | 119 | 120 | MyMessage moisture_message(0, V_LEVEL); // Used to send moisture level data to the gateway. Should be V_LEVEL. 121 | MyMessage threshold_message(1, V_PERCENTAGE); // Used to create a dimmer on the controller that controls the mosture threshold; 122 | MyMessage relay_message(TRANSMIT_CHILD_ID, V_STATUS); // Used to manage the data transission toggle 123 | 124 | 125 | 126 | void before() 127 | { 128 | // Setup pins for input 129 | for (int i = 0; i < NUMBER_OF_SENSORS; i++) { 130 | pinMode(analog_pins[i], INPUT); 131 | pinMode(trigger_pins[i], OUTPUT); 132 | } 133 | } 134 | 135 | 136 | void presentation() 137 | { 138 | // Send the sketch version information to the gateway and Controller 139 | sendSketchInfo(F("Plant Health"), F("1.0")); wait(RADIO_DELAY); 140 | 141 | present(TRANSMIT_CHILD_ID, S_BINARY, F("Data transmission")); wait(RADIO_DELAY); 142 | present(WATERING_CHILD_ID, S_BINARY, F("Triggering")); wait(RADIO_DELAY); 143 | 144 | // Present the sensors 145 | 146 | // For now, it uses S_MOISTURE instead of S_MOISTURE. 147 | //for (byte i=0; i 1){ 153 | present(1, S_MOISTURE, F("Sensor 2")); wait(RADIO_DELAY); // present all the sensors 154 | #ifdef WATERING 155 | present(11, S_DIMMER, F("Threshold 2")); wait(RADIO_DELAY); // present the dimmers to set the level with. 156 | #endif 157 | if(NUMBER_OF_SENSORS > 2){ 158 | present(2, S_MOISTURE, F("Sensor 3")); wait(RADIO_DELAY); // present all the sensors 159 | #ifdef WATERING 160 | present(12, S_DIMMER, F("Threshold 3")); wait(RADIO_DELAY); // present the dimmers to set the level with. 161 | #endif 162 | if(NUMBER_OF_SENSORS > 3){ 163 | present(3, S_MOISTURE, F("Sensor 4")); wait(RADIO_DELAY); // present all the sensors 164 | #ifdef WATERING 165 | present(13, S_DIMMER, F("Threshold 4")); wait(RADIO_DELAY); // present the dimmers to set the level with. 166 | #endif 167 | if(NUMBER_OF_SENSORS > 4){ 168 | present(4, S_MOISTURE, F("Sensor 5")); wait(RADIO_DELAY); // present all the sensors 169 | #ifdef WATERING 170 | present(14, S_DIMMER, F("Threshold 5")); wait(RADIO_DELAY); // present the dimmers to set the level with. 171 | #endif 172 | if(NUMBER_OF_SENSORS > 5){ 173 | present(5, S_MOISTURE, F("Sensor 6")); wait(RADIO_DELAY); // present all the sensors 174 | #ifdef WATERING 175 | present(15, S_DIMMER, F("Threshold 6")); wait(RADIO_DELAY); // present the dimmers to set the level with. 176 | #endif 177 | } 178 | } 179 | } 180 | } 181 | } 182 | 183 | send_all_values = true; 184 | } 185 | #endif 186 | 187 | 188 | void setup() 189 | { 190 | Serial.begin(115200); // Start serial output of data. 191 | delay(1000); // Wait for serial connection to be initiated 192 | Serial.println(F("Hello, I am a plant health device")); 193 | 194 | #ifdef HAS_DISPLAY 195 | // Initiate the display 196 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 197 | oled.setFont(Adafruit5x7); 198 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 199 | oled.setScroll(false); 200 | oled.setCursor(0,0); 201 | oled.print(F("PLANT HEALTH")); 202 | oled.setCursor(0,2); 203 | oled.print(F("Wait...")); 204 | #endif 205 | 206 | #ifdef ALLOW_CONNECTING_TO_NETWORK 207 | if( isTransportReady() ){ // Check if a network connection has been established 208 | Serial.println(F("Connected to gateway!")); 209 | actually_connected = true; 210 | #ifdef HAS_DISPLAY 211 | // Show connection icon on the display 212 | oled.setCursor(85,0); 213 | oled.print(F("W")); 214 | #endif 215 | 216 | } 217 | else { 218 | Serial.println(F("! NO CONNECTION")); 219 | actually_connected = false; 220 | } 221 | #endif 222 | 223 | if( loadState(TRANSMIT_CHILD_ID) < 2 ){ 224 | previous_may_transmit = may_transmit = loadState(TRANSMIT_CHILD_ID); 225 | } 226 | else{ 227 | previous_may_transmit = may_transmit = true; // By default, data transmission of moisture levels is enabled. 228 | saveState(TRANSMIT_CHILD_ID, true); 229 | } 230 | 231 | if( loadState(WATERING_CHILD_ID) < 2 ){ 232 | previous_may_water = may_water = loadState(WATERING_CHILD_ID); 233 | } 234 | else{ 235 | previous_may_water = may_water = false; // To avoid water damage, by default watering is disabled. 236 | saveState(WATERING_CHILD_ID, false); 237 | } 238 | 239 | // load the threshold level from the built-in EEPROM memory. 240 | for (int i=0; i 99 ){ 243 | moistureThresholds[i] = 35; // If the EEPROM value is not set, reset it to default 244 | } 245 | Serial.print(F("Loaded: ")); Serial.println(moistureThresholds[i]); 246 | //request((i * 2) + 1, V_PERCENTAGE ); wait(RADIO_DELAY); 247 | } 248 | 249 | Serial.println(F("Warming up the sensors (15 seconds).")); // To avoid weird measurements 250 | wait(15000); 251 | 252 | wdt_enable(WDTO_2S); // Starts the watchdog timer. If it is not reset once every 2 seconds, then the entire device will automatically restart. 253 | } 254 | 255 | 256 | 257 | #ifdef ALLOW_CONNECTING_TO_NETWORK 258 | // 259 | // This function get run whenever the controller asks the device for up to date data. This happens, for example, after the controller or the mysensors addon has restarted itself. 260 | // 261 | void send_values() 262 | { 263 | send( relay_message.setSensor(TRANSMIT_CHILD_ID).set(may_transmit) ); wait(RADIO_DELAY); 264 | send( relay_message.setSensor(WATERING_CHILD_ID).set(may_water) ); wait(RADIO_DELAY); 265 | 266 | for (int i=0; i> New transmission state: ")); Serial.println(may_transmit); 296 | } 297 | 298 | if( may_water != previous_may_water ){ 299 | previous_may_water = may_water; 300 | send(relay_message.setSensor(WATERING_CHILD_ID).set(may_water)); 301 | Serial.print(F(">> New triggering state: ")); Serial.println(may_water); 302 | } 303 | 304 | 305 | 306 | 307 | // 308 | // MAIN LOOP 309 | // Runs every second. By counting how often this loop has run, it becomes possible to schedule all kinds of things without using a lot of memory. 310 | // Maximum time that can be scheduled is 4s * 250 loops = 1000 seconds. So the maximum time between sending data can be 16 minutes. 311 | // 312 | 313 | 314 | if( millis() - lastLoopTime > LOOPDURATION ){ 315 | lastLoopTime = millis(); 316 | wdt_reset(); // Reset the watchdog timer 317 | 318 | // Check if values are below the threshold 319 | 320 | for (int i=0; i 650 ){moistureLevel = 650;} 330 | moistureLevels[i] = map(moistureLevel,0,650,0,99); // The maximum voltage output of the capacitive sensor is 3V, so since we're measuring 0-5v about 614 is the theoretical highest value we'll ever get. 331 | Serial.print(i); 332 | Serial.print(F(" moisture level is ")); 333 | Serial.print(moistureLevels[i]); 334 | Serial.println(F("%")); 335 | 336 | if( analogRead(analog_pins[i]) < moistureThresholds[i] ){ 337 | Serial.print(F("Plant ")); Serial.print(i); Serial.print(F(" is below ")); Serial.println( moistureThresholds[i] ); 338 | digitalWrite(trigger_pins[i], HIGH); 339 | } 340 | else{ 341 | digitalWrite(trigger_pins[i], LOW); 342 | } 343 | 344 | 345 | 346 | 347 | 348 | #ifdef HAS_DISPLAY 349 | 350 | oled.set1X(); 351 | if( NUMBER_OF_SENSORS < 4 ){ 352 | oled.setCursor(0,(i * 2) + 2); // If there are more than 4 sensors, the text on the OLED display will get smaller to accomodate. 353 | } 354 | else { 355 | oled.setCursor(0,i + 2); 356 | } 357 | oled.print(i + 1); // Internally, the sensors are labeled 0 through 5. On the OLED display they are called 1 through 6. 358 | oled.print(F(" ")); 359 | if( NUMBER_OF_SENSORS < 4 ){ 360 | oled.set2X(); // Increase font size if less than 4 sensors are attached. 361 | } 362 | 363 | oled.print(moistureLevels[i]); 364 | oled.print(F("%")); 365 | 366 | if(moistureLevels[i] < 10){ 367 | oled.print(F(" ")); // Add an extra space after the numbers 0 through 9. This keeps things aligned on the display. 368 | } 369 | if( moistureLevels[i] < moistureThresholds[i] ){ 370 | oled.print(F(" WATER")); 371 | } 372 | else{ 373 | oled.print(" OK"); 374 | } 375 | oled.clearToEOL(); 376 | 377 | #endif 378 | 379 | } // End of looping over all sensors 380 | 381 | first_run == false; 382 | 383 | if(loopCounter < NUMBER_OF_SENSORS){ // During the first few loops the script will send updated data. 384 | #ifdef ALLOW_CONNECTING_TO_NETWORK 385 | if( may_transmit ){ 386 | send(moisture_message.setSensor(loopCounter).set(moistureLevels[loopCounter]),1); // Ask for a receipt, to make sure the data was sent. 387 | } 388 | #endif 389 | } 390 | 391 | 392 | loopCounter++; 393 | if(loopCounter >= MEASUREMENT_INTERVAL){ // If enough time has passed, the counter is reset, and new data is sent. 394 | loopCounter = 0; 395 | } 396 | 397 | if(loopCounter % 59 == 0){ // Every 59 seconds, let the controller know the device is alive. 398 | //sendHeartbeat(); // Lets the controller know the device is still alive, even is sensor data hasn't been transmitted in a while. 399 | actually_connected = false; // Whether the device is actually succesfully connected to the network. 400 | send( relay_message.setSensor(TRANSMIT_CHILD_ID).set(may_transmit),1 ); // We ask the controller to 'echo' the message back to the device. If this echo is not received, the connection must be down. 401 | } 402 | if(loopCounter % 59 == 1){ // A second later, check if the controller ECHO'd the message back to the device. 403 | #ifdef HAS_DISPLAY 404 | oled.setCursor(85,0); 405 | if( actually_connected ){ 406 | oled.print(F("W")); // Show connection icon on the display 407 | } 408 | else{ 409 | oled.print(F(" ")); // Hide connection icon on the display 410 | } 411 | #endif 412 | } 413 | 414 | 415 | #ifdef HAS_DISPLAY 416 | // Show countdown to measurement 417 | oled.set1X(); 418 | oled.setCursor(100,0); 419 | if(MEASUREMENT_INTERVAL - loopCounter > 59){ 420 | unsigned long minutes = (int) (MEASUREMENT_INTERVAL - loopCounter) / 60; 421 | oled.print(minutes); 422 | oled.print(F("m")); 423 | } 424 | else{ 425 | oled.print(MEASUREMENT_INTERVAL - loopCounter); 426 | } 427 | oled.clearToEOL(); 428 | #endif 429 | 430 | } 431 | } 432 | 433 | 434 | #ifdef ALLOW_CONNECTING_TO_NETWORK 435 | void receive(const MyMessage &message) 436 | { 437 | Serial.print(F("Incoming message for child #")); Serial.println(message.sensor); 438 | 439 | if (message.isAck()) { 440 | Serial.println(F("-Got ECHO")); 441 | actually_connected = true; 442 | return; 443 | } 444 | 445 | else if( message.type == V_STATUS && message.sensor == TRANSMIT_CHILD_ID ){ 446 | may_transmit = message.getBool(); 447 | saveState(TRANSMIT_CHILD_ID, may_transmit); 448 | } 449 | 450 | else if( message.type == V_STATUS && message.sensor == WATERING_CHILD_ID ){ 451 | may_water = message.getBool(); 452 | saveState(WATERING_CHILD_ID, may_water); 453 | } 454 | 455 | #ifdef WATERING 456 | else if (message.type == V_PERCENTAGE) { 457 | 458 | // Retrieve the power or dim level from the incoming request message 459 | int requestedLevel = atoi( message.data ); 460 | Serial.print(F("Requested level is ")); 461 | Serial.println( requestedLevel ); 462 | 463 | byte sensorID = (message.sensor - 10); // Children ID's of thresholds are the sensor child ID + 10 464 | 465 | #ifdef DEBUG 466 | Serial.print(F("Before clipping requested level: ")); 467 | Serial.println( requestedLevel ); 468 | #endif 469 | 470 | // Clip incoming level to valid range of 0 to 100 471 | if(requestedLevel > 100){ requestedLevel = 100;} 472 | if(requestedLevel < 0){ requestedLevel = 0;} 473 | 474 | if( requestedLevel < 1 || requestedLevel > 99 ){ 475 | // Erroneous value, since it cannot act as a treshold at these extreme values. 476 | } 477 | else{ 478 | Serial.print(F("Changing threshold from ")); 479 | Serial.println( moistureThresholds[sensorID] ); 480 | Serial.print(F(" to ")); 481 | Serial.print( byte(requestedLevel) ); 482 | moistureThresholds[sensorID] = byte(requestedLevel); 483 | saveState(sensorID, moistureThresholds[sensorID]); 484 | } 485 | } 486 | #endif // End of automatic watering 487 | } 488 | #endif // end of allow connecting to network 489 | 490 | 491 | 492 | 493 | 494 | /* THANKS TO 495 | * 496 | * The MySensors Arduino library handles the wireless radio link and protocol 497 | * between your home built sensors/actuators and HA controller of choice. 498 | * The sensors forms a self healing radio network with optional repeaters. Each 499 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 500 | * network topology allowing messages to be routed to nodes. 501 | * 502 | * Created by Henrik Ekblad 503 | * Copyright (C) 2013-2015 Sensnology AB 504 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 505 | * 506 | * Documentation: http://www.mysensors.org 507 | * Support Forum: http://forum.mysensors.org 508 | * 509 | * This program is free software; you can redistribute it and/or 510 | * modify it under the terms of the GNU General Public License 511 | * version 2 as published by the Free Software Foundation. 512 | * 513 | */ 514 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Devices 2 | This is the code for the Candle smart home devices. See: 3 | 4 | https://www.candlesmarthome.com/devices for more details. 5 | 6 | All Candle devices allow you to easily toggle whether they are allowed to send sensitive data to the central controller. 7 | 8 | 9 | 10 | 11 | ### Smart lock 12 | 13 | The smart lock allows you to control two electric locks. 14 | 15 | Candle can work without an internet connection. But how can you uplock your front door when you're not home, a popular feature, when there's no internet? The Candle smart lock solves this by adding a GSM modem. There are multiple safety features to make this work well, such as: 16 | - A password is required, one for each door. 17 | - Only phone numbers you provide can toggle the lock 18 | - You can decide when the lock can be remote-togged in the first place. Only enable it when you're on holiday, for example. 19 | 20 | 21 | ### CO2 sensor 22 | 23 | Measuring CO2 can be useful, as high levels can influence how well you sleep and how energized you feel. But CO2 levels can also reveal how many people are in a room. That's why the Candle CO2 sensor has a unique privacy feature: you can tell it to generate fake data for a while. This allows you to pretend you're alone in a room when you're not, for example. 24 | 25 | 26 | ### Dust sensor 27 | 28 | Measure the fine dust levels in a room. 29 | 30 | 31 | ### Temperature and more sensor 32 | 33 | This sensor measures temperature (in Celcius or Fahrenheit), air humidity and air pressure. 34 | 35 | It has an optional TFT screen on which it displays: 36 | - Temperature, also as a simple graph 37 | - humidity, also as a simple graph 38 | - Air pressure 39 | - Weather predition icon 40 | 41 | 42 | ### Energy use meter 43 | 44 | This devices detects light pulses from your electricity meter, and uses this to determine how much power your home uses. It allows you to see watts your home is using at the moment, as well as see longer trends as Kwh in the last hours and/or days. 45 | 46 | If you attach a touch screen you can get simple bar graphs and see live data about the power your home is using. 47 | 48 | 49 | ### Smart alarm clock 50 | 51 | This alarm clock uses a radar sensor to detect when you move in your sleep, and will try to wake you when you're already at a light sleep point in your sleep cycle. If you set it to wake you at 8am, it will start looking for an opportune moment from 7:30. 52 | 53 | It can wake you up slowly with its own LED, but the intended use is to one by one switch on other lights in the room. If all else fails, it can sound a classic buzzer. 54 | 55 | 56 | ### Plant health sensor 57 | 58 | Measure the moisture level of up to 5 plants. You can also set the minimal moisture level that each plant requires. When this level is reached, a "thirsty" switch (each plant has one) will be toggled. You can then create your own automatations to handle this, such as notifying you, or even watering them automatically. 59 | 60 | The code already has some hidden functionality to toggle 5 relays directly. 61 | 62 | 63 | ### Anemone 64 | 65 | This device allows you to control your internet connection. It is designed to disconnect the internet cable going into your router (although you could also limit connectivity to a specific device). 66 | 67 | Candle doesn't require an internet connection to work, and this device enforces that idea. It's a device that producers of cloud dependent smart devices could and would never produce. 68 | 69 | 70 | ### Signal Hub 71 | 72 | This is a device that can learn to recognise and also replay almost any signal from 433Mhz devices. Many people own these type of devices, such as power sockets, because they are cheap and simple. 73 | 74 | It works by recognizing patterns in transmissions. Once recognized, it stores the minimal pattern required in a very space efficient way, while preserving the timings. It can store up to 20 patterns in just 512 bytes. 75 | 76 | It is highly recommended to attach an Open Smart touch screen. This allows you to replay signals by pressing buttons on the screen. It also guides you through the proces of copying the signals, such as when to press the buttons on a remote control that you are copying. 77 | - Signals are stored in the Arduino memory, and it can hold between 10 and 20 signals. 78 | 79 | With different settings it can copy and replay infrared signals instead. 80 | 81 | 82 | ### Candle receiver 83 | 84 | This is the gateway that plugs into your Raspberry Pi. It sets up the connection with the other devices. 85 | -------------------------------------------------------------------------------- /Smart_lock/SoftwareSerial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerial.cpp (formerly NewSoftSerial.cpp) - 3 | Multi-instance software serial library for Arduino/Wiring 4 | -- Interrupt-driven receive and other improvements by ladyada 5 | (http://ladyada.net) 6 | -- Tuning, circular buffer, derivation from class Print/Stream, 7 | multi-instance support, porting to 8MHz processors, 8 | various optimizations, PROGMEM delay tables, inverse logic and 9 | direct port writing by Mikal Hart (http://www.arduiniana.org) 10 | -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) 11 | -- 20MHz processor support by Garrett Mace (http://www.macetech.com) 12 | -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) 13 | 14 | This library is free software; you can redistribute it and/or 15 | modify it under the terms of the GNU Lesser General Public 16 | License as published by the Free Software Foundation; either 17 | version 2.1 of the License, or (at your option) any later version. 18 | 19 | This library is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | Lesser General Public License for more details. 23 | 24 | You should have received a copy of the GNU Lesser General Public 25 | License along with this library; if not, write to the Free Software 26 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 27 | 28 | The latest version of this library can always be found at 29 | http://arduiniana.org. 30 | */ 31 | 32 | // When set, _DEBUG co-opts pins 11 and 13 for debugging with an 33 | // oscilloscope or logic analyzer. Beware: it also slightly modifies 34 | // the bit times, so don't rely on it too much at high baud rates 35 | #define _DEBUG 0 36 | #define _DEBUG_PIN1 11 37 | #define _DEBUG_PIN2 13 38 | // 39 | // Includes 40 | // 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | // 48 | // Statics 49 | // 50 | SoftwareSerial *SoftwareSerial::active_object = 0; 51 | uint8_t SoftwareSerial::_receive_buffer[_SS_MAX_RX_BUFF]; 52 | volatile uint8_t SoftwareSerial::_receive_buffer_tail = 0; 53 | volatile uint8_t SoftwareSerial::_receive_buffer_head = 0; 54 | 55 | // 56 | // Debugging 57 | // 58 | // This function generates a brief pulse 59 | // for debugging or measuring on an oscilloscope. 60 | #if _DEBUG 61 | inline void DebugPulse(uint8_t pin, uint8_t count) 62 | { 63 | volatile uint8_t *pport = portOutputRegister(digitalPinToPort(pin)); 64 | 65 | uint8_t val = *pport; 66 | while (count--) 67 | { 68 | *pport = val | digitalPinToBitMask(pin); 69 | *pport = val; 70 | } 71 | } 72 | #else 73 | inline void DebugPulse(uint8_t, uint8_t) {} 74 | #endif 75 | 76 | // 77 | // Private methods 78 | // 79 | 80 | /* static */ 81 | inline void SoftwareSerial::tunedDelay(uint16_t delay) { 82 | _delay_loop_2(delay); 83 | } 84 | 85 | // This function sets the current object as the "listening" 86 | // one and returns true if it replaces another 87 | bool SoftwareSerial::listen() 88 | { 89 | if (!_rx_delay_stopbit) 90 | return false; 91 | 92 | if (active_object != this) 93 | { 94 | if (active_object) 95 | active_object->stopListening(); 96 | 97 | _buffer_overflow = false; 98 | _receive_buffer_head = _receive_buffer_tail = 0; 99 | active_object = this; 100 | 101 | setRxIntMsk(true); 102 | return true; 103 | } 104 | 105 | return false; 106 | } 107 | 108 | // Stop listening. Returns true if we were actually listening. 109 | bool SoftwareSerial::stopListening() 110 | { 111 | if (active_object == this) 112 | { 113 | setRxIntMsk(false); 114 | active_object = NULL; 115 | return true; 116 | } 117 | return false; 118 | } 119 | 120 | // 121 | // The receive routine called by the interrupt handler 122 | // 123 | void SoftwareSerial::recv() 124 | { 125 | 126 | #if GCC_VERSION < 40302 127 | // Work-around for avr-gcc 4.3.0 OSX version bug 128 | // Preserve the registers that the compiler misses 129 | // (courtesy of Arduino forum user *etracer*) 130 | asm volatile( 131 | "push r18 \n\t" 132 | "push r19 \n\t" 133 | "push r20 \n\t" 134 | "push r21 \n\t" 135 | "push r22 \n\t" 136 | "push r23 \n\t" 137 | "push r26 \n\t" 138 | "push r27 \n\t" 139 | ::); 140 | #endif 141 | 142 | uint8_t d = 0; 143 | 144 | // If RX line is high, then we don't see any start bit 145 | // so interrupt is probably not for us 146 | if (_inverse_logic ? rx_pin_read() : !rx_pin_read()) 147 | { 148 | // Disable further interrupts during reception, this prevents 149 | // triggering another interrupt directly after we return, which can 150 | // cause problems at higher baudrates. 151 | setRxIntMsk(false); 152 | 153 | // Wait approximately 1/2 of a bit width to "center" the sample 154 | tunedDelay(_rx_delay_centering); 155 | DebugPulse(_DEBUG_PIN2, 1); 156 | 157 | // Read each of the 8 bits 158 | for (uint8_t i=8; i > 0; --i) 159 | { 160 | tunedDelay(_rx_delay_intrabit); 161 | d >>= 1; 162 | DebugPulse(_DEBUG_PIN2, 1); 163 | if (rx_pin_read()) 164 | d |= 0x80; 165 | } 166 | 167 | if (_inverse_logic) 168 | d = ~d; 169 | 170 | // if buffer full, set the overflow flag and return 171 | uint8_t next = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; 172 | if (next != _receive_buffer_head) 173 | { 174 | // save new data in buffer: tail points to where byte goes 175 | _receive_buffer[_receive_buffer_tail] = d; // save new byte 176 | _receive_buffer_tail = next; 177 | } 178 | else 179 | { 180 | DebugPulse(_DEBUG_PIN1, 1); 181 | _buffer_overflow = true; 182 | } 183 | 184 | // skip the stop bit 185 | tunedDelay(_rx_delay_stopbit); 186 | DebugPulse(_DEBUG_PIN1, 1); 187 | 188 | // Re-enable interrupts when we're sure to be inside the stop bit 189 | setRxIntMsk(true); 190 | 191 | } 192 | 193 | #if GCC_VERSION < 40302 194 | // Work-around for avr-gcc 4.3.0 OSX version bug 195 | // Restore the registers that the compiler misses 196 | asm volatile( 197 | "pop r27 \n\t" 198 | "pop r26 \n\t" 199 | "pop r23 \n\t" 200 | "pop r22 \n\t" 201 | "pop r21 \n\t" 202 | "pop r20 \n\t" 203 | "pop r19 \n\t" 204 | "pop r18 \n\t" 205 | ::); 206 | #endif 207 | } 208 | 209 | uint8_t SoftwareSerial::rx_pin_read() 210 | { 211 | return *_receivePortRegister & _receiveBitMask; 212 | } 213 | 214 | // 215 | // Interrupt handling 216 | // 217 | 218 | /* static */ 219 | inline void SoftwareSerial::handle_interrupt() 220 | { 221 | if (active_object) 222 | { 223 | active_object->recv(); 224 | } 225 | } 226 | 227 | #if defined(PCINT0_vect) 228 | ISR(PCINT0_vect) 229 | { 230 | SoftwareSerial::handle_interrupt(); 231 | } 232 | #endif 233 | 234 | #if defined(PCINT1_vect) 235 | ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect)); 236 | #endif 237 | 238 | #if defined(PCINT2_vect) 239 | ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect)); 240 | #endif 241 | 242 | #if defined(PCINT3_vect) 243 | ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect)); 244 | #endif 245 | 246 | // 247 | // Constructor 248 | // 249 | SoftwareSerial::SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic /* = false */) : 250 | _rx_delay_centering(0), 251 | _rx_delay_intrabit(0), 252 | _rx_delay_stopbit(0), 253 | _tx_delay(0), 254 | _buffer_overflow(false), 255 | _inverse_logic(inverse_logic) 256 | { 257 | setTX(transmitPin); 258 | setRX(receivePin); 259 | } 260 | 261 | // 262 | // Destructor 263 | // 264 | SoftwareSerial::~SoftwareSerial() 265 | { 266 | end(); 267 | } 268 | 269 | void SoftwareSerial::setTX(uint8_t tx) 270 | { 271 | // First write, then set output. If we do this the other way around, 272 | // the pin would be output low for a short while before switching to 273 | // output high. Now, it is input with pullup for a short while, which 274 | // is fine. With inverse logic, either order is fine. 275 | digitalWrite(tx, _inverse_logic ? LOW : HIGH); 276 | pinMode(tx, OUTPUT); 277 | _transmitBitMask = digitalPinToBitMask(tx); 278 | uint8_t port = digitalPinToPort(tx); 279 | _transmitPortRegister = portOutputRegister(port); 280 | } 281 | 282 | void SoftwareSerial::setRX(uint8_t rx) 283 | { 284 | pinMode(rx, INPUT); 285 | if (!_inverse_logic) 286 | digitalWrite(rx, HIGH); // pullup for normal logic! 287 | _receivePin = rx; 288 | _receiveBitMask = digitalPinToBitMask(rx); 289 | uint8_t port = digitalPinToPort(rx); 290 | _receivePortRegister = portInputRegister(port); 291 | } 292 | 293 | uint16_t SoftwareSerial::subtract_cap(uint16_t num, uint16_t sub) { 294 | if (num > sub) 295 | return num - sub; 296 | else 297 | return 1; 298 | } 299 | 300 | // 301 | // Public methods 302 | // 303 | 304 | void SoftwareSerial::begin(long speed) 305 | { 306 | _rx_delay_centering = _rx_delay_intrabit = _rx_delay_stopbit = _tx_delay = 0; 307 | 308 | // Precalculate the various delays, in number of 4-cycle delays 309 | uint16_t bit_delay = (F_CPU / speed) / 4; 310 | 311 | // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, 312 | // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, 313 | // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit 314 | // These are all close enough to just use 15 cycles, since the inter-bit 315 | // timings are the most critical (deviations stack 8 times) 316 | _tx_delay = subtract_cap(bit_delay, 15 / 4); 317 | 318 | // Only setup rx when we have a valid PCINT for this pin 319 | if (digitalPinToPCICR(_receivePin)) { 320 | #if GCC_VERSION > 40800 321 | // Timings counted from gcc 4.8.2 output. This works up to 115200 on 322 | // 16Mhz and 57600 on 8Mhz. 323 | // 324 | // When the start bit occurs, there are 3 or 4 cycles before the 325 | // interrupt flag is set, 4 cycles before the PC is set to the right 326 | // interrupt vector address and the old PC is pushed on the stack, 327 | // and then 75 cycles of instructions (including the RJMP in the 328 | // ISR vector table) until the first delay. After the delay, there 329 | // are 17 more cycles until the pin value is read (excluding the 330 | // delay in the loop). 331 | // We want to have a total delay of 1.5 bit time. Inside the loop, 332 | // we already wait for 1 bit time - 23 cycles, so here we wait for 333 | // 0.5 bit time - (71 + 18 - 22) cycles. 334 | _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 75 + 17 - 23) / 4); 335 | 336 | // There are 23 cycles in each loop iteration (excluding the delay) 337 | _rx_delay_intrabit = subtract_cap(bit_delay, 23 / 4); 338 | 339 | // There are 37 cycles from the last bit read to the start of 340 | // stopbit delay and 11 cycles from the delay until the interrupt 341 | // mask is enabled again (which _must_ happen during the stopbit). 342 | // This delay aims at 3/4 of a bit time, meaning the end of the 343 | // delay will be at 1/4th of the stopbit. This allows some extra 344 | // time for ISR cleanup, which makes 115200 baud at 16Mhz work more 345 | // reliably 346 | _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (37 + 11) / 4); 347 | #else // Timings counted from gcc 4.3.2 output 348 | // Note that this code is a _lot_ slower, mostly due to bad register 349 | // allocation choices of gcc. This works up to 57600 on 16Mhz and 350 | // 38400 on 8Mhz. 351 | _rx_delay_centering = subtract_cap(bit_delay / 2, (4 + 4 + 97 + 29 - 11) / 4); 352 | _rx_delay_intrabit = subtract_cap(bit_delay, 11 / 4); 353 | _rx_delay_stopbit = subtract_cap(bit_delay * 3 / 4, (44 + 17) / 4); 354 | #endif 355 | 356 | 357 | // Enable the PCINT for the entire port here, but never disable it 358 | // (others might also need it, so we disable the interrupt by using 359 | // the per-pin PCMSK register). 360 | *digitalPinToPCICR(_receivePin) |= _BV(digitalPinToPCICRbit(_receivePin)); 361 | // Precalculate the pcint mask register and value, so setRxIntMask 362 | // can be used inside the ISR without costing too much time. 363 | _pcint_maskreg = digitalPinToPCMSK(_receivePin); 364 | _pcint_maskvalue = _BV(digitalPinToPCMSKbit(_receivePin)); 365 | 366 | tunedDelay(_tx_delay); // if we were low this establishes the end 367 | } 368 | 369 | #if _DEBUG 370 | pinMode(_DEBUG_PIN1, OUTPUT); 371 | pinMode(_DEBUG_PIN2, OUTPUT); 372 | #endif 373 | 374 | listen(); 375 | } 376 | 377 | void SoftwareSerial::setRxIntMsk(bool enable) 378 | { 379 | if (enable) 380 | *_pcint_maskreg |= _pcint_maskvalue; 381 | else 382 | *_pcint_maskreg &= ~_pcint_maskvalue; 383 | } 384 | 385 | void SoftwareSerial::end() 386 | { 387 | stopListening(); 388 | } 389 | 390 | 391 | // Read data from buffer 392 | int SoftwareSerial::read() 393 | { 394 | if (!isListening()) 395 | return -1; 396 | 397 | // Empty buffer? 398 | if (_receive_buffer_head == _receive_buffer_tail) 399 | return -1; 400 | 401 | // Read from "head" 402 | uint8_t d = _receive_buffer[_receive_buffer_head]; // grab next byte 403 | _receive_buffer_head = (_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; 404 | return d; 405 | } 406 | 407 | int SoftwareSerial::available() 408 | { 409 | if (!isListening()) 410 | return 0; 411 | 412 | return (_receive_buffer_tail + _SS_MAX_RX_BUFF - _receive_buffer_head) % _SS_MAX_RX_BUFF; 413 | } 414 | 415 | size_t SoftwareSerial::write(uint8_t b) 416 | { 417 | if (_tx_delay == 0) { 418 | setWriteError(); 419 | return 0; 420 | } 421 | 422 | // By declaring these as local variables, the compiler will put them 423 | // in registers _before_ disabling interrupts and entering the 424 | // critical timing sections below, which makes it a lot easier to 425 | // verify the cycle timings 426 | volatile uint8_t *reg = _transmitPortRegister; 427 | uint8_t reg_mask = _transmitBitMask; 428 | uint8_t inv_mask = ~_transmitBitMask; 429 | uint8_t oldSREG = SREG; 430 | bool inv = _inverse_logic; 431 | uint16_t delay = _tx_delay; 432 | 433 | if (inv) 434 | b = ~b; 435 | 436 | cli(); // turn off interrupts for a clean txmit 437 | 438 | // Write the start bit 439 | if (inv) 440 | *reg |= reg_mask; 441 | else 442 | *reg &= inv_mask; 443 | 444 | tunedDelay(delay); 445 | 446 | // Write each of the 8 bits 447 | for (uint8_t i = 8; i > 0; --i) 448 | { 449 | if (b & 1) // choose bit 450 | *reg |= reg_mask; // send 1 451 | else 452 | *reg &= inv_mask; // send 0 453 | 454 | tunedDelay(delay); 455 | b >>= 1; 456 | } 457 | 458 | // restore pin to natural state 459 | if (inv) 460 | *reg &= inv_mask; 461 | else 462 | *reg |= reg_mask; 463 | 464 | SREG = oldSREG; // turn interrupts back on 465 | tunedDelay(_tx_delay); 466 | 467 | return 1; 468 | } 469 | 470 | void SoftwareSerial::flush() 471 | { 472 | // There is no tx buffering, simply return 473 | } 474 | 475 | int SoftwareSerial::peek() 476 | { 477 | if (!isListening()) 478 | return -1; 479 | 480 | // Empty buffer? 481 | if (_receive_buffer_head == _receive_buffer_tail) 482 | return -1; 483 | 484 | // Read from "head" 485 | return _receive_buffer[_receive_buffer_head]; 486 | } 487 | -------------------------------------------------------------------------------- /Smart_lock/SoftwareSerial.h: -------------------------------------------------------------------------------- 1 | /* 2 | SoftwareSerial.h (formerly NewSoftSerial.h) - 3 | Multi-instance software serial library for Arduino/Wiring 4 | -- Interrupt-driven receive and other improvements by ladyada 5 | (http://ladyada.net) 6 | -- Tuning, circular buffer, derivation from class Print/Stream, 7 | multi-instance support, porting to 8MHz processors, 8 | various optimizations, PROGMEM delay tables, inverse logic and 9 | direct port writing by Mikal Hart (http://www.arduiniana.org) 10 | -- Pin change interrupt macros by Paul Stoffregen (http://www.pjrc.com) 11 | -- 20MHz processor support by Garrett Mace (http://www.macetech.com) 12 | -- ATmega1280/2560 support by Brett Hagman (http://www.roguerobotics.com/) 13 | 14 | This library is free software; you can redistribute it and/or 15 | modify it under the terms of the GNU Lesser General Public 16 | License as published by the Free Software Foundation; either 17 | version 2.1 of the License, or (at your option) any later version. 18 | 19 | This library is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 | Lesser General Public License for more details. 23 | 24 | You should have received a copy of the GNU Lesser General Public 25 | License along with this library; if not, write to the Free Software 26 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 27 | 28 | The latest version of this library can always be found at 29 | http://arduiniana.org. 30 | */ 31 | 32 | #ifndef SoftwareSerial_h 33 | #define SoftwareSerial_h 34 | 35 | #include 36 | #include 37 | 38 | /****************************************************************************** 39 | * Definitions 40 | ******************************************************************************/ 41 | 42 | #ifndef _SS_MAX_RX_BUFF 43 | #define _SS_MAX_RX_BUFF 255 // RX buffer size 44 | #endif 45 | 46 | #ifndef GCC_VERSION 47 | #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) 48 | #endif 49 | 50 | class SoftwareSerial : public Stream 51 | { 52 | private: 53 | // per object data 54 | uint8_t _receivePin; 55 | uint8_t _receiveBitMask; 56 | volatile uint8_t *_receivePortRegister; 57 | uint8_t _transmitBitMask; 58 | volatile uint8_t *_transmitPortRegister; 59 | volatile uint8_t *_pcint_maskreg; 60 | uint8_t _pcint_maskvalue; 61 | 62 | // Expressed as 4-cycle delays (must never be 0!) 63 | uint16_t _rx_delay_centering; 64 | uint16_t _rx_delay_intrabit; 65 | uint16_t _rx_delay_stopbit; 66 | uint16_t _tx_delay; 67 | 68 | uint16_t _buffer_overflow:1; 69 | uint16_t _inverse_logic:1; 70 | 71 | // static data 72 | static uint8_t _receive_buffer[_SS_MAX_RX_BUFF]; 73 | static volatile uint8_t _receive_buffer_tail; 74 | static volatile uint8_t _receive_buffer_head; 75 | static SoftwareSerial *active_object; 76 | 77 | // private methods 78 | inline void recv() __attribute__((__always_inline__)); 79 | uint8_t rx_pin_read(); 80 | void setTX(uint8_t transmitPin); 81 | void setRX(uint8_t receivePin); 82 | inline void setRxIntMsk(bool enable) __attribute__((__always_inline__)); 83 | 84 | // Return num - sub, or 1 if the result would be < 1 85 | static uint16_t subtract_cap(uint16_t num, uint16_t sub); 86 | 87 | // private static method for timing 88 | static inline void tunedDelay(uint16_t delay); 89 | 90 | public: 91 | // public methods 92 | SoftwareSerial(uint8_t receivePin, uint8_t transmitPin, bool inverse_logic = false); 93 | ~SoftwareSerial(); 94 | void begin(long speed); 95 | bool listen(); 96 | void end(); 97 | bool isListening() { return this == active_object; } 98 | bool stopListening(); 99 | bool overflow() { bool ret = _buffer_overflow; if (ret) _buffer_overflow = false; return ret; } 100 | int peek(); 101 | 102 | virtual size_t write(uint8_t byte); 103 | virtual int read(); 104 | virtual int available(); 105 | virtual void flush(); 106 | operator bool() { return true; } 107 | 108 | using Print::write; 109 | 110 | // public only for easy access by interrupt handlers 111 | static inline void handle_interrupt() __attribute__((__always_inline__)); 112 | }; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /Temperature/Temperature.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * This is a temperature and humidity sensor for the Candle project. 4 | * 5 | * It uses the DHT22 sensor to measure temperature and humidity. 6 | * 7 | * These values can be displayed on an OLED screen. 8 | * 9 | * 10 | * SETTINGS */ 11 | 12 | #define MEASUREMENT_INTERVAL 30 // How many seconds do you want between each measurement? The minimum is 4 seconds. 13 | 14 | #define HAS_DISPLAY // Does the sensor have a little OLED display attached? 15 | 16 | //#define MY_REPEATER_FEATURE // Act as a repeater? The devices can pass along messages to each other to increase the range of your network. 17 | 18 | #define RF_NANO // RF-Nano. Check this box if you are using the RF-Nano Arduino, which has a built in radio. The Candle project uses the RF-Nano. 19 | 20 | /* END OF SETTINGS 21 | * 22 | * 23 | */ 24 | 25 | 26 | // PINS 27 | #define SENSOR_PIN 4 // At what pin is the temperature sensor connected? 28 | 29 | #ifdef RF_NANO 30 | // If you are using an RF-Nano, you have to switch CE and CS pins. 31 | #define MY_RF24_CS_PIN 9 // Used by the MySensors library. 32 | #define MY_RF24_CE_PIN 10 // Used by the MySensors library. 33 | #endif 34 | 35 | //#define DEBUG 36 | //#define MY_DEBUG // MySensors debugging. Enable MySensors debug output to the serial monitor, so you can check if the radio is working ok. 37 | 38 | // Enable and select the attached radio type 39 | #define MY_RADIO_RF24 // This is a common and simple radio used with MySensors. Downside is that it uses the same frequency space as WiFi. 40 | //#define MY_RADIO_NRF5_ESB // This is a new type of device that is arduino and radio all in one. Currently not suitable for beginners yet. 41 | //#define MY_RADIO_RFM69 // This is an open source radio on the 433mhz frequency. Great range and built-in encryption, but more expensive and little more difficult to connect. 42 | //#define MY_RADIO_RFM95 // This is a LoRaWan radio, which can have a range of 10km. 43 | 44 | // MySensors: Choose your desired radio power level. High power can cause issues on cheap Chinese NRF24 radio's. 45 | //#define MY_RF24_PA_LEVEL RF24_PA_MIN 46 | //#define MY_RF24_PA_LEVEL RF24_PA_LOW 47 | //#define MY_RF24_PA_LEVEL RF24_PA_HIGH 48 | #define MY_RF24_PA_LEVEL RF24_PA_MAX 49 | 50 | // Mysensors security 51 | #define MY_ENCRYPTION_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 52 | //#define MY_SECURITY_SIMPLE_PASSWD "changeme" // Be aware, the length of the password has an effect on memory use. 53 | //#define MY_SIGNING_SOFT_RANDOMSEED_PIN A7 // Setting a pin to pickup random electromagnetic noise helps make encryption more secure. 54 | 55 | // Mysensors advanced settings 56 | #define MY_TRANSPORT_WAIT_READY_MS 10000 // Try connecting for 10 seconds. Otherwise just continue. 57 | //#define MY_RF24_CHANNEL 100 // In EU the default channel 76 overlaps with wifi, so you could try using channel 100. But you will have to set this up on every device, and also on the controller. 58 | #define MY_RF24_DATARATE RF24_1MBPS // Slower datarate makes the network more stable? 59 | //#define MY_NODE_ID 10 // Giving a node a manual ID can in rare cases fix connection issues. 60 | //#define MY_PARENT_NODE_ID 0 // Fixating the ID of the gatewaynode can in rare cases fix connection issues. 61 | //#define MY_PARENT_NODE_IS_STATIC // Used together with setting the parent node ID. Daking the controller ID static can in rare cases fix connection issues. 62 | #define MY_SPLASH_SCREEN_DISABLED // Saves a little memory. 63 | //#define MY_DISABLE_RAM_ROUTING_TABLE_FEATURE // Saves a little memory. 64 | 65 | 66 | 67 | // LIBRARIES (in the Arduino IDE go to Sketch -> Include Library -> Manage Libraries to add these if you don't have them installed yet.) 68 | #include // MySensors library 69 | #include // DHT22 temperature and humidity sensor library 70 | 71 | 72 | // DHT sensor 73 | SimpleDHT22 dht22(SENSOR_PIN); 74 | //SimpleDHT11 dht22(SENSOR_PIN); // If you'd like to use the older version of the sensor, use this line instead. 75 | 76 | float temperature_value = -100; 77 | float humidity_value = -100; 78 | int err = SimpleDHTErrSuccess; 79 | 80 | #ifdef HAS_DISPLAY 81 | #define OLED_I2C_ADDRESS 0x3C 82 | #include // Simple drivers for the screen. 83 | #include // "SSD1306Ascii". 84 | SSD1306AsciiAvrI2c oled; 85 | #endif 86 | 87 | 88 | 89 | // Mysensors settings 90 | #define CHILD_ID_TEMPERATURE 1 // The child ID of the sensor that will be presented to the controller. 91 | #define CHILD_ID_HUMIDITY 2 // The child ID of the sensor that will be presented to the controller. 92 | 93 | 94 | const byte RF_DELAY = 150; // A few milliseconds delay between sending makes the radio happy. 95 | 96 | MyMessage temperature_message(CHILD_ID_TEMPERATURE, V_TEMP); // Sets up the message format that we'll be sending to the MySensors gateway later. 97 | MyMessage humidity_message(CHILD_ID_HUMIDITY, V_HUM); // Sets up the message format that we'll be sending to the MySensors gateway later. 98 | 99 | 100 | // Other 101 | #define LOOPDURATION 1000 // The main loop runs every x milliseconds. Normally this sensor has a 'heartbeat' of once every second. 102 | boolean send_all_values = true; // If the controller asks the devive to re-present itself, then this is used to also resend all the current sensor values. 103 | boolean received_echo = false; // If we get a response from the controller, then this is set to true. 104 | 105 | 106 | void presentation() 107 | { 108 | // Send the sketch version information to the gateway and Controller 109 | sendSketchInfo(F("Temperature sensor"), F("1.0")); 110 | 111 | // Register all sensors to gateway: 112 | present(CHILD_ID_TEMPERATURE, S_TEMP, F("Temperature")); wait(RF_DELAY); 113 | present(CHILD_ID_HUMIDITY, S_HUM, F("Humidity")); wait(RF_DELAY); 114 | 115 | send_all_values = true; 116 | } 117 | 118 | 119 | void setup() { 120 | Serial.begin(115200); 121 | while (!Serial) {} 122 | 123 | Serial.println(F("Hello, I am a temperature and humidity sensor")); 124 | 125 | #ifdef HAS_DISPLAY 126 | // Initiate the display 127 | oled.begin(&Adafruit128x64, OLED_I2C_ADDRESS); 128 | oled.setFont(Adafruit5x7); 129 | oled.ssd1306WriteCmd(SSD1306_DISPLAYON); 130 | oled.setScroll(false); 131 | oled.setCursor(0,0); 132 | oled.print(F("TEMPERATURE")); 133 | #endif 134 | 135 | 136 | if( isTransportReady() ){ // Check if a network connection has been established 137 | Serial.println(F("Connected to gateway!")); 138 | } 139 | else { 140 | Serial.println(F("! NO CONNECTION")); 141 | } 142 | 143 | 144 | #ifdef HAS_DISPLAY 145 | //oled.setCursor(0,2); // The labels are shown slightly above the values. 146 | //oled.print(F("Temperature:")); 147 | oled.setCursor(0,5); // The labels are shown slightly above the values. 148 | oled.print(F("Humidity:")); 149 | #endif 150 | 151 | wdt_enable(WDTO_8S); // Starts the watchdog timer. If it is not reset once every few seconds, then the entire device will automatically restart. 152 | } 153 | 154 | 155 | void send_values() 156 | { 157 | if( temperature_value != -100){ 158 | send(temperature_message.setSensor(CHILD_ID_TEMPERATURE).set(temperature_value,1),1); 159 | } 160 | if( humidity_value != -100){ 161 | send(humidity_message.setSensor(CHILD_ID_HUMIDITY).set(humidity_value,1),1); 162 | } 163 | } 164 | 165 | 166 | void loop() { 167 | 168 | if( send_all_values ){ 169 | #ifdef DEBUG 170 | Serial.println(F("RESENDING VALUES")); 171 | #endif 172 | send_all_values = 0; 173 | send_values(); 174 | } 175 | 176 | // 177 | // MAIN LOOP 178 | // runs every second (or as long as you want). By counting how often this loop has run (and resetting that counter back to zero after a number of loops), it becomes possible to schedule all kinds of things without using a lot of memory. 179 | // The maximum time that can be scheduled is 255 * the time that one loop takes. So usually 255 seconds. 180 | // 181 | 182 | static unsigned long lastLoopTime = 0; // Holds the last time the main loop ran. 183 | static byte loopCounter = 0; // Count how many loops have passed (reset to 0 after at most 254 loops). 184 | 185 | if (millis() - lastLoopTime > LOOPDURATION) { 186 | lastLoopTime = millis(); 187 | loopCounter++; 188 | if(loopCounter >= MEASUREMENT_INTERVAL){ 189 | loopCounter = 0; 190 | } 191 | 192 | wdt_reset(); // Reset the watchdog timer 193 | 194 | #ifdef DEBUG 195 | Serial.println(loopCounter); 196 | #endif 197 | 198 | #ifdef HAS_DISPLAY 199 | // Show second counter 200 | oled.set1X(); 201 | oled.setCursor(100,0); 202 | oled.print(MEASUREMENT_INTERVAL - loopCounter); oled.print(F(" ")); 203 | #endif 204 | 205 | 206 | // Clock schedule 207 | switch (loopCounter) { 208 | 209 | 210 | case 1: // If we are in the first second of the clock, get and display the data. 211 | 212 | if ((err = dht22.read2(&temperature_value, &humidity_value, NULL)) != SimpleDHTErrSuccess) { 213 | Serial.print(F("Reading sensor failed, error number: ")); Serial.println(err); 214 | 215 | #ifdef HAS_DISPLAY 216 | oled.set2X(); 217 | oled.setCursor(0,3); 218 | oled.print(F("ERROR")); 219 | #endif 220 | 221 | break; 222 | } 223 | Serial.print(F("Asked sensor for data")); 224 | 225 | 226 | case 2: 227 | 228 | // TEMPERATURE 229 | #ifdef HAS_DISPLAY 230 | // Show temperature level on the screen 231 | oled.set2X(); 232 | oled.setCursor(0,2); 233 | 234 | // Display temperature value. 235 | oled.print(temperature_value); oled.println(F(" ")); 236 | #endif 237 | 238 | // HUMIDITY 239 | #ifdef HAS_DISPLAY 240 | // Show humidity level on the screen 241 | oled.set2X(); 242 | oled.setCursor(0,6); 243 | 244 | // Display humidity value. 245 | oled.print(humidity_value); oled.println(F(" ")); 246 | 247 | // Show quality opinion on the screen. 248 | oled.setCursor(70,6); 249 | if (humidity_value > 0 && humidity_value < 30){ oled.print(F("DRY"));} 250 | else if (humidity_value < 65){ oled.print(F("GOOD "));} 251 | //else if (humidity_value >= 1000){ oled.print(F("OK "));} 252 | else { 253 | oled.print(F("MOIST")); 254 | } 255 | #endif 256 | break; 257 | 258 | 259 | case 3: // Send the data 260 | 261 | received_echo = false; // If the controller responds that it has received the data ok, then this will be set back to true. 262 | 263 | if( temperature_value != -100 ){ // Avoid sending erroneous values 264 | Serial.print(F("Sending temperature value: ")); Serial.println(temperature_value); 265 | send(temperature_message.setSensor(CHILD_ID_TEMPERATURE).set(temperature_value,1),1); // We ask the controller to acknowledge that it has received the data. 266 | } 267 | 268 | if( humidity_value != 101 ){ // Avoid sending erroneous values 269 | Serial.print(F("Sending humidity value: ")); Serial.println(humidity_value); 270 | send(humidity_message.setSensor(CHILD_ID_HUMIDITY).set(humidity_value,1),1); // We ask the controller to acknowledge that it has received the data. 271 | } 272 | break; 273 | 274 | 275 | case 4: // Show the connection status on the display 276 | 277 | #ifdef HAS_DISPLAY 278 | oled.set1X(); 279 | oled.setCursor(85,0); 280 | 281 | if( received_echo ){ // Add W icon to the top right corner of the screen, indicating a wireless connection. 282 | oled.print(F("W")); 283 | }else { 284 | oled.print(F(" ")); // Remove W icon 285 | } 286 | #endif 287 | break; 288 | } 289 | } 290 | } 291 | 292 | 293 | void receive(const MyMessage &message) 294 | { 295 | if (message.isAck()) { 296 | Serial.println(F(">> Received acknowledgement")); 297 | received_echo = true; 298 | } 299 | else{ 300 | Serial.println(F(">> Incoming message")); 301 | } 302 | } 303 | 304 | 305 | 306 | /* 307 | * 308 | * This code makes use of the MySensors library: 309 | * 310 | * The MySensors Arduino library handles the wireless radio link and protocol 311 | * between your home built sensors/actuators and HA controller of choice. 312 | * The sensors forms a self healing radio network with optional repeaters. Each 313 | * repeater and gateway builds a routing tables in EEPROM which keeps track of the 314 | * network topology allowing messages to be routed to nodes. 315 | * 316 | * Created by Henrik Ekblad 317 | * Copyright (C) 2013-2015 Sensnology AB 318 | * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors 319 | * 320 | * Documentation: http://www.mysensors.org 321 | * Support Forum: http://forum.mysensors.org 322 | * 323 | * This program is free software; you can redistribute it and/or 324 | * modify it under the terms of the GNU General Public License 325 | * version 2 as published by the Free Software Foundation. 326 | * 327 | ******************************* 328 | * 329 | */ 330 | --------------------------------------------------------------------------------