From 589d11bfd1e1cfc7a383bc0e6e09d9c5ec02288d Mon Sep 17 00:00:00 2001 From: manuel Date: Sun, 26 Mar 2017 01:07:06 +0100 Subject: Initial commit --- .gitignore | 1 + couch_light/couch_light.ino | 283 +++++++++++++++++++++++++++++++++++++ hyperion/hyperion.ino | 279 ++++++++++++++++++++++++++++++++++++ lr_switch/lr_switch.ino | 197 ++++++++++++++++++++++++++ rgbtv_light/rgbtv_light.ino | 241 +++++++++++++++++++++++++++++++ testnode/testnode.ino | 316 +++++++++++++++++++++++++++++++++++++++++ tv_light/tv_light.ino | 335 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1652 insertions(+) create mode 100644 .gitignore create mode 100644 couch_light/couch_light.ino create mode 100644 hyperion/hyperion.ino create mode 100644 lr_switch/lr_switch.ino create mode 100644 rgbtv_light/rgbtv_light.ino create mode 100644 testnode/testnode.ino create mode 100644 tv_light/tv_light.ino diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7129b63 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +aes_key.h diff --git a/couch_light/couch_light.ino b/couch_light/couch_light.ino new file mode 100644 index 0000000..b99381a --- /dev/null +++ b/couch_light/couch_light.ino @@ -0,0 +1,283 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + */ + +// Enable debug prints to serial monitor +//#define MY_DEBUG + +// configure radio +#define MY_RADIO_RFM69 + +/** @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz). */ +#define MY_RFM69_FREQUENCY RF69_868MHZ + +/** @brief Enable this if you're running the RFM69HW model. */ +//#define MY_IS_RFM69HW + +/** @brief RFM69 Network ID. Use the same for all nodes that will talk to each other. */ +#define MY_RFM69_NETWORKID 1 + +/** @brief Node id defaults to AUTO (tries to fetch id from controller). */ +#define MY_NODE_ID 1 + +/** @brief If set, transport traffic is unmonitored and GW connection is optional */ +#define MY_TRANSPORT_DONT_CARE_MODE + +/** @brief Node parent defaults to AUTO (tries to find a parent automatically). */ +#define MY_PARENT_NODE_ID 0 + +/** @brief The user-defined AES key to use for EEPROM personalization */ +#include "aes_key.h" + +// Enable repeater functionality for this node +//#define MY_REPEATER_FEATURE + +/** @brief Enables RFM69 automatic transmit power control class. */ +//#define MY_RFM69_ATC + +#ifdef MY_AES_KEY +/** @brief enables RFM69 encryption */ +#define MY_RFM69_ENABLE_ENCRYPTION +#endif + +#include +#include +#include + +enum sensor_type : uint8_t +{ + SENSOR_RELAY = (1u << 0), + SENSOR_DIMMER = (1u << 1), + SENSOR_BUTTON = (1u << 2), +}; + +struct sensor_t +{ + uint8_t id; + uint8_t type; + struct + { + uint8_t pin; // relay pin + } relay; + struct + { + uint8_t pin; // push button pin + Bounce bounce; + } button; +}; + +struct sensor_t sensors[] = { + { + .id = 0, + .type = SENSOR_RELAY | SENSOR_BUTTON, + .relay = { .pin = 4 }, + .button = { .pin = 6, .bounce = Bounce() }, + }, + { + .id = 1, + .type = SENSOR_RELAY | SENSOR_BUTTON, + .relay = { .pin = 5 }, + .button = { .pin = 7, .bounce = Bounce() }, + }, +}; + +//#define SAVE_RESTORE + +#define NUM(a) (sizeof(a) / sizeof(*a)) + +#define RELAY_ON 1 // GPIO value to write to turn on attached relay +#define RELAY_OFF 0 // GPIO value to write to turn off attached relay + +#define TEMP_SENSOR_ID 254 +#define TEMP_READ_INTERVAL 1000L // read temp every 1 sec +#define TEMP_N_READS_MSG 60*60 // force temp message every n reads +#define TEMP_OFFSET 0 + +MyMessage msg(0, V_STATUS); + +inline void checkTemperature(void); +bool relayRead(struct sensor_t *sensor); +void relayWrite(struct sensor_t *sensor, bool state, bool send_update=false); +void flipRelay(struct sensor_t *sensor, bool send_update=false); +void checkButtons(void); + +void before() +{ + // set relay pins to output mode + restore to last known state + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_RELAY) + { + pinMode(sensor->relay.pin, OUTPUT); +#ifdef SAVE_RESTORE + digitalWrite(sensor->relay.pin, loadState(sensor->id) ? RELAY_ON : RELAY_OFF); +#else + digitalWrite(sensor->relay.pin, RELAY_OFF); +#endif + } + } + +#ifdef MY_AES_KEY + const uint8_t user_aes_key[16] = { MY_AES_KEY }; + uint8_t cur_aes_key[16]; + hwReadConfigBlock((void*)&cur_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(cur_aes_key)); + if (memcmp(&user_aes_key, &cur_aes_key, 16) != 0) + { + hwWriteConfigBlock((void*)user_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(user_aes_key)); + debug(PSTR("AES key written\n")); + } +#endif +} + +void setup() +{ +#ifdef MY_IS_RFM69HW + _radio.setHighPower(true); +#endif +#ifdef MY_RFM69_ATC + _radio.enableAutoPower(-70); + debug(PSTR("ATC enabled\n")); +#endif + + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_BUTTON) + { + pinMode(sensor->button.pin, INPUT_PULLUP); + sensor->button.bounce.attach(sensor->button.pin); + } + } +} + +void presentation() +{ + sendSketchInfo("CouchLight", "1.0"); + + // register all sensors to gw (they will be created as child devices) + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_RELAY || sensor->type & SENSOR_BUTTON) + present(sensor->id, S_BINARY); + } + +#if TEMP_SENSOR_ID >= 0 + present(TEMP_SENSOR_ID, S_TEMP); +#endif +} + +void loop() +{ + //TODO maybe call _radio.rcCalibration() all 1000x changes? + checkButtons(); + +#if TEMP_SENSOR_ID >= 0 + checkTemperature(); +#endif +} + +inline void checkTemperature(void) +{ + static unsigned long lastTempUpdate = millis(); + static unsigned int numTempUpdates = 0; + static float lastTemp = 0; + static MyMessage msgTemp(TEMP_SENSOR_ID, V_TEMP); + + unsigned long now = millis(); + if (now - lastTempUpdate > TEMP_READ_INTERVAL) + { + float temp = _radio.readTemperature() + TEMP_OFFSET; + lastTempUpdate = now; + if (isnan(temp)) + Serial.println(F("Failed reading temperature")); + else if (abs(temp - lastTemp) >= 2 || numTempUpdates == TEMP_N_READS_MSG) + { + lastTemp = temp; + numTempUpdates = 0; + send(msgTemp.set(temp, 2)); +#ifdef MY_DEBUG + char str_temp[6]; + dtostrf(temp, 4, 2, str_temp); + debug(PSTR("Temperature: %s °C\n"), str_temp); +#endif + } + else + ++numTempUpdates; + } +} + +void receive(const MyMessage &message) +{ + if (message.type == V_STATUS || message.type == V_PERCENTAGE) + { + uint8_t sensor_id = message.sensor; + if (sensor_id >= NUM(sensors)) + { + Serial.print(F("Invalid sensor id:")); + Serial.println(sensor_id); + return; + } + + struct sensor_t *sensor = &sensors[sensor_id]; + if (message.type == V_STATUS && sensor->type & SENSOR_RELAY) + { + if (mGetCommand(message) == C_REQ) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(relayRead(sensor))); + else if (mGetCommand(message) == C_SET) + relayWrite(sensor, message.getBool()); + } + } +} + +bool relayRead(struct sensor_t *sensor) +{ + if (sensor->type & SENSOR_RELAY) + return digitalRead(sensor->relay.pin) == RELAY_ON; + return false; +} + +void relayWrite(struct sensor_t *sensor, bool state, bool send_update) +{ + if (!(sensor->type & SENSOR_RELAY)) + return; + + Serial.print(F("Incoming change for relay: ")); + Serial.print(sensor->relay.pin); + Serial.print(F(", New state: ")); + Serial.println(state); + + digitalWrite(sensor->relay.pin, state ? RELAY_ON : RELAY_OFF); + +#ifdef SAVE_RESTORE + saveState(sensor->id, state ? RELAY_ON : RELAY_OFF); +#endif + + if (send_update) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(state)); +} + +void flipRelay(struct sensor_t *sensor, bool send_update) +{ + relayWrite(sensor, relayRead(sensor) ? RELAY_OFF : RELAY_ON, send_update); +} + +inline void checkButtons(void) +{ + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_BUTTON) + { + sensor->button.bounce.update(); + if (sensor->button.bounce.fell()) + flipRelay(sensor, true); + } + } +} + diff --git a/hyperion/hyperion.ino b/hyperion/hyperion.ino new file mode 100644 index 0000000..75e6858 --- /dev/null +++ b/hyperion/hyperion.ino @@ -0,0 +1,279 @@ +// Arduino "bridge" code between host computer and WS2801-based digital +// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use +// with USB-native boards such as Teensy or Adafruit 32u4 Breakout; +// works on normal serial Arduinos, but throughput is severely limited. +// LED data is streamed, not buffered, making this suitable for larger +// installations (e.g. video wall, etc.) than could otherwise be held +// in the Arduino's limited RAM. + +// Some effort is put into avoiding buffer underruns (where the output +// side becomes starved of data). The WS2801 latch protocol, being +// delay-based, could be inadvertently triggered if the USB bus or CPU +// is swamped with other tasks. This code buffers incoming serial data +// and introduces intentional pauses if there's a threat of the buffer +// draining prematurely. The cost of this complexity is somewhat +// reduced throughput, the gain is that most visual glitches are +// avoided (though ultimately a function of the load on the USB bus and +// host CPU, and out of our control). + +// LED data and clock lines are connected to the Arduino's SPI output. +// On traditional Arduino boards, SPI data out is digital pin 11 and +// clock is digital pin 13. On both Teensy and the 32u4 Breakout, +// data out is pin B2, clock is B1. LEDs should be externally +// powered -- trying to run any more than just a few off the Arduino's +// 5V line is generally a Bad Idea. LED ground should also be +// connected to Arduino ground. + +// -------------------------------------------------------------------- +// This file is part of Adalight. + +// Adalight is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. + +// Adalight is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with Adalight. If not, see +// . +// -------------------------------------------------------------------- + + +// -------------------------------------------------------------------- +// Note: This is a fork form the LEDstream. +// +// This version uses the LIB FastLED to work with the WS2812b LEDs. +// +// This Version works with a 150LED-Stripe with a frames/sec: 61, bytes/sec: 11524 +// tested with the programme colorswirl (see the C folder) and an Arduino UNO. +// +// TOOD: +// - Cleanup: remove the SPI code +// - Show a startup Pattern +// - remove flicker when sending more the 63 frames/sec. +// +// -------------------------------------------------------------------- + +#include +#include +#include + +#define NUM_LEDS 190 +#define DATA_PIN 3 +CRGB leds[NUM_LEDS]; + +// A 'magic word' (along with LED count & checksum) precedes each block +// of LED data; this assists the microcontroller in syncing up with the +// host-side software and properly issuing the latch (host I/O is +// likely buffered, making usleep() unreliable for latch). You may see +// an initial glitchy frame or two until the two come into alignment. +// The magic word can be whatever sequence you like, but each character +// should be unique, and frequent pixel values like 0 and 255 are +// avoided -- fewer false positives. The host software will need to +// generate a compatible header: immediately following the magic word +// are three bytes: a 16-bit count of the number of LEDs (high byte +// first) followed by a simple checksum value (high byte XOR low byte +// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B, +// where 0 = off and 255 = max brightness. + +static const uint8_t magic[] = {'A','d','a'}; +#define MAGICSIZE sizeof(magic) +#define HEADERSIZE (MAGICSIZE + 3) + +#define MODE_HEADER 0 +#define MODE_HOLD 1 +#define MODE_DATA 2 + +// If no serial data is received for a while, the LEDs are shut off +// automatically. This avoids the annoying "stuck pixel" look when +// quitting LED display programs on the host computer. +static const unsigned long serialTimeout = 15000; // 15 seconds + +void setup() +{ + // Dirty trick: the circular buffer for serial data is 256 bytes, + // and the "in" and "out" indices are unsigned 8-bit types -- this + // much simplifies the cases where in/out need to "wrap around" the + // beginning/end of the buffer. Otherwise there'd be a ton of bit- + // masking and/or conditional code every time one of these indices + // needs to change, slowing things down tremendously. + uint8_t + buffer[256], + indexIn = 0, + indexOut = 0, + mode = MODE_HEADER, + hi, lo, chk, i, spiFlag, + r,b,g, l; + int16_t + bytesBuffered = 0, + hold = 0, + c; + int32_t + bytesRemaining; + unsigned long + startTime, + lastByteTime, + lastAckTime, + t; + uint32_t value,index = 0, ledcount; + + delay(5000); + +#if 1 + power_adc_disable(); + power_usart0_disable(); + power_usart1_disable(); + power_spi_disable(); + power_twi_disable(); + power_timer1_disable(); + power_timer2_disable(); + power_timer3_disable(); +#endif + + FastLED.addLeds(leds, NUM_LEDS); + + Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK! + + //set the last LED to white + leds[NUM_LEDS-1].setRGB(100,100,100); + FastLED.show(); + //delay(1000); + //reset(); + //delay(1000); + + Serial.print("Ada\n"); // Send ACK string to host + RXLED1; + TXLED1; + + startTime = micros(); + lastByteTime = lastAckTime = millis(); + + // loop() is avoided as even that small bit of function overhead + // has a measurable impact on this code's overall throughput. + + for(;;) + { + RXLED1; + TXLED1; + + // Implementation is a simple finite-state machine. + // Regardless of mode, check for serial input each time: + t = millis(); + if ((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) + { + buffer[indexIn++] = c; + bytesBuffered++; + lastByteTime = lastAckTime = t; // Reset timeout counters + } + else + { + // No data received. If this persists, send an ACK packet + // to host once every second to alert it to our presence. + if((t - lastAckTime) > 1000) + { + Serial.print("Ada\n"); // Send ACK string to host + lastAckTime = t; // Reset counter + } + // If no data received for an extended time, turn off all LEDs. + if((t - lastByteTime) > serialTimeout) + { + reset(); + delay(1); // One millisecond pause = latch + lastByteTime = t; // Reset counter + } + } + + switch(mode) + { + case MODE_HEADER: + // In header-seeking mode. Is there enough data to check? + if(bytesBuffered >= HEADERSIZE) + { + // Indeed. Check for a 'magic word' match. + for(i=0; (i 0) and multiply by 3 for R,G,B. + bytesRemaining = 3L * (256L * (long)hi + (long)lo + 1L); + bytesBuffered -= 3; + ledcount = 0; + mode = MODE_HOLD; // Proceed to latch wait mode + } + else + { + // Checksum didn't match; search resumes after magic word. + indexOut -= 3; // Rewind + } + } // else no header match. Resume at first mismatched byte. + bytesBuffered -= i; + } + break; + + case MODE_HOLD: + // Ostensibly "waiting for the latch from the prior frame + // to complete" mode, but may also revert to this mode when + // underrun prevention necessitates a delay. + + if((micros() - startTime) < hold) break; // Still holding; keep buffering + + // Latch/delay complete. Advance to data-issuing mode... + //LED_PORT &= ~LED_PIN; // LED off + mode = MODE_DATA; // ...and fall through (no break): + + case MODE_DATA: + if(bytesRemaining > 2) + { + if(bytesBuffered > 2) + { + //we read one LED -> 3 Bytes r.g.b + bytesBuffered -= 3; + bytesRemaining -= 3; + leds[ledcount++].setRGB(buffer[indexOut++], buffer[indexOut++], buffer[indexOut++]); + } + // If serial buffer is threatening to underrun, start + // introducing progressively longer pauses to allow more + // data to arrive (up to a point). + if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered) && (mode!=MODE_HEADER)) + { + startTime = micros(); + hold = 100 + (32 - bytesBuffered) * 10; + mode = MODE_HOLD; + } + } + else + { + // End of data -- issue latch: + startTime = micros(); + hold = 1000; // Latch duration = 1000 uS + //LED_PORT |= LED_PIN; // LED on + mode = MODE_HEADER; // Begin next header search + FastLED.show(); + } + } // end switch + } // end for(;;) +} + +void reset() +{ + for (uint16_t i=0; i< NUM_LEDS; i++) + leds[i] = CRGB::Black; + FastLED.show(); +} + +void loop() +{ + // Not used. See note in setup() function. +} + + diff --git a/lr_switch/lr_switch.ino b/lr_switch/lr_switch.ino new file mode 100644 index 0000000..673eb3a --- /dev/null +++ b/lr_switch/lr_switch.ino @@ -0,0 +1,197 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + */ + +// Enable debug prints to serial monitor +//#define MY_DEBUG + +// configure radio +#define MY_RADIO_RFM69 + +/** @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz). */ +#define MY_RFM69_FREQUENCY RF69_868MHZ + +/** @brief Enable this if you're running the RFM69HW model. */ +#define MY_IS_RFM69HW + +/** @brief RFM69 Network ID. Use the same for all nodes that will talk to each other. */ +#define MY_RFM69_NETWORKID 1 + +/** @brief Node id defaults to AUTO (tries to fetch id from controller). */ +#define MY_NODE_ID 3 + +/** @brief If set, transport traffic is unmonitored and GW connection is optional */ +#define MY_TRANSPORT_DONT_CARE_MODE + +/** @brief Node parent defaults to AUTO (tries to find a parent automatically). */ +#define MY_PARENT_NODE_ID 0 + +/** @brief The user-defined AES key to use for EEPROM personalization */ +#include "aes_key.h" + +// Enable repeater functionality for this node +//#define MY_REPEATER_FEATURE + +/** @brief Enables RFM69 automatic transmit power control class. */ +//#define MY_RFM69_ATC + +#ifdef MY_AES_KEY +/** @brief enables RFM69 encryption */ +#define MY_RFM69_ENABLE_ENCRYPTION +#endif + +#include +#include + +enum sensor_type : uint8_t +{ + SENSOR_RELAY = (1u << 0), + SENSOR_DIMMER = (1u << 1), + SENSOR_BUTTON = (1u << 2), + SENSOR_SCENE = (1u << 3), +}; + +struct sensor_t +{ + uint8_t id; + uint8_t type; + struct + { + uint8_t pin; // push button pin + } button; +}; + +struct sensor_t sensors[] = { + { + .id = 0, + .type = SENSOR_BUTTON | SENSOR_SCENE, + .button = { .pin = 3 }, + }, +}; + +#define NUM(a) (sizeof(a) / sizeof(*a)) + +#define TEMP_SENSOR_ID -1 +#define TEMP_READ_INTERVAL 1000L // read temp every 1 sec +#define TEMP_N_READS_MSG 60*60 // force temp message every n reads +#define TEMP_OFFSET 0 + +MyMessage msg(0, V_SCENE_ON); + +inline void checkTemperature(void); +void wakeUp(void); + +void before() +{ +#ifdef MY_AES_KEY + const uint8_t user_aes_key[16] = { MY_AES_KEY }; + uint8_t cur_aes_key[16]; + hwReadConfigBlock((void*)&cur_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(cur_aes_key)); + if (memcmp(&user_aes_key, &cur_aes_key, 16) != 0) + { + hwWriteConfigBlock((void*)user_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(user_aes_key)); + debug(PSTR("AES key written\n")); + } +#endif +} + +void setup() +{ +#ifdef MY_IS_RFM69HW + _radio.setHighPower(true); +#endif + //_radio.setPowerLevel(10); +#ifdef MY_RFM69_ATC + _radio.enableAutoPower(-70); + debug(PSTR("ATC enabled\n")); +#endif + + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_BUTTON) + pinMode(sensor->button.pin, INPUT_PULLUP); + } +} + +void presentation() +{ + sendSketchInfo("LRSwitch", "1.0"); + + // register all sensors to gw (they will be created as child devices) + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_SCENE) + present(sensor->id, S_SCENE_CONTROLLER); + } + +#if TEMP_SENSOR_ID >= 0 + present(TEMP_SENSOR_ID, S_TEMP); +#endif +} + +void loop() +{ + //TODO maybe call _radio.rcCalibration() all 1000x changes? + + struct sensor_t *sensor = &sensors[0]; + uint8_t pin = sensor->button.pin; + + // delay() instead of sleep() + // sleep() will for whatever reason trigger the external interrupt?! + delay(1000); + + Serial.println("r"); + Serial.flush(); + uint8_t state_before = digitalRead(pin); + int8_t intr = sleep(digitalPinToInterrupt(pin), CHANGE, 0); + if (intr == digitalPinToInterrupt(pin)) + { + uint8_t state_now = digitalRead(pin); + if (state_before != state_now) + { + Serial.println("m"); + send(msg.setType(V_SCENE_ON).setSensor(sensor->id).set(1)); + } + } + +#if TEMP_SENSOR_ID >= 0 + checkTemperature(); +#endif +} + +inline void checkTemperature(void) +{ + static unsigned long lastTempUpdate = millis(); + static unsigned int numTempUpdates = 0; + static float lastTemp = 0; + static MyMessage msgTemp(TEMP_SENSOR_ID, V_TEMP); + + unsigned long now = millis(); + if (now - lastTempUpdate > TEMP_READ_INTERVAL) + { + float temp = _radio.readTemperature() + TEMP_OFFSET; + lastTempUpdate = now; + if (isnan(temp)) + Serial.println(F("Failed reading temperature")); + else if (abs(temp - lastTemp) >= 2 || numTempUpdates == TEMP_N_READS_MSG) + { + lastTemp = temp; + numTempUpdates = 0; + send(msgTemp.set(temp, 2)); +#ifdef MY_DEBUG + char str_temp[6]; + dtostrf(temp, 4, 2, str_temp); + debug(PSTR("Temperature: %s °C\n"), str_temp); +#endif + } + else + ++numTempUpdates; + } +} + diff --git a/rgbtv_light/rgbtv_light.ino b/rgbtv_light/rgbtv_light.ino new file mode 100644 index 0000000..98120a6 --- /dev/null +++ b/rgbtv_light/rgbtv_light.ino @@ -0,0 +1,241 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + */ + +// Enable debug prints to serial monitor +#define MY_DEBUG + +// configure radio +#define MY_RADIO_RFM69 + +/** @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz). */ +#define MY_RFM69_FREQUENCY RF69_868MHZ + +/** @brief Enable this if you're running the RFM69HW model. */ +//#define MY_IS_RFM69HW + +/** @brief RFM69 Network ID. Use the same for all nodes that will talk to each other. */ +#define MY_RFM69_NETWORKID 1 + +/** @brief Node id defaults to AUTO (tries to fetch id from controller). */ +#define MY_NODE_ID 3 + +/** @brief If set, transport traffic is unmonitored and GW connection is optional */ +#define MY_TRANSPORT_DONT_CARE_MODE + +/** @brief Node parent defaults to AUTO (tries to find a parent automatically). */ +#define MY_PARENT_NODE_ID 0 + +/** @brief The user-defined AES key to use for EEPROM personalization */ +#include "aes_key.h" + +// Enable repeater functionality for this node +//#define MY_REPEATER_FEATURE + +/** @brief Enables RFM69 automatic transmit power control class. */ +//#define MY_RFM69_ATC + +#ifdef MY_AES_KEY +/** @brief enables RFM69 encryption */ +#define MY_RFM69_ENABLE_ENCRYPTION +#endif + +#include +#include +#include + +#define RELAY_1_PIN 4 // pin number of first relay (second on pin+1 etc) +#define NUMBER_OF_RELAYS 1 // Total number of attached relays +#define RELAY_ON 1 // GPIO value to write to turn on attached relay +#define RELAY_OFF 0 // GPIO value to write to turn off attached relay + +#define RGB_PIN 7 +#define NUM_LEDS 30 +#define RGB_CHIPSET WS2812B +#define RGB_COLOR_ORDER GRB +#define RGB_CHILD_ID 0 + +#define TEMP_READ_INTERVAL 1000L // read temp every 1 sec +#define TEMP_N_READS_MSG 60*60 // force temp message every n reads +#define TEMP_OFFSET 0 +#define TEMP_CHILD_ID 254 + +MyMessage msgRGB(RGB_CHILD_ID, 0); +static uint8_t brightness = 128; + +MyMessage msgRelais(0, V_STATUS); + +unsigned long lastTempUpdate = millis(); +unsigned int numTempUpdates = 0; +float lastTemp = 0; +MyMessage msgTemp(TEMP_CHILD_ID, V_TEMP); + +CRGB leds[NUM_LEDS]; + +void changeRelay(uint8_t relay, uint8_t val, bool send_update=false); + +void before() +{ + // set relay pins to output mode + restore to last known state + for (uint8_t relay = 0; relay < NUMBER_OF_RELAYS; relay++) + { + pinMode(relay + RELAY_1_PIN, OUTPUT); + digitalWrite(relay + RELAY_1_PIN, loadState(relay) ? RELAY_ON : RELAY_OFF); + } + +#ifdef MY_AES_KEY + const uint8_t user_aes_key[16] = { MY_AES_KEY }; + uint8_t cur_aes_key[16]; + hwReadConfigBlock((void*)&cur_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(cur_aes_key)); + if (memcmp(&user_aes_key, &cur_aes_key, 16) != 0) + { + hwWriteConfigBlock((void*)user_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(user_aes_key)); + debug(PSTR("AES key written\n")); + } +#endif +} + +void setup() +{ +#ifdef MY_RFM69_ATC + _radio.enableAutoPower(-70); + debug(PSTR("ATC enabled\n")); +#endif + FastLED.addLeds(leds, NUM_LEDS); + //TODO restore mode(static/ambilight)/color/brightness from flash? + FastLED.setBrightness(brightness); +} + +void presentation() +{ + // Send the sketch version information to the gateway and Controller + sendSketchInfo("Ambilight", "1.0"); + + // Register all sensors to gw (they will be created as child devices) + present(0, S_RGB_LIGHT, "ambilight"); +#if 0 + for (uint8_t relay = 0; relay < NUMBER_OF_RELAYS; relay++) + present(relay + 1, S_BINARY); + present(TEMP_CHILD_ID, S_TEMP); +#endif + + delay(3000); + send(msgRGB.setType(V_STATUS).set(1)); + delay(500); + send(msgRGB.setType(V_DIMMER).set(FastLED.getBrightness())); + delay(500); + send(msgRGB.setType(V_RGB).set("ffffff")); + FastLED.show(); +} + +void loop() +{ + //TODO maybe call _radio.rcCalibration() all 1000x changes? + + //FastLED.show(); + //FastLED.delay(8); + +#if 0 + // check temperature + unsigned long now = millis(); + if (now - lastTempUpdate > TEMP_READ_INTERVAL) + { + float temp = _radio.readTemperature() + TEMP_OFFSET; + lastTempUpdate = now; + if (isnan(temp)) + Serial.println("Failed reading temperature"); + else if (abs(temp - lastTemp) >= 2 || numTempUpdates == TEMP_N_READS_MSG) + { + lastTemp = temp; + numTempUpdates = 0; + send(msgTemp.set(temp, 2)); +#ifdef MY_DEBUG + char str_temp[6]; + dtostrf(temp, 4, 2, str_temp); + debug(PSTR("Temperature: %s °C\n"), str_temp); +#endif + } + else + ++numTempUpdates; + } +#endif +} + +void receive(const MyMessage &message) +{ + Serial.println(_radio.readRSSI()); + if (message.sensor == RGB_CHILD_ID) + { + if (mGetCommand(message) == C_SET) + { + if (message.type == V_STATUS) + { + bool val = message.getBool(); + // datatype=0, message=0/1 + Serial.println("light on/off"); + //TODO restore brightness. + } + else if (message.type == V_RGB && mGetLength(message) == 6) + { + uint32_t colorcode = strtol(message.getString(), NULL, 16); + fill_solid(leds, NUM_LEDS, CRGB(colorcode)); + FastLED.show(); + } + else if (message.type == V_PERCENTAGE) + { + //TODO fade? + uint8_t val = message.getByte(); + if (val < 0 || val > 100) + return; + Serial.print("dim: "); + Serial.println(val, DEC); + brightness = map(val, 0, 100, 0, 255); + Serial.println(brightness, DEC); + // datatype=0, message=1-100 + FastLED.setBrightness(brightness); + FastLED.show(); + } + } + } + +#if 0 + if (message.type == V_STATUS && message.sensor >= 1) + { + uint8_t relay = message.sensor - 1; + if (relay >= NUMBER_OF_RELAYS) + { + Serial.print("Invalid relay index:"); + Serial.println(relay); + return; + } + + if (mGetCommand(message) == C_REQ) + send(msg.setSensor(relay + 1).set(digitalRead(relay + RELAY_1_PIN))); + else if (mGetCommand(message) == C_SET) + changeRelay(relay, message.getBool() ? RELAY_ON : RELAY_OFF); + } +#endif +} + +void changeRelay(uint8_t relay, uint8_t value, bool send_update) +{ + if (relay >= NUMBER_OF_RELAYS) + return; + Serial.print("Incoming change for relay: "); + Serial.print(relay); + Serial.print(", New status: "); + Serial.println(value); + + // change relay state + store state in eeprom + digitalWrite(relay + RELAY_1_PIN, value); + saveState(relay, value); + + // send msg + if (send_update) + send(msgRelais.setSensor(relay + 1).set(value)); +} + diff --git a/testnode/testnode.ino b/testnode/testnode.ino new file mode 100644 index 0000000..999c32a --- /dev/null +++ b/testnode/testnode.ino @@ -0,0 +1,316 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + */ + +// Enable debug prints to serial monitor +#define MY_DEBUG + +// configure radio +#define MY_RADIO_RFM69 + +/** @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz). */ +#define MY_RFM69_FREQUENCY RF69_868MHZ + +/** @brief Enable this if you're running the RFM69HW model. */ +//#define MY_IS_RFM69HW + +/** @brief RFM69 Network ID. Use the same for all nodes that will talk to each other. */ +#define MY_RFM69_NETWORKID 1 + +/** @brief Node id defaults to AUTO (tries to fetch id from controller). */ +#define MY_NODE_ID 4 + +/** @brief If set, transport traffic is unmonitored and GW connection is optional */ +#define MY_TRANSPORT_DONT_CARE_MODE + +/** @brief Node parent defaults to AUTO (tries to find a parent automatically). */ +#define MY_PARENT_NODE_ID 0 + +/** @brief The user-defined AES key to use for EEPROM personalization */ +#include "aes_key.h" + +// Enable repeater functionality for this node +//#define MY_REPEATER_FEATURE + +/** @brief Enables RFM69 automatic transmit power control class. */ +//#define MY_RFM69_ATC + +#ifdef MY_AES_KEY +/** @brief enables RFM69 encryption */ +#define MY_RFM69_ENABLE_ENCRYPTION +#endif + +#include +#include + +enum sensor_type : uint8_t +{ + SENSOR_RELAY = (1u << 0), + SENSOR_DIMMER = (1u << 1), + SENSOR_BUTTON = (1u << 2), + SENSOR_SCENE = (1u << 3), +}; + +struct sensor_t +{ + uint8_t id; + uint8_t type; + struct + { + uint8_t pin; // relay pin + } relay; + struct + { + uint8_t pin; // dimmer pin + uint16_t level; // current dim level (0 to 100) + } dimmer; +}; + +struct sensor_t sensors[] = { + { + .id = 0, + .type = SENSOR_RELAY, + .relay = { .pin = 3 }, + }, + { + .id = 1, + .type = SENSOR_RELAY | SENSOR_DIMMER, + .relay = { .pin = 4 }, + .dimmer = { .pin = 6, .level = 100 }, + }, +}; + +//#define SAVE_RESTORE + +#define NUM(a) (sizeof(a) / sizeof(*a)) + +#define RELAY_ON 1 // GPIO value to write to turn on attached relay +#define RELAY_OFF 0 // GPIO value to write to turn off attached relay + +#define DIMMER_FADE_DELAY 10 // Delay in ms for each percentage fade up/down (10ms = 1s full-range dim) + +#define TEMP_SENSOR_ID 254 +#define TEMP_READ_INTERVAL 1000L // read temp every 1 sec +#define TEMP_N_READS_MSG 60*60 // force temp message every n reads +#define TEMP_OFFSET 0 + +MyMessage msg(0, V_STATUS); + +inline void checkTemperature(void); +bool relayRead(struct sensor_t *sensor); +void relayWrite(struct sensor_t *sensor, bool state, bool send_update=false); +void flipRelay(struct sensor_t *sensor, bool send_update=false); +void fadeDimmer(struct sensor_t *sensor, uint8_t level, bool send_update=false); + +void before() +{ + // set relay pins to output mode + restore to last known state + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_RELAY) + { + pinMode(sensor->relay.pin, OUTPUT); +#ifdef SAVE_RESTORE + digitalWrite(sensor->relay.pin, loadState(sensor->id) ? RELAY_ON : RELAY_OFF); +#else + digitalWrite(sensor->relay.pin, RELAY_ON); +#endif + } + + if (sensor->type & SENSOR_DIMMER) + { + pinMode(sensor->dimmer.pin, OUTPUT); +#ifdef SAVE_RESTORE + digitalWrite(sensor->relay.pin, loadState(NUM(sensors) + sensor->id)); +#else + analogWrite(sensor->dimmer.pin, map(sensor->dimmer.level, 0, 100, 0, 255)); +#endif + } + } + +#ifdef MY_AES_KEY + const uint8_t user_aes_key[16] = { MY_AES_KEY }; + uint8_t cur_aes_key[16]; + hwReadConfigBlock((void*)&cur_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(cur_aes_key)); + if (memcmp(&user_aes_key, &cur_aes_key, 16) != 0) + { + hwWriteConfigBlock((void*)user_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(user_aes_key)); + debug(PSTR("AES key written\n")); + } +#endif +} + +void setup() +{ +#ifdef MY_IS_RFM69HW + _radio.setHighPower(true); +#endif +#ifdef MY_RFM69_ATC + _radio.enableAutoPower(-70); + debug(PSTR("ATC enabled\n")); +#endif +} + +void presentation() +{ + sendSketchInfo("TVLight", "1.0"); + + // register all sensors to gw (they will be created as child devices) + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_DIMMER) + present(sensor->id, S_DIMMER); + else if (sensor->type & SENSOR_RELAY) + present(sensor->id, S_BINARY); + } + +#if TEMP_SENSOR_ID >= 0 + present(TEMP_SENSOR_ID, S_TEMP); +#endif +} + +void loop() +{ + //TODO maybe call _radio.rcCalibration() all 1000x changes? +#if TEMP_SENSOR_ID >= 0 + checkTemperature(); +#endif +} + +inline void checkTemperature(void) +{ + static unsigned long lastTempUpdate = millis(); + static unsigned int numTempUpdates = 0; + static float lastTemp = 0; + static MyMessage msgTemp(TEMP_SENSOR_ID, V_TEMP); + + unsigned long now = millis(); + if (now - lastTempUpdate > TEMP_READ_INTERVAL) + { + float temp = _radio.readTemperature() + TEMP_OFFSET; + lastTempUpdate = now; + if (isnan(temp)) + Serial.println(F("Failed reading temperature")); + else if (abs(temp - lastTemp) >= 2 || numTempUpdates == TEMP_N_READS_MSG) + { + lastTemp = temp; + numTempUpdates = 0; + send(msgTemp.set(temp, 2)); +#ifdef MY_DEBUG + char str_temp[6]; + dtostrf(temp, 4, 2, str_temp); + debug(PSTR("Temperature: %s °C\n"), str_temp); +#endif + } + else + ++numTempUpdates; + } +} + +void receive(const MyMessage &message) +{ + if (message.type == V_STATUS || message.type == V_PERCENTAGE) + { + uint8_t sensor_id = message.sensor; + if (sensor_id >= NUM(sensors)) + { + Serial.print(F("Invalid sensor id:")); + Serial.println(sensor_id); + return; + } + + struct sensor_t *sensor = &sensors[sensor_id]; + if (message.type == V_STATUS && sensor->type & SENSOR_RELAY) + { + if (mGetCommand(message) == C_REQ) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(relayRead(sensor))); + else if (mGetCommand(message) == C_SET) + relayWrite(sensor, message.getBool()); + } + else if (message.type == V_PERCENTAGE && sensor->type & SENSOR_DIMMER) + { + if (mGetCommand(message) == C_REQ) + send(msg.setType(V_PERCENTAGE).setSensor(sensor->id).set(sensor->dimmer.level)); + else if (mGetCommand(message) == C_SET) + { + uint16_t level = message.getUInt(); + if (level > 255) + return; + fadeDimmer(sensor, level); + } + } + } +} + +bool relayRead(struct sensor_t *sensor) +{ + if (sensor->type & SENSOR_RELAY) + return digitalRead(sensor->relay.pin) == RELAY_ON; + return false; +} + +void relayWrite(struct sensor_t *sensor, bool state, bool send_update) +{ + if (!(sensor->type & SENSOR_RELAY)) + return; + + Serial.print(F("Incoming change for relay: ")); + Serial.print(sensor->relay.pin); + Serial.print(F(", New state: ")); + Serial.println(state); + + digitalWrite(sensor->relay.pin, state ? RELAY_ON : RELAY_OFF); + +#ifdef SAVE_RESTORE + saveState(sensor->id, state ? RELAY_ON : RELAY_OFF); +#endif + + if (send_update) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(state)); +} + +void flipRelay(struct sensor_t *sensor, bool send_update) +{ + relayWrite(sensor, relayRead(sensor) ? RELAY_OFF : RELAY_ON, send_update); +} + +void fadeDimmer(struct sensor_t *sensor, uint8_t level, bool send_update) +{ + if (!(sensor->type & SENSOR_DIMMER)) + return; + + Serial.print(F("Incoming change for dimmer: ")); + Serial.print(sensor->dimmer.pin); + Serial.print(F(", Old level: ")); + Serial.print(sensor->dimmer.level); + Serial.print(F(", New level: ")); + Serial.println(level); + + if (level > 0 && sensor->type & SENSOR_RELAY && !relayRead(sensor)) + relayWrite(sensor, RELAY_ON); + + int delta = ((int8_t)(level - sensor->dimmer.level) < 0) ? -1 : 1; + while (sensor->dimmer.level != level) + { + sensor->dimmer.level += delta; + analogWrite(sensor->dimmer.pin, map(sensor->dimmer.level, 0, 100, 0, 255)); + delay(DIMMER_FADE_DELAY); + } + + if (level == 0 && sensor->type & SENSOR_RELAY && relayRead(sensor)) + relayWrite(sensor, RELAY_OFF); + +#ifdef SAVE_RESTORE + saveState(NUM(sensors) + sensor->id, level); +#endif + + if (send_update) + send(msg.setType(V_PERCENTAGE).setSensor(sensor->id).set(level)); +} + diff --git a/tv_light/tv_light.ino b/tv_light/tv_light.ino new file mode 100644 index 0000000..5588cef --- /dev/null +++ b/tv_light/tv_light.ino @@ -0,0 +1,335 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + */ + +// Enable debug prints to serial monitor +//#define MY_DEBUG + +// configure radio +#define MY_RADIO_RFM69 + +/** @brief RFM69 frequency to use (RF69_433MHZ for 433MHz, RF69_868MHZ for 868MHz or RF69_915MHZ for 915MHz). */ +#define MY_RFM69_FREQUENCY RF69_868MHZ + +/** @brief Enable this if you're running the RFM69HW model. */ +#define MY_IS_RFM69HW + +/** @brief RFM69 Network ID. Use the same for all nodes that will talk to each other. */ +#define MY_RFM69_NETWORKID 1 + +/** @brief Node id defaults to AUTO (tries to fetch id from controller). */ +#define MY_NODE_ID 2 + +/** @brief If set, transport traffic is unmonitored and GW connection is optional */ +#define MY_TRANSPORT_DONT_CARE_MODE + +/** @brief Node parent defaults to AUTO (tries to find a parent automatically). */ +#define MY_PARENT_NODE_ID 0 + +/** @brief The user-defined AES key to use for EEPROM personalization */ +#include "aes_key.h" + +// Enable repeater functionality for this node +//#define MY_REPEATER_FEATURE + +/** @brief Enables RFM69 automatic transmit power control class. */ +//#define MY_RFM69_ATC + +#ifdef MY_AES_KEY +/** @brief enables RFM69 encryption */ +#define MY_RFM69_ENABLE_ENCRYPTION +#endif + +#include +#include +#include + +enum sensor_type : uint8_t +{ + SENSOR_RELAY = (1u << 0), + SENSOR_DIMMER = (1u << 1), + SENSOR_BUTTON = (1u << 2), +}; + +struct sensor_t +{ + uint8_t id; + uint8_t type; + struct + { + uint8_t pin; // relay pin + } relay; + struct + { + uint8_t pin; // dimmer pin + uint16_t level; // current dim level (0 to 100) + } dimmer; +}; + +struct sensor_t sensors[] = { + { + .id = 0, + .type = SENSOR_RELAY, + .relay = { .pin = 4 }, + }, + { + .id = 1, + .type = SENSOR_RELAY | SENSOR_DIMMER, + //.type = SENSOR_RELAY, + .relay = { .pin = 5 }, + .dimmer = { .pin = 6, .level = 100 }, + }, +}; + +//#define SAVE_RESTORE + +#define NUM(a) (sizeof(a) / sizeof(*a)) + +#define RELAY_ON 1 // GPIO value to write to turn on attached relay +#define RELAY_OFF 0 // GPIO value to write to turn off attached relay + +#define DIMMER_FADE_DELAY 40 // Delay in ms for each percentage fade up/down (10ms = 1s full-range dim) + +#define TEMP_SENSOR_ID 254 +#define TEMP_READ_INTERVAL 1000L // read temp every 1 sec +#define TEMP_N_READS_MSG 60*60 // force temp message every n reads +#define TEMP_OFFSET 0 + +MyMessage msg(0, V_STATUS); + +inline void checkTemperature(void); +bool relayRead(struct sensor_t *sensor); +void relayWrite(struct sensor_t *sensor, bool state, bool send_update=false); +void flipRelay(struct sensor_t *sensor, bool send_update=false); +inline uint8_t pwmValue(uint8_t level); +void fadeDimmer(struct sensor_t *sensor, uint8_t level, bool send_update=false); + +void before() +{ + // set relay pins to output mode + restore to last known state + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_RELAY) + { + pinMode(sensor->relay.pin, OUTPUT); +#ifdef SAVE_RESTORE + digitalWrite(sensor->relay.pin, loadState(sensor->id) ? RELAY_ON : RELAY_OFF); +#else + digitalWrite(sensor->relay.pin, RELAY_OFF); +#endif + } + + if (sensor->type & SENSOR_DIMMER) + { + pinMode(sensor->dimmer.pin, OUTPUT); +#ifdef SAVE_RESTORE + uint8_t level = loadState(NUM(sensors) + sensor->id; +#else + uint8_t level = sensor->dimmer.level; +#endif + analogWrite(sensor->dimmer.pin, pwmValue(level)); + } + } + +#ifdef MY_AES_KEY + const uint8_t user_aes_key[16] = { MY_AES_KEY }; + uint8_t cur_aes_key[16]; + hwReadConfigBlock((void*)&cur_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(cur_aes_key)); + if (memcmp(&user_aes_key, &cur_aes_key, 16) != 0) + { + hwWriteConfigBlock((void*)user_aes_key, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, sizeof(user_aes_key)); + debug(PSTR("AES key written\n")); + } +#endif +} + +void setup() +{ +#ifdef MY_IS_RFM69HW + _radio.setHighPower(true); +#endif +#ifdef MY_RFM69_ATC + _radio.enableAutoPower(-70); + debug(PSTR("ATC enabled\n")); +#endif +} + +void presentation() +{ + sendSketchInfo("TVLight", "1.0"); + + // register all sensors to gw (they will be created as child devices) + for (uint8_t i = 0; i < NUM(sensors); i++) + { + struct sensor_t *sensor = &sensors[i]; + if (sensor->type & SENSOR_DIMMER) + present(sensor->id, S_DIMMER); + else if (sensor->type & SENSOR_RELAY || sensor->type & SENSOR_BUTTON) + present(sensor->id, S_BINARY); + } + +#if TEMP_SENSOR_ID >= 0 + present(TEMP_SENSOR_ID, S_TEMP); +#endif +} + +void loop() +{ + //TODO maybe call _radio.rcCalibration() all 1000x changes? +#if TEMP_SENSOR_ID >= 0 + checkTemperature(); +#endif +} + +inline void checkTemperature(void) +{ + static unsigned long lastTempUpdate = millis(); + static unsigned int numTempUpdates = 0; + static float lastTemp = 0; + static MyMessage msgTemp(TEMP_SENSOR_ID, V_TEMP); + + unsigned long now = millis(); + if (now - lastTempUpdate > TEMP_READ_INTERVAL) + { + float temp = _radio.readTemperature() + TEMP_OFFSET; + lastTempUpdate = now; + if (isnan(temp)) + Serial.println(F("Failed reading temperature")); + else if (abs(temp - lastTemp) >= 2 || numTempUpdates == TEMP_N_READS_MSG) + { + lastTemp = temp; + numTempUpdates = 0; + send(msgTemp.set(temp, 2)); +#ifdef MY_DEBUG + char str_temp[6]; + dtostrf(temp, 4, 2, str_temp); + debug(PSTR("Temperature: %s °C\n"), str_temp); +#endif + } + else + ++numTempUpdates; + } +} + +void receive(const MyMessage &message) +{ + if (message.type == V_STATUS || message.type == V_PERCENTAGE) + { + uint8_t sensor_id = message.sensor; + if (sensor_id >= NUM(sensors)) + { + Serial.print(F("Invalid sensor id:")); + Serial.println(sensor_id); + return; + } + + struct sensor_t *sensor = &sensors[sensor_id]; + if (message.type == V_STATUS && sensor->type & SENSOR_RELAY) + { + if (mGetCommand(message) == C_REQ) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(relayRead(sensor))); + else if (mGetCommand(message) == C_SET) + relayWrite(sensor, message.getBool()); + } + else if (message.type == V_PERCENTAGE && sensor->type & SENSOR_DIMMER) + { + if (mGetCommand(message) == C_REQ) + send(msg.setType(V_PERCENTAGE).setSensor(sensor->id).set(sensor->dimmer.level)); + else if (mGetCommand(message) == C_SET) + { + uint16_t level = message.getUInt(); + fadeDimmer(sensor, (level > 100) ? 100 : level); + } + } + } +} + +bool relayRead(struct sensor_t *sensor) +{ + if (sensor->type & SENSOR_RELAY) + return digitalRead(sensor->relay.pin) == RELAY_ON; + return false; +} + +void relayWrite(struct sensor_t *sensor, bool state, bool send_update) +{ + if (!(sensor->type & SENSOR_RELAY)) + return; + + Serial.print(F("Incoming change for relay: ")); + Serial.print(sensor->relay.pin); + Serial.print(F(", New state: ")); + Serial.println(state); + + digitalWrite(sensor->relay.pin, state ? RELAY_ON : RELAY_OFF); + +#ifdef SAVE_RESTORE + saveState(sensor->id, state ? RELAY_ON : RELAY_OFF); +#endif + + if (send_update) + send(msg.setType(V_STATUS).setSensor(sensor->id).set(state)); +} + +void flipRelay(struct sensor_t *sensor, bool send_update) +{ + relayWrite(sensor, relayRead(sensor) ? RELAY_OFF : RELAY_ON, send_update); +} + +inline uint8_t pwmValue(uint8_t level) +{ + const uint8_t pwmtable[101] PROGMEM = { + 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 7, 7, 8, 8, 8, 9, + 9, 10, 11, 11, 12, 12, 13, 14, 15, 16, + 16, 17, 18, 19, 20, 22, 23, 24, 25, 27, + 28, 30, 32, 33, 35, 37, 39, 42, 44, 47, + 49, 52, 55, 58, 61, 65, 68, 72, 76, 81, + 85, 90, 95, 100, 106, 112, 118, 125, 132, 139, + 147, 156, 164, 174, 183, 194, 205, 216, 228, 241, + 255 + }; + return (uint8_t)pgm_read_byte(&pwmtable[level]); +} + +void fadeDimmer(struct sensor_t *sensor, uint8_t level, bool send_update) +{ + if (!(sensor->type & SENSOR_DIMMER)) + return; + if (level > 100) + level = 100; + + Serial.print(F("Incoming change for dimmer: ")); + Serial.print(sensor->dimmer.pin); + Serial.print(F(", New level: ")); + Serial.println(level); + + if (level > 0 && sensor->type & SENSOR_RELAY && !relayRead(sensor)) + relayWrite(sensor, RELAY_ON); + + int delta = ((int8_t)(level - sensor->dimmer.level) < 0) ? -1 : 1; + while (sensor->dimmer.level != level) + { + sensor->dimmer.level += delta; + analogWrite(sensor->dimmer.pin, pwmValue(sensor->dimmer.level)); + delay(DIMMER_FADE_DELAY); + } + + if (level == 0 && sensor->type & SENSOR_RELAY && relayRead(sensor)) + relayWrite(sensor, RELAY_OFF); + +#ifdef SAVE_RESTORE + saveState(NUM(sensors) + sensor->id, level); +#endif + + if (send_update) + send(msg.setType(V_PERCENTAGE).setSensor(sensor->id).set(level)); +} + -- cgit v1.2.3