From 3df1227147d248385eb67c800e9663b3fdf223d1 Mon Sep 17 00:00:00 2001 From: Jeeva Kandasamy Date: Mon, 24 Aug 2015 12:46:08 +0530 Subject: [PATCH 01/35] Fix to enable Tx/Rx as IO pin --- libraries/MySensors/MyConfig.h | 3 +++ libraries/MySensors/MyHwATMega328.cpp | 12 +++++++++--- libraries/MySensors/MySensor.cpp | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index a79119a..204389d 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -26,6 +26,9 @@ // to see what is actually is happening when developing #define DEBUG +//Comment this this line, If you are using TX(1), RX(0) as IO pin disable this +#define ENABLED_SERIAL + // Serial output baud rate (for debug prints and serial gateway) #define BAUD_RATE 115200 diff --git a/libraries/MySensors/MyHwATMega328.cpp b/libraries/MySensors/MyHwATMega328.cpp index a9e6de7..42517db 100644 --- a/libraries/MySensors/MyHwATMega328.cpp +++ b/libraries/MySensors/MyHwATMega328.cpp @@ -119,7 +119,9 @@ void powerDown(period_t period) { void MyHwATMega328::internalSleep(unsigned long ms) { // Let serial prints finish (debug, log etc) - Serial.flush(); + #ifdef ENABLED_SERIAL + Serial.flush(); + #endif pinIntTrigger = 0; while (!pinIntTrigger && ms >= 8000) { powerDown(SLEEP_8S); ms -= 8000; } if (!pinIntTrigger && ms >= 4000) { powerDown(SLEEP_4S); ms -= 4000; } @@ -148,7 +150,9 @@ bool MyHwATMega328::sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { pinTriggeredWakeup = false; } } else { - Serial.flush(); + #ifdef ENABLED_SERIAL + Serial.flush(); + #endif powerDown(SLEEP_FOREVER); } detachInterrupt(interrupt); @@ -166,7 +170,9 @@ inline uint8_t MyHwATMega328::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t i retVal = -1; } } else { - Serial.flush(); + #ifdef ENABLED_SERIAL + Serial.flush(); + #endif powerDown(SLEEP_FOREVER); } detachInterrupt(interrupt1); diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index e5b2755..dc784c8 100644 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -153,7 +153,9 @@ void MySensor::errBlink(uint8_t cnt) { #endif void MySensor::begin(void (*_msgCallback)(const MyMessage &), uint8_t _nodeId, boolean _repeaterMode, uint8_t _parentNodeId) { - hw_init(); + #ifdef ENABLED_SERIAL + hw_init(); + #endif repeaterMode = _repeaterMode; msgCallback = _msgCallback; failedTransmissions = 0; From d7459ae0503feac0b38d3c923b8b16f4faa8e777 Mon Sep 17 00:00:00 2001 From: Jeeva Kandasamy Date: Mon, 24 Aug 2015 12:48:30 +0530 Subject: [PATCH 02/35] modified comment for ENABLED_SERIAL --- libraries/MySensors/MyConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 204389d..13978d5 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -26,7 +26,7 @@ // to see what is actually is happening when developing #define DEBUG -//Comment this this line, If you are using TX(1), RX(0) as IO pin disable this +// Disable this line, If you are using TX(1), RX(0) as normal I/O pin #define ENABLED_SERIAL // Serial output baud rate (for debug prints and serial gateway) From 86f71091e973bf2fc684e223c6d3147a63a73bea Mon Sep 17 00:00:00 2001 From: gizmocuz Date: Mon, 24 Aug 2015 20:45:29 +0200 Subject: [PATCH 03/35] initialize digital pins internal pullup resistor Always send volume when not in sleep mode send frequency set to 30 seconds --- .../WaterMeterPulseSensor.ino | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/libraries/MySensors/examples/WaterMeterPulseSensor/WaterMeterPulseSensor.ino b/libraries/MySensors/examples/WaterMeterPulseSensor/WaterMeterPulseSensor.ino index d676d6c..81c0c8e 100644 --- a/libraries/MySensors/examples/WaterMeterPulseSensor/WaterMeterPulseSensor.ino +++ b/libraries/MySensors/examples/WaterMeterPulseSensor/WaterMeterPulseSensor.ino @@ -20,6 +20,7 @@ * * REVISION HISTORY * Version 1.0 - Henrik Ekblad + * Version 1.1 - GizMoCuz * * DESCRIPTION * Use this sensor to measure volume and flow of your house watermeter. @@ -33,8 +34,8 @@ * http://www.mysensors.org/build/pulse_water */ -#include #include +#include #define DIGITAL_INPUT_SENSOR 3 // The digital input you attached your sensor. (Only 2 and 3 generates interrupt!) #define SENSOR_INTERRUPT DIGITAL_INPUT_SENSOR-2 // Usually the interrupt = pin -2 (on uno/nano anyway) @@ -47,7 +48,7 @@ #define CHILD_ID 1 // Id of the sensor child -unsigned long SEND_FREQUENCY = 20000; // Minimum time between send (in milliseconds). We don't want to spam the gateway. +unsigned long SEND_FREQUENCY = 30000; // Minimum time between send (in milliseconds). We don't want to spam the gateway. MySensor gw; MyMessage flowMsg(CHILD_ID,V_FLOW); @@ -72,8 +73,11 @@ void setup() { gw.begin(incomingMessage); + // initialize our digital pins internal pullup resistor so one pulse switches from high to low (less distortion) + pinMode(DIGITAL_INPUT_SENSOR, INPUT_PULLUP); + // Send the sketch version information to the gateway and Controller - gw.sendSketchInfo("Water Meter", "1.2"); + gw.sendSketchInfo("Water Meter", "1.1"); // Register this device as Waterflow sensor gw.present(CHILD_ID, S_WATER); @@ -85,7 +89,7 @@ void setup() lastSend = lastPulse = millis(); - attachInterrupt(SENSOR_INTERRUPT, onPulse, RISING); + attachInterrupt(SENSOR_INTERRUPT, onPulse, FALLING); } @@ -93,7 +97,7 @@ void loop() { gw.process(); unsigned long currentTime = millis(); - + // Only send values at a maximum frequency or woken up from sleep if (SLEEP_MODE || (currentTime - lastSend > SEND_FREQUENCY)) { @@ -124,7 +128,7 @@ void loop() } // Pulse count has changed - if (pulseCount != oldPulseCount) { + if ((pulseCount != oldPulseCount)||(!SLEEP_MODE)) { oldPulseCount = pulseCount; Serial.print("pulsecount:"); @@ -133,7 +137,7 @@ void loop() gw.send(lastCounterMsg.set(pulseCount)); // Send pulsecount value to gw in VAR1 double volume = ((double)pulseCount/((double)PULSE_FACTOR)); - if (volume != oldvolume) { + if ((volume != oldvolume)||(!SLEEP_MODE)) { oldvolume = volume; Serial.print("volume:"); @@ -152,6 +156,7 @@ void incomingMessage(const MyMessage &message) { if (message.type==V_VAR1) { unsigned long gwPulseCount=message.getULong(); pulseCount += gwPulseCount; + flow=oldflow=0; Serial.print("Received last pulse count from gw:"); Serial.println(pulseCount); pcReceived = true; @@ -178,4 +183,3 @@ void onPulse() } pulseCount++; } - From 64e28005b4574af467c4fad051e687f9c92f372f Mon Sep 17 00:00:00 2001 From: Yveaux Date: Wed, 26 Aug 2015 12:00:29 +0200 Subject: [PATCH 04/35] Added initial support for ESP8266 as WiFi gateway for MySensors --- libraries/MySensors/MyHwATMega328.cpp | 3 + libraries/MySensors/MyHwATMega328.h | 3 + libraries/MySensors/MyHwESP8266.cpp | 184 +++++++++++++++ libraries/MySensors/MyHwESP8266.h | 86 +++++++ libraries/MySensors/MyMessage.h | 2 + libraries/MySensors/MySensor.h | 5 + libraries/MySensors/MySigningAtsha204.cpp | 2 + .../Esp8266Gateway/Esp8266Gateway.ino | 216 ++++++++++++++++++ .../examples/Esp8266Gateway/GatewayUtil.h | 133 +++++++++++ libraries/MySensors/utility/ATSHA204.cpp | 4 + libraries/MySensors/utility/RF24.cpp | 47 ++-- libraries/MySensors/utility/RF24_config.h | 11 +- libraries/MySensors/utility/RFM69.cpp | 4 + libraries/MySensors/utility/RFM69.h | 4 + libraries/MySensors/utility/SPIFlash.cpp | 6 + libraries/MySensors/utility/nRF24L01.h | 2 +- libraries/MySensors/utility/sha256.cpp | 8 +- 17 files changed, 694 insertions(+), 26 deletions(-) create mode 100644 libraries/MySensors/MyHwESP8266.cpp create mode 100644 libraries/MySensors/MyHwESP8266.h create mode 100644 libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino create mode 100644 libraries/MySensors/examples/Esp8266Gateway/GatewayUtil.h diff --git a/libraries/MySensors/MyHwATMega328.cpp b/libraries/MySensors/MyHwATMega328.cpp index 42517db..dfe4b5c 100644 --- a/libraries/MySensors/MyHwATMega328.cpp +++ b/libraries/MySensors/MyHwATMega328.cpp @@ -17,6 +17,8 @@ * version 2 as published by the Free Software Foundation. */ +#ifdef __AVR_ATmega328P__ + #include "MyHw.h" #include "MyHwATMega328.h" @@ -215,3 +217,4 @@ void MyHwATMega328::debugPrint(bool isGW, const char *fmt, ... ) { } #endif +#endif // #ifdef __AVR_ATmega328P__ diff --git a/libraries/MySensors/MyHwATMega328.h b/libraries/MySensors/MyHwATMega328.h index 9e42b55..a873ba9 100644 --- a/libraries/MySensors/MyHwATMega328.h +++ b/libraries/MySensors/MyHwATMega328.h @@ -20,6 +20,8 @@ #ifndef MyHwATMega328_h #define MyHwATMega328_h +#ifdef __AVR_ATmega328P__ + #include "MyHw.h" #include "MyConfig.h" #include "MyMessage.h" @@ -116,3 +118,4 @@ private: void internalSleep(unsigned long ms); }; #endif +#endif // #ifdef __AVR_ATmega328P__ diff --git a/libraries/MySensors/MyHwESP8266.cpp b/libraries/MySensors/MyHwESP8266.cpp new file mode 100644 index 0000000..7d83215 --- /dev/null +++ b/libraries/MySensors/MyHwESP8266.cpp @@ -0,0 +1,184 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifdef ESP8266 + +#include "MyHw.h" +#include "MyHwESP8266.h" +#include + +/* +int8_t pinIntTrigger = 0; +void wakeUp() //place to send the interrupts +{ + pinIntTrigger = 1; +} +void wakeUp2() //place to send the second interrupts +{ + pinIntTrigger = 2; +} + +// Watchdog Timer interrupt service routine. This routine is required +// to allow automatic WDIF and WDIE bit clearance in hardware. +ISR (WDT_vect) +{ + // WDIE & WDIF is cleared in hardware upon entering this ISR + wdt_disable(); +} +*/ + +static void hw_initConfigBlock( size_t length = 1024 /*ATMega328 has 1024 bytes*/ ) +{ + static bool initDone = false; + if (!initDone) + { + EEPROM.begin(length); + initDone = true; + } +} + +void hw_readConfigBlock(void* buf, void* adr, size_t length) +{ + hw_initConfigBlock(); + uint8_t* dst = static_cast(buf); + int offs = reinterpret_cast(adr); + while (length-- > 0) + { + *dst++ = EEPROM.read(offs++); + } +} + +void hw_writeConfigBlock(void* buf, void* adr, size_t length) +{ + hw_initConfigBlock(); + uint8_t* src = static_cast(buf); + int offs = reinterpret_cast(adr); + while (length-- > 0) + { + EEPROM.write(offs++, *src++); + } + EEPROM.commit(); +} + +uint8_t hw_readConfig(int adr) +{ + uint8_t value; + hw_readConfigBlock(&value, reinterpret_cast(adr), 1); + return value; +} + +void hw_writeConfig(int adr, uint8_t value) +{ + uint8_t curr = hw_readConfig(adr); + if (curr != value) + { + hw_writeConfigBlock(&value, reinterpret_cast(adr), 1); + } +} + + + +MyHwESP8266::MyHwESP8266() : MyHw() +{ +} + + +/* + +// The following was redifined as macros to save space + +inline uint8_t MyHwATMega328::readConfig(uint8_t pos) { + return eeprom_read_byte((uint8_t*)pos); +} + +inline void MyHwATMega328::writeConfig(uint8_t pos, uint8_t value) { + eeprom_update_byte((uint8_t*)pos, value); +} + +inline void MyHwATMega328::readConfigBlock(void* buf, void * pos, size_t length) { + eeprom_read_block(buf, (void*)pos, length); +} + +inline void MyHwATMega328::writeConfigBlock(void* pos, void* buf, size_t length) { + eeprom_write_block((void*)pos, (void*)buf, length); +} + + +inline void MyHwATMega328::init() { + Serial.begin(BAUD_RATE); +} + +inline void MyHwATMega328::watchdogReset() { + wdt_reset(); +} + +inline void MyHwATMega328::reboot() { + wdt_enable(WDTO_15MS); for (;;); +} + +inline unsigned long MyHwATMega328::millis() { + return ::millis(); +} +*/ + + +void MyHwESP8266::sleep(unsigned long ms) { + // TODO: Not supported! +} + +bool MyHwESP8266::sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { + // TODO: Not supported! + return false; +} + +inline uint8_t MyHwESP8266::sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { + // TODO: Not supported! + return 0; +} + + + +#ifdef DEBUG +void MyHwESP8266::debugPrint(bool isGW, const char *fmt, ... ) { + char fmtBuffer[300]; + if (isGW) { + // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) + snprintf_P(fmtBuffer, 299, PSTR("0;0;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); + Serial.print(fmtBuffer); + } + va_list args; + va_start (args, fmt ); + va_end (args); + if (isGW) { + // Truncate message if this is gateway node + vsnprintf_P(fmtBuffer, 60, fmt, args); + fmtBuffer[59] = '\n'; + fmtBuffer[60] = '\0'; + } else { + vsnprintf_P(fmtBuffer, 299, fmt, args); + } + va_end (args); + Serial.print(fmtBuffer); + Serial.flush(); + + //Serial.write(freeRam()); +} +#endif + +#endif // #ifdef ESP8266 diff --git a/libraries/MySensors/MyHwESP8266.h b/libraries/MySensors/MyHwESP8266.h new file mode 100644 index 0000000..87ea8e8 --- /dev/null +++ b/libraries/MySensors/MyHwESP8266.h @@ -0,0 +1,86 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifdef ESP8266 + +#ifndef MyHwESP8266_h +#define MyHwESP8266_h + +#include "MyHw.h" +#include "MyConfig.h" +#include "MyMessage.h" + + +#ifdef __cplusplus +#include +#include +#endif + +// Define these as macros to save valuable space + +#define hw_digitalWrite(__pin, __value) (digitalWrite(__pin, __value)) +#define hw_init() Serial.begin(BAUD_RATE) +#define hw_watchdogReset() wdt_reset() +#define hw_reboot() wdt_enable(WDTO_15MS); while (1) +#define hw_millis() millis() + +void hw_readConfigBlock(void* buf, void* adr, size_t length); +void hw_writeConfigBlock(void* buf, void* adr, size_t length); +void hw_writeConfig(int adr, uint8_t value); +uint8_t hw_readConfig(int adr); + +enum period_t +{ + SLEEP_15Ms, + SLEEP_30MS, + SLEEP_60MS, + SLEEP_120MS, + SLEEP_250MS, + SLEEP_500MS, + SLEEP_1S, + SLEEP_2S, + SLEEP_4S, + SLEEP_8S, + SLEEP_FOREVER +}; + +class MyHwESP8266 : public MyHw +{ +public: + MyHwESP8266(); + +/* void init(); + void watchdogReset(); + void reboot(); + unsigned long millis(); + uint8_t readConfig(uint8_t pos); + void writeConfig(uint8_t pos, uint8_t value); + void readConfigBlock(void* buf, void * pos, size_t length); + void writeConfigBlock(void* pos, void* buf, size_t length); */ + + void sleep(unsigned long ms); + bool sleep(uint8_t interrupt, uint8_t mode, unsigned long ms); + uint8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms); +#ifdef DEBUG + void debugPrint(bool isGW, const char *fmt, ... ); +#endif +}; +#endif + +#endif // #ifdef ESP8266 diff --git a/libraries/MySensors/MyMessage.h b/libraries/MySensors/MyMessage.h index 6c7c471..d3be005 100644 --- a/libraries/MySensors/MyMessage.h +++ b/libraries/MySensors/MyMessage.h @@ -163,7 +163,9 @@ typedef enum { +#ifndef BIT #define BIT(n) ( 1<<(n) ) +#endif // Create a bitmask of length len. #define BIT_MASK(len) ( BIT(len)-1 ) // Create a bitfield mask of length starting at bit 'start'. diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index 3577693..3444077 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -41,8 +41,13 @@ // Set the hardware driver to use (initialized by MySensor-class) //#if defined __AVR_ATmega328P__ +#if defined(ESP8266) +#include "MyHwESP8266.h" +typedef MyHwESP8266 MyHwDriver; +#else #include "MyHwATMega328.h" typedef MyHwATMega328 MyHwDriver; +#endif //#endif diff --git a/libraries/MySensors/MySigningAtsha204.cpp b/libraries/MySensors/MySigningAtsha204.cpp index 29c4d59..6444dd3 100644 --- a/libraries/MySensors/MySigningAtsha204.cpp +++ b/libraries/MySensors/MySigningAtsha204.cpp @@ -24,6 +24,7 @@ * HMAC-SHA256 authentication with a readout-protected key. * */ +#if defined(__AVR__) #include "MySigning.h" #include "MySigningAtsha204.h" @@ -266,3 +267,4 @@ uint8_t* MySigningAtsha204::sha256(const uint8_t* data, size_t sz) { DEBUG_SIGNING_PRINTBUF(F("SHA:"), &rx_buffer[SHA204_BUFFER_POS_DATA], 32); return &rx_buffer[SHA204_BUFFER_POS_DATA]; } +#endif // #if defined(__AVR__) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino new file mode 100644 index 0000000..e71af42 --- /dev/null +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -0,0 +1,216 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + ******************************* + * + * REVISION HISTORY + * Version 1.0 - Henrik EKblad + * Contribution by a-lurker and Anticimex, + * Contribution by Norbert Truchsess + * Contribution by Ivo Pullens (ESP8266 support) + * + * DESCRIPTION + * The EthernetGateway sends data received from sensors to the WiFi link. + * The gateway also accepts input on ethernet interface, which is then sent out to the radio network. + * + * VERA CONFIGURATION: + * Enter "ip-number:port" in the ip-field of the Arduino GW device. This will temporarily override any serial configuration for the Vera plugin. + * E.g. If you want to use the defualt values in this sketch enter: 192.168.178.66:5003 + * + * LED purposes: + * - To use the feature, uncomment WITH_LEDS_BLINKING in MyConfig.h + * - RX (green) - blink fast on radio message recieved. In inclusion mode will blink fast only on presentation recieved + * - TX (yellow) - blink fast on radio message transmitted. In inclusion mode will blink slowly + * - ERR (red) - fast blink on error during transmission error or recieve crc error + * + * See http://www.mysensors.org/build/ethernet_gateway for wiring instructions. + * The ESP8266 however requires different wiring: + * nRF24L01+ ESP8266 + * VCC VCC + * CE GPIO4 + * CSN/CS GPIO15 + * SCK GPIO14 + * MISO GPIO12 + * MOSI GPIO13 + * + * Not all ESP8266 modules have all pins available on their external interface. + * This code has been tested on an ESP-12 module. + * The ESP8266 requires a certain pin configuration to download code, and another one to run code: + * - Connect REST (reset) via 10K pullup resistor to VCC, and via switch to GND ('reset switch') + * - Connect GPIO15 via 10K pulldown resistor to GND + * - Connect CH_PD via 10K resistor to VCC + * - Connect GPIO2 via 10K resistor to VCC + * - Connect GPIO0 via 10K resistor to VCC, and via switch to GND ('bootload switch') + * + * Signing, RF69 radio, inclusion button and a separate SPI flash are not supported yet! + * + * Make sure to fill in your ssid and WiFi password below for ssid & pass. + */ +#define NO_PORTB_PINCHANGES + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include "GatewayUtil.h" + +const char *ssid = "MySSID"; // cannot be longer than 32 characters! +const char *pass = "MyVerySecretPassword"; // + + #define INCLUSION_MODE_TIME 1 // Number of minutes inclusion mode is enabled +// #define INCLUSION_MODE_PIN 3 // Digital pin used for inclusion mode button + +#define RADIO_CE_PIN 4 // radio chip enable +#define RADIO_SPI_SS_PIN 15 // radio SPI serial select + +#define RADIO_ERROR_LED_PIN 7 // Error led pin +#define RADIO_RX_LED_PIN 8 // Receive led pin +#define RADIO_TX_LED_PIN 9 // the PCB, on board LED + + +// NRFRF24L01 radio driver (set low transmit power by default) +MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW); + +// Hardware profile +MyHwESP8266 hw; + +// Construct MySensors library (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h) +// To use LEDs blinking, uncomment WITH_LEDS_BLINKING in MyConfig.h +#ifdef WITH_LEDS_BLINKING +MySensor gw(transport, hw /*, signer*/, RADIO_RX_LED_PIN, RADIO_TX_LED_PIN, RADIO_ERROR_LED_PIN); +#else +MySensor gw(transport, hw /*, signer*/); +#endif + + +#define IP_PORT 5003 // The port you want to open + +// a R/W server on the port +WiFiServer server(IP_PORT); +// handle to open connection +WiFiClient client; + +char inputString[MAX_RECEIVE_LENGTH] = ""; // A string to hold incoming commands from serial/ethernet interface +int inputPos = 0; +bool sentReady = false; + +void output(const char *fmt, ... ) { + va_list args; + va_start (args, fmt ); + vsnprintf_P(serialBuffer, MAX_SEND_LENGTH, fmt, args); + va_end (args); + Serial.print(serialBuffer); + server.write(serialBuffer); +} + +void setup() +{ + // Setup console + Serial.begin(115200); + + Serial.println(); + Serial.println("ESP8266 MySensors Gateway"); + Serial.print("Connecting to "); Serial.println(ssid); + + (void)WiFi.begin(ssid, pass); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println("Connected!"); + Serial.print("IP: "); Serial.println(WiFi.localIP()); + Serial.flush(); + +#ifndef INCLUSION_MODE_PIN + setupGateway(255, INCLUSION_MODE_TIME, output); +#else + setupGateway(INCLUSION_MODE_PIN, INCLUSION_MODE_TIME, output); + + // Add interrupt for inclusion button to pin + PCintPort::attachInterrupt(pinInclusion, startInclusionInterrupt, RISING); +#endif + + // Initialize gateway at maximum PA level, channel 70 and callback for write operations + gw.begin(incomingMessage, 0, true, 0); + + // start listening for clients + server.begin(); +} + + +void loop() { + gw.process(); + +#ifdef INCLUSION_MODE_PIN + checkButtonTriggeredInclusion(); + checkInclusionFinished(); +#endif + + //check if there are any new clients + if (server.hasClient()) + { + if (client) + { + client.stop(); + } + client = server.available(); + output(PSTR("0;0;%d;0;%d;Gateway startup complete.\n"), C_INTERNAL, I_GATEWAY_READY); + } + + if (client) { + if (!client.connected()) { + client.stop(); + } else if (client.available()) { + // read the bytes incoming from the client + char inChar = client.read(); + if (inputPos + + +#ifdef ARDUINO + + +uint8_t inclusionTime = 1; // Number of minutes inclusion mode is enabled +uint8_t pinInclusion = 3; // Input pin that should trigger inclusion mode + +#define MAX_RECEIVE_LENGTH 100 // Max buffersize needed for messages coming from controller +#define MAX_SEND_LENGTH 120 // Max buffersize needed for messages destined for controller + +volatile boolean buttonTriggeredInclusion; +boolean inclusionMode; // Keeps track on inclusion mode +bool inclusionButtonSupported = false; +void (*serial)(const char *fmt, ... ); + +MyParserSerial parser; + +void setInclusionMode(boolean newMode); + +char convBuf[MAX_PAYLOAD*2+1]; +char serialBuffer[MAX_SEND_LENGTH]; // Buffer for building string when sending data to vera +unsigned long inclusionStartTime; + +void setupGateway(uint8_t _inc, uint8_t _incTime, void (* _serial)(const char *, ... )) { + inclusionMode = 0; + buttonTriggeredInclusion = false; + serial = _serial; + + inclusionTime = _incTime; + inclusionButtonSupported = (_inc != 255); + if (inclusionButtonSupported) + { + pinInclusion = _inc; + + // Setup digital in that triggers inclusion mode + pinMode(pinInclusion, INPUT); + digitalWrite(pinInclusion, HIGH); + } +} + + + +void startInclusionInterrupt() { + buttonTriggeredInclusion = true; +} + +void incomingMessage(const MyMessage &message) { +// if (mGetCommand(message) == C_PRESENTATION && inclusionMode) { +// gw.rxBlink(3); +// } else { +// gw.rxBlink(1); +// } + // Pass along the message from sensors to serial line + serial(PSTR("%d;%d;%d;%d;%d;%s\n"),message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type, message.getString(convBuf)); +} + + + +void checkButtonTriggeredInclusion() { + if (inclusionButtonSupported) + { + if (buttonTriggeredInclusion) { + // Ok, someone pressed the inclusion button on the gateway + // start inclusion mode for 1 munute. + #ifdef DEBUG + serial(PSTR("0;0;%d;0;%d;Inclusion started by button.\n"), C_INTERNAL, I_LOG_MESSAGE); + #endif + buttonTriggeredInclusion = false; + setInclusionMode(true); + } + } +} + +void checkInclusionFinished() { + if (inclusionMode && millis()-inclusionStartTime>60000UL*inclusionTime) { + // inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode + setInclusionMode(false); + } +} + +void parseAndSend(MySensor &gw, char *commandBuffer) { + boolean ok; + MyMessage &msg = gw.getLastMessage(); + + if (parser.parse(msg, commandBuffer)) { + uint8_t command = mGetCommand(msg); + + if (msg.destination==GATEWAY_ADDRESS && command==C_INTERNAL) { + // Handle messages directed to gateway + if (msg.type == I_VERSION) { + // Request for version + serial(PSTR("0;0;%d;0;%d;%s\n"), C_INTERNAL, I_VERSION, LIBRARY_VERSION); + } else if (msg.type == I_INCLUSION_MODE) { + // Request to change inclusion mode + setInclusionMode(atoi(msg.data) == 1); + } + } else { + #ifdef WITH_LEDS_BLINKING + gw.txBlink(1); + #endif + ok = gw.sendRoute(msg); + if (!ok) { + #ifdef WITH_LEDS_BLINKING + gw.errBlink(1); + #endif + } + } + } +} + +void setInclusionMode(boolean newMode) { + if (newMode != inclusionMode) { + inclusionMode = newMode; + // Send back mode change on serial line to ack command + serial(PSTR("0;0;%d;0;%d;%d\n"), C_INTERNAL, I_INCLUSION_MODE, inclusionMode?1:0); + + if (inclusionMode) { + inclusionStartTime = millis(); + } + } +} + + +#else +#error This example is only for use on Arduino. +#endif // ARDUINO + +#endif diff --git a/libraries/MySensors/utility/ATSHA204.cpp b/libraries/MySensors/utility/ATSHA204.cpp index f639bab..5fb8288 100644 --- a/libraries/MySensors/utility/ATSHA204.cpp +++ b/libraries/MySensors/utility/ATSHA204.cpp @@ -1,3 +1,5 @@ +#if defined(__AVR__) + #include "Arduino.h" #include "ATSHA204.h" @@ -572,3 +574,5 @@ uint8_t ATSHA204Class::sha204c_check_crc(uint8_t *response) return (crc[0] == response[count] && crc[1] == response[count + 1]) ? SHA204_SUCCESS : SHA204_BAD_CRC; } + +#endif // defined(__AVR__) \ No newline at end of file diff --git a/libraries/MySensors/utility/RF24.cpp b/libraries/MySensors/utility/RF24.cpp index a0d281f..c117f4b 100644 --- a/libraries/MySensors/utility/RF24.cpp +++ b/libraries/MySensors/utility/RF24.cpp @@ -498,7 +498,7 @@ void RF24::begin(void) // Reset current status // Notice reset and flush is the last thing we do - write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); + write_register(RF24_STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); // Set up default configuration. Callers can always change it later. // This channel should be universally safe and not bleed over into adjacent @@ -525,7 +525,7 @@ void RF24::startListening(void) powerUp(); #endif write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX)); - write_register(STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); + write_register(RF24_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); // Restore the pipe0 adddress, if exists if (pipe0_reading_address[0] > 0){ @@ -546,22 +546,31 @@ void RF24::startListening(void) } /****************************************************************************/ -static const uint8_t child_pipe_enable[] PROGMEM = +static uint8_t get_child_pipe_mask(const uint8_t idx) { - ERX_P0, ERX_P1, ERX_P2, ERX_P3, ERX_P4, ERX_P5 -}; + static const uint8_t child_pipe_enable[] PROGMEM = + { + _BV(ERX_P0), _BV(ERX_P1), _BV(ERX_P2), _BV(ERX_P3), _BV(ERX_P4), _BV(ERX_P5) + }; +#ifdef ESP8266 + return child_pipe_enable[0]; +#else + return pgm_read_byte(&child_pipe_enable[0]); +#endif +} void RF24::stopListening(void) { - ce(LOW); #if defined(__arm__) delayMicroseconds(300); #endif delayMicroseconds(130); + if(read_register(FEATURE) & _BV(EN_ACK_PAY)){ flush_tx(); } + //flush_rx(); write_register(CONFIG, ( read_register(CONFIG) ) & ~_BV(PRIM_RX) ); @@ -569,13 +578,13 @@ void RF24::stopListening(void) // for 3 pins solution TX mode is only left with additonal powerDown/powerUp cycle if (ce_pin == csn_pin) { powerDown(); - powerUp(); + powerUp(); } #endif - write_register(EN_RXADDR,read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[0]))); // Enable RX on pipe0 + + write_register(EN_RXADDR,read_register(EN_RXADDR) | get_child_pipe_mask(0)); // Enable RX on pipe0 delayMicroseconds(100); - } /****************************************************************************/ @@ -636,7 +645,7 @@ bool RF24::write( const void* buf, uint8_t len, const bool multicast ) ce(LOW); - uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); + uint8_t status = write_register(RF24_STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); //Max retries exceeded if( status & _BV(MAX_RT)){ @@ -686,7 +695,7 @@ bool RF24::writeBlocking( const void* buf, uint8_t len, uint32_t timeout ) /****************************************************************************/ void RF24::reUseTX(){ - write_register(STATUS,_BV(MAX_RT) ); //Clear max retry flag + write_register(RF24_STATUS,_BV(MAX_RT) ); //Clear max retry flag spiTrans( REUSE_TX_PL ); ce(LOW); //Re-Transfer packet ce(HIGH); @@ -709,7 +718,7 @@ bool RF24::writeFast( const void* buf, uint8_t len, const bool multicast ) if( get_status() & _BV(MAX_RT)){ //reUseTX(); //Set re-transmit - write_register(STATUS,_BV(MAX_RT) ); //Clear max retry flag + write_register(RF24_STATUS,_BV(MAX_RT) ); //Clear max retry flag return 0; //Return 0. The previous payload has been retransmitted //From the user perspective, if you get a 0, just keep trying to send the same payload } @@ -777,7 +786,7 @@ bool RF24::txStandBy(){ #endif while( ! (read_register(FIFO_STATUS) & _BV(TX_EMPTY)) ){ if( get_status() & _BV(MAX_RT)){ - write_register(STATUS,_BV(MAX_RT) ); + write_register(RF24_STATUS,_BV(MAX_RT) ); ce(LOW); flush_tx(); //Non blocking, flush the data return 0; @@ -802,7 +811,7 @@ bool RF24::txStandBy(uint32_t timeout){ while( ! (read_register(FIFO_STATUS) & _BV(TX_EMPTY)) ){ if( get_status() & _BV(MAX_RT)){ - write_register(STATUS,_BV(MAX_RT) ); + write_register(RF24_STATUS,_BV(MAX_RT) ); ce(LOW); //Set re-transmit ce(HIGH); if(millis() - start >= timeout){ @@ -891,7 +900,7 @@ void RF24::read( void* buf, uint8_t len ){ read_payload( buf, len ); //Clear the two possible interrupt flags with one command - write_register(STATUS,_BV(RX_DR) | _BV(MAX_RT) | _BV(TX_DS) ); + write_register(RF24_STATUS,_BV(RX_DR) | _BV(MAX_RT) | _BV(TX_DS) ); } @@ -901,7 +910,7 @@ void RF24::whatHappened(bool& tx_ok,bool& tx_fail,bool& rx_ready) { // Read the status & reset the status in one easy call // Or is that such a good idea? - uint8_t status = write_register(STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); + uint8_t status = write_register(RF24_STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); // Report to the user what happened tx_ok = status & _BV(TX_DS); @@ -972,7 +981,7 @@ void RF24::openReadingPipe(uint8_t child, uint64_t address) // Note it would be more efficient to set all of the bits for all open // pipes at once. However, I thought it would make the calling code // more simple to do it this way. - write_register(EN_RXADDR,read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[child]))); + write_register(EN_RXADDR,read_register(EN_RXADDR) | get_child_pipe_mask(child)); } } @@ -1009,7 +1018,7 @@ void RF24::openReadingPipe(uint8_t child, const uint8_t *address) // Note it would be more efficient to set all of the bits for all open // pipes at once. However, I thought it would make the calling code // more simple to do it this way. - write_register(EN_RXADDR,read_register(EN_RXADDR) | _BV(pgm_read_byte(&child_pipe_enable[child]))); + write_register(EN_RXADDR,read_register(EN_RXADDR) | get_child_pipe_mask(child)); } } @@ -1018,7 +1027,7 @@ void RF24::openReadingPipe(uint8_t child, const uint8_t *address) void RF24::closeReadingPipe( uint8_t pipe ) { - write_register(EN_RXADDR,read_register(EN_RXADDR) & ~_BV(pgm_read_byte(&child_pipe_enable[pipe]))); + write_register(EN_RXADDR,read_register(EN_RXADDR) & ~get_child_pipe_mask(pipe)); } /****************************************************************************/ diff --git a/libraries/MySensors/utility/RF24_config.h b/libraries/MySensors/utility/RF24_config.h index 2addc27..d38ac88 100644 --- a/libraries/MySensors/utility/RF24_config.h +++ b/libraries/MySensors/utility/RF24_config.h @@ -87,7 +87,7 @@ // Avoid spurious warnings // Arduino DUE is arm and uses traditional PROGMEM constructs #if 1 -#if ! defined( NATIVE ) && defined( ARDUINO ) && ! defined(__arm__) && ! defined( CORE_TEENSY3 ) +#if ! defined( NATIVE ) && defined( ARDUINO ) && ! defined(__arm__) && ! defined( CORE_TEENSY3 ) && ! defined(ESP8266) #undef PROGMEM #define PROGMEM __attribute__(( section(".progmem.data") )) #undef PSTR @@ -97,18 +97,21 @@ // Progmem is Arduino-specific // Arduino DUE is arm and does not include avr/pgmspace -#if defined(ARDUINO) && ! defined(__arm__) +#if defined(__AVR__) #include #define PRIPSTR "%S" #else +#if defined(ESP8266) +#include +#else #if ! defined(ARDUINO) // This doesn't work on Arduino DUE typedef char const char; #else // Fill in pgm_read_byte that is used, but missing from DUE #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) #endif +#endif - -#if !defined ( CORE_TEENSY ) +#if !defined ( CORE_TEENSY ) && ! defined(ESP8266) typedef uint16_t prog_uint16_t; #define PSTR(x) (x) #define printf_P printf diff --git a/libraries/MySensors/utility/RFM69.cpp b/libraries/MySensors/utility/RFM69.cpp index 4663480..56e47e5 100644 --- a/libraries/MySensors/utility/RFM69.cpp +++ b/libraries/MySensors/utility/RFM69.cpp @@ -384,8 +384,10 @@ void RFM69::writeReg(byte addr, byte value) void RFM69::select() { noInterrupts(); //save current SPI settings +#ifndef ESP8266 _SPCR = SPCR; _SPSR = SPSR; +#endif //set RFM69 SPI settings SPI.setDataMode(SPI_MODE0); SPI.setBitOrder(MSBFIRST); @@ -397,8 +399,10 @@ void RFM69::select() { void RFM69::unselect() { digitalWrite(_slaveSelectPin, HIGH); //restore SPI settings to what they were before talking to RFM69 +#ifndef ESP8266 SPCR = _SPCR; SPSR = _SPSR; +#endif interrupts(); } diff --git a/libraries/MySensors/utility/RFM69.h b/libraries/MySensors/utility/RFM69.h index c3e8d05..bcc3201 100644 --- a/libraries/MySensors/utility/RFM69.h +++ b/libraries/MySensors/utility/RFM69.h @@ -43,6 +43,10 @@ #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega2560__) #define RF69_IRQ_PIN 2 #define RF69_IRQ_NUM 2 +#elif defined(ESP8266) + // TODO !! + #define RF69_IRQ_PIN 2 + #define RF69_IRQ_NUM 0 #endif #define CSMA_LIMIT -90 // upper RX signal sensitivity threshold in dBm for carrier sense access diff --git a/libraries/MySensors/utility/SPIFlash.cpp b/libraries/MySensors/utility/SPIFlash.cpp index e250c17..00e1601 100644 --- a/libraries/MySensors/utility/SPIFlash.cpp +++ b/libraries/MySensors/utility/SPIFlash.cpp @@ -56,8 +56,10 @@ void SPIFlash::select() { #ifndef SPI_HAS_TRANSACTION noInterrupts(); #endif +#ifndef ESP8266 _SPCR = SPCR; _SPSR = SPSR; +#endif #ifdef SPI_HAS_TRANSACTION SPI.beginTransaction(_settings); @@ -80,15 +82,19 @@ void SPIFlash::unselect() { #else interrupts(); #endif +#ifndef ESP8266 SPCR = _SPCR; SPSR = _SPSR; +#endif } /// setup SPI, read device ID etc... boolean SPIFlash::initialize() { +#ifndef ESP8266 _SPCR = SPCR; _SPSR = SPSR; +#endif pinMode(_slaveSelectPin, OUTPUT); #ifdef SPI_HAS_TRANSACTION _settings = SPISettings(4000000, MSBFIRST, SPI_MODE0); diff --git a/libraries/MySensors/utility/nRF24L01.h b/libraries/MySensors/utility/nRF24L01.h index 132de94..02e1d4f 100644 --- a/libraries/MySensors/utility/nRF24L01.h +++ b/libraries/MySensors/utility/nRF24L01.h @@ -31,7 +31,7 @@ #define SETUP_RETR 0x04 #define RF_CH 0x05 #define RF_SETUP 0x06 -#define STATUS 0x07 +#define RF24_STATUS 0x07 #define OBSERVE_TX 0x08 #define CD 0x09 #define RX_ADDR_P0 0x0A diff --git a/libraries/MySensors/utility/sha256.cpp b/libraries/MySensors/utility/sha256.cpp index f0376ab..511d78a 100644 --- a/libraries/MySensors/utility/sha256.cpp +++ b/libraries/MySensors/utility/sha256.cpp @@ -1,6 +1,10 @@ #include -#include -#include +#if defined(__AVR__) + #include + #define PRIPSTR "%S" +#elif defined(ESP8266) +#include +#endif #include "sha256.h" From 0bb5e223b2d63d1d9f406d5e494f09053ae9a22d Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 10:53:35 +0200 Subject: [PATCH 05/35] Fixed inclusion mode --- .../Esp8266Gateway/Esp8266Gateway.ino | 22 +++++--------- .../examples/Esp8266Gateway/GatewayUtil.h | 30 +++++++++---------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino index e71af42..ae7b9e2 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -57,7 +57,10 @@ * - Connect GPIO2 via 10K resistor to VCC * - Connect GPIO0 via 10K resistor to VCC, and via switch to GND ('bootload switch') * - * Signing, RF69 radio, inclusion button and a separate SPI flash are not supported yet! + * Inclusion mode button: + * - Connect GPIO5 via switch to GND ('inclusion switch') + * + * * Signing, RF69 radio and a separate SPI flash are not supported yet! * * Make sure to fill in your ssid and WiFi password below for ssid & pass. */ @@ -78,8 +81,8 @@ const char *ssid = "MySSID"; // cannot be longer than 32 characters! const char *pass = "MyVerySecretPassword"; // - #define INCLUSION_MODE_TIME 1 // Number of minutes inclusion mode is enabled -// #define INCLUSION_MODE_PIN 3 // Digital pin used for inclusion mode button +#define INCLUSION_MODE_TIME 1 // Number of minutes inclusion mode is enabled +#define INCLUSION_MODE_PIN 5 // Digital pin used for inclusion mode button #define RADIO_CE_PIN 4 // radio chip enable #define RADIO_SPI_SS_PIN 15 // radio SPI serial select @@ -127,9 +130,9 @@ void output(const char *fmt, ... ) { void setup() { // Setup console - Serial.begin(115200); + hw_init(); - Serial.println(); + Serial.println(); Serial.println(); Serial.println("ESP8266 MySensors Gateway"); Serial.print("Connecting to "); Serial.println(ssid); @@ -142,15 +145,8 @@ void setup() Serial.print("IP: "); Serial.println(WiFi.localIP()); Serial.flush(); -#ifndef INCLUSION_MODE_PIN - setupGateway(255, INCLUSION_MODE_TIME, output); -#else setupGateway(INCLUSION_MODE_PIN, INCLUSION_MODE_TIME, output); - // Add interrupt for inclusion button to pin - PCintPort::attachInterrupt(pinInclusion, startInclusionInterrupt, RISING); -#endif - // Initialize gateway at maximum PA level, channel 70 and callback for write operations gw.begin(incomingMessage, 0, true, 0); @@ -162,10 +158,8 @@ void setup() void loop() { gw.process(); -#ifdef INCLUSION_MODE_PIN checkButtonTriggeredInclusion(); checkInclusionFinished(); -#endif //check if there are any new clients if (server.hasClient()) diff --git a/libraries/MySensors/examples/Esp8266Gateway/GatewayUtil.h b/libraries/MySensors/examples/Esp8266Gateway/GatewayUtil.h index 96a65a2..c926f5e 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/GatewayUtil.h +++ b/libraries/MySensors/examples/Esp8266Gateway/GatewayUtil.h @@ -1,20 +1,17 @@ #ifndef __GATEWAYUTIL_H__ #define __GATEWAYUTIL_H__ -#include - - #ifdef ARDUINO -uint8_t inclusionTime = 1; // Number of minutes inclusion mode is enabled -uint8_t pinInclusion = 3; // Input pin that should trigger inclusion mode +static uint8_t inclusionTime = 1; // Number of minutes inclusion mode is enabled +static uint8_t pinInclusion = 3; // Input pin that should trigger inclusion mode #define MAX_RECEIVE_LENGTH 100 // Max buffersize needed for messages coming from controller #define MAX_SEND_LENGTH 120 // Max buffersize needed for messages destined for controller -volatile boolean buttonTriggeredInclusion; -boolean inclusionMode; // Keeps track on inclusion mode +static volatile boolean buttonTriggeredInclusion; +static boolean inclusionMode; // Keeps track on inclusion mode bool inclusionButtonSupported = false; void (*serial)(const char *fmt, ... ); @@ -26,6 +23,11 @@ char convBuf[MAX_PAYLOAD*2+1]; char serialBuffer[MAX_SEND_LENGTH]; // Buffer for building string when sending data to vera unsigned long inclusionStartTime; + +void startInclusionInterrupt() { + buttonTriggeredInclusion = true; +} + void setupGateway(uint8_t _inc, uint8_t _incTime, void (* _serial)(const char *, ... )) { inclusionMode = 0; buttonTriggeredInclusion = false; @@ -36,19 +38,15 @@ void setupGateway(uint8_t _inc, uint8_t _incTime, void (* _serial)(const char *, if (inclusionButtonSupported) { pinInclusion = _inc; - + // Setup digital in that triggers inclusion mode - pinMode(pinInclusion, INPUT); - digitalWrite(pinInclusion, HIGH); + pinMode(pinInclusion, INPUT_PULLUP); + + // Add interrupt for inclusion button to pin + attachInterrupt(pinInclusion, startInclusionInterrupt, FALLING); } } - - -void startInclusionInterrupt() { - buttonTriggeredInclusion = true; -} - void incomingMessage(const MyMessage &message) { // if (mGetCommand(message) == C_PRESENTATION && inclusionMode) { // gw.rxBlink(3); From d6cc1b46fc4b2c7fcaca0a6cda51a835116f194e Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 11:20:35 +0200 Subject: [PATCH 06/35] Fix led blinking --- libraries/MySensors/MySensor.cpp | 6 ++---- libraries/MySensors/MySensor.h | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index dc784c8..6c3a348 100644 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -95,14 +95,12 @@ bool MySensor::isValidFirmware() { #ifdef WITH_LEDS_BLINKING void MySensor::handleLedsBlinking() { - static unsigned long next_time = hw_millis() + ledBlinkPeriod; - // Just return if it is not the time... // http://playground.arduino.cc/Code/TimingRollover - if ((long)(hw_millis() - next_time) < 0) + if ((long)(hw_millis() - blink_next_time) < 0) return; else - next_time = hw_millis() + ledBlinkPeriod; + blink_next_time = hw_millis() + ledBlinkPeriod; // do the actual blinking if(countRx && countRx != 255) { diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index 3444077..7190d7f 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -361,6 +361,7 @@ class MySensor unsigned long ledBlinkPeriod; void handleLedsBlinking(); // do the actual blinking + unsigned long blink_next_time; #endif MyTransport& radio; From e2c715c387e5c4a5faa020dc20b00101ed259b5b Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 12:11:49 +0200 Subject: [PATCH 07/35] Fix support for signing. Only software ATSHA204 supported! --- .../Esp8266Gateway/Esp8266Gateway.ino | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino index ae7b9e2..dc678f3 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -60,7 +60,7 @@ * Inclusion mode button: * - Connect GPIO5 via switch to GND ('inclusion switch') * - * * Signing, RF69 radio and a separate SPI flash are not supported yet! + * Hardware SHA204 signing, RF69 radio and a separate SPI flash are not supported! * * Make sure to fill in your ssid and WiFi password below for ssid & pass. */ @@ -68,6 +68,8 @@ #include +#include +#include #include #include #include @@ -87,25 +89,36 @@ const char *pass = "MyVerySecretPassword"; // #define RADIO_CE_PIN 4 // radio chip enable #define RADIO_SPI_SS_PIN 15 // radio SPI serial select +#ifdef WITH_LEDS_BLINKING #define RADIO_ERROR_LED_PIN 7 // Error led pin #define RADIO_RX_LED_PIN 8 // Receive led pin #define RADIO_TX_LED_PIN 9 // the PCB, on board LED +#endif // NRFRF24L01 radio driver (set low transmit power by default) MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW); +// Message signing driver (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h) +#ifdef MY_SIGNING_FEATURE +MySigningNone signer; +//MySigningAtsha204Soft signer; +#endif + // Hardware profile MyHwESP8266 hw; // Construct MySensors library (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h) // To use LEDs blinking, uncomment WITH_LEDS_BLINKING in MyConfig.h -#ifdef WITH_LEDS_BLINKING -MySensor gw(transport, hw /*, signer*/, RADIO_RX_LED_PIN, RADIO_TX_LED_PIN, RADIO_ERROR_LED_PIN); -#else -MySensor gw(transport, hw /*, signer*/); +MySensor gw(transport, hw +#ifdef MY_SIGNING_FEATURE + , signer #endif - +#ifdef WITH_LEDS_BLINKING + , RADIO_RX_LED_PIN, RADIO_TX_LED_PIN, RADIO_ERROR_LED_PIN +#endif + ); + #define IP_PORT 5003 // The port you want to open From c0d2177afa0c928120b1f804ac06c309f5c3c1c4 Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 12:12:16 +0200 Subject: [PATCH 08/35] Software SHA204 signing uses only analog input avaiable: A0 --- libraries/MySensors/MyConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 13978d5..6766b2b 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -101,7 +101,7 @@ #define MY_ATSHA204_PIN 17 // A3 - pin where ATSHA204 is attached // MySigningAtsha204Soft default settings -#define MY_RANDOMSEED_PIN 7 // A7 - Pin used for random generation (do not connect anything to this) +#define MY_RANDOMSEED_PIN A0 // A0 - Pin used for random generation (do not connect anything to this) // Key to use for HMAC calculation in MySigningAtsha204Soft (32 bytes) #define MY_HMAC_KEY 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 From dae4f8172bb654c3ea828db3906c0880905c54ae Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 12:21:31 +0200 Subject: [PATCH 09/35] Got RF69 support compiling --- .../MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino index dc678f3..23aec57 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -60,7 +60,7 @@ * Inclusion mode button: * - Connect GPIO5 via switch to GND ('inclusion switch') * - * Hardware SHA204 signing, RF69 radio and a separate SPI flash are not supported! + * Hardware SHA204 signing and a separate SPI flash are not supported! * * Make sure to fill in your ssid and WiFi password below for ssid & pass. */ @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +98,9 @@ const char *pass = "MyVerySecretPassword"; // // NRFRF24L01 radio driver (set low transmit power by default) -MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW); +MyTransportNRF24 transport(RADIO_CE_PIN, RADIO_SPI_SS_PIN, RF24_PA_LEVEL_GW); +//MyTransportRFM69 transport; + // Message signing driver (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h) #ifdef MY_SIGNING_FEATURE From f407617dda298ac748fc5441516e4f796b329999 Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 27 Aug 2015 12:32:48 +0200 Subject: [PATCH 10/35] Removed comment about external SPI flash not supported by gateway. External SPI flash is not required by gateway. ESP8266 has sufficient internal flash should it be required. --- libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino index 23aec57..c5be624 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -60,7 +60,7 @@ * Inclusion mode button: * - Connect GPIO5 via switch to GND ('inclusion switch') * - * Hardware SHA204 signing and a separate SPI flash are not supported! + * Hardware SHA204 signing is currently not supported! * * Make sure to fill in your ssid and WiFi password below for ssid & pass. */ From cb771ab680b2d9714173c7fa524354f1042141d3 Mon Sep 17 00:00:00 2001 From: Yveaux Date: Mon, 31 Aug 2015 19:36:43 +0200 Subject: [PATCH 11/35] Fixes for compiling for Mega Make more consistent use of architecture #defines. --- libraries/MySensors/MyHwATMega328.cpp | 4 ++-- libraries/MySensors/MyHwATMega328.h | 4 ++-- libraries/MySensors/MyHwESP8266.cpp | 4 ++-- libraries/MySensors/MyHwESP8266.h | 4 ++-- libraries/MySensors/MySensor.h | 4 ++-- libraries/MySensors/MySigningAtsha204.cpp | 4 ++-- libraries/MySensors/utility/ATSHA204.cpp | 4 ++-- libraries/MySensors/utility/RFM69.h | 4 ++-- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/MySensors/MyHwATMega328.cpp b/libraries/MySensors/MyHwATMega328.cpp index dfe4b5c..7d67563 100644 --- a/libraries/MySensors/MyHwATMega328.cpp +++ b/libraries/MySensors/MyHwATMega328.cpp @@ -17,7 +17,7 @@ * version 2 as published by the Free Software Foundation. */ -#ifdef __AVR_ATmega328P__ +#ifdef ARDUINO_ARCH_AVR #include "MyHw.h" #include "MyHwATMega328.h" @@ -217,4 +217,4 @@ void MyHwATMega328::debugPrint(bool isGW, const char *fmt, ... ) { } #endif -#endif // #ifdef __AVR_ATmega328P__ +#endif // #ifdef ARDUINO_ARCH_AVR diff --git a/libraries/MySensors/MyHwATMega328.h b/libraries/MySensors/MyHwATMega328.h index a873ba9..e18b72e 100644 --- a/libraries/MySensors/MyHwATMega328.h +++ b/libraries/MySensors/MyHwATMega328.h @@ -20,7 +20,7 @@ #ifndef MyHwATMega328_h #define MyHwATMega328_h -#ifdef __AVR_ATmega328P__ +#ifdef ARDUINO_ARCH_AVR #include "MyHw.h" #include "MyConfig.h" @@ -118,4 +118,4 @@ private: void internalSleep(unsigned long ms); }; #endif -#endif // #ifdef __AVR_ATmega328P__ +#endif // #ifdef ARDUINO_ARCH_AVR diff --git a/libraries/MySensors/MyHwESP8266.cpp b/libraries/MySensors/MyHwESP8266.cpp index 7d83215..9c85f62 100644 --- a/libraries/MySensors/MyHwESP8266.cpp +++ b/libraries/MySensors/MyHwESP8266.cpp @@ -17,7 +17,7 @@ * version 2 as published by the Free Software Foundation. */ -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 #include "MyHw.h" #include "MyHwESP8266.h" @@ -181,4 +181,4 @@ void MyHwESP8266::debugPrint(bool isGW, const char *fmt, ... ) { } #endif -#endif // #ifdef ESP8266 +#endif // #ifdef ARDUINO_ARCH_ESP8266 diff --git a/libraries/MySensors/MyHwESP8266.h b/libraries/MySensors/MyHwESP8266.h index 87ea8e8..e281421 100644 --- a/libraries/MySensors/MyHwESP8266.h +++ b/libraries/MySensors/MyHwESP8266.h @@ -17,7 +17,7 @@ * version 2 as published by the Free Software Foundation. */ -#ifdef ESP8266 +#ifdef ARDUINO_ARCH_ESP8266 #ifndef MyHwESP8266_h #define MyHwESP8266_h @@ -83,4 +83,4 @@ public: }; #endif -#endif // #ifdef ESP8266 +#endif // #ifdef ARDUINO_ARCH_ESP8266 diff --git a/libraries/MySensors/MySensor.h b/libraries/MySensors/MySensor.h index 7190d7f..18f4cb7 100644 --- a/libraries/MySensors/MySensor.h +++ b/libraries/MySensors/MySensor.h @@ -41,10 +41,10 @@ // Set the hardware driver to use (initialized by MySensor-class) //#if defined __AVR_ATmega328P__ -#if defined(ESP8266) +#if defined(ARDUINO_ARCH_ESP8266) #include "MyHwESP8266.h" typedef MyHwESP8266 MyHwDriver; -#else +#elif defined(ARDUINO_ARCH_AVR) #include "MyHwATMega328.h" typedef MyHwATMega328 MyHwDriver; #endif diff --git a/libraries/MySensors/MySigningAtsha204.cpp b/libraries/MySensors/MySigningAtsha204.cpp index 6444dd3..06d1b45 100644 --- a/libraries/MySensors/MySigningAtsha204.cpp +++ b/libraries/MySensors/MySigningAtsha204.cpp @@ -24,7 +24,7 @@ * HMAC-SHA256 authentication with a readout-protected key. * */ -#if defined(__AVR__) +#if defined(ARDUINO_ARCH_AVR) #include "MySigning.h" #include "MySigningAtsha204.h" @@ -267,4 +267,4 @@ uint8_t* MySigningAtsha204::sha256(const uint8_t* data, size_t sz) { DEBUG_SIGNING_PRINTBUF(F("SHA:"), &rx_buffer[SHA204_BUFFER_POS_DATA], 32); return &rx_buffer[SHA204_BUFFER_POS_DATA]; } -#endif // #if defined(__AVR__) +#endif // #if defined(ARDUINO_ARCH_AVR) diff --git a/libraries/MySensors/utility/ATSHA204.cpp b/libraries/MySensors/utility/ATSHA204.cpp index 5fb8288..113038f 100644 --- a/libraries/MySensors/utility/ATSHA204.cpp +++ b/libraries/MySensors/utility/ATSHA204.cpp @@ -1,4 +1,4 @@ -#if defined(__AVR__) +#if defined(ARDUINO_ARCH_AVR) #include "Arduino.h" #include "ATSHA204.h" @@ -575,4 +575,4 @@ uint8_t ATSHA204Class::sha204c_check_crc(uint8_t *response) ? SHA204_SUCCESS : SHA204_BAD_CRC; } -#endif // defined(__AVR__) \ No newline at end of file +#endif // defined(ARDUINO_ARCH_AVR) \ No newline at end of file diff --git a/libraries/MySensors/utility/RFM69.h b/libraries/MySensors/utility/RFM69.h index bcc3201..fe7abd1 100644 --- a/libraries/MySensors/utility/RFM69.h +++ b/libraries/MySensors/utility/RFM69.h @@ -43,8 +43,8 @@ #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega2560__) #define RF69_IRQ_PIN 2 #define RF69_IRQ_NUM 2 -#elif defined(ESP8266) - // TODO !! +#elif defined(ARDUINO_ARCH_ESP8266) + // TODO !! Untested code! Entering unknown territory... #define RF69_IRQ_PIN 2 #define RF69_IRQ_NUM 0 #endif From 688261f069de908b26781cc72d2a293ec1bb598e Mon Sep 17 00:00:00 2001 From: Yveaux Date: Mon, 31 Aug 2015 20:13:03 +0200 Subject: [PATCH 12/35] Reverted default analog input for random seed generator --- libraries/MySensors/MyConfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MyConfig.h b/libraries/MySensors/MyConfig.h index 6766b2b..13978d5 100644 --- a/libraries/MySensors/MyConfig.h +++ b/libraries/MySensors/MyConfig.h @@ -101,7 +101,7 @@ #define MY_ATSHA204_PIN 17 // A3 - pin where ATSHA204 is attached // MySigningAtsha204Soft default settings -#define MY_RANDOMSEED_PIN A0 // A0 - Pin used for random generation (do not connect anything to this) +#define MY_RANDOMSEED_PIN 7 // A7 - Pin used for random generation (do not connect anything to this) // Key to use for HMAC calculation in MySigningAtsha204Soft (32 bytes) #define MY_HMAC_KEY 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 From c430672c3253241aa14fd1c011467bd70fa556a1 Mon Sep 17 00:00:00 2001 From: Henrik Ekblad Date: Tue, 1 Sep 2015 07:18:02 +0200 Subject: [PATCH 13/35] Fix timeout in parking sensor that turns light off when parked --- .../examples/ParkingSensor/ParkingSensor.ino | 86 +++++++++++-------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/libraries/MySensors/examples/ParkingSensor/ParkingSensor.ino b/libraries/MySensors/examples/ParkingSensor/ParkingSensor.ino index f8ad6de..50e3e6c 100644 --- a/libraries/MySensors/examples/ParkingSensor/ParkingSensor.ino +++ b/libraries/MySensors/examples/ParkingSensor/ParkingSensor.ino @@ -56,6 +56,8 @@ #define PANIC_DISTANCE 5 // Mix distance we red warning indication should be active (in cm) #define PARKED_DISTANCE 20 // Distance when "parked signal" should be sent to controller (in cm) +#define PARK_OFF_TIMEOUT 20000 // Number of milliseconds until turning off light when parked. + // Note that for older NeoPixel strips you might need to change the third parameter--see the strandtest // example for more information on possible values. @@ -67,10 +69,11 @@ NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and #define CHILD_ID 1 MySensor gw; MyMessage msg(CHILD_ID,V_TRIPPED); -int oldParkedStatus=-1; +#endif unsigned long sendInterval = 5000; // Send park status at maximum every 5 second. unsigned long lastSend; -#endif + +int oldParkedStatus=-1; unsigned long blinkInterval = 100; // blink interval (milliseconds) unsigned long lastBlinkPeriod; @@ -109,7 +112,6 @@ void loop() { if (now-lastDebouncePeriod > distDebounce) { lastDebouncePeriod = now; -#ifdef SEND_STATUS_TO_CONTROLLER // Update parked status int parked = displayDist != 0 && displayDist sendInterval) { @@ -117,48 +119,56 @@ void loop() { Serial.println("Car Parked"); else Serial.println("Car Gone"); +#ifdef SEND_STATUS_TO_CONTROLLER gw.send(msg.set(parked)); +#endif oldParkedStatus = parked; lastSend = now; } -#endif - - if (displayDist == 0) { - // No reading from sensor, assume no object found - numLightPixels--; - } else { - skipZero = 0; - int newLightPixels = NUMPIXELS - (NUMPIXELS*(displayDist-PANIC_DISTANCE)/MAX_DISTANCE); - if (newLightPixels>numLightPixels) { - // Fast raise - numLightPixels += max((newLightPixels - numLightPixels) / 2, 1); - } else if (newLightPixels=NUMPIXELS) { - // Do some intense red blinking - if (now-lastBlinkPeriod > blinkInterval) { - blinkColor = !blinkColor; - lastBlinkPeriod = now; - } - for(int i=0;i PARK_OFF_TIMEOUT) { + // We've been parked for a while now. Turn off all pixels + for(int i=0;inumLightPixels) { + // Fast raise + numLightPixels += max((newLightPixels - numLightPixels) / 2, 1); + } else if (newLightPixels=NUMPIXELS) { + // Do some intense red blinking + if (now-lastBlinkPeriod > blinkInterval) { + blinkColor = !blinkColor; + lastBlinkPeriod = now; + } + for(int i=0;i Date: Wed, 2 Sep 2015 17:46:37 +0200 Subject: [PATCH 14/35] Bugfix --- libraries/MySensors/utility/RF24.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/MySensors/utility/RF24.cpp b/libraries/MySensors/utility/RF24.cpp index c117f4b..7be6b98 100644 --- a/libraries/MySensors/utility/RF24.cpp +++ b/libraries/MySensors/utility/RF24.cpp @@ -553,9 +553,9 @@ static uint8_t get_child_pipe_mask(const uint8_t idx) _BV(ERX_P0), _BV(ERX_P1), _BV(ERX_P2), _BV(ERX_P3), _BV(ERX_P4), _BV(ERX_P5) }; #ifdef ESP8266 - return child_pipe_enable[0]; + return child_pipe_enable[idx]; #else - return pgm_read_byte(&child_pipe_enable[0]); + return pgm_read_byte(&child_pipe_enable[idx]); #endif } From f4ef926f025dae0466c68d7a3942ab0de4a4ff6c Mon Sep 17 00:00:00 2001 From: nicbrink Date: Wed, 2 Sep 2015 21:33:53 -0400 Subject: [PATCH 15/35] Update MyTransportRFM69.cpp defined in myconfig.h as RFM69_ENCRYPTKEY --- libraries/MySensors/MyTransportRFM69.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MyTransportRFM69.cpp b/libraries/MySensors/MyTransportRFM69.cpp index 4ad8fb4..757c9a5 100644 --- a/libraries/MySensors/MyTransportRFM69.cpp +++ b/libraries/MySensors/MyTransportRFM69.cpp @@ -33,7 +33,7 @@ bool MyTransportRFM69::init() { // Start up the radio library (_address will be set later by the MySensors library) radio.initialize(_freqBand, _address, _networkId); #ifdef RFM69_ENABLE_ENCRYPTION - radio.encrypt(ENCRYPTKEY); + radio.encrypt(RFM69_ENCRYPTKEY); #endif return true; } From b997861f5965344d95f7f59b469d4d99a7cae9a4 Mon Sep 17 00:00:00 2001 From: Yveaux Date: Thu, 3 Sep 2015 20:33:34 +0200 Subject: [PATCH 16/35] Fix crash at startup --- libraries/MySensors/utility/RF24.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/MySensors/utility/RF24.cpp b/libraries/MySensors/utility/RF24.cpp index 7be6b98..16e7857 100644 --- a/libraries/MySensors/utility/RF24.cpp +++ b/libraries/MySensors/utility/RF24.cpp @@ -548,13 +548,17 @@ void RF24::startListening(void) /****************************************************************************/ static uint8_t get_child_pipe_mask(const uint8_t idx) { +#ifdef ESP8266 + static const uint8_t child_pipe_enable[] = + { + _BV(ERX_P0), _BV(ERX_P1), _BV(ERX_P2), _BV(ERX_P3), _BV(ERX_P4), _BV(ERX_P5) + }; + return child_pipe_enable[idx]; +#else static const uint8_t child_pipe_enable[] PROGMEM = { _BV(ERX_P0), _BV(ERX_P1), _BV(ERX_P2), _BV(ERX_P3), _BV(ERX_P4), _BV(ERX_P5) }; -#ifdef ESP8266 - return child_pipe_enable[idx]; -#else return pgm_read_byte(&child_pipe_enable[idx]); #endif } From f03f4f9412e465006ac7e52944691f3406a880a7 Mon Sep 17 00:00:00 2001 From: Yveaux Date: Fri, 4 Sep 2015 19:54:45 +0200 Subject: [PATCH 17/35] Hardened (dis)connections Added support fot multiple sessions (Currently 5 max. Each connection costs RAM!) Each session can send data to the gateway. Data from the gateway will be broadcasted to all connected clients. Added serial logging of client (dis)connections. Inspect serial terminal in case of problems. --- .../Esp8266Gateway/Esp8266Gateway.ino | 147 +++++++++++------- .../examples/Esp8266Gateway/GatewayUtil.h | 9 +- 2 files changed, 100 insertions(+), 56 deletions(-) diff --git a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino index c5be624..ee828b5 100644 --- a/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino +++ b/libraries/MySensors/examples/Esp8266Gateway/Esp8266Gateway.ino @@ -123,24 +123,34 @@ MySensor gw(transport, hw ); -#define IP_PORT 5003 // The port you want to open +#define IP_PORT 5003 // The port you want to open +#define MAX_SRV_CLIENTS 5 // how many clients should be able to telnet to this ESP8266 // a R/W server on the port -WiFiServer server(IP_PORT); -// handle to open connection -WiFiClient client; +static WiFiServer server(IP_PORT); +static WiFiClient clients[MAX_SRV_CLIENTS]; +static bool clientsConnected[MAX_SRV_CLIENTS]; +static inputBuffer inputString[MAX_SRV_CLIENTS]; -char inputString[MAX_RECEIVE_LENGTH] = ""; // A string to hold incoming commands from serial/ethernet interface -int inputPos = 0; -bool sentReady = false; +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -void output(const char *fmt, ... ) { - va_list args; - va_start (args, fmt ); - vsnprintf_P(serialBuffer, MAX_SEND_LENGTH, fmt, args); - va_end (args); - Serial.print(serialBuffer); - server.write(serialBuffer); + +void output(const char *fmt, ... ) +{ + char serialBuffer[MAX_SEND_LENGTH]; + va_list args; + va_start (args, fmt ); + vsnprintf_P(serialBuffer, MAX_SEND_LENGTH, fmt, args); + va_end (args); + Serial.print(serialBuffer); + for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) + { + if (clients[i] && clients[i].connected()) + { +// Serial.print("Client "); Serial.print(i); Serial.println(" write"); + clients[i].write((uint8_t*)serialBuffer, strlen(serialBuffer)); + } + } } void setup() @@ -153,7 +163,8 @@ void setup() Serial.print("Connecting to "); Serial.println(ssid); (void)WiFi.begin(ssid, pass); - while (WiFi.status() != WL_CONNECTED) { + while (WiFi.status() != WL_CONNECTED) + { delay(500); Serial.print("."); } @@ -168,6 +179,7 @@ void setup() // start listening for clients server.begin(); + server.setNoDelay(true); } @@ -176,47 +188,74 @@ void loop() { checkButtonTriggeredInclusion(); checkInclusionFinished(); - - //check if there are any new clients - if (server.hasClient()) - { - if (client) - { - client.stop(); - } - client = server.available(); - output(PSTR("0;0;%d;0;%d;Gateway startup complete.\n"), C_INTERNAL, I_GATEWAY_READY); - } - if (client) { - if (!client.connected()) { - client.stop(); - } else if (client.available()) { - // read the bytes incoming from the client - char inChar = client.read(); - if (inputPos Date: Sat, 5 Sep 2015 11:46:37 +0200 Subject: [PATCH 18/35] Added V_STATUS to S_HEATER (as it is supported also) Added S_MOISTURE (a moisture sensor) --- .../examples/MockMySensors/MockMySensors.ino | 93 +++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino index 9510270..db15a6b 100644 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -26,7 +26,7 @@ #define SHORT_WAIT 50 #define SKETCH_NAME "MockMySensors " -#define SKETCH_VERSION "v0.4" +#define SKETCH_VERSION "v0.5" // Define Sensors ids /* S_DOOR, S_MOTION, S_SMOKE, S_LIGHT, S_DIMMER, S_COVER, S_TEMP, S_HUM, S_BARO, S_WIND, @@ -42,8 +42,8 @@ // Some of these ID's have not been updated for v1.5. Uncommenting too many of them // will make the sketch too large for a pro mini's memory so it's probably best to try // one at a time. -#define ID_S_ARMED 0 // dummy to controll armed stated for several sensors -#define ID_S_DOOR 1 +//#define ID_S_ARMED 0 // dummy to controll armed stated for several sensors +//#define ID_S_DOOR 1 //#define ID_S_MOTION 2 //#define ID_S_SMOKE 3 //#define ID_S_LIGHT 4 @@ -66,6 +66,8 @@ //#define ID_S_AIR_QUALITY 21 //#define ID_S_DUST 22 //#define ID_S_SCENE_CONTROLLER 23 +#define ID_S_MOISTURE 24 + //#define ID_S_CUSTOM 99 // Global Vars @@ -173,11 +175,15 @@ MyHwATMega328 hw; // MyMessage msg_S_POWER_C(ID_S_POWER,V_CURRENT); #endif -#ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP - MyMessage msg_S_HEATER_S(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC - MyMessage msg_S_HEATER_F(ID_S_HEATER,V_HVAC_FLOW_STATE); // HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver"). S_HEATER, S_HVAC - int heatTemp=18; - const char* heatState="Off"; +#ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP, V_STATUS + float heater_setpoint=21.5; + float heater_temp=23.5; + bool heater_status=false; + String heater_flow_state = "Off"; + MyMessage msg_S_HEATER_SETPOINT(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); + MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); + MyMessage msg_S_HEATER_TEMP(ID_S_HEATER,V_TEMP); + MyMessage msg_S_HEATER_FLOWSTATE(ID_S_HEATER,V_HVAC_FLOW_STATE); #endif #ifdef ID_S_DISTANCE @@ -229,6 +235,10 @@ MyHwATMega328 hw; #endif +#ifdef ID_S_MOISTURE + MyMessage msg_S_MOISTURE(ID_S_MOISTURE,V_LEVEL); +#endif + #ifdef ID_S_CUSTOM MyMessage msg_S_CUSTOM_1(ID_S_CUSTOM,V_VAR1); MyMessage msg_S_CUSTOM_2(ID_S_CUSTOM,V_VAR2); @@ -409,6 +419,12 @@ void setup() gw.present(ID_S_SCENE_CONTROLLER,S_SCENE_CONTROLLER); gw.wait(SHORT_WAIT); #endif + + #ifdef ID_S_MOISTURE + Serial.println(" S_MOISTURE"); + gw.present(ID_S_MOISTURE,S_MOISTURE); + gw.wait(SHORT_WAIT); + #endif #ifdef ID_S_CUSTOM Serial.println(" S_CUSTOM"); @@ -535,6 +551,10 @@ void loop() scene(); #endif + #ifdef ID_S_MOISTURE + moisture(); + #endif + #ifdef ID_S_CUSTOM custom(); #endif @@ -789,17 +809,21 @@ void power(){ #ifdef ID_S_HEATER void heater(){ + Serial.print("Heater flow state is: " ); + Serial.println(heater_flow_state); + gw.send(msg_S_HEATER_FLOWSTATE.set(heater_flow_state.c_str())); - Serial.print("Heater mode is: " ); - Serial.println(heatState); + Serial.print("Heater on/off is: " ); + Serial.println((heater_status==true)?"On":"Off"); + gw.send(msg_S_HEATER_STATUS.set(heater_status)); - gw.send(msg_S_HEATER_F.set(heatState)); + Serial.print("Heater Temperature is: " ); + Serial.println(heater_temp,1); + gw.send(msg_S_HEATER_TEMP.set(heater_temp,1)); - Serial.print("Heater Set Point is: " ); - Serial.println(heatTemp); - - gw.send(msg_S_HEATER_S.set(heatTemp)); - + Serial.print("Heater Setpoint: " ); + Serial.println(heater_setpoint,1); + gw.send(msg_S_HEATER_SETPOINT.set(heater_setpoint,1)); } #endif @@ -899,6 +923,16 @@ void scene(){ } #endif +#ifdef ID_S_MOISTURE +void moisture(){ + + Serial.print("Moisture level is: " ); + Serial.println(randNumber); + + gw.send(msg_S_MOISTURE.set(randNumber)); +} +#endif + #ifdef ID_S_CUSTOM void custom(){ @@ -980,21 +1014,26 @@ void incomingMessage(const MyMessage &message) { #endif #ifdef ID_S_HEATER - case V_HVAC_FLOW_STATE: - heatState = message.getString(); - Serial.print("Incoming change for ID_S_HEATER:"); - Serial.print(message.sensor); - Serial.print(", New status: "); - Serial.println(heatState); - - break; - case V_HVAC_SETPOINT_HEAT: - heatTemp = message.getInt(); + Serial.print("Incoming set point for ID_S_HEATER:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + heater_setpoint=message.getFloat(); + Serial.println(heater_setpoint,1); + break; + case V_STATUS: Serial.print("Incoming change for ID_S_HEATER:"); Serial.print(message.sensor); Serial.print(", New status: "); - Serial.println(heatTemp); + heater_status = message.getBool(); + Serial.println(heater_status); + break; + case V_HVAC_FLOW_STATE: + Serial.print("Incoming flow state change for ID_S_HEATER:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + heater_flow_state=message.getString(); + Serial.println(heater_flow_state); break; #endif From 6dcb162858234a76adf5655233a530abebd2015b Mon Sep 17 00:00:00 2001 From: Barduino Date: Sat, 5 Sep 2015 14:36:33 -0400 Subject: [PATCH 19/35] S_HEATER & S_SCENES fixed implementation of s_heater & s_scene_controller --- .../examples/MockMySensors/MockMySensors.ino | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino index 9510270..7fa7659 100644 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -44,32 +44,32 @@ // one at a time. #define ID_S_ARMED 0 // dummy to controll armed stated for several sensors #define ID_S_DOOR 1 -//#define ID_S_MOTION 2 -//#define ID_S_SMOKE 3 -//#define ID_S_LIGHT 4 -//#define ID_S_DIMMER 5 -//#define ID_S_COVER 6 -//#define ID_S_TEMP 7 -//#define ID_S_HUM 8 -//#define ID_S_BARO 9 -//#define ID_S_WIND 10 -//#define ID_S_RAIN 11 -//#define ID_S_UV 12 -//#define ID_S_WEIGHT 13 -//#define ID_S_POWER 14 -//#define ID_S_HEATER 15 -//#define ID_S_DISTANCE 16 -//#define ID_S_LIGHT_LEVEL 17 -//#define ID_S_LOCK 18 -//#define ID_S_IR 19 -//#define ID_S_WATER 20 -//#define ID_S_AIR_QUALITY 21 -//#define ID_S_DUST 22 -//#define ID_S_SCENE_CONTROLLER 23 -//#define ID_S_CUSTOM 99 +#define ID_S_MOTION 2 +#define ID_S_SMOKE 3 +#define ID_S_LIGHT 4 +#define ID_S_DIMMER 5 +#define ID_S_COVER 6 +#define ID_S_TEMP 7 +#define ID_S_HUM 8 +#define ID_S_BARO 9 +#define ID_S_WIND 10 +#define ID_S_RAIN 11 +#define ID_S_UV 12 +#define ID_S_WEIGHT 13 +#define ID_S_POWER 14 +#define ID_S_HEATER 15 +#define ID_S_DISTANCE 16 +#define ID_S_LIGHT_LEVEL 17 +#define ID_S_LOCK 18 +#define ID_S_IR 19 +#define ID_S_WATER 20 +#define ID_S_AIR_QUALITY 21 +#define ID_S_DUST 22 +#define ID_S_SCENE_CONTROLLER 23 +#define ID_S_CUSTOM 99 // Global Vars -unsigned long SLEEP_TIME = 300000; // Sleep time between reads (in milliseconds) +unsigned long SLEEP_TIME = 900000; // Sleep time between reads (in milliseconds) boolean metric = true; long randNumber; @@ -175,9 +175,9 @@ MyHwATMega328 hw; #ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP MyMessage msg_S_HEATER_S(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC - MyMessage msg_S_HEATER_F(ID_S_HEATER,V_HVAC_FLOW_STATE); // HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver"). S_HEATER, S_HVAC + MyMessage msg_S_HEATER_F(ID_S_HEATER,V_HVAC_FLOW_STATE); // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" // S_HVAC, S_HEATER int heatTemp=18; - const char* heatState="Off"; + String heatState="Off"; #endif #ifdef ID_S_DISTANCE @@ -218,12 +218,12 @@ MyHwATMega328 hw; // not sure if scene controller sends int or chars // betting on ints as Touch Display Scen by Hek // compiler warnings char *scenes[] = { - "All off", - "Good Morning", - "Clean Up!", - "All Lights Off", - "Music On/Off" + (char *)"Good Morning", + (char *)"Clean Up!", + (char *)"All Lights Off", + (char *)"Music On/Off" }; + int sceneVal=0; int sceneValPrevious=0; @@ -789,11 +789,13 @@ void power(){ #ifdef ID_S_HEATER void heater(){ + char cBuff[heatState.length()+1]; + heatState.toCharArray(cBuff,heatState.length()+1); Serial.print("Heater mode is: " ); - Serial.println(heatState); + Serial.println(cBuff); - gw.send(msg_S_HEATER_F.set(heatState)); + gw.send(msg_S_HEATER_F.set(cBuff)); Serial.print("Heater Set Point is: " ); Serial.println(heatTemp); @@ -924,7 +926,15 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println((isArmed ? "Armed":"Disarmed" )); - door();//temp ack for door + #ifdef ID_S_DOOR + door();//temp ack for door + #endif + #ifdef ID_S_MOTION + motion();//temp ack + #endif + #ifdef ID_S_SMOKE + smoke();//temp ack + #endif break; #endif @@ -950,6 +960,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(message.getInt()); + dimmer();// temp ack break; #endif @@ -986,6 +997,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(heatState); + heater();//temp ack break; @@ -995,6 +1007,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(heatTemp); + heater();//temp ack break; #endif @@ -1038,6 +1051,7 @@ void incomingMessage(const MyMessage &message) { Serial.println(" Off"); scene(); break; + scene(); // tmp ack #endif default: From fe86934360be67d68cff20c8013364218f3c2590 Mon Sep 17 00:00:00 2001 From: Barduino Date: Sun, 6 Sep 2015 10:27:00 -0400 Subject: [PATCH 20/35] S_COVER & S_IR implementation --- .../examples/MockMySensors/MockMySensors.ino | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) mode change 100644 => 100755 libraries/MySensors/examples/MockMySensors/MockMySensors.ino diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino old mode 100644 new mode 100755 index 7fa7659..629bfe3 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -130,7 +130,8 @@ MyHwATMega328 hw; MyMessage msg_S_COVER_U(ID_S_COVER,V_UP); MyMessage msg_S_COVER_D(ID_S_COVER,V_DOWN); MyMessage msg_S_COVER_S(ID_S_COVER,V_STOP); - const char* coverVal = "V_STOP"; //compiler warnings + MyMessage msg_S_COVER_V(ID_S_COVER,V_VAR1); + int coverState=0; //0=Stop; 1=up; -1=down #endif #ifdef ID_S_TEMP @@ -196,7 +197,7 @@ MyHwATMega328 hw; #ifdef ID_S_IR MyMessage msg_S_IR_S(ID_S_IR,V_IR_SEND); MyMessage msg_S_IR_R(ID_S_IR,V_IR_RECEIVE); - const char* irVal = ""; + long irVal = 0; #endif #ifdef ID_S_WATER @@ -646,17 +647,17 @@ void cover(){ Serial.print("Cover is : " ); - if (coverVal == "V_UP"){ + if (coverState == 1){ Serial.println("Opening"); - gw.send(msg_S_COVER_U.set(V_UP)); - }else if (coverVal == "V_DOWN"){ + gw.send(msg_S_COVER_U.set(1)); + }else if (coverState == -1){ Serial.println("Closing"); - gw.send(msg_S_COVER_D.set(V_DOWN)); + gw.send(msg_S_COVER_D.set(0)); }else{ Serial.println("Idle"); - gw.send(msg_S_COVER_S.set(V_STOP)); + gw.send(msg_S_COVER_S.set(-1)); } - + gw.send(msg_S_COVER_V.set(coverState)); } #endif @@ -844,6 +845,7 @@ void ir(){ Serial.println(irVal); gw.send(msg_S_IR_S.set(irVal)); + gw.send(msg_S_IR_R.set(irVal)); } #endif @@ -966,27 +968,30 @@ void incomingMessage(const MyMessage &message) { #ifdef ID_S_COVER case V_UP: - coverVal="V_UP"; + coverState=1; Serial.print("Incoming change for ID_S_COVER:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println("V_UP"); + cover(); // temp ack break; case V_DOWN: - coverVal="V_DOWN"; + coverState=-1; Serial.print("Incoming change for ID_S_COVER:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println("V_DOWN"); + cover(); //temp ack break; case V_STOP: - coverVal="V_STOP"; + coverState=0; Serial.print("Incoming change for ID_S_COVER:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println("V_STOP"); + cover(); //temp ack break; #endif @@ -1023,12 +1028,21 @@ void incomingMessage(const MyMessage &message) { #endif #ifdef ID_S_IR - case V_IR_RECEIVE: - irVal = message.getString(); + case V_IR_SEND: + irVal = message.getLong(); Serial.print("Incoming change for ID_S_IR:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(irVal); + ir(); // temp ack + break; + case V_IR_RECEIVE: + irVal = message.getLong(); + Serial.print("Incoming change for ID_S_IR:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(irVal); + ir(); // temp ack break; #endif From a19865318fdff2d9d3fbb7ed935f2b365bc29f1a Mon Sep 17 00:00:00 2001 From: Barduino Date: Sun, 6 Sep 2015 11:11:15 -0400 Subject: [PATCH 21/35] Merge w/ Gizmocuz; Implement S_COVER, S_IR, S_SCENE --- .../examples/MockMySensors/MockMySensors.ino | 102 +++++------------- 1 file changed, 29 insertions(+), 73 deletions(-) mode change 100755 => 100644 libraries/MySensors/examples/MockMySensors/MockMySensors.ino diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino old mode 100755 new mode 100644 index c5f7eb0..ce1e338 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -42,35 +42,9 @@ // Some of these ID's have not been updated for v1.5. Uncommenting too many of them // will make the sketch too large for a pro mini's memory so it's probably best to try // one at a time. -<<<<<<< HEAD + #define ID_S_ARMED 0 // dummy to controll armed stated for several sensors #define ID_S_DOOR 1 -#define ID_S_MOTION 2 -#define ID_S_SMOKE 3 -#define ID_S_LIGHT 4 -#define ID_S_DIMMER 5 -#define ID_S_COVER 6 -#define ID_S_TEMP 7 -#define ID_S_HUM 8 -#define ID_S_BARO 9 -#define ID_S_WIND 10 -#define ID_S_RAIN 11 -#define ID_S_UV 12 -#define ID_S_WEIGHT 13 -#define ID_S_POWER 14 -#define ID_S_HEATER 15 -#define ID_S_DISTANCE 16 -#define ID_S_LIGHT_LEVEL 17 -#define ID_S_LOCK 18 -#define ID_S_IR 19 -#define ID_S_WATER 20 -#define ID_S_AIR_QUALITY 21 -#define ID_S_DUST 22 -#define ID_S_SCENE_CONTROLLER 23 -#define ID_S_CUSTOM 99 -======= -//#define ID_S_ARMED 0 // dummy to controll armed stated for several sensors -//#define ID_S_DOOR 1 //#define ID_S_MOTION 2 //#define ID_S_SMOKE 3 //#define ID_S_LIGHT 4 @@ -93,10 +67,11 @@ //#define ID_S_AIR_QUALITY 21 //#define ID_S_DUST 22 //#define ID_S_SCENE_CONTROLLER 23 -#define ID_S_MOISTURE 24 +//#define ID_S_MOISTURE 24 + +#define ID_S_CUSTOM 99 + -//#define ID_S_CUSTOM 99 ->>>>>>> mysensors/development // Global Vars unsigned long SLEEP_TIME = 900000; // Sleep time between reads (in milliseconds) @@ -204,23 +179,18 @@ MyHwATMega328 hw; // MyMessage msg_S_POWER_C(ID_S_POWER,V_CURRENT); #endif -<<<<<<< HEAD -#ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP - MyMessage msg_S_HEATER_S(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC - MyMessage msg_S_HEATER_F(ID_S_HEATER,V_HVAC_FLOW_STATE); // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" // S_HVAC, S_HEATER - int heatTemp=18; - String heatState="Off"; -======= + #ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP, V_STATUS + MyMessage msg_S_HEATER_SET_POINT(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC + MyMessage msg_S_HEATER_FLOW_STATE(ID_S_HEATER,V_HVAC_FLOW_STATE); // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" // S_HVAC, S_HEATER + MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); + MyMessage msg_S_HEATER_TEMP(ID_S_HEATER,V_TEMP); + float heater_setpoint=21.5; float heater_temp=23.5; bool heater_status=false; - String heater_flow_state = "Off"; - MyMessage msg_S_HEATER_SETPOINT(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); - MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); - MyMessage msg_S_HEATER_TEMP(ID_S_HEATER,V_TEMP); - MyMessage msg_S_HEATER_FLOWSTATE(ID_S_HEATER,V_HVAC_FLOW_STATE); ->>>>>>> mysensors/development + String heater_flow_state="Off"; + const bool dup_v_status=false; // V_STATUS & V_LIGHT are the same, does not compile in case statement of incoming s_heater message #endif #ifdef ID_S_DISTANCE @@ -846,18 +816,14 @@ void power(){ #ifdef ID_S_HEATER void heater(){ -<<<<<<< HEAD - char cBuff[heatState.length()+1]; - heatState.toCharArray(cBuff,heatState.length()+1); - - Serial.print("Heater mode is: " ); - Serial.println(cBuff); +// float heater_setpoint=21.5; +// float heater_temp=23.5; +// bool heater_status=false; +// String heatState="Off"; - gw.send(msg_S_HEATER_F.set(cBuff)); -======= Serial.print("Heater flow state is: " ); Serial.println(heater_flow_state); - gw.send(msg_S_HEATER_FLOWSTATE.set(heater_flow_state.c_str())); + gw.send(msg_S_HEATER_FLOW_STATE.set(heater_flow_state.c_str())); Serial.print("Heater on/off is: " ); Serial.println((heater_status==true)?"On":"Off"); @@ -866,11 +832,10 @@ void heater(){ Serial.print("Heater Temperature is: " ); Serial.println(heater_temp,1); gw.send(msg_S_HEATER_TEMP.set(heater_temp,1)); ->>>>>>> mysensors/development Serial.print("Heater Setpoint: " ); Serial.println(heater_setpoint,1); - gw.send(msg_S_HEATER_SETPOINT.set(heater_setpoint,1)); + gw.send(msg_S_HEATER_SET_POINT.set(heater_setpoint,1)); } #endif @@ -1073,38 +1038,30 @@ void incomingMessage(const MyMessage &message) { break; #endif - #ifdef ID_S_HEATER + #ifdef ID_S_HEATER case V_HVAC_SETPOINT_HEAT: + heater_setpoint=message.getFloat(); + Serial.print("Incoming set point for ID_S_HEATER:"); Serial.print(message.sensor); Serial.print(", New status: "); -<<<<<<< HEAD - Serial.println(heatState); - heater();//temp ack - -======= - heater_setpoint=message.getFloat(); Serial.println(heater_setpoint,1); ->>>>>>> mysensors/development + heater();//temp ack break; - case V_STATUS: + case (V_STATUS || dup_v_status): //V_LIGHT & V_STATUS are the same; does not compile without hack + heater_status = message.getBool(); Serial.print("Incoming change for ID_S_HEATER:"); Serial.print(message.sensor); Serial.print(", New status: "); -<<<<<<< HEAD - Serial.println(heatTemp); - heater();//temp ack -======= - heater_status = message.getBool(); Serial.println(heater_status); + heater();//temp ack break; case V_HVAC_FLOW_STATE: + heater_flow_state=message.getString(); Serial.print("Incoming flow state change for ID_S_HEATER:"); Serial.print(message.sensor); Serial.print(", New status: "); - heater_flow_state=message.getString(); Serial.println(heater_flow_state); ->>>>>>> mysensors/development break; #endif @@ -1146,7 +1103,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(", New status: "); Serial.print(scenes[sceneVal]); Serial.println(" On"); - scene(); + scene();// temp ack break; case V_SCENE_OFF: sceneVal = message.getInt(); @@ -1155,9 +1112,8 @@ void incomingMessage(const MyMessage &message) { Serial.print(", New status: "); Serial.print(scenes[sceneVal]); Serial.println(" Off"); - scene(); + scene();// temp ack break; - scene(); // tmp ack #endif default: From 92b1b8d67a67c75179364050487fcec2290fa53f Mon Sep 17 00:00:00 2001 From: Barduino Date: Sun, 6 Sep 2015 11:18:40 -0400 Subject: [PATCH 22/35] Change Log --- libraries/MySensors/examples/MockMySensors/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/MySensors/examples/MockMySensors/README.md b/libraries/MySensors/examples/MockMySensors/README.md index c835b2d..fef8191 100644 --- a/libraries/MySensors/examples/MockMySensors/README.md +++ b/libraries/MySensors/examples/MockMySensors/README.md @@ -16,8 +16,14 @@ With a Mega you can have them all Changes Log ----------------- +2015-09-06 + +Merge with Gizmocuz (implementation of S_HEATER & S_MOISTURE +Implementation of S_COVER, S_IR, S_SCENE_CONTROLLER + 2015-08-19 + Removed the load/save to eeprom Touched the heater sensor Created a pseudo sensor to handle the V_ARMED message. From 896ca7850bbfcac6f8ae300c677aaf1b3ea4b1c1 Mon Sep 17 00:00:00 2001 From: Barduino Date: Mon, 7 Sep 2015 19:19:10 -0400 Subject: [PATCH 23/35] Five more sensors S_RGB_LIGHT S_RGBW_LIGHT S_COLOR_SENSOR S_HVAC S_MULTIMETER --- .../examples/MockMySensors/MockMySensors.ino | 471 +++++++++++++++--- .../examples/MockMySensors/README.md | 10 + 2 files changed, 420 insertions(+), 61 deletions(-) diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino index ce1e338..aee2435 100644 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -43,33 +43,43 @@ // will make the sketch too large for a pro mini's memory so it's probably best to try // one at a time. -#define ID_S_ARMED 0 // dummy to controll armed stated for several sensors -#define ID_S_DOOR 1 -//#define ID_S_MOTION 2 -//#define ID_S_SMOKE 3 -//#define ID_S_LIGHT 4 -//#define ID_S_DIMMER 5 -//#define ID_S_COVER 6 -//#define ID_S_TEMP 7 -//#define ID_S_HUM 8 -//#define ID_S_BARO 9 -//#define ID_S_WIND 10 -//#define ID_S_RAIN 11 -//#define ID_S_UV 12 -//#define ID_S_WEIGHT 13 -//#define ID_S_POWER 14 -//#define ID_S_HEATER 15 -//#define ID_S_DISTANCE 16 -//#define ID_S_LIGHT_LEVEL 17 -//#define ID_S_LOCK 18 -//#define ID_S_IR 19 -//#define ID_S_WATER 20 -//#define ID_S_AIR_QUALITY 21 -//#define ID_S_DUST 22 -//#define ID_S_SCENE_CONTROLLER 23 -//#define ID_S_MOISTURE 24 +#define ID_S_ARMED 0 // dummy to controll armed stated for several sensors +#define ID_S_DOOR 1 +#define ID_S_MOTION 2 +#define ID_S_SMOKE 3 +#define ID_S_LIGHT 4 +#define ID_S_DIMMER 5 +#define ID_S_COVER 6 +#define ID_S_TEMP 7 +#define ID_S_HUM 8 +#define ID_S_BARO 9 +#define ID_S_WIND 10 +#define ID_S_RAIN 11 +#define ID_S_UV 12 +#define ID_S_WEIGHT 13 +#define ID_S_POWER 14 +#define ID_S_HEATER 15 +#define ID_S_DISTANCE 16 +#define ID_S_LIGHT_LEVEL 17 +#define ID_S_LOCK 18 +#define ID_S_IR 19 +#define ID_S_WATER 20 +#define ID_S_AIR_QUALITY 21 +#define ID_S_DUST 22 +#define ID_S_SCENE_CONTROLLER 23 +// Lib 1.5 sensors +#define ID_S_RGB_LIGHT 24 +#define ID_S_RGBW_LIGHT 25 +#define ID_S_COLOR_SENSOR 26 +#define ID_S_HVAC 27 +#define ID_S_MULTIMETER 28 +#define ID_S_SPRINKLER 29 +#define ID_S_WATER_LEAK 30 +#define ID_S_SOUND 31 +#define ID_S_VIBRATION 32 +#define ID_S_MOISTURE 33 -#define ID_S_CUSTOM 99 +//#define ID_S_CUSTOM 99 @@ -174,13 +184,13 @@ MyHwATMega328 hw; #ifdef ID_S_POWER MyMessage msg_S_POWER_W(ID_S_POWER,V_WATT); MyMessage msg_S_POWER_K(ID_S_POWER,V_KWH); -// MyMessage msg_S_POWER_I(ID_S_POWER,V_IMPEDANCE); -// MyMessage msg_S_POWER_V(ID_S_POWER,V_VOLTAGE); -// MyMessage msg_S_POWER_C(ID_S_POWER,V_CURRENT); #endif -#ifdef ID_S_HEATER //V_HVAC_SETPOINT_HEAT, V_HVAC_FLOW_STATE, V_TEMP, V_STATUS +#ifdef ID_S_HEATER + + //////// REVIEW IMPLEMENTATION //////////// + MyMessage msg_S_HEATER_SET_POINT(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC MyMessage msg_S_HEATER_FLOW_STATE(ID_S_HEATER,V_HVAC_FLOW_STATE); // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" // S_HVAC, S_HEATER MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); @@ -190,7 +200,12 @@ MyHwATMega328 hw; float heater_temp=23.5; bool heater_status=false; String heater_flow_state="Off"; - const bool dup_v_status=false; // V_STATUS & V_LIGHT are the same, does not compile in case statement of incoming s_heater message + + // V_TEMP // Temperature + // V_STATUS // Binary status. 0=off 1=on + // V_HVAC_FLOW_STATE // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" + // V_HVAC_SPEED // HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto") + // V_HVAC_SETPOINT_HEAT // HVAC/Heater setpoint #endif #ifdef ID_S_DISTANCE @@ -242,6 +257,80 @@ MyHwATMega328 hw; #endif +#ifdef ID_S_RGB_LIGHT + MyMessage msg_S_RGB_LIGHT_V_RGB(ID_S_RGB_LIGHT,V_RGB); + MyMessage msg_S_RGB_LIGHT_V_WATT(ID_S_RGB_LIGHT,V_WATT); + String rgbState="000000"; + //RGB light V_RGB, V_WATT + //RGB value transmitted as ASCII hex string (I.e "ff0000" for red) +#endif + +#ifdef ID_S_RGBW_LIGHT + MyMessage msg_S_RGBW_LIGHT_V_RGBW(ID_S_RGB_LIGHT,V_RGBW); + MyMessage msg_S_RGBW_LIGHT_V_WATT(ID_S_RGB_LIGHT,V_WATT); + String rgbwState="00000000"; + //RGBW light (with separate white component) V_RGBW, V_WATT + //RGBW value transmitted as ASCII hex string (I.e "ff0000ff" for red + full white) S_RGBW_LIGHT +#endif + +#ifdef ID_S_COLOR_SENSOR + MyMessage msg_S_COLOR_SENSOR_V_RGB(ID_S_COLOR_SENSOR,V_RGB); + //Color sensor V_RGB + //RGB value transmitted as ASCII hex string (I.e "ff0000" for red) S_RGB_LIGHT, S_COLOR_SENSOR +#endif + +#ifdef ID_S_HVAC + MyMessage msg_S_HVAC_V_HVAC_SETPOINT_HEAT(ID_S_HVAC,V_HVAC_SETPOINT_HEAT); + MyMessage msg_S_HVAC_V_HVAC_SETPOINT_COOL(ID_S_HVAC,V_HVAC_SETPOINT_COOL); + MyMessage msg_S_HVAC_V_HVAC_FLOW_STATET(ID_S_HVAC,V_HVAC_FLOW_STATE); + MyMessage msg_S_HVAC_V_HVAC_FLOW_MODE(ID_S_HVAC,V_HVAC_FLOW_MODE); + MyMessage msg_S_HVAC_V_HVAC_SPEED(ID_S_HVAC,V_HVAC_SPEED); + + float hvac_SetPointHeat = 16.5; + float hvac_SetPointCool = 25.5; + String hvac_FlowState = "AutoChangeOver"; + String hvac_FlowMode = "Auto"; + String hvac_Speed = "Normal"; + + //Thermostat/HVAC device + //V_HVAC_SETPOINT_HEAT, // HVAC/Heater setpoint + //V_HVAC_SETPOINT_COOL, // HVAC cold setpoint + //V_HVAC_FLOW_STATE, // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" + //V_HVAC_FLOW_MODE, // Flow mode for HVAC ("Auto", "ContinuousOn", "PeriodicOn") + //V_HVAC_SPEED // HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto") + + // NOT IMPLEMENTED YET + //V_TEMP // Temperature + //V_STATUS // Binary status. 0=off 1=on +#endif + +#ifdef ID_S_MULTIMETER + MyMessage msg_S_MULTIMETER_V_IMPEDANCE(ID_S_POWER,V_IMPEDANCE); + MyMessage msg_S_MULTIMETER_V_VOLTAGE(ID_S_POWER,V_VOLTAGE); + MyMessage msg_S_MULTIMETER_V_CURRENT(ID_S_POWER,V_CURRENT); + + // Multimeter device V_VOLTAGE, V_CURRENT, V_IMPEDANCE + // V_IMPEDANCE 14 Impedance value + // V_VOLTAGE 38 Voltage level + // V_CURRENT 39 Current level +#endif + +#ifdef ID_S_SPRINKLER + // S_SPRINKLER 31 Sprinkler device V_STATUS (turn on/off), V_TRIPPED (if fire detecting device) + // V_STATUS 2 Binary status. 0=off 1=on + // V_ARMED 15 Armed status of a security sensor. 1=Armed, 0=Bypassed + // V_TRIPPED 16 Tripped status of a security sensor. 1=Tripped, 0=Untripped +#endif + +#ifdef ID_S_WATER_LEAK +#endif +#ifdef ID_S_SOUND +#endif +#ifdef ID_S_VIBRATION +#endif +#ifdef ID_S_MOISTURE +#endif + #ifdef ID_S_MOISTURE MyMessage msg_S_MOISTURE(ID_S_MOISTURE,V_LEVEL); #endif @@ -426,6 +515,47 @@ void setup() gw.present(ID_S_SCENE_CONTROLLER,S_SCENE_CONTROLLER); gw.wait(SHORT_WAIT); #endif + + #ifdef ID_S_RGB_LIGHT + Serial.println(" RGB_LIGHT"); + gw.present(ID_S_RGB_LIGHT,S_RGB_LIGHT); + gw.wait(SHORT_WAIT); + #endif + + #ifdef ID_S_RGBW_LIGHT + Serial.println(" RGBW_LIGHT"); + gw.present(ID_S_RGBW_LIGHT,S_RGBW_LIGHT); + gw.wait(SHORT_WAIT); + #endif + + #ifdef ID_S_COLOR_SENSOR + Serial.println(" COLOR_SENSOR"); + gw.present(ID_S_COLOR_SENSOR,S_COLOR_SENSOR); + gw.wait(SHORT_WAIT); + #endif + + #ifdef ID_S_HVAC + Serial.println(" HVAC"); + gw.present(ID_S_HVAC,S_HVAC); + gw.wait(SHORT_WAIT); + #endif + + #ifdef ID_S_MULTIMETER + Serial.println(" MULTIMETER"); + gw.present(ID_S_MULTIMETER,S_MULTIMETER); + gw.wait(SHORT_WAIT); + #endif + + #ifdef ID_S_SPRINKLER + #endif + #ifdef ID_S_WATER_LEAK + #endif + #ifdef ID_S_SOUND + #endif + #ifdef ID_S_VIBRATION + #endif + #ifdef ID_S_MOISTURE + #endif #ifdef ID_S_MOISTURE Serial.println(" S_MOISTURE"); @@ -558,6 +688,37 @@ void loop() scene(); #endif + #ifdef ID_S_RGB_LIGHT + rgbLight(); + #endif + + #ifdef ID_S_RGBW_LIGHT + rgbwLight(); + #endif + + #ifdef ID_S_COLOR_SENSOR + color(); + #endif + + #ifdef ID_S_HVAC + hvac(); + #endif + + #ifdef ID_S_MULTIMETER + multimeter(); + #endif + + #ifdef ID_S_SPRINKLER + #endif + #ifdef ID_S_WATER_LEAK + #endif + #ifdef ID_S_SOUND + #endif + #ifdef ID_S_VIBRATION + #endif + #ifdef ID_S_MOISTURE + #endif + #ifdef ID_S_MOISTURE moisture(); #endif @@ -787,30 +948,13 @@ void weight(){ void power(){ Serial.print("Watt is: " ); - Serial.println(map(randNumber,1,100,0,150)); - + Serial.println(map(randNumber,1,100,0,150)); gw.send(msg_S_POWER_W.set(map(randNumber,1,100,0,150))); Serial.print("KWH is: " ); - Serial.println(map(randNumber,1,100,0,150)); - + Serial.println(map(randNumber,1,100,0,150)); gw.send(msg_S_POWER_K.set(map(randNumber,1,100,0,150))); -// Serial.print("Impedance is: " ); -// Serial.println(map(randNumber,1,100,0,150)); -// -// gw.send(msg_S_POWER_I.set(map(randNumber,1,100,0,150))); - -// Serial.print("Voltage is: " ); -// Serial.println(map(randNumber,1,100,0,150)); -// -// gw.send(msg_S_POWER_V.set(map(randNumber,1,100,0,150))); - -// Serial.print("Current is: " ); -// Serial.println(map(randNumber,1,100,0,150)); -// -// gw.send(msg_S_POWER_C.set(map(randNumber,1,100,0,150))); - } #endif @@ -936,6 +1080,115 @@ void scene(){ } #endif +#ifdef ID_S_RGB_LIGHT +void rgbLight(){ + + Serial.print("RGB Light state is: " ); + Serial.println(rgbState); + gw.send(msg_S_RGB_LIGHT_V_RGB.set(rgbState.c_str())); + + Serial.print("RGB Light Watt is: " ); + Serial.println(map(randNumber,1,100,0,150)); + gw.send(msg_S_RGB_LIGHT_V_WATT.set(map(randNumber,1,100,0,150))); + +} +#endif + +#ifdef ID_S_RGBW_LIGHT +void rgbwLight(){ + + Serial.print("RGBW Light state is: " ); + Serial.println(rgbwState); + gw.send(msg_S_RGBW_LIGHT_V_RGBW.set(rgbwState.c_str())); + + Serial.print("RGBW Light Watt is: " ); + Serial.println(map(randNumber,1,100,0,150)); + gw.send(msg_S_RGBW_LIGHT_V_WATT.set(map(randNumber,1,100,0,150))); + +} +#endif + +#ifdef ID_S_COLOR_SENSOR +void color(){ + String colorState; + + String red = String(random(0,256),HEX); + String green = String(random(0,256),HEX); + String blue = String(random(0,256),HEX); + + colorState=String(red + green + blue); + + Serial.print("Color state is: " ); + Serial.println(colorState); + gw.send(msg_S_COLOR_SENSOR_V_RGB.set(colorState.c_str())); + +} +#endif + +#ifdef ID_S_HVAC +void hvac(){ + +// float hvac_SetPointHeat = 16.5; +// float hvac_SetPointCool = 25.5; +// String hvac_FlowState = "AutoChangeOver"; +// String hvac_FlowMode = "Auto"; +// String hvac_Speed = "Normal"; + + Serial.print("HVAC Set Point Heat is: " ); + Serial.println(hvac_SetPointHeat); + gw.send(msg_S_HVAC_V_HVAC_SETPOINT_HEAT.set(hvac_SetPointHeat,1)); + + Serial.print("HVAC Set Point Cool is: " ); + Serial.println(hvac_SetPointCool); + gw.send(msg_S_HVAC_V_HVAC_SETPOINT_COOL.set(hvac_SetPointCool,1)); + + Serial.print("HVAC Flow State is: " ); + Serial.println(hvac_FlowState); + gw.send(msg_S_HVAC_V_HVAC_FLOW_STATET.set(hvac_FlowState.c_str())); + + Serial.print("HVAC Flow Mode is: " ); + Serial.println(hvac_FlowMode); + gw.send(msg_S_HVAC_V_HVAC_FLOW_MODE.set(hvac_FlowMode.c_str())); + + Serial.print("HVAC Speed is: " ); + Serial.println(hvac_Speed); + gw.send(msg_S_HVAC_V_HVAC_SPEED.set(hvac_Speed.c_str())); + +} +#endif + +#ifdef ID_S_MULTIMETER +void multimeter(){ + int impedance=map(randNumber,1,100,0,15000); + int volt=map(randNumber,1,100,0,380); + int amps=map(randNumber,1,100,0,16); + + Serial.print("Impedance is: " ); + Serial.println(impedance); + gw.send(msg_S_MULTIMETER_V_IMPEDANCE.set(impedance)); + + Serial.print("Voltage is: " ); + Serial.println(volt); + gw.send(msg_S_MULTIMETER_V_VOLTAGE.set(volt)); + + Serial.print("Current is: " ); + Serial.println(amps); + gw.send(msg_S_MULTIMETER_V_CURRENT.set(amps)); + +} +#endif + +#ifdef ID_S_SPRINKLER +#endif +#ifdef ID_S_WATER_LEAK +#endif +#ifdef ID_S_SOUND +#endif +#ifdef ID_S_VIBRATION +#endif +#ifdef ID_S_MOISTURE +#endif + #ifdef ID_S_MOISTURE void moisture(){ @@ -983,16 +1236,30 @@ void incomingMessage(const MyMessage &message) { break; #endif + + case V_STATUS: // V_LIGHT: #ifdef ID_S_LIGHT - case V_LIGHT: + if(message.sensor==ID_S_LIGHT){ isLightOn = message.getBool(); Serial.print("Incoming change for ID_S_LIGHT:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println((isLightOn ? "On":"Off")); light(); // temp ack - break; + } #endif + #ifdef ID_S_HEATER + if(message.sensor == ID_S_HEATER){ + heater_status = message.getBool(); + Serial.print("Incoming change for ID_S_HEATER:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(heater_status); + heater();//temp ack + } + #endif + break; + #ifdef ID_S_DIMMER case V_DIMMER: @@ -1038,8 +1305,11 @@ void incomingMessage(const MyMessage &message) { break; #endif - #ifdef ID_S_HEATER + case V_HVAC_SETPOINT_HEAT: + + #ifdef ID_S_HEATER + if(message.sensor == ID_S_HEATER){ heater_setpoint=message.getFloat(); Serial.print("Incoming set point for ID_S_HEATER:"); @@ -1047,23 +1317,44 @@ void incomingMessage(const MyMessage &message) { Serial.print(", New status: "); Serial.println(heater_setpoint,1); heater();//temp ack - break; - case (V_STATUS || dup_v_status): //V_LIGHT & V_STATUS are the same; does not compile without hack - heater_status = message.getBool(); - Serial.print("Incoming change for ID_S_HEATER:"); + } + #endif + + #ifdef ID_S_HVAC + if(message.sensor == ID_S_HVAC){ + hvac_SetPointHeat=message.getFloat(); + Serial.print("Incoming set point for ID_S_HVAC:"); Serial.print(message.sensor); Serial.print(", New status: "); - Serial.println(heater_status); - heater();//temp ack + Serial.println(hvac_SetPointHeat,1); + hvac();//temp ack + } + #endif break; + case V_HVAC_FLOW_STATE: + #ifdef ID_S_HEATER + if(message.sensor == ID_S_HEATER){ heater_flow_state=message.getString(); Serial.print("Incoming flow state change for ID_S_HEATER:"); Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(heater_flow_state); + } + #endif + + #ifdef ID_S_HVAC + if(message.sensor == ID_S_HVAC){ + hvac_FlowState=message.getString(); + + Serial.print("Incoming set point for ID_S_HVAC:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(hvac_FlowState); + hvac();//temp ack + } + #endif break; - #endif #ifdef ID_S_LOCK case V_LOCK_STATUS: @@ -1115,7 +1406,65 @@ void incomingMessage(const MyMessage &message) { scene();// temp ack break; #endif - + + #ifdef ID_S_RGB_LIGHT + case V_RGB: + rgbState=message.getString(); + Serial.print("Incoming flow state change for ID_S_RGB_LIGHT:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(rgbState); + break; + #endif + + #ifdef ID_S_RGBW_LIGHT + case V_RGBW: + rgbwState=message.getString(); + Serial.print("Incoming flow state change for ID_S_RGBW_LIGHT:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(rgbwState); + break; + #endif + + #ifdef ID_S_HVAC + // hvac_SetPointHeat + // hvac_SetPointCool + // hvac_FlowState + // hvac_FlowMode + // hvac_Speed + + case V_HVAC_SETPOINT_COOL: + hvac_SetPointCool=message.getFloat(); + + Serial.print("Incoming set point for ID_S_HVAC:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(hvac_SetPointCool,1); + hvac();//temp ack + break; + + case V_HVAC_FLOW_MODE: + hvac_Speed=message.getString(); + + Serial.print("Incoming set point for ID_S_HVAC:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(hvac_Speed); + hvac();//temp ack + break; + + case V_HVAC_SPEED: + hvac_FlowMode=message.getString(); + + Serial.print("Incoming set point for ID_S_HVAC:"); + Serial.print(message.sensor); + Serial.print(", New status: "); + Serial.println(hvac_FlowMode); + hvac();//temp ack + break; + #endif + default: Serial.print("Unknown/UnImplemented message type: "); Serial.println(message.type); diff --git a/libraries/MySensors/examples/MockMySensors/README.md b/libraries/MySensors/examples/MockMySensors/README.md index fef8191..ef2062e 100644 --- a/libraries/MySensors/examples/MockMySensors/README.md +++ b/libraries/MySensors/examples/MockMySensors/README.md @@ -16,6 +16,16 @@ With a Mega you can have them all Changes Log ----------------- + +2015-09-07 +Five more sensors + S_RGB_LIGHT + S_RGBW_LIGHT + S_COLOR_SENSOR + S_HVAC + S_MULTIMETER +Consolidated HVAC and HEATER incoming message + 2015-09-06 Merge with Gizmocuz (implementation of S_HEATER & S_MOISTURE From 10d37716e72a3b3297042834755fb0ebec26657f Mon Sep 17 00:00:00 2001 From: Barduino Date: Mon, 7 Sep 2015 19:23:17 -0400 Subject: [PATCH 24/35] Set for Jenkins build Most code commented --- .../examples/MockMySensors/MockMySensors.ino | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino index aee2435..36cddd5 100644 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -45,39 +45,39 @@ #define ID_S_ARMED 0 // dummy to controll armed stated for several sensors #define ID_S_DOOR 1 -#define ID_S_MOTION 2 -#define ID_S_SMOKE 3 -#define ID_S_LIGHT 4 -#define ID_S_DIMMER 5 -#define ID_S_COVER 6 -#define ID_S_TEMP 7 -#define ID_S_HUM 8 -#define ID_S_BARO 9 -#define ID_S_WIND 10 -#define ID_S_RAIN 11 -#define ID_S_UV 12 -#define ID_S_WEIGHT 13 -#define ID_S_POWER 14 -#define ID_S_HEATER 15 -#define ID_S_DISTANCE 16 -#define ID_S_LIGHT_LEVEL 17 -#define ID_S_LOCK 18 -#define ID_S_IR 19 -#define ID_S_WATER 20 -#define ID_S_AIR_QUALITY 21 -#define ID_S_DUST 22 -#define ID_S_SCENE_CONTROLLER 23 -// Lib 1.5 sensors -#define ID_S_RGB_LIGHT 24 -#define ID_S_RGBW_LIGHT 25 -#define ID_S_COLOR_SENSOR 26 -#define ID_S_HVAC 27 -#define ID_S_MULTIMETER 28 -#define ID_S_SPRINKLER 29 -#define ID_S_WATER_LEAK 30 -#define ID_S_SOUND 31 -#define ID_S_VIBRATION 32 -#define ID_S_MOISTURE 33 +//#define ID_S_MOTION 2 +//#define ID_S_SMOKE 3 +//#define ID_S_LIGHT 4 +//#define ID_S_DIMMER 5 +//#define ID_S_COVER 6 +//#define ID_S_TEMP 7 +//#define ID_S_HUM 8 +//#define ID_S_BARO 9 +//#define ID_S_WIND 10 +//#define ID_S_RAIN 11 +//#define ID_S_UV 12 +//#define ID_S_WEIGHT 13 +//#define ID_S_POWER 14 +//#define ID_S_HEATER 15 +//#define ID_S_DISTANCE 16 +//#define ID_S_LIGHT_LEVEL 17 +//#define ID_S_LOCK 18 +//#define ID_S_IR 19 +//#define ID_S_WATER 20 +//#define ID_S_AIR_QUALITY 21 +//#define ID_S_DUST 22 +//#define ID_S_SCENE_CONTROLLER 23 +//// Lib 1.5 sensors +//#define ID_S_RGB_LIGHT 24 +//#define ID_S_RGBW_LIGHT 25 +//#define ID_S_COLOR_SENSOR 26 +//#define ID_S_HVAC 27 +//#define ID_S_MULTIMETER 28 +//#define ID_S_SPRINKLER 29 +//#define ID_S_WATER_LEAK 30 +//#define ID_S_SOUND 31 +//#define ID_S_VIBRATION 32 +//#define ID_S_MOISTURE 33 //#define ID_S_CUSTOM 99 From 255a8d821c12ceef4fcd148df61846d3e36097a7 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Tue, 8 Sep 2015 23:41:33 +0200 Subject: [PATCH 25/35] Fixed "invalid suffix on literal" warning --- .../BinarySwitchSleepSensor/BinarySwitchSleepSensor.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/examples/BinarySwitchSleepSensor/BinarySwitchSleepSensor.ino b/libraries/MySensors/examples/BinarySwitchSleepSensor/BinarySwitchSleepSensor.ino index 34e0a17..bd3a81f 100644 --- a/libraries/MySensors/examples/BinarySwitchSleepSensor/BinarySwitchSleepSensor.ino +++ b/libraries/MySensors/examples/BinarySwitchSleepSensor/BinarySwitchSleepSensor.ino @@ -75,7 +75,7 @@ void setup() digitalWrite(SECONDARY_BUTTON_PIN, HIGH); // Send the sketch version information to the gateway and Controller - sensor_node.sendSketchInfo(SKETCH_NAME, SKETCH_MAJOR_VER"."SKETCH_MINOR_VER); + sensor_node.sendSketchInfo(SKETCH_NAME, SKETCH_MAJOR_VER "." SKETCH_MINOR_VER); // Register binary input sensor to sensor_node (they will be created as child devices) // You can use S_DOOR, S_MOTION or S_LIGHT here depending on your usage. From a6e13bb4f1883c3ee3d9a25dd7ded6941cfeb726 Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Wed, 9 Sep 2015 11:48:22 +0200 Subject: [PATCH 26/35] Added build badges for the nightly builds of "future" Arduino IDE --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 3e4a778..c79ceb0 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,7 @@ Please visit www.mysensors.org for more information Current build status of master branch: [![Build Status](http://ci.mysensors.org/job/MySensorsArduino/branch/master/badge/icon)](http://ci.mysensors.org/job/MySensorsArduino/branch/master/) Current build status of development branch: [![Build Status](http://ci.mysensors.org/job/MySensorsArduino/branch/development/badge/icon)](http://ci.mysensors.org/job/MySensorsArduino/branch/development/) + +Current build status of master branch (nightly build of Arduino IDE): [![Build Status](http://ci.mysensors.org/job/MySensorsArduinoNightly/branch/master/badge/icon)](http://ci.mysensors.org/job/MySensorsArduinoNightly/branch/master/) + +Current build status of development branch (nightly build of Arduino IDE): [![Build Status](http://ci.mysensors.org/job/MySensorsArduinoNightly/branch/development/badge/icon)](http://ci.mysensors.org/job/MySensorsArduinoNightly/branch/development/) From 253540c3e18f8bfaeca6e2d3e141a66b13c87733 Mon Sep 17 00:00:00 2001 From: Barduino Date: Thu, 10 Sep 2015 08:32:01 -0400 Subject: [PATCH 27/35] Generel fixes --- .../examples/MockMySensors/MockMySensors.ino | 119 +++++++++--------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino index 36cddd5..c88a700 100644 --- a/libraries/MySensors/examples/MockMySensors/MockMySensors.ino +++ b/libraries/MySensors/examples/MockMySensors/MockMySensors.ino @@ -78,7 +78,7 @@ //#define ID_S_SOUND 31 //#define ID_S_VIBRATION 32 //#define ID_S_MOISTURE 33 - +// //#define ID_S_CUSTOM 99 @@ -193,14 +193,17 @@ MyHwATMega328 hw; MyMessage msg_S_HEATER_SET_POINT(ID_S_HEATER,V_HVAC_SETPOINT_HEAT); // HVAC/Heater setpoint (Integer between 0-100). S_HEATER, S_HVAC MyMessage msg_S_HEATER_FLOW_STATE(ID_S_HEATER,V_HVAC_FLOW_STATE); // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" // S_HVAC, S_HEATER - MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); - MyMessage msg_S_HEATER_TEMP(ID_S_HEATER,V_TEMP); + + //MyMessage msg_S_HEATER_STATUS(ID_S_HEATER,V_STATUS); + //MyMessage msg_S_HEATER_TEMP(ID_S_HEATER,V_TEMP); float heater_setpoint=21.5; - float heater_temp=23.5; - bool heater_status=false; String heater_flow_state="Off"; +// float heater_temp=23.5; +// bool heater_status=false; + + // V_TEMP // Temperature // V_STATUS // Binary status. 0=off 1=on // V_HVAC_FLOW_STATE // Mode of header. One of "Off", "HeatOn", "CoolOn", or "AutoChangeOver" @@ -266,8 +269,8 @@ MyHwATMega328 hw; #endif #ifdef ID_S_RGBW_LIGHT - MyMessage msg_S_RGBW_LIGHT_V_RGBW(ID_S_RGB_LIGHT,V_RGBW); - MyMessage msg_S_RGBW_LIGHT_V_WATT(ID_S_RGB_LIGHT,V_WATT); + MyMessage msg_S_RGBW_LIGHT_V_RGBW(ID_S_RGBW_LIGHT,V_RGBW); + MyMessage msg_S_RGBW_LIGHT_V_WATT(ID_S_RGBW_LIGHT,V_WATT); String rgbwState="00000000"; //RGBW light (with separate white component) V_RGBW, V_WATT //RGBW value transmitted as ASCII hex string (I.e "ff0000ff" for red + full white) S_RGBW_LIGHT @@ -305,9 +308,9 @@ MyHwATMega328 hw; #endif #ifdef ID_S_MULTIMETER - MyMessage msg_S_MULTIMETER_V_IMPEDANCE(ID_S_POWER,V_IMPEDANCE); - MyMessage msg_S_MULTIMETER_V_VOLTAGE(ID_S_POWER,V_VOLTAGE); - MyMessage msg_S_MULTIMETER_V_CURRENT(ID_S_POWER,V_CURRENT); + MyMessage msg_S_MULTIMETER_V_IMPEDANCE(ID_S_MULTIMETER,V_IMPEDANCE); + MyMessage msg_S_MULTIMETER_V_VOLTAGE(ID_S_MULTIMETER,V_VOLTAGE); + MyMessage msg_S_MULTIMETER_V_CURRENT(ID_S_MULTIMETER,V_CURRENT); // Multimeter device V_VOLTAGE, V_CURRENT, V_IMPEDANCE // V_IMPEDANCE 14 Impedance value @@ -380,169 +383,169 @@ void setup() #ifdef ID_S_DOOR Serial.println(" S_DOOR"); - gw.present(ID_S_DOOR,S_DOOR); + gw.present(ID_S_DOOR,S_DOOR,"Outside Door"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_MOTION Serial.println(" S_MOTION"); - gw.present(ID_S_MOTION,S_MOTION); + gw.present(ID_S_MOTION,S_MOTION,"Outside Motion"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_SMOKE Serial.println(" S_SMOKE"); - gw.present(ID_S_SMOKE,S_SMOKE); + gw.present(ID_S_SMOKE,S_SMOKE,"Kitchen Smoke"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_LIGHT Serial.println(" S_LIGHT"); - gw.present(ID_S_LIGHT,S_LIGHT); + gw.present(ID_S_LIGHT,S_LIGHT,"Hall Light"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_DIMMER Serial.println(" S_DIMMER"); - gw.present(ID_S_DIMMER,S_DIMMER); + gw.present(ID_S_DIMMER,S_DIMMER,"Living room dimmer"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_COVER Serial.println(" S_COVER"); - gw.present(ID_S_COVER,S_COVER); + gw.present(ID_S_COVER,S_COVER,"Window cover"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_TEMP Serial.println(" S_TEMP"); - gw.present(ID_S_TEMP,S_TEMP); + gw.present(ID_S_TEMP,S_TEMP,"House Temperarue"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_HUM Serial.println(" S_HUM"); - gw.present(ID_S_HUM,S_HUM); + gw.present(ID_S_HUM,S_HUM,"Current Humidity"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_BARO Serial.println(" S_BARO"); - gw.present(ID_S_BARO,S_BARO); + gw.present(ID_S_BARO,S_BARO," Air pressure"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_WIND Serial.println(" S_WIND"); - gw.present(ID_S_WIND,S_WIND); + gw.present(ID_S_WIND,S_WIND,"Wind Station"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_RAIN Serial.println(" S_RAIN"); - gw.present(ID_S_RAIN,S_RAIN); + gw.present(ID_S_RAIN,S_RAIN,"Rain Station"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_UV Serial.println(" S_UV"); - gw.present(ID_S_UV,S_UV); + gw.present(ID_S_UV,S_UV,"Ultra Violet"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_WEIGHT Serial.println(" S_WEIGHT"); - gw.present(ID_S_WEIGHT,S_WEIGHT); + gw.present(ID_S_WEIGHT,S_WEIGHT,"Outdoor Scale"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_POWER Serial.println(" S_POWER"); - gw.present(ID_S_POWER,S_POWER); + gw.present(ID_S_POWER,S_POWER,"Power Metric"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_HEATER Serial.println(" S_HEATER"); - gw.present(ID_S_HEATER,S_HEATER); + gw.present(ID_S_HEATER,S_HEATER,"Garage Heater"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_DISTANCE Serial.println(" S_DISTANCE"); - gw.present(ID_S_DISTANCE,S_DISTANCE); + gw.present(ID_S_DISTANCE,S_DISTANCE,"Distance Measure"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_LIGHT_LEVEL Serial.println(" S_LIGHT_LEVEL"); - gw.present(ID_S_LIGHT_LEVEL,S_LIGHT_LEVEL); + gw.present(ID_S_LIGHT_LEVEL,S_LIGHT_LEVEL,"Outside Light Level"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_LOCK Serial.println(" S_LOCK"); - gw.present(ID_S_LOCK,S_LOCK); + gw.present(ID_S_LOCK,S_LOCK,"Front Door Lock"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_IR Serial.println(" S_IR"); - gw.present(ID_S_IR,S_IR); + gw.present(ID_S_IR,S_IR,"Univeral Command"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_WATER Serial.println(" S_WATER"); - gw.present(ID_S_WATER,S_WATER); + gw.present(ID_S_WATER,S_WATER,"Water Level"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_AIR_QUALITY Serial.println(" S_AIR_QUALITY"); - gw.present(ID_S_AIR_QUALITY,S_AIR_QUALITY); + gw.present(ID_S_AIR_QUALITY,S_AIR_QUALITY,"Air Station"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_DUST Serial.println(" S_DUST"); - gw.present(ID_S_DUST,S_DUST); + gw.present(ID_S_DUST,S_DUST,"Dust Level"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_SCENE_CONTROLLER Serial.println(" S_SCENE_CONTROLLER"); - gw.present(ID_S_SCENE_CONTROLLER,S_SCENE_CONTROLLER); + gw.present(ID_S_SCENE_CONTROLLER,S_SCENE_CONTROLLER,"Scene Controller"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_RGB_LIGHT Serial.println(" RGB_LIGHT"); - gw.present(ID_S_RGB_LIGHT,S_RGB_LIGHT); + gw.present(ID_S_RGB_LIGHT,S_RGB_LIGHT,"Mood Light"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_RGBW_LIGHT Serial.println(" RGBW_LIGHT"); - gw.present(ID_S_RGBW_LIGHT,S_RGBW_LIGHT); + gw.present(ID_S_RGBW_LIGHT,S_RGBW_LIGHT,"Mood Light 2"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_COLOR_SENSOR Serial.println(" COLOR_SENSOR"); - gw.present(ID_S_COLOR_SENSOR,S_COLOR_SENSOR); + gw.present(ID_S_COLOR_SENSOR,S_COLOR_SENSOR,"Hall Painting"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_HVAC Serial.println(" HVAC"); - gw.present(ID_S_HVAC,S_HVAC); + gw.present(ID_S_HVAC,S_HVAC,"HVAC"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_MULTIMETER Serial.println(" MULTIMETER"); - gw.present(ID_S_MULTIMETER,S_MULTIMETER); + gw.present(ID_S_MULTIMETER,S_MULTIMETER,"Electric Staion"); gw.wait(SHORT_WAIT); #endif @@ -559,13 +562,13 @@ void setup() #ifdef ID_S_MOISTURE Serial.println(" S_MOISTURE"); - gw.present(ID_S_MOISTURE,S_MOISTURE); + gw.present(ID_S_MOISTURE,S_MOISTURE,"Basement Sensor"); gw.wait(SHORT_WAIT); #endif #ifdef ID_S_CUSTOM Serial.println(" S_CUSTOM"); - gw.present(ID_S_CUSTOM,S_CUSTOM); + gw.present(ID_S_CUSTOM,S_CUSTOM,"Other Stuff"); gw.wait(SHORT_WAIT); #endif @@ -969,13 +972,13 @@ void heater(){ Serial.println(heater_flow_state); gw.send(msg_S_HEATER_FLOW_STATE.set(heater_flow_state.c_str())); - Serial.print("Heater on/off is: " ); - Serial.println((heater_status==true)?"On":"Off"); - gw.send(msg_S_HEATER_STATUS.set(heater_status)); +// Serial.print("Heater on/off is: " ); +// Serial.println((heater_status==true)?"On":"Off"); +// gw.send(msg_S_HEATER_STATUS.set(heater_status)); - Serial.print("Heater Temperature is: " ); - Serial.println(heater_temp,1); - gw.send(msg_S_HEATER_TEMP.set(heater_temp,1)); +// Serial.print("Heater Temperature is: " ); +// Serial.println(heater_temp,1); +// gw.send(msg_S_HEATER_TEMP.set(heater_temp,1)); Serial.print("Heater Setpoint: " ); Serial.println(heater_setpoint,1); @@ -1248,16 +1251,16 @@ void incomingMessage(const MyMessage &message) { light(); // temp ack } #endif - #ifdef ID_S_HEATER - if(message.sensor == ID_S_HEATER){ - heater_status = message.getBool(); - Serial.print("Incoming change for ID_S_HEATER:"); - Serial.print(message.sensor); - Serial.print(", New status: "); - Serial.println(heater_status); - heater();//temp ack - } - #endif +// #ifdef ID_S_HEATER +// if(message.sensor == ID_S_HEATER){ +// heater_status = message.getBool(); +// Serial.print("Incoming change for ID_S_HEATER:"); +// Serial.print(message.sensor); +// Serial.print(", New status: "); +// Serial.println(heater_status); +// heater();//temp ack +// } +// #endif break; @@ -1340,6 +1343,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(heater_flow_state); + heater();//temp ack } #endif @@ -1414,6 +1418,8 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(rgbState); + rgbLight(); // temp ack + break; #endif @@ -1424,6 +1430,7 @@ void incomingMessage(const MyMessage &message) { Serial.print(message.sensor); Serial.print(", New status: "); Serial.println(rgbwState); + rgbwLight(); break; #endif From 785882592767f696406928e00bc20c0ed57f7597 Mon Sep 17 00:00:00 2001 From: fleinze Date: Tue, 22 Sep 2015 11:15:05 +0200 Subject: [PATCH 28/35] Update MySensor.cpp only verify signature from nodes that require signing --- libraries/MySensors/MySensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index 65bcf35..c455a32 100644 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -567,7 +567,7 @@ boolean MySensor::process() { #ifdef MY_SIGNING_FEATURE // Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us) // Note that we do not care at all about any signature found if we do not require signing, nor do we care about ACKs (they are never signed) - if (signer.requestSignatures() && msg.destination == nc.nodeId && mGetLength(msg) && !mGetAck(msg) && + if (DO_SIGN(msg.sender) && signer.requestSignatures() && msg.destination == nc.nodeId && mGetLength(msg) && !mGetAck(msg) && (mGetCommand(msg) != C_INTERNAL || (msg.type != I_GET_NONCE_RESPONSE && msg.type != I_GET_NONCE && msg.type != I_REQUEST_SIGNING && msg.type != I_ID_REQUEST && msg.type != I_ID_RESPONSE && From 518ed6af7c71fafc8198801c47036f6e74872222 Mon Sep 17 00:00:00 2001 From: fleinze Date: Tue, 22 Sep 2015 11:43:29 +0200 Subject: [PATCH 29/35] Update MySensor.cpp Gateway only expects signing only from nodes it knows in turn require signed messages. --- libraries/MySensors/MySensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/MySensors/MySensor.cpp b/libraries/MySensors/MySensor.cpp index c455a32..b9ca0e4 100644 --- a/libraries/MySensors/MySensor.cpp +++ b/libraries/MySensors/MySensor.cpp @@ -567,7 +567,7 @@ boolean MySensor::process() { #ifdef MY_SIGNING_FEATURE // Before processing message, reject unsigned messages if signing is required and check signature (if it is signed and addressed to us) // Note that we do not care at all about any signature found if we do not require signing, nor do we care about ACKs (they are never signed) - if (DO_SIGN(msg.sender) && signer.requestSignatures() && msg.destination == nc.nodeId && mGetLength(msg) && !mGetAck(msg) && + if ((!isGateway || DO_SIGN(msg.sender)) && signer.requestSignatures() && msg.destination == nc.nodeId && mGetLength(msg) && !mGetAck(msg) && (mGetCommand(msg) != C_INTERNAL || (msg.type != I_GET_NONCE_RESPONSE && msg.type != I_GET_NONCE && msg.type != I_REQUEST_SIGNING && msg.type != I_ID_REQUEST && msg.type != I_ID_RESPONSE && From a33e9cce488dc14d6dd619c69f974acd4c1e632d Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Tue, 22 Sep 2015 23:25:43 +0200 Subject: [PATCH 30/35] Added preprocessor definitions to MySensors AVR boards This allows the MySensors AVR boards (SenseBender) to be built with upcoming versions of the Arduino IDE. --- hardware/MySensors/avr/platform.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hardware/MySensors/avr/platform.txt b/hardware/MySensors/avr/platform.txt index 8f61be7..08cbe31 100644 --- a/hardware/MySensors/avr/platform.txt +++ b/hardware/MySensors/avr/platform.txt @@ -79,6 +79,12 @@ recipe.size.regex=^(?:\.text|\.data|\.bootloader)\s+([0-9]+).* recipe.size.regex.data=^(?:\.data|\.bss|\.noinit)\s+([0-9]+).* recipe.size.regex.eeprom=^(?:\.eeprom)\s+([0-9]+).* +## Preprocessor +preproc.includes.flags=-w -x c++ -M -MG -MP +recipe.preproc.includes="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} {preproc.includes.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} {includes} "{source_file}" + +preproc.macros.flags=-w -x c++ -E -CC +recipe.preproc.macros="{compiler.path}{compiler.cpp.cmd}" {compiler.cpp.flags} {preproc.macros.flags} -mmcu={build.mcu} -DF_CPU={build.f_cpu} -DARDUINO={runtime.ide.version} -DARDUINO_{build.board} -DARDUINO_ARCH_{build.arch} {compiler.cpp.extra_flags} {build.extra_flags} {includes} "{source_file}" # AVR Uploader/Programmers tools # ------------------------------ From 921b9fd3fc5307c018c1e42a74e69e5700fe433a Mon Sep 17 00:00:00 2001 From: Patrick Fallberg Date: Thu, 24 Sep 2015 10:45:08 +0200 Subject: [PATCH 31/35] Forward declare all inline functions This seem to be mandatory for newer Arduino IDE versions. --- .../MySensors/examples/MQTTClientGateway/MQTTClientGateway.ino | 2 ++ libraries/MySensors/examples/MQTTGateway/MQTTGateway.ino | 1 + 2 files changed, 3 insertions(+) diff --git a/libraries/MySensors/examples/MQTTClientGateway/MQTTClientGateway.ino b/libraries/MySensors/examples/MQTTClientGateway/MQTTClientGateway.ino index 9c5b60b..23d657d 100644 --- a/libraries/MySensors/examples/MQTTClientGateway/MQTTClientGateway.ino +++ b/libraries/MySensors/examples/MQTTClientGateway/MQTTClientGateway.ino @@ -162,6 +162,8 @@ volatile uint8_t countErr; void processMQTTMessages(char* topic, byte* payload, unsigned int length); PubSubClient client(remote_ip, remote_port, processMQTTMessages, ethClient); +inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, + uint8_t command, uint8_t type, bool enableAck); //////////////////////////////////////////////////////////////// diff --git a/libraries/MySensors/examples/MQTTGateway/MQTTGateway.ino b/libraries/MySensors/examples/MQTTGateway/MQTTGateway.ino index 34d3fff..59f63bb 100644 --- a/libraries/MySensors/examples/MQTTGateway/MQTTGateway.ino +++ b/libraries/MySensors/examples/MQTTGateway/MQTTGateway.ino @@ -132,6 +132,7 @@ MyHwATMega328 hw; // Construct MySensors library (signer needed if MY_SIGNING_FEATURE is turned on in MyConfig.h) MySensor gw(transport, hw /*, signer*/); +inline MyMessage& build (MyMessage &msg, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck); EthernetServer server = EthernetServer(TCP_PORT); EthernetClient *currentClient = NULL; From c9ffef8da361dbc5f01c378d1d99855d3ead8239 Mon Sep 17 00:00:00 2001 From: ToniA Date: Tue, 29 Sep 2015 20:21:42 +0300 Subject: [PATCH 32/35] HeatpumpIR library + sensors example to send IR commands to heatpumps --- libraries/HeatpumpIR/CarrierHeatpumpIR.cpp | 167 +++++++++ libraries/HeatpumpIR/CarrierHeatpumpIR.h | 42 +++ libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp | 193 ++++++++++ libraries/HeatpumpIR/FujitsuHeatpumpIR.h | 54 +++ libraries/HeatpumpIR/HeatpumpIR.cpp | 21 ++ libraries/HeatpumpIR/HeatpumpIR.h | 69 ++++ libraries/HeatpumpIR/HisenseHeatpumpIR.cpp | 123 +++++++ libraries/HeatpumpIR/HisenseHeatpumpIR.h | 73 ++++ libraries/HeatpumpIR/IRSender.cpp | 206 +++++++++++ libraries/HeatpumpIR/IRSender.h | 23 ++ libraries/HeatpumpIR/LICENSE | 339 ++++++++++++++++++ libraries/HeatpumpIR/MideaHeatpumpIR.cpp | 147 ++++++++ libraries/HeatpumpIR/MideaHeatpumpIR.h | 47 +++ libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp | 214 +++++++++++ libraries/HeatpumpIR/MitsubishiHeatpumpIR.h | 79 ++++ .../HeatpumpIR/PanasonicCKPHeatpumpIR.cpp | 262 ++++++++++++++ libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h | 55 +++ libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp | 237 ++++++++++++ libraries/HeatpumpIR/PanasonicHeatpumpIR.h | 83 +++++ libraries/HeatpumpIR/README.md | 21 ++ libraries/HeatpumpIR/SamsungHeatpumpIR.cpp | 172 +++++++++ libraries/HeatpumpIR/SamsungHeatpumpIR.h | 42 +++ libraries/HeatpumpIR/arduino_irsender.png | Bin 0 -> 2752919 bytes .../examples/HisenseTest/HisenseTest.ino | 56 +++ .../MideaRelayControl/MideaRelayControl.ino | 92 +++++ .../examples/MySensorsNode/MySensorsNode.ino | 204 +++++++++++ .../examples/MySensorsNode/README.md | 73 ++++ .../PanasonicCKPTimer/PanasonicCKPTimer.ino | 56 +++ .../HeatpumpIR/examples/simple/simple.ino | 57 +++ libraries/HeatpumpIR/keywords.txt | 26 ++ .../HeatpumpIRController.ino | 209 +++++++++++ .../examples/HeatpumpIRController/README.md | 73 ++++ libraries/Timer/Event.cpp | 65 ++++ libraries/Timer/Event.h | 49 +++ libraries/Timer/ReadMe.txt | 29 ++ libraries/Timer/Timer.cpp | 138 +++++++ libraries/Timer/Timer.h | 67 ++++ libraries/Timer/examples/blink2/blink2.ino | 29 ++ .../examples/kitchen_sink/kitchen_sink.pde | 42 +++ .../pin_high_10_mins/pin_high_10_mins.pde | 17 + .../read_A0_flashLED/read_A0_flashLED.pde | 22 ++ libraries/Timer/keywords.txt | 31 ++ 42 files changed, 4004 insertions(+) create mode 100644 libraries/HeatpumpIR/CarrierHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/CarrierHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/FujitsuHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/HeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/HeatpumpIR.h create mode 100644 libraries/HeatpumpIR/HisenseHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/HisenseHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/IRSender.cpp create mode 100644 libraries/HeatpumpIR/IRSender.h create mode 100644 libraries/HeatpumpIR/LICENSE create mode 100644 libraries/HeatpumpIR/MideaHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/MideaHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/MitsubishiHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/PanasonicHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/README.md create mode 100644 libraries/HeatpumpIR/SamsungHeatpumpIR.cpp create mode 100644 libraries/HeatpumpIR/SamsungHeatpumpIR.h create mode 100644 libraries/HeatpumpIR/arduino_irsender.png create mode 100644 libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino create mode 100644 libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino create mode 100644 libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino create mode 100644 libraries/HeatpumpIR/examples/MySensorsNode/README.md create mode 100644 libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino create mode 100644 libraries/HeatpumpIR/examples/simple/simple.ino create mode 100644 libraries/HeatpumpIR/keywords.txt create mode 100644 libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino create mode 100644 libraries/MySensors/examples/HeatpumpIRController/README.md create mode 100644 libraries/Timer/Event.cpp create mode 100644 libraries/Timer/Event.h create mode 100644 libraries/Timer/ReadMe.txt create mode 100644 libraries/Timer/Timer.cpp create mode 100644 libraries/Timer/Timer.h create mode 100644 libraries/Timer/examples/blink2/blink2.ino create mode 100644 libraries/Timer/examples/kitchen_sink/kitchen_sink.pde create mode 100644 libraries/Timer/examples/pin_high_10_mins/pin_high_10_mins.pde create mode 100644 libraries/Timer/examples/read_A0_flashLED/read_A0_flashLED.pde create mode 100644 libraries/Timer/keywords.txt diff --git a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp new file mode 100644 index 0000000..a4d6d70 --- /dev/null +++ b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp @@ -0,0 +1,167 @@ +#include + +CarrierHeatpumpIR::CarrierHeatpumpIR() : HeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "carrier"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"carrier\",\"dn\":\"Carrier\",\"mT\":17,\"xT\":30,\"fs\":6}"; + + _model = model; + _info = info; +} + + +void CarrierHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + + byte operatingMode = CARRIER_AIRCON1_MODE_HEAT; + byte fanSpeed = CARRIER_AIRCON1_FAN_AUTO; + byte temperature = 23; + + if (powerModeCmd == POWER_OFF) + { + operatingMode = CARRIER_AIRCON1_MODE_OFF; + } + else + { + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = CARRIER_AIRCON1_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode = CARRIER_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = CARRIER_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = CARRIER_AIRCON1_MODE_DRY; + fanSpeedCmd = FAN_AUTO; // Fan speed is always 'AUTO' in DRY mode + break; + case MODE_FAN: + operatingMode = CARRIER_AIRCON1_MODE_FAN; + temperatureCmd = 22; // Temperature is always 22 in FAN mode + break; + } + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = CARRIER_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = CARRIER_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = CARRIER_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = CARRIER_AIRCON1_FAN3; + break; + case FAN_4: + fanSpeed = CARRIER_AIRCON1_FAN4; + break; + case FAN_5: + fanSpeed = CARRIER_AIRCON1_FAN5; + break; + } + + if ( temperatureCmd > 16 && temperatureCmd < 31) + { + temperature = temperatureCmd; + } + + sendCarrier(IR, operatingMode, fanSpeed, temperature); +} + +// Send the Carrier code +// Carrier has the LSB and MSB in different format than Panasonic + +void CarrierHeatpumpIR::sendCarrier(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature) +{ + byte sendBuffer[9] = { 0x4f, 0xb0, 0xc0, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00 }; // The data is on the last four bytes + + static const prog_uint8_t temperatures[] PROGMEM = { 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b }; + byte checksum = 0; + + // PROGMEM arrays cannot be addressed directly, see http://forum.arduino.cc/index.php?topic=106603.0 + sendBuffer[5] = pgm_read_byte(&(temperatures[(temperature-17)])); + + sendBuffer[6] = operatingMode | fanSpeed; + + // Checksum + + for (int i=0; i<8; i++) { + checksum += IR.bitReverse(sendBuffer[i]); + } + + // There's something really strange with the checksum calculation... + // With these many of the codes matchs with the code from the real Carrier remote + // Still certain temperatures do not work with fan speeds 1, 2 or 5 + + switch (sendBuffer[6] & 0xF0) { + case 0x00: // MODE_AUTO - certain temperature / fan speed combinations do not work + checksum += 0x02; + switch (sendBuffer[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + case 0x40: // MODE_DRY - all settings should work + checksum += 0x02; + break; + case 0xC0: // MODE_HEAT - certain temperature / fan speed combinations do not work + switch (sendBuffer[6] & 0x0F) { + case 0x05: // FAN4 + case 0x06: // FAN2 + checksum += 0xC0; + break; + } + break; + case 0x20: // MODE_FAN - all settings should work + checksum += 0x02; + switch (sendBuffer[6] & 0x0F) { + case 0x02: // FAN1 + case 0x03: // FAN5 + case 0x06: // FAN2 + checksum += 0x80; + break; + } + break; + } + + sendBuffer[8] = IR.bitReverse(checksum); + + // 40 kHz PWM frequency + IR.setFrequency(40); + + // Header + IR.mark(CARRIER_AIRCON1_HDR_MARK); + IR.space(CARRIER_AIRCON1_HDR_SPACE); + + // Payload + for (int i=0; i + +// Carrier (42NQV035G / 38NYV035H2) timing constants (remote control P/N WH-L05SE) +#define CARRIER_AIRCON1_HDR_MARK 4320 +#define CARRIER_AIRCON1_HDR_SPACE 4350 +#define CARRIER_AIRCON1_BIT_MARK 500 +#define CARRIER_AIRCON1_ONE_SPACE 1650 +#define CARRIER_AIRCON1_ZERO_SPACE 550 +#define CARRIER_AIRCON1_MSG_SPACE 7400 + +// Carrier codes +#define CARRIER_AIRCON1_MODE_AUTO 0x00 // Operating mode +#define CARRIER_AIRCON1_MODE_HEAT 0xC0 +#define CARRIER_AIRCON1_MODE_COOL 0x80 +#define CARRIER_AIRCON1_MODE_DRY 0x40 +#define CARRIER_AIRCON1_MODE_FAN 0x20 +#define CARRIER_AIRCON1_MODE_OFF 0xE0 // Power OFF +#define CARRIER_AIRCON1_FAN_AUTO 0x00 // Fan speed +#define CARRIER_AIRCON1_FAN1 0x02 +#define CARRIER_AIRCON1_FAN2 0x06 +#define CARRIER_AIRCON1_FAN3 0x01 +#define CARRIER_AIRCON1_FAN4 0x05 +#define CARRIER_AIRCON1_FAN5 0x03 + + +class CarrierHeatpumpIR : public HeatpumpIR +{ + public: + CarrierHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendCarrier(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature); +}; + +#endif diff --git a/libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp b/libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp new file mode 100644 index 0000000..d048ff1 --- /dev/null +++ b/libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp @@ -0,0 +1,193 @@ +#include + +FujitsuHeatpumpIR::FujitsuHeatpumpIR() : HeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "fujitsu_awyz"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"fujitsu_awyz\",\"dn\":\"Fujitsu AWYZ\",\"mT\":16,\"xT\":30,\"fs\":5}"; + + _model = model; + _info = info; +} + + +void FujitsuHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + send(IR, powerModeCmd, operatingModeCmd, fanSpeedCmd, temperatureCmd, swingVCmd, swingHCmd, false); +} + + +void FujitsuHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd, bool ecoModeCmd) +{ + // Sensible defaults for the heat pump mode + + byte operatingMode = FUJITSU_AIRCON1_MODE_HEAT; + byte fanSpeed = FUJITSU_AIRCON1_FAN_AUTO; + byte temperature = 23; + byte swingV = FUJITSU_AIRCON1_VDIR_MANUAL; + byte swingH = FUJITSU_AIRCON1_HDIR_MANUAL; + byte ecoMode = FUJITSU_AIRCON1_ECO_OFF; + + if (powerModeCmd == POWER_OFF) + { + operatingMode = FUJITSU_AIRCON1_MODE_OFF; + } + else + { + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = FUJITSU_AIRCON1_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode = FUJITSU_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = FUJITSU_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = FUJITSU_AIRCON1_MODE_DRY; + break; + case MODE_FAN: + operatingMode = FUJITSU_AIRCON1_MODE_FAN; + // When Fujitsu goes to FAN mode, it sets the low bit of the byte with the temperature. What is the meaning of that? + break; + } + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = FUJITSU_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = FUJITSU_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = FUJITSU_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = FUJITSU_AIRCON1_FAN3; + break; + case FAN_4: + fanSpeed = FUJITSU_AIRCON1_FAN4; + break; + } + + if ( temperatureCmd > 15 && temperatureCmd < 31) + { + temperature = temperatureCmd; + } + + // Only support 'don't move' or 'swing' at the moment, not any specific position + if ( swingVCmd == VDIR_SWING) { + swingV = FUJITSU_AIRCON1_VDIR_SWING; + } + + if ( swingHCmd == HDIR_SWING) { + swingH = FUJITSU_AIRCON1_HDIR_SWING; + } + + if (ecoModeCmd) { + ecoMode = FUJITSU_AIRCON1_ECO_ON; + } + + sendFujitsu(IR, operatingMode, fanSpeed, temperature, swingV, swingH, ecoMode); +} + + +void FujitsuHeatpumpIR::sendFujitsu(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH, byte ecoMode) +{ + // ON, HEAT, AUTO FAN, +24 degrees + byte FujitsuTemplate[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0xFE, 0x09, 0x30, 0x80, 0x04, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00 }; + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + byte OFF_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x02, 0xFD }; + byte checksum = 0x00; + +/* + + Fujitsu does not have codes to set the air direction to any specific position, but just go to the next position: + + byte nextVerticalPosition_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x6C, 0x93 }; + byte nextHorizontalPosition_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x79,0x86 }; + + These would need to be sent separately... +*/ + // Set the operatingmode on the template message + FujitsuTemplate[9] = operatingMode; + + // Set the eco mode on the template message + FujitsuTemplate[14] = ecoMode; + + // Set the temperature on the template message. The least significant bit should be set to '1' + FujitsuTemplate[8] = (temperature - 16) << 4 | 0x01; + + // Set the fan speed and air direction on the template message + FujitsuTemplate[10] = fanSpeed + swingV + swingH; + + // Calculate the checksum + for (int i=0; i<15; i++) { + checksum += FujitsuTemplate[i]; + } + + FujitsuTemplate[15] = (byte)(0x9E - checksum); + + if (operatingMode == FUJITSU_AIRCON1_MODE_OFF) { + // OFF + sendFujitsuMsg(IR, sizeof(OFF_msg), OFF_msg); + } else { + sendFujitsuMsg(IR, sizeof(FujitsuTemplate), FujitsuTemplate); + } +} + + +void FujitsuHeatpumpIR::sendFujitsuHiPower(IRSender& IR) +{ + byte HiPower_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x39, 0xC6 }; + + sendFujitsuMsg(IR, sizeof(HiPower_msg), HiPower_msg); +} + + +void FujitsuHeatpumpIR::sendFujitsuFilterClean(IRSender& IR) +{ + byte FilterClean_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x02, 0xFD }; + + sendFujitsuMsg(IR, sizeof(FilterClean_msg), FilterClean_msg); +} + + +void FujitsuHeatpumpIR::sendFujitsuSuperQuiet(IRSender& IR) +{ + byte SuperQuiet_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x02, 0xFD }; + + sendFujitsuMsg(IR, sizeof(SuperQuiet_msg), SuperQuiet_msg); +} + + +void FujitsuHeatpumpIR::sendFujitsuTestRun(IRSender& IR) +{ + byte TestRun_msg[] = { 0x14, 0x63, 0x00, 0x10, 0x10, 0x02, 0xFD }; + + sendFujitsuMsg(IR, sizeof(TestRun_msg), TestRun_msg); +} + + +void FujitsuHeatpumpIR::sendFujitsuMsg(IRSender& IR, byte msgSize, byte *msg) +{ + // 40 kHz PWM frequency + IR.setFrequency(40); + + // Header + IR.mark(FUJITSU_AIRCON1_HDR_MARK); + IR.space(FUJITSU_AIRCON1_HDR_SPACE); + + // Data + for (byte i=0; i + + +// Fujitsu Nocria (AWYZ14) timing constants (remote control P/N AR-PZ2) +#define FUJITSU_AIRCON1_HDR_MARK 3210 +#define FUJITSU_AIRCON1_HDR_SPACE 1680 +#define FUJITSU_AIRCON1_BIT_MARK 410 +#define FUJITSU_AIRCON1_ONE_SPACE 1230 +#define FUJITSU_AIRCON1_ZERO_SPACE 440 + +// Fujitsu codes +#define FUJITSU_AIRCON1_MODE_AUTO 0x00 // Operating mode +#define FUJITSU_AIRCON1_MODE_HEAT 0x04 +#define FUJITSU_AIRCON1_MODE_COOL 0x01 +#define FUJITSU_AIRCON1_MODE_DRY 0x02 +#define FUJITSU_AIRCON1_MODE_FAN 0x03 +#define FUJITSU_AIRCON1_MODE_OFF 0xFF // Power OFF - not real codes, but we need something... +#define FUJITSU_AIRCON1_FAN_AUTO 0x00 // Fan speed +#define FUJITSU_AIRCON1_FAN1 0x04 +#define FUJITSU_AIRCON1_FAN2 0x03 +#define FUJITSU_AIRCON1_FAN3 0x02 +#define FUJITSU_AIRCON1_FAN4 0x01 +#define FUJITSU_AIRCON1_VDIR_MANUAL 0x00 // Air direction modes +#define FUJITSU_AIRCON1_VDIR_SWING 0x10 +#define FUJITSU_AIRCON1_HDIR_MANUAL 0x00 +#define FUJITSU_AIRCON1_HDIR_SWING 0x20 +#define FUJITSU_AIRCON1_ECO_OFF 0x20 +#define FUJITSU_AIRCON1_ECO_ON 0x00 + + + +class FujitsuHeatpumpIR : public HeatpumpIR +{ + public: + FujitsuHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd, bool ecoModeCmd); + void sendFujitsuHiPower(IRSender& IR); + void sendFujitsuFilterClean(IRSender& IR); + void sendFujitsuSuperQuiet(IRSender& IR); + void sendFujitsuTestRun(IRSender& IR); + + private: + void sendFujitsu(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH, byte ecoMode); + void sendFujitsuMsg(IRSender& IR, byte msgSize, byte *msg); +}; + +#endif diff --git a/libraries/HeatpumpIR/HeatpumpIR.cpp b/libraries/HeatpumpIR/HeatpumpIR.cpp new file mode 100644 index 0000000..a741ea4 --- /dev/null +++ b/libraries/HeatpumpIR/HeatpumpIR.cpp @@ -0,0 +1,21 @@ +#include + +HeatpumpIR::HeatpumpIR() +{ +} + +// This is a virtual function, i.e. never called +void HeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ +} + +// Heatpump model and info getters +const char PROGMEM* HeatpumpIR::model() +{ + return _model; +} + +const char PROGMEM* HeatpumpIR::info() +{ + return _info; +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/HeatpumpIR.h b/libraries/HeatpumpIR/HeatpumpIR.h new file mode 100644 index 0000000..c9a6b7b --- /dev/null +++ b/libraries/HeatpumpIR/HeatpumpIR.h @@ -0,0 +1,69 @@ +/* + The generic heatpump interface + * Constants for modes + * The constructor and the 'send' command interface +*/ + +#ifndef HeatpumpIR_h +#define HeatpumpIR_h + +#define __PROG_TYPES_COMPAT__ +#include +#include + + +// Power state +#define POWER_OFF 0 +#define POWER_ON 1 + +// Operating modes +#define MODE_AUTO 1 +#define MODE_HEAT 2 +#define MODE_COOL 3 +#define MODE_DRY 4 +#define MODE_FAN 5 +#define MODE_MAINT 6 + +// Fan speeds. Note that some heatpumps have less than 5 fan speeds +#define FAN_AUTO 0 +#define FAN_1 1 +#define FAN_2 2 +#define FAN_3 3 +#define FAN_4 4 +#define FAN_5 5 + +// Vertical air directions. Note that these cannot be set on all heat pumps +#define VDIR_AUTO 0 +#define VDIR_MANUAL 0 +#define VDIR_SWING 1 +#define VDIR_UP 2 +#define VDIR_MUP 3 +#define VDIR_MIDDLE 4 +#define VDIR_MDOWN 5 +#define VDIR_DOWN 6 + +// Horizontal air directions. Note that these cannot be set on all heat pumps +#define HDIR_AUTO 0 +#define HDIR_MANUAL 0 +#define HDIR_SWING 1 +#define HDIR_MIDDLE 2 +#define HDIR_LEFT 3 +#define HDIR_MLEFT 4 +#define HDIR_MRIGHT 5 +#define HDIR_RIGHT 6 + + +class HeatpumpIR +{ + protected: + HeatpumpIR(); // Cannot create generic heatpump instances + const char PROGMEM* _model; + const char PROGMEM* _info; + + public: + virtual void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + const char PROGMEM* model(); + const char PROGMEM* info(); +}; + +#endif diff --git a/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp new file mode 100644 index 0000000..542ceb9 --- /dev/null +++ b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp @@ -0,0 +1,123 @@ +#include + +HisenseHeatpumpIR::HisenseHeatpumpIR() : HeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "hisense_aud"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"hisense_aud\",\"dn\":\"Hisense AUD\",\"mT\":18,\"xT\":32,\"fs\":3}"; + + _model = model; + _info = info; +} + + +void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd , byte fanSpeedCmd , byte temperatureCmd , byte swingVCmd , byte swingHCmd ) +{ + // Sensible defaults for the heat pump mode + + byte powerMode = HISENSE_AIRCON1_POWER_ON; + byte operatingMode = HISENSE_AIRCON1_MODE_HEAT; + byte fanSpeed = HISENSE_AIRCON1_FAN_AUTO; + byte temperature = 21; + byte swingV=0; + byte swingH=0; + + if (powerModeCmd == POWER_OFF) + { + powerMode = HISENSE_AIRCON1_POWER_OFF; + } + else + { + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = HISENSE_AIRCON1_MODE_AUTO; + fanSpeedCmd = FAN_AUTO; // Fan speed is always 'AUTO' in AUTO mode + break; + case MODE_HEAT: + operatingMode = HISENSE_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = HISENSE_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = HISENSE_AIRCON1_MODE_DRY; + fanSpeedCmd = FAN_AUTO; // Fan speed is always 'AUTO' in DRY mode + break; + case MODE_FAN: + operatingMode = HISENSE_AIRCON1_MODE_FAN; + if ( fanSpeedCmd == FAN_AUTO ) { + fanSpeedCmd = FAN_1; // Fan speed cannot be 'AUTO' in FAN mode + temperatureCmd = 25; // Fixed temperature FAN mode + } + break; + } + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = HISENSE_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = HISENSE_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = HISENSE_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = HISENSE_AIRCON1_FAN3; + break; + } + + if ( temperatureCmd > 17 && temperatureCmd < 33) + { + temperature = temperatureCmd; + } + + + sendHisense(IR, powerMode, operatingMode, fanSpeed, temperature, swingV, swingH); +} + +// Send the Hisense code +void HisenseHeatpumpIR::sendHisense(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV ,byte swingH) +{ + byte HisenseTemplate[] = { 0x87, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // Header byte 0-1 + 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 }; // + + byte i; + + // Set the Fan speed, On/Off. + HisenseTemplate[2] = fanSpeed | powerMode; + HisenseTemplate[3] = (((temperature - 18) << 4) | operatingMode ) ; + + // Calculate the byte checksum EXOR byte 2 to 12 + HisenseTemplate[13] = HisenseTemplate[2]; + for (i=3; i<13; i++) { + HisenseTemplate[13]= HisenseTemplate[i] ^ HisenseTemplate[13]; + } + + // 38 kHz PWM frequency + IR.setFrequency(38); + + // Send Header mark + IR.mark(HISENSE_AIRCON1_HDR_MARK); + IR.space(HISENSE_AIRCON1_HDR_SPACE); + + // Payload header part + for (i=0; i<7; i++) { + IR.sendIRByte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); + } + + // Mesage space + IR.mark(HISENSE_AIRCON1_BIT_MARK); + IR.space(HISENSE_AIRCON1_MSG_SPACE); + + // Payload message part + for (; i<14; i++) { + IR.sendIRByte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); + } + + // End mark + IR.mark(HISENSE_AIRCON1_BIT_MARK); + IR.space(0); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/HisenseHeatpumpIR.h b/libraries/HeatpumpIR/HisenseHeatpumpIR.h new file mode 100644 index 0000000..4cc2b01 --- /dev/null +++ b/libraries/HeatpumpIR/HisenseHeatpumpIR.h @@ -0,0 +1,73 @@ +/* + Hisense AQV12PSBN / AQV09ASA heatpump control (remote control P/N zzz) +*/ +#ifndef HisenseHeatpumpIR_h +#define HisenseHeatpumpIR_h + +#include + + +// Hisense timing constants +#define HISENSE_AIRCON1_HDR_MARK 9060 +#define HISENSE_AIRCON1_HDR_SPACE 4550 +#define HISENSE_AIRCON1_BIT_MARK 520 +#define HISENSE_AIRCON1_ONE_SPACE 1700 +#define HISENSE_AIRCON1_ZERO_SPACE 630 +#define HISENSE_AIRCON1_MSG_SPACE 8140 + +// Power state +#define HISENSE_AIRCON1_POWER_OFF 0x04 //** +#define HISENSE_AIRCON1_POWER_ON 0x00 //** + +// Operating modes +// Hisense codes +#define HISENSE_AIRCON1_MODE_AUTO 0x04 // Not available 0x00 +#define HISENSE_AIRCON1_MODE_HEAT 0x00 +#define HISENSE_AIRCON1_MODE_COOL 0x02 +#define HISENSE_AIRCON1_MODE_DRY 0x03 +#define HISENSE_AIRCON1_MODE_FAN 0x04 +#define HISENSE_AIRCON1_MODE_MAINT 0x04 // Power OFF + +// Fan speeds. Note that some heatpumps have less than 5 fan speeds + +#define HISENSE_AIRCON1_FAN_AUTO 0x00 // Fan speed +#define HISENSE_AIRCON1_FAN1 0x03 // * low +#define HISENSE_AIRCON1_FAN2 0x02 // * med +#define HISENSE_AIRCON1_FAN3 0x01 // * high +#define HISENSE_AIRCON1_FAN4 0x01 // * high Not available +#define HISENSE_AIRCON1_FAN5 0x01 // * high Not available + +// Not available in this model. +// Vertical air directions. Note that these cannot be set on all heat pumps +#define HISENSE_VDIR_AUTO 0 +#define HISENSE_VDIR_MANUAL 0 +#define HISENSE_VDIR_SWING 0 +#define HISENSE_VDIR_UP 0 +#define HISENSE_VDIR_MUP 0 +#define HISENSE_VDIR_MIDDLE 0 +#define HISENSE_VDIR_MDOWN 0 +#define HISENSE_VDIR_DOWN 0 + +// Not available in this model. +// Horizontal air directions. Note that these cannot be set on all heat pumps +#define HISENSE_HDIR_AUTO 0 +#define HISENSE_HDIR_MANUAL 0 +#define HISENSE_HDIR_SWING 0 +#define HISENSE_HDIR_MIDDLE 0 +#define HISENSE_HDIR_LEFT 0 +#define HISENSE_HDIR_MLEFT 0 +#define HISENSE_HDIR_MRIGHT 0 +#define HISENSE_HDIR_RIGHT 0 + + +class HisenseHeatpumpIR : public HeatpumpIR +{ + public: + HisenseHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendHisense(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH); +}; + +#endif diff --git a/libraries/HeatpumpIR/IRSender.cpp b/libraries/HeatpumpIR/IRSender.cpp new file mode 100644 index 0000000..8daffe6 --- /dev/null +++ b/libraries/HeatpumpIR/IRSender.cpp @@ -0,0 +1,206 @@ +#include +#include + +// Heavily based on Ken Shirriff's IRRemote library: +// https://github.com/shirriff/Arduino-IRremote +// +// For PWM on Arduino, see http://playground.arduino.cc/Main/TimerPWMCheatsheet + +IRSender::IRSender(byte pin) +{ + _pin = pin; +} + +// Set the PWM frequency. The selected pin determines which timer to use +void IRSender::setFrequency(int frequency) +{ + uint8_t pwmval8 = F_CPU / 2000 / (frequency); + uint16_t pwmval16 = F_CPU / 2000 / (frequency); + + pinMode(_pin, OUTPUT); + digitalWrite(_pin, LOW); // When not sending PWM, we want it low + + switch (_pin) + { +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +// Arduino Mega + case 9: + // Fall-through to 10, timer2 controls both 9 and 10 + case 10: + TCCR2A = _BV(WGM20); + TCCR2B = _BV(WGM22) | _BV(CS20); + OCR2A = pwmval8; + OCR2B = pwmval8 / 3; + break; + case 11: + // Fall-through to 12, timer1 controls both 11 and 12 + case 12: + TCCR1A = _BV(WGM11); + TCCR1B = _BV(WGM13) | _BV(CS10); + ICR1 = pwmval16; + OCR1A = pwmval16 / 3; + OCR1B = pwmval16 / 3; + break; + case 44: + // Fall-through to 46, timer 5 controls pins 44, 45 and 46 on Arduino Mega + case 45: + case 46: + TCCR5A = _BV(WGM51) | _BV(WGM50); + TCCR5B = _BV(WGM53) | _BV(CS50); + ICR5 = pwmval16; + OCR5A = pwmval16 / 3; +#else +// Arduino Duemilanove etc + case 3: + // Fall-through to 11, timer2 controls both 3 and 11 + case 11: + TCCR2A = _BV(WGM20); + TCCR2B = _BV(WGM22) | _BV(CS20); + OCR2A = pwmval8; + OCR2B = pwmval8 / 3; + break; + case 9: + // Fall-through to 10, timer1 controls both 9 and 10 + case 10: + TCCR1A = _BV(WGM11); + TCCR1B = _BV(WGM13) | _BV(CS10); + ICR1 = pwmval16; + OCR1A = pwmval16 / 3; + OCR1B = pwmval16 / 3; + break; +#endif + } +} + +// Send a byte (8 bits) over IR +void IRSender::sendIRByte(byte sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength) +{ + for (int i=0; i<8 ; i++) + { + if (sendByte & 0x01) + { + mark(bitMarkLength); + space(oneSpaceLength); + } + else + { + mark(bitMarkLength); + space(zeroSpaceLength); + } + + sendByte >>= 1; + } +} + +// The Carrier IR protocol has the bits in a reverse order (compared to the other heatpumps) +// See http://www.nrtm.org/index.php/2013/07/25/reverse-bits-in-a-byte/ +byte IRSender::bitReverse(byte x) +{ + // 01010101 | 10101010 + x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); + // 00110011 | 11001100 + x = ((x >> 2) & 0x33) | ((x << 2) & 0xcc); + // 00001111 | 11110000 + x = ((x >> 4) & 0x0f) | ((x << 4) & 0xf0); + return x; +} + +// Send an IR 'mark' symbol, i.e. transmitter ON +void IRSender::mark(int markLength) +{ + switch (_pin) + { +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +// Arduino Mega + case 9: + (TCCR2A |= _BV(COM2B1)); // Enable pin 3 PWM output + break; + case 11: + (TCCR1A |= _BV(COM1A1)); // Enable pin 9 PWM output + break; + case 12: + (TCCR1A |= _BV(COM1B1)); // Enable pin 10 PWM output + break; + case 10: + (TCCR2A |= _BV(COM2A1)); // Enable pin 11 PWM output + break; + case 44: + (TCCR5A |= _BV(COM5C1)); // Enable pin 44 PWM output on Arduino Mega + break; + case 45: + (TCCR5A |= _BV(COM5B1)); // Enable pin 45 PWM output on Arduino Mega + break; + case 46: + (TCCR5A |= _BV(COM5A1)); // Enable pin 46 PWM output on Arduino Mega + break; +#else +// Arduino Duemilanove etc + case 3: + (TCCR2A |= _BV(COM2B1)); // Enable pin 3 PWM output + break; + case 9: + (TCCR1A |= _BV(COM1A1)); // Enable pin 9 PWM output + break; + case 10: + (TCCR1A |= _BV(COM1B1)); // Enable pin 10 PWM output + break; + case 11: + (TCCR2A |= _BV(COM2A1)); // Enable pin 11 PWM output + break; +#endif + } + + delayMicroseconds(markLength); +} + +// Send an IR 'space' symbol, i.e. transmitter OFF +void IRSender::space(int spaceLength) +{ + switch (_pin) + { +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +// Arduino Mega + case 9: + (TCCR2A &= ~(_BV(COM2B1))); // Disable pin 3 PWM output + break; + case 11: + (TCCR1A &= ~(_BV(COM1A1))); // Disable pin 9 PWM output + break; + case 12: + (TCCR1A &= ~(_BV(COM1B1))); // Disable pin 10 PWM output + break; + case 10: + (TCCR2A &= ~(_BV(COM2A1))); // Disable pin 11 PWM output + break; + case 44: + (TCCR5A &= ~(_BV(COM5C1))); // Disable pin 44 PWM output on Arduino Mega + case 45: + (TCCR5A &= ~(_BV(COM5B1))); // Disable pin 45 PWM output on Arduino Mega + case 46: + (TCCR5A &= ~(_BV(COM5A1))); // Disable pin 46 PWM output on Arduino Mega +#else +// Arduino Duemilanove etc + case 3: + (TCCR2A &= ~(_BV(COM2B1))); // Disable pin 3 PWM output + break; + case 9: + (TCCR1A &= ~(_BV(COM1A1))); // Disable pin 9 PWM output + break; + case 10: + (TCCR1A &= ~(_BV(COM1B1))); // Disable pin 10 PWM output + break; + case 11: + (TCCR2A &= ~(_BV(COM2A1))); // Disable pin 11 PWM output + break; +#endif +} + + // Mitsubishi heatpump uses > 16383us spaces, and delayMicroseconds only works up to 2^14 - 1 us + // Use the less accurate milliseconds delay for longer delays + + if (spaceLength < 16383) { + delayMicroseconds(spaceLength); + } else { + delay(spaceLength/1000); + } +} diff --git a/libraries/HeatpumpIR/IRSender.h b/libraries/HeatpumpIR/IRSender.h new file mode 100644 index 0000000..b0028ac --- /dev/null +++ b/libraries/HeatpumpIR/IRSender.h @@ -0,0 +1,23 @@ +/* + Class to send IR signals using the Arduino PWM +*/ +#ifndef IRSender_h +#define IRSender_h + +#include + +class IRSender +{ + public: + IRSender(byte pin); + void setFrequency(int frequency); + void sendIRByte(byte sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength); + byte bitReverse(byte x); + void space(int spaceLength); + void mark(int markLength); + + private: + byte _pin; +}; + +#endif diff --git a/libraries/HeatpumpIR/LICENSE b/libraries/HeatpumpIR/LICENSE new file mode 100644 index 0000000..d7f1051 --- /dev/null +++ b/libraries/HeatpumpIR/LICENSE @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/libraries/HeatpumpIR/MideaHeatpumpIR.cpp b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp new file mode 100644 index 0000000..faec8ed --- /dev/null +++ b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp @@ -0,0 +1,147 @@ +#include + +MideaHeatpumpIR::MideaHeatpumpIR() : HeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "midea"; // The basic and inverter Midea's are the same, i.e. at least Pro Plus 10FP, Pro Plus 13Fp, Pro Plus 9 Inverter and Pro Plus 12 Inverter should work + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"midea\",\"dn\":\"Ultimate Pro Plus\",\"mT\":16,\"xT\":30,\"fs\":4,\"maint\":[10]}"; + + _model = model; + _info = info; +} + + +void MideaHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + byte operatingMode = MIDEA_AIRCON1_MODE_HEAT; + byte fanSpeed = MIDEA_AIRCON1_FAN_AUTO; + byte temperature = 23; + + switch (powerModeCmd) + { + case 0: + // OFF is a special case + operatingMode = MIDEA_AIRCON1_MODE_OFF; + sendMidea(IR, operatingMode, fanSpeed, temperature); + return; + } + + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = MIDEA_AIRCON1_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode = MIDEA_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = MIDEA_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = MIDEA_AIRCON1_MODE_DRY; + break; + case MODE_FAN: + operatingMode = MIDEA_AIRCON1_MODE_FAN; + break; + case MODE_MAINT: + // Maintenance mode ('FP' on the remote) is a special mode on Midea + // Also, this is a switch between 'normal' operation and 'maintenance' operation, + // i.e. if already running on maintenance, the heatpump will go back to normal operation + operatingMode = MIDEA_AIRCON1_MODE_FP; + break; + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = MIDEA_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = MIDEA_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = MIDEA_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = MIDEA_AIRCON1_FAN3; + break; + } + + if ( temperatureCmd > 16 && temperatureCmd < 31) + { + temperature = temperatureCmd; + } + + sendMidea(IR, operatingMode, fanSpeed, temperature); +} + +// Send the Midea code +void MideaHeatpumpIR::sendMidea(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature) +{ + byte sendBuffer[3] = { 0x4D, 0x00, 0x00 }; // First byte is always 0x4D + + static const prog_uint8_t temperatures[] PROGMEM = {0, 8, 12, 4, 6, 14, 10, 2, 3, 11, 9, 1, 5, 13 }; + + static const prog_uint8_t OffMsg[] PROGMEM = {0x4D, 0xDE, 0x07 }; + static const prog_uint8_t FPMsg[] PROGMEM = {0xAD, 0xAF, 0xB5 }; + + if (operatingMode == MIDEA_AIRCON1_MODE_OFF) + { + memcpy_P(sendBuffer, OffMsg, sizeof(sendBuffer)); + } + else if (operatingMode == MIDEA_AIRCON1_MODE_FP) + { + memcpy_P(sendBuffer, FPMsg, sizeof(sendBuffer)); + } + else + { + sendBuffer[1] = ~fanSpeed; + + if ( operatingMode == MIDEA_AIRCON1_MODE_FAN ) + { + sendBuffer[2] = MIDEA_AIRCON1_MODE_DRY | 0x07; + } + else + { + sendBuffer[2] = operatingMode | pgm_read_byte(&(temperatures[(temperature-17)])); + } + } + + // Send the code + sendMidearaw(IR, sendBuffer); +} + +// Send the Midea raw code +void MideaHeatpumpIR::sendMidearaw(IRSender& IR, byte sendBuffer[]) +{ + // 40 kHz PWM frequency + IR.setFrequency(40); + + // Header + IR.mark(MIDEA_AIRCON1_HDR_MARK); + IR.space(MIDEA_AIRCON1_HDR_SPACE); + + // Six bytes, every second byte is a bitwise not of the previous byte + for (int i=0; i<3; i++) { + IR.sendIRByte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRByte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + } + + // Pause + IR.mark(MIDEA_AIRCON1_BIT_MARK); + IR.space(MIDEA_AIRCON1_MSG_SPACE); + + // Header, two last bytes repeated + IR.mark(MIDEA_AIRCON1_HDR_MARK); + IR.space(MIDEA_AIRCON1_HDR_SPACE); + + // Six bytes, every second byte is a bitwise not of the previous byte + for (int i=0; i<3; i++) { + IR.sendIRByte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRByte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + } + + // End mark + IR.mark(MIDEA_AIRCON1_BIT_MARK); + IR.space(0); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/MideaHeatpumpIR.h b/libraries/HeatpumpIR/MideaHeatpumpIR.h new file mode 100644 index 0000000..d3490fe --- /dev/null +++ b/libraries/HeatpumpIR/MideaHeatpumpIR.h @@ -0,0 +1,47 @@ +/* + Midea MSR1-12HRN1-QC2 + MOA1-12HN1-QC2 heatpump control (remote control P/N RG51M1/E) + This heatpump is sold as 'Ultimate Pro Plus 13FP' in Finland, by www.ultimatemarket.com + + Midea MSR1U-12HRDN1-QRC4W + MOB-12HFN1-QRC4W (remote control P/N RG51I20/BGE) + This heatpump is sold as 'Ultimate 12 Pro Plus Inverter' in Finland, by www.ultimatemarket.com +*/ +#ifndef MideaHeatpumpIR_h +#define MideaHeatpumpIR_h + +#include + +// Midea timing constants +#define MIDEA_AIRCON1_HDR_MARK 4420 +#define MIDEA_AIRCON1_HDR_SPACE 4300 +#define MIDEA_AIRCON1_BIT_MARK 620 +#define MIDEA_AIRCON1_ONE_SPACE 1560 +#define MIDEA_AIRCON1_ZERO_SPACE 480 +#define MIDEA_AIRCON1_MSG_SPACE 5100 + +// MIDEA codes +#define MIDEA_AIRCON1_MODE_AUTO 0x10 // Operating mode +#define MIDEA_AIRCON1_MODE_HEAT 0x30 +#define MIDEA_AIRCON1_MODE_COOL 0x00 +#define MIDEA_AIRCON1_MODE_DRY 0x20 +#define MIDEA_AIRCON1_MODE_FAN 0x60 +#define MIDEA_AIRCON1_MODE_FP 0x70 // Not a real mode... +#define MIDEA_AIRCON1_MODE_OFF 0xFE // Power OFF - not real codes, but we need something... +#define MIDEA_AIRCON1_MODE_ON 0xFF // Power ON +#define MIDEA_AIRCON1_FAN_AUTO 0x02 // Fan speed +#define MIDEA_AIRCON1_FAN1 0x06 +#define MIDEA_AIRCON1_FAN2 0x05 +#define MIDEA_AIRCON1_FAN3 0x03 + + +class MideaHeatpumpIR : public HeatpumpIR +{ + public: + MideaHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendMidea(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature); + void sendMidearaw(IRSender& IR, byte sendBuffer[]); +}; + +#endif diff --git a/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp b/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp new file mode 100644 index 0000000..fb0cbb4 --- /dev/null +++ b/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp @@ -0,0 +1,214 @@ +#include + +// These are protected methods, i.e. generic Mitsubishi instances cannot be created directly +MitsubishiHeatpumpIR::MitsubishiHeatpumpIR() : HeatpumpIR() +{ +} + + +// The different models just set the model accordingly + +MitsubishiFDHeatpumpIR::MitsubishiFDHeatpumpIR() : MitsubishiHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "mitsubishi_fd"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"mitsubishi_fd\",\"dn\":\"Mitsubishi FD\",\"mT\":16,\"xT\":31,\"fs\":5}"; + + _model = model; + _info = info; + + _mitsubishiModel = MITSUBISHI_FD; +} + +MitsubishiFEHeatpumpIR::MitsubishiFEHeatpumpIR() : MitsubishiHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "mitsubishi_fe"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"mitsubishi_fe\",\"dn\":\"Mitsubishi FE\",\"mT\":16,\"xT\":31,\"fs\":5,\"maint\":[10]}"; + + _model = model; + _info = info; + + _mitsubishiModel = MITSUBISHI_FE; +} + + +void MitsubishiHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + + byte powerMode = MITSUBISHI_AIRCON1_MODE_ON; + byte operatingMode = MITSUBISHI_AIRCON1_MODE_HEAT; + byte fanSpeed = MITSUBISHI_AIRCON1_FAN_AUTO; + byte temperature = 23; + byte swingV = MITSUBISHI_AIRCON1_VS_AUTO; + byte swingH = MITSUBISHI_AIRCON1_HS_SWING; + + if (powerModeCmd == 0) + { + powerMode = MITSUBISHI_AIRCON1_MODE_OFF; + } + + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = MITSUBISHI_AIRCON1_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode = MITSUBISHI_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = MITSUBISHI_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = MITSUBISHI_AIRCON1_MODE_DRY; + break; + case MODE_FAN: + if (_mitsubishiModel == MITSUBISHI_FE) { + operatingMode = MITSUBISHI_AIRCON1_MODE_FAN; + temperatureCmd = 24; + } else { + operatingMode = MITSUBISHI_AIRCON1_MODE_COOL; + // Temperature needs to be set to 31 degrees for 'simulated' FAN mode + temperatureCmd = 31; + } + break; + case MODE_MAINT: // Maintenance mode is just the heat mode at +10, FAN5 + if (_mitsubishiModel == MITSUBISHI_FE) { + operatingMode |= MITSUBISHI_AIRCON1_MODE_HEAT; + temperature = 10; // Default to +10 degrees + fanSpeedCmd = FAN_AUTO; + } + break; + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = MITSUBISHI_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = MITSUBISHI_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = MITSUBISHI_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = MITSUBISHI_AIRCON1_FAN3; + break; + case FAN_4: + fanSpeed = MITSUBISHI_AIRCON1_FAN4; + break; + } + + if ( temperatureCmd > 16 && temperatureCmd < 32) + { + temperature = temperatureCmd; + } + + switch (swingVCmd) + { + case VDIR_AUTO: + swingV = MITSUBISHI_AIRCON1_VS_AUTO; + break; + case VDIR_SWING: + swingV = MITSUBISHI_AIRCON1_VS_SWING; + break; + case VDIR_UP: + swingV = MITSUBISHI_AIRCON1_VS_UP; + break; + case VDIR_MUP: + swingV = MITSUBISHI_AIRCON1_VS_MUP; + break; + case VDIR_MIDDLE: + swingV = MITSUBISHI_AIRCON1_VS_MIDDLE; + break; + case VDIR_MDOWN: + swingV = MITSUBISHI_AIRCON1_VS_MDOWN; + break; + case VDIR_DOWN: + swingV = MITSUBISHI_AIRCON1_VS_DOWN; + break; + } + + switch (swingHCmd) + { + case HDIR_AUTO: + case HDIR_SWING: + swingH = MITSUBISHI_AIRCON1_HS_SWING; + break; + case HDIR_MIDDLE: + swingH = MITSUBISHI_AIRCON1_HS_MIDDLE; + break; + case HDIR_LEFT: + swingH = MITSUBISHI_AIRCON1_HS_LEFT; + break; + case HDIR_MLEFT: + swingH = MITSUBISHI_AIRCON1_HS_MLEFT; + break; + case HDIR_RIGHT: + swingH = MITSUBISHI_AIRCON1_HS_RIGHT; + break; + case HDIR_MRIGHT: + swingH = MITSUBISHI_AIRCON1_HS_MRIGHT; + break; + } + + sendMitsubishi(IR, powerMode, operatingMode, fanSpeed, temperature, swingV, swingH); +} + +void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH) +{ + byte MitsubishiTemplate[] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x48, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00 }; + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + + byte checksum = 0x00; + + // Set the operatingmode on the template message + MitsubishiTemplate[5] = powerMode; + MitsubishiTemplate[6] = operatingMode; + + // Set the temperature on the template message + if (temperature == 10) { + MitsubishiTemplate[7] = 0x00; // Maintenance mode + MitsubishiTemplate[15] = 0x20; // This seems to be set to 0x20 on maintenance mode + } else { + MitsubishiTemplate[7] = temperature - 16; + } + + // Set the horizontal air direction on the template message + MitsubishiTemplate[8] = swingH; + + // Set the fan speed and vertical air direction on the template message + MitsubishiTemplate[9] = fanSpeed | swingV; + + // Calculate the checksum + for (int i=0; i<17; i++) { + checksum += MitsubishiTemplate[i]; + } + + MitsubishiTemplate[17] = checksum; + + // 40 kHz PWM frequency + IR.setFrequency(40); + + // The Mitsubishi data is repeated twice + for (int j=0; j<2; j++) { + // Header + IR.mark(MITSUBISHI_AIRCON1_HDR_MARK); + IR.space(MITSUBISHI_AIRCON1_HDR_SPACE); + + // Data + for (int i=0; i + + +// Mitsubishi MSZ FD-25 timing constants (remote control P/N KM09D 0052376) +#define MITSUBISHI_AIRCON1_HDR_MARK 3500 +#define MITSUBISHI_AIRCON1_HDR_SPACE 1700 +#define MITSUBISHI_AIRCON1_BIT_MARK 430 +#define MITSUBISHI_AIRCON1_ONE_SPACE 1250 +#define MITSUBISHI_AIRCON1_ZERO_SPACE 390 +#define MITSUBISHI_AIRCON1_MSG_SPACE 17500 + +// Mitsubishi codes +#define MITSUBISHI_AIRCON1_MODE_AUTO 0x60 // Operating mode +#define MITSUBISHI_AIRCON1_MODE_HEAT 0x48 +#define MITSUBISHI_AIRCON1_MODE_COOL 0x58 +#define MITSUBISHI_AIRCON1_MODE_DRY 0x50 +#define MITSUBISHI_AIRCON1_MODE_FAN 0x38 // EF 'FAN' mode +#define MITSUBISHI_AIRCON1_MODE_OFF 0x00 // Power OFF +#define MITSUBISHI_AIRCON1_MODE_ON 0x20 // Power ON +#define MITSUBISHI_AIRCON1_FAN_AUTO 0x00 // Fan speed - mixed with vertical swing... +#define MITSUBISHI_AIRCON1_FAN1 0x01 +#define MITSUBISHI_AIRCON1_FAN2 0x02 +#define MITSUBISHI_AIRCON1_FAN3 0x03 +#define MITSUBISHI_AIRCON1_FAN4 0x04 + +#define MITSUBISHI_AIRCON1_VS_SWING 0x78 // Vertical swing +#define MITSUBISHI_AIRCON1_VS_AUTO 0x40 // Automatic setting +#define MITSUBISHI_AIRCON1_VS_UP 0x48 +#define MITSUBISHI_AIRCON1_VS_MUP 0x50 +#define MITSUBISHI_AIRCON1_VS_MIDDLE 0x58 +#define MITSUBISHI_AIRCON1_VS_MDOWN 0x60 +#define MITSUBISHI_AIRCON1_VS_DOWN 0x68 + +#define MITSUBISHI_AIRCON1_VS_AUTO2 0x80 // ??? +#define MITSUBISHI_AIRCON1_VS_AUTO3 0xB8 // ??? + +#define MITSUBISHI_AIRCON1_HS_SWING 0xC0 // Horizontal swing +#define MITSUBISHI_AIRCON1_HS_MIDDLE 0x30 +#define MITSUBISHI_AIRCON1_HS_LEFT 0x10 +#define MITSUBISHI_AIRCON1_HS_MLEFT 0x20 +#define MITSUBISHI_AIRCON1_HS_MRIGHT 0x40 +#define MITSUBISHI_AIRCON1_HS_RIGHT 0x50 + +// Mitsubishi model codes +#define MITSUBISHI_FD 0 +#define MITSUBISHI_FE 1 + +class MitsubishiHeatpumpIR : public HeatpumpIR +{ + protected: // Cannot create generic Mitsubishi heatpump instances + MitsubishiHeatpumpIR(); + byte _mitsubishiModel; // Tells whether this is FD or EF (or other supported model...) + + public: + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendMitsubishi(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingVCmd, byte swingHCmd); +}; + +class MitsubishiFDHeatpumpIR : public MitsubishiHeatpumpIR +{ + public: + MitsubishiFDHeatpumpIR(); +}; + +class MitsubishiFEHeatpumpIR : public MitsubishiHeatpumpIR +{ + public: + MitsubishiFEHeatpumpIR(); +}; + +#endif diff --git a/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.cpp b/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.cpp new file mode 100644 index 0000000..946cc85 --- /dev/null +++ b/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.cpp @@ -0,0 +1,262 @@ +#include + + +PanasonicCKPHeatpumpIR::PanasonicCKPHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "panasonic_ckp"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"panasonic_ckp\",\"dn\":\"Panasonic CKP\",\"mT\":16,\"xT\":30,\"fs\":6}"; + + _model = model; + _info = info; +} + + +// Panasonic CKP numeric values to command bytes +void PanasonicCKPHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + + byte powerMode = false; + byte operatingMode = PANASONIC_AIRCON1_MODE_KEEP; + byte fanSpeed = PANASONIC_AIRCON1_FAN_AUTO; + byte temperature = 23; + byte swingV = PANASONIC_AIRCON1_VS_UP; + byte swingH = PANASONIC_AIRCON1_HS_SWING; + + switch (powerModeCmd) + { + case POWER_ON: + powerMode = true; + break; + } + + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode |= PANASONIC_AIRCON1_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode |= PANASONIC_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode |= PANASONIC_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode |= PANASONIC_AIRCON1_MODE_DRY; + break; + case MODE_FAN: + operatingMode |= PANASONIC_AIRCON1_MODE_FAN; + temperatureCmd = 27; // Temperature is always 27 in FAN mode + break; + default: + operatingMode |= PANASONIC_AIRCON1_MODE_HEAT; + break; + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = PANASONIC_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = PANASONIC_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = PANASONIC_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = PANASONIC_AIRCON1_FAN3; + break; + case FAN_4: + fanSpeed = PANASONIC_AIRCON1_FAN4; + break; + case FAN_5: + fanSpeed = PANASONIC_AIRCON1_FAN5; + break; + } + + if ( temperatureCmd > 15 && temperatureCmd < 31) + { + temperature = temperatureCmd; + } + + switch (swingVCmd) + { + case VDIR_SWING: + swingV = PANASONIC_AIRCON1_VS_SWING; + break; + case VDIR_UP: + swingV = PANASONIC_AIRCON1_VS_UP; + break; + case VDIR_MUP: + swingV = PANASONIC_AIRCON1_VS_MUP; + break; + case VDIR_MIDDLE: + swingV = PANASONIC_AIRCON1_VS_MIDDLE; + break; + case VDIR_MDOWN: + swingV = PANASONIC_AIRCON1_VS_MDOWN; + break; + case VDIR_DOWN: + swingV = PANASONIC_AIRCON1_VS_DOWN; + break; + } + + switch (swingHCmd) + { + case HDIR_SWING: + swingH = PANASONIC_AIRCON1_HS_SWING; + break; + case HDIR_AUTO: // Well, just set it to manual + swingH = PANASONIC_AIRCON1_HS_MANUAL; + break; + } + + sendPanasonicCKP(IR, operatingMode, fanSpeed, temperature, swingV, swingH); + delay(1000); // Sleep 1 second between the messages + + // This will change the power state in one minute from now + sendPanasonicCKPOnOffTimerCancel(IR, powerMode, false); +/* + // Send the 'timer cancel' signal 2 minutes later + if (panasonicCancelTimer != 0) + { + timer.stop(panasonicCancelTimer); + panasonicCancelTimer = 0; + } + + // Note that the argument to 'timer.after' has to be explicitly cast into 'long' + panasonicCancelTimer = timer.after(2L*60L*1000L, sendPanasonicCKPCancelTimer); + + Serial.print(F("'Timer cancel' timer ID: ")); + Serial.println(panasonicCancelTimer); +*/ +} + +// Send the Panasonic CKP code +void PanasonicCKPHeatpumpIR::sendPanasonicCKP(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH) +{ + byte sendBuffer[4]; + + // Fan speed & temperature, temperature needs to be 27 in FAN mode + if (operatingMode == PANASONIC_AIRCON1_MODE_FAN || operatingMode == (PANASONIC_AIRCON1_MODE_FAN | PANASONIC_AIRCON1_MODE_KEEP )) + { + temperature = 27; + } + + sendBuffer[0] = fanSpeed | (temperature - 15); + + // Power toggle & operation mode + sendBuffer[1] = operatingMode; + + // Swings + sendBuffer[2] = swingV | swingH; + + // Always 0x36 + sendBuffer[3] = 0x36; + + // Send the code + sendPanasonicCKPraw(IR, sendBuffer); +} + +// Send the Panasonic CKP raw code +void PanasonicCKPHeatpumpIR::sendPanasonicCKPraw(IRSender& IR, byte sendBuffer[]) +{ + // 40 kHz PWM frequency + IR.setFrequency(40); + + // Header, two first bytes repeated + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + + for (int i=0; i<2; i++) { + IR.sendIRByte(sendBuffer[0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + } + + // Pause + + IR.mark(PANASONIC_AIRCON1_BIT_MARK); + IR.space(PANASONIC_AIRCON1_MSG_SPACE); + + // Header, two last bytes repeated + + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + + for (int i=0; i<2; i++) { + IR.sendIRByte(sendBuffer[2], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[2], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[3], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[3], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + } + + IR.mark(PANASONIC_AIRCON1_BIT_MARK); + IR.space(0); +} + +// Send the Panasonic CKP On/Off code +// +// CKP does not have discrete ON/OFF commands, but this can be emulated by using the timer +// The side-effects of using the timer are: +// * ONE minute delay before the power state changes +// * the 'TIMER' led (orange) is lit +// * a timer event is scheduled to cancel the timer after TWO minutes (the 'TIMER' led turns off +void PanasonicCKPHeatpumpIR::sendPanasonicCKPOnOffTimerCancel(IRSender& IR, boolean powerState, boolean cancelTimer) +{ + static const prog_uint8_t ON_msg[] PROGMEM = { 0x7F, 0x38, 0xBF, 0x38, 0x10, 0x3D, 0x80, 0x3D, 0x09, 0x34, 0x80, 0x34 }; // ON at 00:10, time now 00:09, no OFF timing + static const prog_uint8_t OFF_msg[] PROGMEM = { 0x10, 0x38, 0x80, 0x38, 0x7F, 0x3D, 0xBF, 0x3D, 0x09, 0x34, 0x80, 0x34 }; // OFF at 00:10, time now 00:09, no ON timing + static const prog_uint8_t CANCEL_msg[] PROGMEM = { 0x7F, 0x38, 0xBF, 0x38, 0x7F, 0x3D, 0xBF, 0x3D, 0x17, 0x34, 0x80, 0x34 }; // Timer CANCEL + + // Save some SRAM by only having one copy of the template on the SRAM + byte sendBuffer[sizeof(ON_msg)]; + + if ( cancelTimer == true ) { + memcpy_P(sendBuffer, CANCEL_msg, sizeof(ON_msg)); + } else if ( powerState == true ) { + memcpy_P(sendBuffer, ON_msg, sizeof(ON_msg)); + } else { + memcpy_P(sendBuffer, OFF_msg, sizeof(ON_msg)); + } + + // 40 kHz PWM frequency + IR.setFrequency(40); + + for (int i=0; i<6; i++) { + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + + IR.sendIRByte(sendBuffer[i*2 + 0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[i*2 + 0], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[i*2 + 1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + IR.sendIRByte(sendBuffer[i*2 + 1], PANASONIC_AIRCON1_BIT_MARK, PANASONIC_AIRCON1_ZERO_SPACE, PANASONIC_AIRCON1_ONE_SPACE); + + IR.mark(PANASONIC_AIRCON1_HDR_MARK); + IR.space(PANASONIC_AIRCON1_HDR_SPACE); + + if ( i < 5 ) { + IR.mark(PANASONIC_AIRCON1_BIT_MARK); + IR.space(PANASONIC_AIRCON1_MSG_SPACE); + } + } + + IR.mark(PANASONIC_AIRCON1_BIT_MARK); + IR.space(0); +} + +// Send the Panasonic CKP timer cancel +void PanasonicCKPHeatpumpIR::sendPanasonicCKPCancelTimer(IRSender& IR) +{ + Serial.println(F("Sending Panasonic CKP timer cancel")); + + sendPanasonicCKPOnOffTimerCancel(IR, false, true); +} + diff --git a/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h b/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h new file mode 100644 index 0000000..2d171c6 --- /dev/null +++ b/libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h @@ -0,0 +1,55 @@ +/* + Panasonic CKP heatpump control (remote control P/N A75C2295) +*/ +#ifndef PanasonicCKPHeatpumpIR_h +#define PanasonicCKPHeatpumpIR_h + +#include + + +// Panasonic CKP timing constants (remote control P/N A75C2295) +#define PANASONIC_AIRCON1_HDR_MARK 3400 +#define PANASONIC_AIRCON1_HDR_SPACE 3500 +#define PANASONIC_AIRCON1_BIT_MARK 800 +#define PANASONIC_AIRCON1_ONE_SPACE 2700 +#define PANASONIC_AIRCON1_ZERO_SPACE 1000 +#define PANASONIC_AIRCON1_MSG_SPACE 14000 + +// Panasonic CKP codes +#define PANASONIC_AIRCON1_MODE_AUTO 0x06 // Operating mode +#define PANASONIC_AIRCON1_MODE_HEAT 0x04 +#define PANASONIC_AIRCON1_MODE_COOL 0x02 +#define PANASONIC_AIRCON1_MODE_DRY 0x03 +#define PANASONIC_AIRCON1_MODE_FAN 0x01 +#define PANASONIC_AIRCON1_MODE_ONOFF 0x00 // Toggle ON/OFF +#define PANASONIC_AIRCON1_MODE_KEEP 0x08 // Do not toggle ON/OFF +#define PANASONIC_AIRCON1_FAN_AUTO 0xF0 // Fan speed +#define PANASONIC_AIRCON1_FAN1 0x20 +#define PANASONIC_AIRCON1_FAN2 0x30 +#define PANASONIC_AIRCON1_FAN3 0x40 +#define PANASONIC_AIRCON1_FAN4 0x50 +#define PANASONIC_AIRCON1_FAN5 0x60 +#define PANASONIC_AIRCON1_VS_SWING 0xF0 // Vertical swing +#define PANASONIC_AIRCON1_VS_UP 0x90 +#define PANASONIC_AIRCON1_VS_MUP 0xA0 +#define PANASONIC_AIRCON1_VS_MIDDLE 0xB0 +#define PANASONIC_AIRCON1_VS_MDOWN 0xC0 +#define PANASONIC_AIRCON1_VS_DOWN 0xD0 +#define PANASONIC_AIRCON1_HS_SWING 0x08 // Horizontal swing +#define PANASONIC_AIRCON1_HS_MANUAL 0x00 + + +class PanasonicCKPHeatpumpIR : public HeatpumpIR +{ + public: + PanasonicCKPHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + void sendPanasonicCKPCancelTimer(IRSender& IR); + + private: + void sendPanasonicCKP(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH); + void sendPanasonicCKPraw(IRSender& IR, byte sendBuffer[]); + void sendPanasonicCKPOnOffTimerCancel(IRSender& IR, boolean powerState, boolean cancelTimer); +}; + +#endif diff --git a/libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp b/libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp new file mode 100644 index 0000000..1125a77 --- /dev/null +++ b/libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp @@ -0,0 +1,237 @@ +#include + +// This is a protected method, i.e. generic Panasonic instances cannot be created +PanasonicHeatpumpIR::PanasonicHeatpumpIR() : HeatpumpIR() +{ +} + + +// The different models just set the model accordingly +PanasonicDKEHeatpumpIR::PanasonicDKEHeatpumpIR() : PanasonicHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "panasonic_dke"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"panasonic_dke\",\"dn\":\"Panasonic DKE\",\"mT\":16,\"xT\":30,\"fs\":6}"; + + _model = model; + _info = info; + + _panasonicModel = PANASONIC_DKE; +} + +PanasonicJKEHeatpumpIR::PanasonicJKEHeatpumpIR() : PanasonicHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "panasonic_jke"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"panasonic_jke\",\"dn\":\"Panasonic JKE\",\"mT\":16,\"xT\":30,\"fs\":6}"; + + _model = model; + _info = info; + + _panasonicModel = PANASONIC_JKE; +} + +PanasonicNKEHeatpumpIR::PanasonicNKEHeatpumpIR() : PanasonicHeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "panasonic_nke"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"panasonic_nke\",\"dn\":\"Panasonic NKE\",\"mT\":16,\"xT\":30,\"fs\":6,\"maint\":[8,10]}"; + + _model = model; + _info = info; + + _panasonicModel = PANASONIC_NKE; +} + + +// Panasonic DKE/NKE/JKE numeric values to command bytes +void PanasonicHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + + byte operatingMode = PANASONIC_AIRCON2_TIMER_CNL; + byte fanSpeed = PANASONIC_AIRCON2_FAN_AUTO; + byte temperature = 23; + byte swingV = PANASONIC_AIRCON2_VS_UP; + byte swingH = PANASONIC_AIRCON2_HS_AUTO; + + switch (powerModeCmd) + { + case POWER_ON: + operatingMode |= PANASONIC_AIRCON2_MODE_ON; + break; + } + + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode |= PANASONIC_AIRCON2_MODE_AUTO; + break; + case MODE_HEAT: + operatingMode |= PANASONIC_AIRCON2_MODE_HEAT; + break; + case MODE_COOL: + operatingMode |= PANASONIC_AIRCON2_MODE_COOL; + break; + case MODE_DRY: + operatingMode |= PANASONIC_AIRCON2_MODE_DRY; + break; + case MODE_FAN: + operatingMode |= PANASONIC_AIRCON2_MODE_FAN; + temperatureCmd = 27; // Temperature is always 27 in FAN mode + break; + case MODE_MAINT: // Maintenance mode is just the heat mode at +8 or +10, FAN5 + operatingMode |= PANASONIC_AIRCON2_MODE_HEAT; + temperature = 10; // Default to +10 degrees + fanSpeedCmd = FAN_5; + break; + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = PANASONIC_AIRCON2_FAN_AUTO; + break; + case FAN_1: + fanSpeed = PANASONIC_AIRCON2_FAN1; + break; + case FAN_2: + fanSpeed = PANASONIC_AIRCON2_FAN2; + break; + case FAN_3: + fanSpeed = PANASONIC_AIRCON2_FAN3; + break; + case FAN_4: + fanSpeed = PANASONIC_AIRCON2_FAN4; + break; + case FAN_5: + fanSpeed = PANASONIC_AIRCON2_FAN5; + break; + } + + if ( temperatureCmd > 15 && temperatureCmd < 31) + { + temperature = temperatureCmd; + } + + switch (swingVCmd) + { + case VDIR_AUTO: + case VDIR_SWING: + swingV = PANASONIC_AIRCON2_VS_AUTO; + break; + case VDIR_UP: + swingV = PANASONIC_AIRCON2_VS_UP; + break; + case VDIR_MUP: + swingV = PANASONIC_AIRCON2_VS_MUP; + break; + case VDIR_MIDDLE: + swingV = PANASONIC_AIRCON2_VS_MIDDLE; + break; + case VDIR_MDOWN: + swingV = PANASONIC_AIRCON2_VS_MDOWN; + break; + case VDIR_DOWN: + swingV = PANASONIC_AIRCON2_VS_DOWN; + break; + } + + switch (swingHCmd) + { + case HDIR_AUTO: + case HDIR_SWING: + swingH = PANASONIC_AIRCON2_HS_AUTO; + break; + case HDIR_MIDDLE: + swingH = PANASONIC_AIRCON2_HS_MIDDLE; + break; + case HDIR_LEFT: + swingH = PANASONIC_AIRCON2_HS_LEFT; + break; + case HDIR_MLEFT: + swingH = PANASONIC_AIRCON2_HS_MLEFT; + break; + case HDIR_RIGHT: + swingH = PANASONIC_AIRCON2_HS_RIGHT; + break; + case HDIR_MRIGHT: + swingH = PANASONIC_AIRCON2_HS_MRIGHT; + break; + } + + // NKE has +8 / + 10 maintenance heating, which also means MAX fanspeed + if ( _panasonicModel == PANASONIC_NKE ) + { + if ( temperatureCmd == 8 || temperatureCmd == 10 ) + { + temperature = temperatureCmd; + fanSpeed = PANASONIC_AIRCON2_FAN5; + } + } + + sendPanasonic(IR, operatingMode, fanSpeed, temperature, swingV, swingH); +} + +// Send the Panasonic DKE/JKE/NKE code +void PanasonicHeatpumpIR::sendPanasonic(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH) +{ + // Only bytes 13, 14, 16, 17 and 26 are modified, DKE and JKE seem to share the same template? + static const prog_uint8_t panasonicProgmemTemplate[][27] PROGMEM = { + // DKE, model 0 + { 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0E, 0xE0, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00 }, + // JKE, model 1 + { 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00 }, + // NKE, model 2 + { 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x06, 0x02, 0x20, 0xE0, 0x04, 0x00, 0x00, 0x00, 0x80, 0x00, 0x06, 0x00, 0x0E, 0xE0, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00 } + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 + }; + + // Save some SRAM by only having one copy of the template on the SRAM + byte panasonicTemplate[27]; + memcpy_P(panasonicTemplate, panasonicProgmemTemplate[_panasonicModel], sizeof(panasonicTemplate)); + + panasonicTemplate[13] = operatingMode; + panasonicTemplate[14] = temperature << 1; + panasonicTemplate[16] = fanSpeed | swingV; + + // Only the DKE model has a setting for the horizontal air flow + if ( _panasonicModel == PANASONIC_DKE) { + panasonicTemplate[17] = swingH; + } + + // Checksum calculation + + byte checksum = 0xF4; + + for (int i=0; i<26; i++) { + checksum += panasonicTemplate[i]; + } + + panasonicTemplate[26] = checksum; + + // 40 kHz PWM frequency + IR.setFrequency(40); + + // Header + IR.mark(PANASONIC_AIRCON2_HDR_MARK); + IR.space(PANASONIC_AIRCON2_HDR_SPACE); + + // First 8 bytes + for (int i=0; i<8; i++) { + IR.sendIRByte(panasonicTemplate[i], PANASONIC_AIRCON2_BIT_MARK, PANASONIC_AIRCON2_ZERO_SPACE, PANASONIC_AIRCON2_ONE_SPACE); + } + + // Pause + IR.mark(PANASONIC_AIRCON2_BIT_MARK); + IR.space(PANASONIC_AIRCON2_MSG_SPACE); + + // Header + IR.mark(PANASONIC_AIRCON2_HDR_MARK); + IR.space(PANASONIC_AIRCON2_HDR_SPACE); + + // Last 19 bytes + for (int i=8; i<27; i++) { + IR.sendIRByte(panasonicTemplate[i], PANASONIC_AIRCON2_BIT_MARK, PANASONIC_AIRCON2_ZERO_SPACE, PANASONIC_AIRCON2_ONE_SPACE); + } + + IR.mark(PANASONIC_AIRCON2_BIT_MARK); + IR.space(0); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/PanasonicHeatpumpIR.h b/libraries/HeatpumpIR/PanasonicHeatpumpIR.h new file mode 100644 index 0000000..f7cdc8b --- /dev/null +++ b/libraries/HeatpumpIR/PanasonicHeatpumpIR.h @@ -0,0 +1,83 @@ +/* + Panasonic DKE/JKE/NKE heatpump control (DKE remote control P/N A75C2616 etc) +*/ +#ifndef PanasonicHeatpumpIR_h +#define PanasonicHeatpumpIR_h + +#include + + +// Panasonic DKE, JKE & NKE timing constants (DKE remote control P/N A75C2616) +#define PANASONIC_AIRCON2_HDR_MARK 3500 +#define PANASONIC_AIRCON2_HDR_SPACE 1800 +#define PANASONIC_AIRCON2_BIT_MARK 420 +#define PANASONIC_AIRCON2_ONE_SPACE 1350 +#define PANASONIC_AIRCON2_ZERO_SPACE 470 +#define PANASONIC_AIRCON2_MSG_SPACE 10000 + +// Panasonic DKE, JNE & NKE codes +#define PANASONIC_AIRCON2_MODE_AUTO 0x00 // Operating mode +#define PANASONIC_AIRCON2_MODE_HEAT 0x40 +#define PANASONIC_AIRCON2_MODE_COOL 0x30 +#define PANASONIC_AIRCON2_MODE_DRY 0x20 +#define PANASONIC_AIRCON2_MODE_FAN 0x60 +#define PANASONIC_AIRCON2_MODE_OFF 0x00 // Power OFF +#define PANASONIC_AIRCON2_MODE_ON 0x01 +#define PANASONIC_AIRCON2_TIMER_CNL 0x08 +#define PANASONIC_AIRCON2_FAN_AUTO 0xA0 // Fan speed +#define PANASONIC_AIRCON2_FAN1 0x30 +#define PANASONIC_AIRCON2_FAN2 0x40 +#define PANASONIC_AIRCON2_FAN3 0x50 +#define PANASONIC_AIRCON2_FAN4 0x60 +#define PANASONIC_AIRCON2_FAN5 0x70 +#define PANASONIC_AIRCON2_VS_AUTO 0x0F // Vertical swing +#define PANASONIC_AIRCON2_VS_UP 0x01 +#define PANASONIC_AIRCON2_VS_MUP 0x02 +#define PANASONIC_AIRCON2_VS_MIDDLE 0x03 +#define PANASONIC_AIRCON2_VS_MDOWN 0x04 +#define PANASONIC_AIRCON2_VS_DOWN 0x05 +#define PANASONIC_AIRCON2_HS_AUTO 0x0D // Horizontal swing +#define PANASONIC_AIRCON2_HS_MIDDLE 0x06 +#define PANASONIC_AIRCON2_HS_LEFT 0x09 +#define PANASONIC_AIRCON2_HS_MLEFT 0x0A +#define PANASONIC_AIRCON2_HS_MRIGHT 0x0B +#define PANASONIC_AIRCON2_HS_RIGHT 0x0C + +// Panasonic model codes +#define PANASONIC_DKE 0 +#define PANASONIC_JKE 1 +#define PANASONIC_NKE 2 + + +class PanasonicHeatpumpIR : public HeatpumpIR +{ + protected: // Cannot create generic Panasonic heatpump instances + PanasonicHeatpumpIR(); + byte _panasonicModel; // Tells whether this is DKE, NKE or JKE (or other supported model...) + + public: + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendPanasonic(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH); +}; + +class PanasonicDKEHeatpumpIR : public PanasonicHeatpumpIR +{ + public: + PanasonicDKEHeatpumpIR(); +}; + +class PanasonicJKEHeatpumpIR : public PanasonicHeatpumpIR +{ + public: + PanasonicJKEHeatpumpIR(); +}; + +class PanasonicNKEHeatpumpIR : public PanasonicHeatpumpIR +{ + public: + PanasonicNKEHeatpumpIR(); +}; + +#endif \ No newline at end of file diff --git a/libraries/HeatpumpIR/README.md b/libraries/HeatpumpIR/README.md new file mode 100644 index 0000000..5631c86 --- /dev/null +++ b/libraries/HeatpumpIR/README.md @@ -0,0 +1,21 @@ +arduino-heatpumpir +================== + +An Arduino library to control a Panasonic, Midea, Carrier, Fujitsu or Mitsubishi heat pump/split unit air conditioner. +Currently supports at least these models +* Panasonic E9/E12-CKP (Panasonic remote control P/N A75C2295) +* Panasonic E9/E12-DKE (Panasonic remote control P/N A75C2616) +* Panasonic E9/E12-JKE and E9/E12-NKE +* Midea MSR1-12HRN1-QC2 + MOA1-12HN1-QC2, sold as Ultimate Pro Plus Basic 13FP in Finland (Midea remote control P/N RG51M1/E) +* Carrier 42NQV035G / 38NYV035H2 (Carrier remote control P/N WH-L05SE) +* Fujitsu Nocria AWYZ14 (remote control P/N AR-PZ2) +* Mitsubishi MSZ FD-25, probably also FD-35 (remote control P/N KM09D 0052376) +* Hisense AUD (remote control Y-H1-01, Y-H1-02(E), Y-J1, Y-E4-07) probably AUC model + + +Instructions +------------ +* Download the library, and place it under your personal Arduino 'libraries' directory, under directory 'HeatpumpIR' +* See the example sketches + +![Schema](https://raw.github.com/ToniA/arduino-heatpumpir/master/arduino_irsender.png) diff --git a/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp b/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp new file mode 100644 index 0000000..274504f --- /dev/null +++ b/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp @@ -0,0 +1,172 @@ +#include + +SamsungHeatpumpIR::SamsungHeatpumpIR() : HeatpumpIR() +{ + static const char PROGMEM model[] PROGMEM = "samsung"; + static const char PROGMEM info[] PROGMEM = "{\"mdl\":\"samsung\",\"dn\":\"Samsung\",\"mT\":16,\"xT\":27,\"fs\":4}"; + + _model = model; + _info = info; +} + + +void SamsungHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +{ + // Sensible defaults for the heat pump mode + + byte powerMode = SAMSUNG_AIRCON1_MODE_ON; + byte operatingMode = SAMSUNG_AIRCON1_MODE_HEAT; + byte fanSpeed = SAMSUNG_AIRCON1_FAN_AUTO; + byte temperature = 23; + byte swingV = SAMSUNG_AIRCON1_VS_AUTO; + + if (powerModeCmd == POWER_OFF) + { + powerMode = SAMSUNG_AIRCON1_MODE_OFF; + } + else + { + switch (operatingModeCmd) + { + case MODE_AUTO: + operatingMode = SAMSUNG_AIRCON1_MODE_AUTO; + fanSpeedCmd = FAN_AUTO; // Fan speed is always 'AUTO' in AUTO mode + break; + case MODE_HEAT: + operatingMode = SAMSUNG_AIRCON1_MODE_HEAT; + break; + case MODE_COOL: + operatingMode = SAMSUNG_AIRCON1_MODE_COOL; + break; + case MODE_DRY: + operatingMode = SAMSUNG_AIRCON1_MODE_DRY; + fanSpeedCmd = FAN_AUTO; // Fan speed is always 'AUTO' in DRY mode + break; + case MODE_FAN: + operatingMode = SAMSUNG_AIRCON1_MODE_FAN; + if ( fanSpeedCmd == FAN_AUTO ) { + fanSpeedCmd = FAN_1; // Fan speed cannot be 'AUTO' in FAN mode + } + break; + } + } + + switch (fanSpeedCmd) + { + case FAN_AUTO: + fanSpeed = SAMSUNG_AIRCON1_FAN_AUTO; + break; + case FAN_1: + fanSpeed = SAMSUNG_AIRCON1_FAN1; + break; + case FAN_2: + fanSpeed = SAMSUNG_AIRCON1_FAN2; + break; + case FAN_3: + fanSpeed = SAMSUNG_AIRCON1_FAN3; + break; + } + + if ( temperatureCmd > 15 && temperatureCmd < 28) + { + temperature = temperatureCmd; + } + + switch (swingVCmd) + { + case VDIR_SWING: + swingV = SAMSUNG_AIRCON1_VS_SWING; + break; + } + + sendSamsung(IR, powerMode, operatingMode, fanSpeed, temperature, swingV); +} + +// Send the Samsung code + +void SamsungHeatpumpIR::sendSamsung(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV) +{ + byte SamsungTemplate[] = { 0x02, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, // Header part + 0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00, // Always the same data on POWER messages + 0x01, 0x00, 0xFE, 0x71, 0x00, 0x00, 0x00 }; // The actual data is in this part, on bytes 14-20 + + byte SamsungChecksum = 0; + + // Set the power mode on the template message, also add the first part checksum + SamsungTemplate[6] = powerMode; + if ( powerMode == SAMSUNG_AIRCON1_MODE_ON ) { + SamsungTemplate[1] = 0x92; + } else { + SamsungTemplate[1] = 0xB2; + } + + SamsungTemplate[20] = powerMode; + + // Set the fan speed and the operating mode on the template message + SamsungTemplate[19] = operatingMode | fanSpeed; + + // Set the temperature on the template message + SamsungTemplate[18] = (temperature - 16) << 4; + + // Set the vertical swing mode on the template message + SamsungTemplate[16] = swingV; + + // Calculate the byte 15 checksum + // Count the number of ONE bits on message bytes 15-20 + for (byte j=15; j<21; j++) { + byte SamsungByte = SamsungTemplate[j]; + for (byte i=0; i<8; i++) { + if ( (SamsungByte & 0x01) == 0x01 ) { + SamsungChecksum++; + } + SamsungByte >>= 1; + } + } + + // Transform the number of ONE bits to the actual checksum + SamsungChecksum = 32 - SamsungChecksum; + SamsungChecksum <<= 4; + SamsungChecksum += 0x02; + + SamsungTemplate[15] = SamsungChecksum; + + // 38 kHz PWM frequency + IR.setFrequency(38); + + // Header + IR.mark(SAMSUNG_AIRCON1_HDR_MARK); + IR.space(SAMSUNG_AIRCON1_HDR_SPACE); + + // Payload header part + for (int i=0; i<7; i++) { + IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + } + + // Pause + new header + IR.mark(SAMSUNG_AIRCON1_BIT_MARK); + IR.space(SAMSUNG_AIRCON1_MSG_SPACE); + + IR.mark(SAMSUNG_AIRCON1_HDR_MARK); + IR.space(SAMSUNG_AIRCON1_HDR_SPACE); + + // Payload power message part + for (int i=7; i<14; i++) { + IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + } + + // Pause + new header + IR.mark(SAMSUNG_AIRCON1_BIT_MARK); + IR.space(SAMSUNG_AIRCON1_MSG_SPACE); + + IR.mark(SAMSUNG_AIRCON1_HDR_MARK); + IR.space(SAMSUNG_AIRCON1_HDR_SPACE); + + // Payload data message part + for (int i=14; i<21; i++) { + IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + } + + // End mark + IR.mark(SAMSUNG_AIRCON1_BIT_MARK); + IR.space(0); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/SamsungHeatpumpIR.h b/libraries/HeatpumpIR/SamsungHeatpumpIR.h new file mode 100644 index 0000000..aab79fd --- /dev/null +++ b/libraries/HeatpumpIR/SamsungHeatpumpIR.h @@ -0,0 +1,42 @@ +/* + Samsung AQV12PSBN / AQV09ASA heatpump control (remote control P/N zzz) +*/ +#ifndef SamsungHeatpumpIR_h +#define SamsungHeatpumpIR_h + +#include + +// Samsung timing constants +#define SAMSUNG_AIRCON1_HDR_MARK 3000 +#define SAMSUNG_AIRCON1_HDR_SPACE 9000 +#define SAMSUNG_AIRCON1_BIT_MARK 500 +#define SAMSUNG_AIRCON1_ONE_SPACE 1500 +#define SAMSUNG_AIRCON1_ZERO_SPACE 500 +#define SAMSUNG_AIRCON1_MSG_SPACE 2000 + +// Samsung codes +#define SAMSUNG_AIRCON1_MODE_AUTO 0x00 // Operating mode +#define SAMSUNG_AIRCON1_MODE_HEAT 0x40 +#define SAMSUNG_AIRCON1_MODE_COOL 0x10 +#define SAMSUNG_AIRCON1_MODE_DRY 0x20 +#define SAMSUNG_AIRCON1_MODE_FAN 0x30 +#define SAMSUNG_AIRCON1_MODE_OFF 0xC0 // Power OFF +#define SAMSUNG_AIRCON1_MODE_ON 0xF0 // Power ON +#define SAMSUNG_AIRCON1_FAN_AUTO 0x01 // Fan speed +#define SAMSUNG_AIRCON1_FAN1 0x05 // * low +#define SAMSUNG_AIRCON1_FAN2 0x09 // * med +#define SAMSUNG_AIRCON1_FAN3 0x0B // * high +#define SAMSUNG_AIRCON1_VS_SWING 0xAE // Vertical swing +#define SAMSUNG_AIRCON1_VS_AUTO 0xFE + +class SamsungHeatpumpIR : public HeatpumpIR +{ + public: + SamsungHeatpumpIR(); + void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + + private: + void sendSamsung(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV); +}; + +#endif diff --git a/libraries/HeatpumpIR/arduino_irsender.png b/libraries/HeatpumpIR/arduino_irsender.png new file mode 100644 index 0000000000000000000000000000000000000000..7c9e1fba2c4732c7e44a76da784d5f848430937f GIT binary patch literal 2752919 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

d3Ic*adqn`M<38mH z>f%bIjn-i`E}=Nl)^HgG_|queF=H})@whQri=TP#Q}pqc-OXisbnw&Xw_isuEn835 zKlVoRc^nMx-kBEOdJR4E=~}w!sdtjDIq^$}igG2KBhu%#Me5`$r^CUpV~6(eH8beU z(Sw@#yXv^nbm||UX#Oq-f`A~%6{AU2 zajLZ0SQSg=^}#ZlUF)V@(bnMzGhy@cvJHw>;_~q$Y1@HXn!Ro-O&ZjPM)&U4s>=TQ zAg$h0Lzf*pk{(&GoS-sS=~ow?Og^uN=5O4go^tHxccx9CpPe_A*0C1nEEcF#mA_*d zHp$rIUpb%7J!&XnZomBKt0rsPQ+!J)J5D)}P8iCC1XJOp(cPbY`{2)!k#%)1$w2KCH`XI%CkwCVL=yd-FxK>Cniu z^&oGI@Fi$iMU>uR31m@ycJXJ|;d;fk zvcHjv#g+6c2nYg#K#3qwQI?=D_b?`CU`Xz=82Q`*(|@R^M@YIfqf?9~_>&iNsrBx9 z{X-i0vxi#=6e#z{_ZHKLSr5~e{fASl(Sl7o3C8zhj_6L;PdQoCb#v=$G;Cm{xr-T{W3(YuMu+0*ZLA<1WHa(V)D&K%xk1grov+NN zb9ndsc6J1TW5+1A!TKL(eS&7g>2&s(!AUHhGGc&Y50)A{KB-U%eJLwTo^My0l)p|G z&d*-^kRJVL1ywb9k8jv_&dI%}at%lFzrc;LZ zPg_<_78ZfcHHT=$u6-mzM`3Z6)&&74ARxkq6J|kp5CjB)l0;xwMV#CY<$R>SaGIAt z&t9U$%Ei#{owLM(_@;#iaR`Ugg3m@Oot2{bNWCuK1pz@o5D*0NLqJTb^21MR3j%^b zJ4T?p-V^k=BQDtL09$~rh|$aQr0)S@elo}Q=|SDA{7S`B*+l8rZ+&FG5u0nInpUaZ z!=K~U=Vq&4O?@KCNhB6)C3r!5GMt5+GX4ij@V^=k`?c#(ovHRy3&S^Kc+W26FK@gF z;kdu!U>&y|P>LoSSEbfY{~hsydv~lfrETcOiCsFemJ6B+qvU2&FPP%F%aZpy!;IhB zz5Da>147#)dv&9YY^If(90tJxhV|@9m=l;4)`r5gspeo_{b9_|^7Zf9v3ZVF91aIV zY5kzo>+&fGv_}N=UYqR^6B)B0AP5Koh7j=ZBGRb@vwoQJ$1G+M?f&NCQfvk7rYJ{Ty7a*pn_~u}ttU86 zEs~U^(&sz&((NzJrTH7o#S(^)@x9Y0(zj0;pEiTz-1Xb(hcC>bC@AeP)AL$0JMjA(7$+N0X@n6nx%o= zI?-?W{)cwVmEt?2wuSi1cfLH2-dMRwJtd!lK)XgDd5fW4Lm`6~1Ox#=z#Ia#=GJ!R z8qc$IFi}PkivN(qDqe*2~?(h3?Va7@2m^x`)!Av4jjpm})m z`U~ie8B@{<8ff=q_Ca~~<|~`S;o_VTn#%PcXn>Gt#RyFwHAoROQ1)Vjb$WHE%qVD} zoeMd9Z>|JE0)hqz(bJ&en8w;RWTX7S8IxKG8Ynk_H4+be@hn1e7Bd84Y4$BwDf5>} z&_H`5Sd;RU~>d}H_me$ z2gsW_6}i=fMewZiPGU2nINko@2TC@*hvzS)8%{o!Zs8mrcfYyN>Mkv54`yE#Y*M4r zE6X>~cP?d;*aF3++S zER%5KW=wLWQ~}^O7oS2WYJ|*e?f&%K6X~@T8)<#De#ESkhV`Rozkab|nw70h^%|hL z{p`tv-^@>6nWvt%?K95v6mzj2*80al+A+A;yw4qrQkg}0Rl4v>bzpgdI+Vp(OIG+n z)bnF}6K7M+6y-1Npyi}t1hNPohTD*Wf`A|(2((KC_C(5PJ1^?{^Gnfb^huqu7ql79 zZ)Fn|*!p8(4zm%^-R!$@#e`AxUEVQ%Y|#qET*YZvXtfxKjI@*eDBfDNxmnmao>Q&E z9^Xs~a_s^L8jr6|8J9F{__RFIlvEI9&mO}9%@wCBWqNX0;WJoU@G1*E+31$jCMF3Q z%XjXh8=riKwsWK~5PTE9eEgBTsGXviEoEyf-L+t}MNL=PARr(**dt#)N4aR_S~6A8fHrRCtMjO)uF=P3Y~P;r@Bcej(bPcjyL|j8 zUH>DLyZ;MkCJ7pUoAW9C?SoGh{~ZiM6?&$yp!1h2&S=j00!<3$`J>Id8i6SA|1pn; zLM-6KTrI+NfxB#Jo*yQjXoM=lVRC5%*_HzON4>lWI8{ro(4Q_poilh(qVGQa zUh_4l9B3m^y~#ob2#ZjR8H+?X8^bK;8IuXi)>O_w|;JADf3kOesnw;qF z(T|qXw;0RO>^u#E1GpYtGwI0YA|GztL0A0aB_$kj^GQDV-8&z%55s@CDqGb{cgo)HA@@+@5MQ-CV9`AE%Z7&T3kN9 z(WY!T+e7IQ)Esc+Q0a%y&!&f1#g~p=_oFAB}34ri0zz_F(&%EC% zvC$yD*Zydw6D zg8JBb(R(SR);IHAqz^M_Nm)Ta5D)}X5ZKA;j+YO6=)z9C^=9ndl+uE=U!i(=@sROt zPAtmq<1O3w*KWAv++kZ(LR zimre6f0^w!N3;?)RJ%;|Q(I9zm+7`S5D3!HpFXIqqSy&8x$RoI zj5T)Ygbi#$+yKqm7OG4x2YXfKmyI|DSL;C=hI;5RU8PExXmrhCdPe_QJnEpA-r! zUFCZ@gW@a8HYnxxXV`&P>!mKJ)$`dtx9&fj?Q*MYslp5vN~t@wttx3wAkm1^28*=fVK;i@h;Dj=1;1SKNytQ(3^Dz#V?xEE+?tXM>9O<&GFaxncWhuFSO+~IE?WR`h<}omVdW{t5dUp%7FGlXq ze_S&|@!c9e>ml8dz+dBEO(Ea3g^`tX)~oZIPr;<>vFpyyN$5};>A{+j|8g6ra)wRq zb5p%;^fTQr&S+gJCkO}vf`A|(2sjo2Xf3p)4gI7&uO1)Wx0ezF7zrfW1Ep#?{A`+2 z>SRO+)GnHg?EV^QvzWL!cd2d+6?LcHSZRApnc+!}b1AT!ojswY7huWbf9Ybi{@UyQcom0R3;oXnb!@NK78^rGxNs(aw`P#QHQz8U@ z^|9BJeA9;U@AL{zHe(ebKKi|LmKZi2P3>2NjZ{SByC6{72pl@U_8X;X3sBm)3-=C2 z06rYpWmE~ffSIBVVHZ#J=Rnp#(}G6<7ELgZ(2_Q7y7s(!j4;4|zv@i&tD(<;#(^3Y zvPEwieMEP%z|lg=CcqDHRD zR36W?=Q;PwCG6L6{gk8W!TFyz7rllvgkmGbDuQ5zesUd)2p(3=gtQ|F2m*qDAmA7T zw9%=MKjB=?RgsClb@H)F=tx{YZR9{Dop&a|BvTt!pGW^lkFMqsx#`sLG@ln2T5}+^ zPh>iLYOrR1N<)Ejn~FQ3q|!@PO_p-e?BIbR-$P{hN+hJ}kuD;R^IVMN$i z$>GJPcPyhSjtb=@YTy{ZO9T4;C*T?8iI+X}VqWa)2^Z-a|H;eqXvUF4==K?tIhW8H zQlsCU&d)Kc=?u=_k*ly_3`MIsmi7bzK|l}?1d4_L7S~T+e__%elEyvGVH1CR=J9Er z3%wi}pP9>=Me}CyTI)Ci2)JsMCuFb)rm-j+@y`!(3rVQy@G`_VRKl#b( z=C2TfUgwB0BRS+{RfSji>%zhTl70Q}D;Jm+G}MN*xkGSYz}_6~!y$FrhygV32UpXN z_?|$l{(K2-K(IkHo{AKQZS0)l8veAoFUMPY-okdb>(uiI3)G4*@8`a2HR(jPfNC>uS_O3dVk z!%z4o1wq9D)uh#QWS`vI6jg`SvmxyX0)jxhMgZw^hw)z*-nMfWz}_YmTqNY!9C6W_ zpo;>@rZ}lZo4+HaEmleC=-i`*(uqU+(M1nDpL7|ktScvurgQlE#9u#}l(^w5Q^wNQ zPCQzP*@zU_#U`i+U}vP#>nk^@hiN}I>^n$^U8|;tKATgJf3SXg+Oq0Nt@mdRz87jU zSwDZzhT!mkSI2@(U5kG7<*aqI7mYZDK*E`M?ukk@#M4~5h{cZ2E%L|IS~1tN9Z8`3|RPerm~@=5t05jCgvds*B(?Ul$5z>;h`1 zZ$I%iJ@vH+Lm#IFH%mtDS(JIN&P~ghC!=KKzSFj){x%18>qJnub?;EgDpt2* z--*=XAZEi5{e_)75EE%$3Z~RIWWqJwZSa5CqyV z0)4#+I=@Sdj?jtmSm~*!Zhrls>#0!H1bR>)T9tZQ`F=FTDAGAj3u?<~ZoQk3g4_t5 zcmK1rUX$4gFS~zyX$GCjTihdhc1;RV${eS0G{C}j_S&tPiZ-8rg9Q?7^@Ao2)^}8hY9x$!io->7qj?@6qZj7r zv1`wxlZW@u+8S(y?&kQuKR~w z?Pn7HtfqbJ$^O=1H?0pEQ7$z04&g-eI55Q0o2xb{nE{by4|b7iX1;e=O$HSvo?GdJ zZESLAH#*Q6%KpC>KTuTFcVBo4ec_)k8{)xiA)~+dbnWU)XSynKP7n|T1OY)ni@<2k z5OrBMHdxl4%y8Vl<7S#MB(p<7hHCU$L&u-hHIv5B*H1b|G3-S;+S|{btaxevlH(_8 z?NBl@YQqJDbAe$qyvHBs5L8+N{!HEfXVD5}mk{vQkZs@*ko>9Hf}*#5ofv+aA8EQhIOQ@@9J z(Q?UyFVN4=n@V3|kJD5yb=Ym(^~OSaV(}`a>bo3=6E;`anbp%CseRS(efH_Ly#y_d zQF?pTW8Yu{7>#Gma#(nzE6YHxF&}vRkj`*U`K&VFWdL5#l)BpUWVowO0 zw@d`PwOOxyNH4HwuifO0gvrYe9>~BK<&6Ye(vkDC#>Kw_qYfO|Fj>b7{`EmG4JnV) z!14rjZa);<_zTW=ea9){(t$e|^AMM@^*;^L=|e3k&yu@l1jP z(3bs&xs9Om+l9vDuGbf^wxadDypn(8Rcw!#O0V(r=p~Mo+n+Tg*aU*sF5R;twVs*p zaE!~9%5J{trbIZLS(TV;!&0wpBT!ey2EU2qogzC%Jl{Wnws|_)(N)Gif<7NbJ*|K2 z87s(zq9Nc9G?-HB=fY?&)@ppMN0dAUH^oMFU6GsMp(AW)d0)mJkIx^>EC$`rA&n5s z{hEKh%8RpuN>w;zlzUwC=gZF^m|Puu_oHcFc2}}j?1NXIP4BMRLU+D0kLG^wYDLJH z^xOZW^=#JiW#=(=3i0AyzSFoy37)~-s{G2L-3YH(Bw&XT3vxRMg1{KI%Z5M%Ptz#x zXm8`bcWImrPzH9^D|XgU*g%*dE#GjefWrvd!0mjH1)Vi)LX~QU0-U_!RB z91wzB26o>Ma{_~vyK0O!tvG;;>(_(lZNK4r5So-Rj1%sUTp|SkgF)RoE6yOQ*Do|K z&<3tegPGyMNI^ls0t8sE4$z*?nFASQs0XlYWSr)sulKOj`Hu1R?zpJeUGmH?;BZ% zblVGa*mgNVaBzf~$;-=%?<}5`VYw&>2m*pYJ3`=ptdW3lZ58e1O&OSx=1MjR9=Wn7 z^#lPyKoD>)0zX8M@bxllQN(0jokR1_t ziiM%{D+mYzfj0TTVYtNk)LO$V#WC={8bV%A`l&q+jmdF17&av5~Wa zfFO_`0>5El!HiE-rrzG?h<}ePT$bswq4Oem7=lR(2?BzEAkZ2CHJ*CwE6zA$)YuDx z2LzC>P8p{-9cSYnvE#pc+C-}1s4NJ_3a7xkE@%nKnys!}69fbSK|m0&2LYH=UHOlf zlxKUcJwjOd%70#O6+FUhdm?O@MNG;I0)l`b(1buu+_{g?gB(*{FD$Mqj35!(9WTvQ zoWOr`(WzPI_ZOV&0TF(F#e&8k-d#+qIYi?X<44hC4x=k12nYg#fFR%;1h9#8*1b>D zl5KlTGlfWm=iK*<=>Qz*I&$utG99;a&Y1}OYS5_z+kq{>ti()b;xAk|2Z4|A=?`g8(ehs@U$SG!q+b3YltiQuZ7Dw?QfiZ%t7Trzq#a8CGB|H zigHpA5CjAPL7*rIAW;$|5X3Y$l6(d+w{L#xUAp|?7YPZ0tRM%QBU>>%X+sbY1O$P? zA%KigOF0(pu^f}uNdzCij~X&3GAI*`34bm|t1GfzBU0OvxUHa92WtfFK|U2m;PQ02@R1&R#+f&0k9A9yOF> zoDZb2eS2`a^GYR+`R>CFO4vfg)`f~Amn1dWMk~TbHgx2gARq_`0y+r1bI3&q3)R`B zS}EyNu;KE%wQf2P&D;@^2_Qj2>aGiFozAJ3Cl2hb#MaeblVd@keIsz&ICn)mumxz}lU$Ochdu3cpRHB?>>Plk z9k&zhHq=*pPCMR)rcs7s5dz)Z3A(0Rl)4(&${Emc6HV?r+jP$hcAie#YX@bm2MehW z>RdFZF1v;U8ViJ}LL9|ZLi+I@9sGmCBaIDhLPy4n|-+fG{mP*rAP5Kof`B04 zLFXn3b5>R;EqHAqWTpf})=Lgf_{E-$wol0;dVBHRR6z;VaQ$rlWeE6%A1g#-aXKoAfF1OW#iASP7~m;_-# z5D)}P8-ZYjuPA~>DrzyeiyA5`2^tom_KU#A?bn_vwgByS{$=EXfRhmrEr*k5M|cwi z1Odk*5M>jp2>&^Sp!Ep&{Zv)oz$}SC-1pz_8p$LSEU`C|}1+*LyuZO%5_U8~11Ox#=KoBTd1Qy(S z4GrqnsTKd{-uEnhxN%3T^G-Y=!iE!Pr9JcX&BaSz6I+1xdN|snvIrjb$#smRDKoHPF;GXvuCl&eD$;Z;&7oB295X7W@^Z6peMn33C zT|qz)Xx9iN5cbx=l;GDELBKQuKe;p5qg~ko%u38O4N^&0IOnLLbk^v> zhR1MZpYDdsI;EgUA5NJG;X@D*1WFEpvV?jDA_8p{8x9!#KfAZkIx@DYJz}eQwPD4xh5d;JQK|qTDZ%$Q2!rC*A zICilBJWY2H1B)NDvSN1OY+7=?J{FYBP=Q-HkpS|J6bJR9k>;dknEXd+kCV`evvt_gnIIqt2m%%( zz`AW;C`exRnFwhxyYB7M)oFE_w2m*pYTSY*GjkY?o zGA=2FF6qgg}T@Vli1OY+7xd@2h;oP|q z&IAELpo9@9zA#Y2;0f!3fFK|U2m);efwF}Q79`>hjwfF#NI&%9QVwNi%+V)xOfk2I zBHrTJeS4x&#T+A*uWa9uZn+pq&-QvcP(AyO2nhm$fFK|U2m*qDARq`h8v(_C`|pC&ti;TWeU!5ictt3b@w!|P1Ox#= zKoAfF1OY)n5D)}%K|t)PazV}NIz>0ewt7I)o**Cy2m*qDARq_`0)l`Z0wdPUC4a*K zy<$bb4ilG+!>3Svhwi3%5n;nLWY$+GhJBKD6fO~Vv~b9=ARq_`0)jvj0(~yN{WeQ& z0R&k=KoCfa!06TQlfV98+H1}`8KV(v=hC{7Cz}>NB<;8ZW)ng5i%BjHwId`52m*qD zARq_`0)l`bU=ISLSCx{W(TEA6qCnuNs;e;tl4vTh^tvNVmZep(F2MGzjWLN z5#Wp%ahHn|a_}D&K0QVm_u`60IhsQo#8QSN2nYg#KpRA0^vd@*p8BDZ9GNO`tQ#?v z>O1x@GzJki48c_RLI@WX_1Hfh?l=}HZFH4#&+!)w6kCA8-@MX8YXn%v2z$$w)Z=j$ zI&3BWIpcX!9tm^G@gis7u=k7y9jbLFbMc7Io-I;LWVk#m~*d3*}U+DIsJJ!qF#|tK|l}?1Ox$x zAOJz5vP88TsSrZLK4L8k9HUR6`l@cJmy^DWu#p5u@xFMlC@Zhv#UZa6Y@#w=5MnVX zR9FOpK3}VZE6RGb2fQg& zmgTd8cPH(n3eLeHR6qoH5!k>x-+Eadu~XmR^XF||huwelr?=CN1$u+OiV}GWxezJ> zEL1^@l8w}I*~;i&V~qU%Ml7@RhcQ`!r9M9`B``im2pugfQHX^PMFXTcCx?Q7ARq_` z0)jv>5D+a#F;LIYC+x4WJB!6&p~=HG-d?u#7AhP9A=X;O*sRGAVRxDr?v)JfM#t);coe6Q)N}~jANF>H4WI5h*p$cBm8~t4XOBPHj z>+5sE(&(5_4tPV@a2i1Xj4)r4(6eP`8cp%o^pEdd%$r?K{cbk(D`(Tmf@$7(uA^*T zh)s>gfCcu9&6S8NK0}2tn{1gi<3|{~V%iB)OMbrDHoth6Hkrm6=T=Gz0+|uG_`Kt& zNB7E%1y-%uOY=Wko$R zFm-8gRVpuyLg4{lfeJR^5h@%4c-fg zLv0%ZgNI+rW_?w#h=3DzD(yJ(foqM}0>E$#eftB2z2||aO*AVpGl!lq*&=-~aF9m% zhZez2#m9CMR=|p*4LjQ?wvGHCHv5Cf#xK-aA(phU8(=pfLINIo_^~2tLDB9to>TA< z^73v-G3J+lXZWqTy)ngBR>DBqeJ8fP{(N4xxo7#gTUWLxPN6j$4v?$NI@|aPel{t6 zb?cJc6E7TBuiZjcJz8vgT_%r0zGXrHM)p2leZmvf=(csHw$h!4XRSXFDEJm|y1v`f zxhQ*z=R$aCVcyLyNZ_cE3q`n|S?L4P0Je+G62^rUIwTM?1Wa;SL`2@?Ui`^0Sjyvt$}4TNmQKcJq6-rL(%j4Rr8O$g*BU*su&r>l$*i{fVFLU0^4Jg`HJ|7aRl) ziJNH^qM6&#bEL>vlV99ez{2j$C@G8h9Pbl&o13Lv1P!$h;NmfgYDyauGlm3b2u;JNrgtE=z@l35K4GFFWXMq=p3ln zYdS#CP+`EcILN|nWkbDsN9p7sNSsDnX-JjCjFL!2|bc*g=a@6&Gkb3_B;O zl~O+8z)=h{suVU_eGRc`Q$Ej!Rt@T$FymCF)0AbGvALobF~qXYq#;3|L=X^R!@&~; ztwbf8SHZwPR~0SlA^xgAz+TDRo=_V^AV)!?I7pbk2hG>8Z)DI&I+c(Rr9G2sp6Emj zA}|xogPgjUg`knMR?;LM5ZIg=jt!=&LV|!G(1JjIPrR0Px@Jb8W;b3Er-7Glf z>4!h!%xg+`6x3r|c_**d3Wb}rD+t(t05p)$7TUGIGBYGN@Rlb1bH>*n&cPNS%U}_v z%Bhs^y%W#Dm&z_IFgCOPVY%>@vk7Z1otdnIjITn1K)wjTs!5waEs-#8c5Sxra41Ys zO~S6YV&ZMxeJp~9*%>KTc?cjZe5em`JkVhmoM1;E$Q5O5I{~M2Xf<4Xla$oPcd;;) zegy#w5I}SuBtmi0j}99T_-oIkF-3M94EdTJIe9NB-?15;EIVT08s#Om9Oef&SP>u~ zwQc#{J`kum9H&*Q*CY+LjPI#Fz55b}!?u+6?LR=P*R$iehPW&0+p7z?^VGsUtxKC;*ct={ z0$9Z2-9Up~>lSmdSzbN_O?JM{=lS%Xt*+aqUz}zk!x8Hcz5oqELU-9 zv(9znm*1VgOQ*28aQ2xR6Tu@BGICxJ5CjAP69~YBsVY{lgkx3mfGDYtO*D{4Q4c$b z!{;m4026t1?bxbr#gEPYWN=0RfpD4+Az!a2$Tu}B5ah(ObMTYfFR&}1b8=73G~1Vf1%X3_`Jdk}^t1BH_%TlY>+G13Jh*#g~Bm1nhf=NyY0)l`bAP5KorGWqz z7Dz@E;8f7XUXa4(&Clt^t+IVnq6BbzL0@qlhj1srj>yM?o!%nY^1Fr?-IdDx`Uok= zi%FPQ2$EJub&0cmW|*@o#yH7`(F7zBZv#Wo{)7$K8rF5_ko7izlM(J$sGy1=cx)PR zA`MwPzhDAs*1;FxVr|Esw5#tJ-IgW$pDsjFKoDRAUcd6aM=Z7l5L^WTLBL)Fu(-hD z6pMa0+a}q)I99@%@q)Cfp0ly?qQ^=?+m#IsO2h!gYg=dc+seLyE0RA|GOm6eUojSO z>UhvU_<-#ilmK=T67*DX*t*Jwddr25NV%8lt2(fqV7XzT!_KjnVqY=NEr4|O4V9He zNvd?HV=vk?@+7+-Lj{>C2p#K3PBqlFXgLhQB!vV4K|l}?1WFkJEJRgD3cDO3umCUg zA{_0Pvm*umV6LGgs<2_jZO7Qm+;0#h{SYRZ5=!KT!h=vq44~P-qSy*b@IA71+cw(H zf3F$8m%7PyVI~?!>D7&jyd&> zXF$O3Z`_1nq1{e`z_DTYNi=Nzhb$c1xI+61ea9=@f0cvV$=;OtUIsJBvM>xi}ZTog*9OK4~qd@})CXLhR7YOP>zmFaher6(4UxB<%@cO>yPc*gZ}ZQ8Pv0bzGF^-S&`-i z0YN|z5CjAPL7>10gv+hdZhW$2Nh_g4%l_WIduiFSW!h7YIS#Oy+@g;^P7*Y<%t3pz zbm>y6uCCUeHgy~?a}_PL9Tx2nI26sKDXxoGI2S*{jiIid@&_BB8ZS6AP8he06Ua$ zV8lzzj+Y2_b3L34#LY=L?B1QrjDlPd1O$Ox5WsGGl%F3~(0Scsg0L?LI0XSXHd{rzcI`6U6w2o67h$*)69u8c z*tL7Np>dwFHd_<>>nobEjWx8R|UGLN1Ea;7uP>d9XgumI_~;Q=L$pg|q`*&HMjY zcUKpFoYI3JAP5Kojzj=jjX))bV9nniI_L3WcQjN{P7NFimJQj31OY*y1Q969u*sOq zP?QC@!HV@9?aWCOV`pxy&xRgv_4WBI){Eo$13_c-vbU)sSf}_NfeR=9A-n3Jjf<%3 z!JT^3(dxPg8?7Lb6M}#sAP5*oK#g{S#l8~=f=|$4>6e{=i|`-_lmr62ptFk1ID;ed zlamO%n-RS7<=^hdce9`oc9xKr`5u^c(JjAEwgtc&Z}5hX%nKiH&N^$RNFSM@mMell z9th9ea4krBkO)hRUWZlRQ#K&?t3uKCq+pg$!pz*vJ5koE8M~LBJD>=Um-ovMz)TS0cCm;Bjo0 z@=2@f$)P_FXV=P!_~qd$aG-UlIh?wv4|C=)AwfV8C@lo=JXaIPX=Y$(ZXz<{{gsd{m~9(&-V=+ zVd!wDURew0e>Z1sUM{90Fl9(zx_o@@KaAA&vVAw>Ii_vL@mkz%^jbiwO!(H%3U zv?{iGcZ~=hj0zrL9v0LPd(SZOyCBy%eO?Yx6!w%W>2{6wr|_8LL$*8=R#LVa?F;3+ zTt|Bk8Lg}E$%0kXq``584KWX~D0M0|rd~EJuQ_!%1>p-?D%JeVsoJW74BTX+Fx>rViMJz`|z>ZX)Xi-XzWo(QHKs4 zXw8~6N#5kpY78DUh(?bdowJ4m_p=`%u57fYs%S%khBBv&VV48jMcByY1}|it`{#Z} zFD=_Z?xsbGHuf!+Etz%~b{{q$h9MF z5H(a{m(-Z$B6hQKHCIv|nxUZKFhwZu3j}kX1?+M{z=;?%+rhZ zpDUK;KuNz_Xs(UImF9P~o$t}Y4aDM>qaHUQ!;wPwFP(`GI+3(P8jlyHxs)rp1 zYg?U5bK=N>zH`mn0;FjOr>ZK;Y4nI5v};cT9ewn8^83@Spyek0VE??gk{F|Q!Y69rGYZDiwH zw{9J6+_Z_}IV{xoA2>kxTfKTUjUF|M2C@)hmY~(>!5W)<2=5X2_ra_fCdp2HK3;O( zan!q4hqOaY@N<3T+PyS)!K$>^&7QpU!V{=h_w?E{^tF8T9-9BrYO~F@DldXZD@fYj z2^VWr#*FMseUIoyojNDqRJC1P;8M!mQF(Urvp^HJW+BP8EOXJ8#0mo_D=4 zhA^Tc@y=(9ki(`IoC>>~`@(fA2t4~4OPE$E{S_EXBRn@pH))zvBX#U51K%s576kD7 z3pp^NSPO@xd4%75h_kK%UuaJA-NYzO6bi>Ro_meLobj~S}0J!lyV z28HSmrb~YQQ^v;qL7GA&jzaAk!6_eqN`D2$Qt2eie-I`;Irx@jY0{cYJl~Bw&DJzm z(yj0<8K_{5m<7(aC5=OYna6XjfftXyP(U%YDsXp=&A<7FtG1`H1pqG*Y`Cx%RCxxf zJ3Iws3R+$~3-PSCYJ;VK?3!vC{u&!LZd5cAnVil|HK-{JjmHOb=hDQ96A8X1La7KO zsG;rvZ40udL$iEqs0-7<1H7gq3)fye&vms2s6LR~R~`3t_)v{yeVJczreY%BO(Edr zh5yhVU1-f7W9Ccn`54iw8^IwvUvjgvccMAwR8wmfY7yThiH_=HOq&1dlyS81)@$g*q5X30QtAl;fiFObvSi5; zCEAsGPCnZv0wQeKJg;4v{3N-?!N4i2HHJHg#{q2IC-RxSYlPr zaPBf1I?t*;v$cb`)KK-Z}q;v8`5D)~+BcP}}I8HHany~E&qjEb4 z&N@hr&=%}DtilW7(_rr+R9pmtzH+`Vk-UQ)yb3_@T6Y@7*mgR?VWr^MQEC+CNG6|s z{<-aJ!^Y}U_E4WPd2+@)0k$*cfDQwp1bdOUFcL@h%>q$~`{J<7%@j{)zBHfB{Xm|^ zxnpN(O{62g;pAiKwio8m!GQHUlVJ`mwQkc+LZ}EW;T_=dIw!TcmNz*T1WFMB2pw(B zd$g410gF8+E<#HQL+f~L-06qbFaPbbWUdez&qsnEwY zH8r$j#}4Y>zdyR#{^@y_iY-7B==%;vY4zH5t)`$)@4lRzq}A!XPVA|P(7FxlliI*$ zH~KP@%s8omlrJK9qySQ^ujh>&MpsQ7O;0Rd)f^7WDqi3T_Q8j%DI7+6&DON)aF8Nl zYbDy&y~r6sKoAfF3XedD6K)7O2LWjNU|walsb9<{En+T6#@(g2v_h7^?Y!_WCc%`e zZ{NPAvjrBD=R)+oXJ44MZr{e44qHFV_8r(y9Xl9v2sXmoF79jF_U-BW;>?RxRo#p< zqg$r*ei6DuD$N4w4v@P556}qf$zFMD9-VQ@khI{;|7aWSKbT$+Nn0VulfyVf*Bvt^ z)19x(r_^kS{kwFepPzpc{p!tyw7!}HPHU)F$4a{GtP`jom(dc0s0FOK`P-aN2{C)M zbk0#j>AJ~dwWpNh1N8xVa>*Kcf9=***W`pCAPAH;0!|JJ9eZphyMr!j`mFN&(q7cS8o|SvvvDQHp9=g zV-YrTMJ~^JgSvI1n)((~rXC#{v+@n>){({>p*^2HJ5Bsl zL3^p^($zD2ZraEJbm_4p>8(|p>4{I0e@rJ1?WM8jY1S~_~qMC(wi!Fd4tF#$B;B94b~@w{C6sf`^JlTly5i zqe!#)w=Yaj+w&hUJ3a0Bj3>)nv|+=Bw3mks?%I65ArPTA-%DSvxlq%=rcIktPbuHK zot@w9;%eHJLK^f`QXS*fGTQ5-dv~J%YidsW^OFQEg-R7{FFWU!tLXf3!&(U*YMCEC zKbu}!+G3Z8$hi|a~(S}F($&+ z?fkn_G~2oIRfu{@>h;o*HK@9}I`al*AppwK=05i6fFrq=HI_nc2Z8)nk8KAB+eWfv z+g|z~`)HV@?G9KJM?K|vIsciB%%x)V*rJtm>-0(V%L}H`6%W5eam|hvgb7a;Gql6O zFzu;rP@7VY@B83WHs6{`Ck^Yb_{%h3lmkIP5GYv$!V;Cr(Q^kACTQ*tP znD*KU?};sdE@)O2c-?ImL*9H#`+E$cJ-vqORHRiH*o*-SL@u0KL0<3y0>?t1Z_B0YTvZ zvv(c5fZ_lZCmdDoi z&bK&BHqmEmW=TFLjldBayA{bIUYoy6;2CWvWkrSZyCe6Lff`!rflucdW$nsk%dy5N zR#As@*Q{@3pWa>N)X@Xw)>9_Q-RjUdh5FBcj&0?xvyM=2^l|dY zXA46TgG8*1CrN+=TuGqbCj6Z%JIYR_ESwm2OA34%%22=S;ZdHB#UyOer(3y6#YYIg zjc!sn@*+fpNyYe^t4>ql$fO;Pt?hfsiayS4sRFrD*$AYOhS&S`=_*fLbezDMVDs)O zTV-Qlr?&FY`NuS^!XG-Qm+X7HuVUfn~IgoLe#CrE$G`B*Ug2q~@Hmdf004IjR9cI9&0jth`*j+uajQ1^vkt@%U;rbkz*q zp=+3zxOv?8o?X*xq-^Tg5P>I_zS4gq7FtOtTjXg#ZcVV^QIh~9iFoL|qoqK@u3+{4 z=TE&WyQ&*LfOc8&lF*&BR^e;R)>LZacTv(f@kdxHtIYW28S=uVCk7q+L+{-kQ`P&$Urdn+T94lux9agUXArQ;@NS)~KHahD^IMl-$c+_FsWKWB<& zrt2;+`8iL+GhyXE+vJOt8;l`cr#>nZ`gIdi$$-*zaQ~i$?JZ2Ctg=fJXzbgotD%%t zTcVrP==(Nh#nwxj-d{9fgptU@JOIb>k=;8Qd}l3PV;<%+36KB@kU$y;WJ@A~G@wdb z6Ybl#mlZ2lrmekb4WU2TE=`Jt-(3;HoN$N20f}X|gUW^zvMDSxUVS?r(4CDgt;*#c zjhuAxV{fV%RdotmY`&`JuGu8Nd+9^D;pmBS%BX=s9f1<@hgUw7d22({pZ@x}8S?mr z#|g~Mtb*%qdSg#EK~`kPULj2Vcp9v*$@yRjy1kF^p}^ z_T5rd>$4pqd^1MeG+lbOZKbUCL?7|%b(?lqs@YlSAME4m8}sgWD~$Sp4t^nE4@EBf zl)0C-p7#gZ0@!|dcrf<}z;rW5qTFQq&CSn}bjz839|HSdsE*oQx^$6s>q4yz0?Vc2 zb($P$_wUO`hs?zbcX0QyM!!%o`;%qn;<%5bxoRH(aY)7Nh@@=ObD#%I)C+16Z zXX7nhdAG{%jvOx+s!0$g&Pz6KwVqDZvdzw~0aw#O zJeGD?mIO#3^9XoU*~qMPYnjJ5Sv}6Q#CXXXx7+LK*SBx8D;rRHdStn>F>Jv*S)pi{ zpJyl>wHm!@d%K?IA=_sv8@5#7HSFfhD+QIegYl$LpIQBjeaD1=5d ztH7R1qZAh8%bzsN7KH>zfCQSGfT4Zh>uJwdUUPSe?b{IW)cLa_bDqu6vf|K);lt(q znKPxX88pNeEm{b+TPWE{fLk5Sa9=TJkEUp#ThWvZr(MU=!A0jEtG~nc*?s!-9U@OY zJ4-fh+TyfImLmZYAOY79K(;q19Da?NO>r-Q0-wFpo_l+oTRkh+L>NPd4wZTH!e`V? z1raQkju|t?cF{By?YX_qQ;r)aojR4-b`#%q^A>HC4?bRKdp_2Kryf5}x47)vLJqaL z^Vi8ov%ibA+K}>8HbRhO-vR7u4CvEYI(KO+ZCaNG4RGhKD*5W0$OT54*Fq*Hs#98Hyu1&g;zeO>ZA&(T(`w^%x~D-7w${6(8o8BES% z0TquByxDo6w6s80uG%CkRtMW0Y~Hj(%F9AOWYH+p)cWPj`(F+zwyP$2e*__w^8g8u z011R4P@u|2eqE8&*_eZdxrB$)NC1ikVi{B1O#nL%qxRlge0smuZ)ixOmC7Cu9Wq3E z_KfU#oXYmyT6cBD4p~`Urjz|WJ*=qk%FZ3IMzJ@K-%~P%SNbPexqF+etZJ!kX~@4u z*}dBZPJ$xA^^g613I&5)Tz>*AkBHR^_J7zsH-swl=wKrB2y+@{ryAXbTYT zzVbK;#6_U0v_yObvF}91)tCz|%F`?=E%XbQLISZ8DD>8fSL1AQrz2K!sV!fq3L)Yc zr&4gwYxUmyJOxsZBu?tRm})^Q@FBwZvdvG$-0f)Fwyn&cKR>C?0 zRH)`z=Z+f|G9F~yd+zJSA;)=u1W14c(m}vjX;$-{+Gd!1#yGxte|hOIxnm2U^SjdO zs+3OjU`|(Cn0}?hdf)w;u&T1OOvtovWK*;@G>aWpPm5!2<$rWwjGzBk+~ zRaHI-%#Ki>BqwgWF;`Xk#TSU9KJbm*6|o(o;t?CKj4fSVX@ zCRA=vG|WE1l&iX=R27bjs5=qnHQ}H0bmCkban(NjwW&GNrcE0ojIB?@9BO zxl^jDs>G|4ckCS?j~DWTK|yKLy0vuB9TH?2#14lmM7v=qhPo39g~P2rMjN)&%95qu z2O$H)&Yil7>YhoHdcA8KH+yBtvZWz)cJ16vlBLH}*$Ba#eFy4v`*6mr1yZYD{C2W! zTZMSl?wLXYBtQb$L7?7PQO8O%eL0%vj>4y**qcIADjimHm{CQe=~oMYTHTGn_!E&- zC>l@-cJ10_bgPwyppAEz0r|bUbm?L|5*cGG*VgMiX2*^lQVj(`mCV+v_;_O2?XhdC z21%2(-bC+}UEPTsTeq=B)~s7AMMa^epiw z$Ej?@ge$WO@6?cH-^|}2Tenxq7aDCMnR4BJ(?87}TYwnIO{_AuxA;~$~#x!H;xlg2udp}rAn7Hsez>T<`=3l2%H;IDOj#{>wT>)EJ$cZ+u$UU zyoe#tIKI{Fep|K#XZ!8gv7;c>IJPC+D$qJsX>yQ7ixz2;iapvaG$7MCB#~_tXdtk! zN|hr{JX~jf{9TmG8Z^i#g&dup`EfLx_~f)j?ZeUS$jm|og)QaV?<$SXV_CwfIQhf% z>R$PeA9=&5^1gG9a#}UZkwEqmP|X5%9$wvuPWL1T%XJ8q0~-N1{b)A#%|-%9coyE&tP%wo=IV<8g;YHT%Cr?N<_Ts4WBI8x^0_$`st?{Yc(aTq(0l|CH{|-50Q3dQM|NbsGCJeAkzro2dhdaw9PdE zGJi1fqgkpe%1r6J=Mw})3_BcH!Gt21w(X`pUC4=7#!gaAwK{FY8ImMMO8CsXRqA){ z?0$TJEdcr&Pk1DRUV@!gGQ_WOiNE~vOIxMGtiM)m!M^z73px1UgAHd4^SB$HH*VZ$ zt8@hR4a(CJRX`95D_+5_5*3el={dVDIiF}}ZLw4Ufxhiq%lJNBWmwk^L1`reiWo0F zYL+pyYkTYAW_j<=p`;Ma0+@|vB!LE{qpaDK4lA^(Vol(p3J4{b045U%BZM$Dlq3?+ zoeh{!l~-1zP3cHt?^9EiGC@4nc%(i-CxyT^*REM(cy6fbnzFlLd!J(HddY~ zdkDZz8zxrp?uF-Zx=r8q^dt*j=|`);SF5SsH5`@UKa}1vxjCk(q0q+rr_!Y zq&tMrQ?Zy{N2)6s=b$`n-MZCLRWPrDXSuZOpJQELdZs3lk+sRT|QHNa3b7Ha(1yFUC{W-MMQV|sR$-yA-!sh#d^ zTFQgx9W5_kcCw*pJpI*o^39q}P3QT71X4$!Z1tad1=s?luA!`}xn6&;YKz>rYK0$B zq~E645YS&sgsH)*Jd_ZaqUFUqNwldx+Ygz^48RYje9fVBfH4%0cubbkaeopDgOtE( zy>)E0F_^l=Qr6l|D&E(rIT#*J1*T!~3O+~a`Yd@1N@UT9BQ(KEX2l2>SWw~j_-zklgNc~6y&^5Q~SbJOKAsz)a&(IgjFP8ut}Jaiu^)hHL=uH7Wp zJU>%D}tAnUp=czv3x>xUoB#t)As?r;W32Uk0P+M)7#392P5_vT2 zeyCAQ5?7ovnI2L#7wW_niiTTu&M-MF(iproYC`BHLEr`iafnwudOgO51%pJTG=@xj zW$KIOCP8A4LR~O%szj7&G`e^t-8*Kc!ZGCAw>8j5{GXM0$D8PNu__v9kIF`()MLEf z)A$AAZQO#c^)+bjebk?0&Tr0@UhP^Lrd3cz9{6;gAelzC$WJDYl$%dHIP7q0^#|?r z`(Ba{zF!?w=9SA&mfbbAa_0Rn2OS5>=Po|cnCzW=&kI54E<0dvx#{?Wf{t0={Q9{W z24m}Sz9#{v1e(iH+)4lH;BbohO7?t(I2m@_Wia6|CLIZ^kjC9OFN}*ok=`pP)qY*5 zbAv!hC`mN6y8gXlXCxWPM!I(GD$9TP!4VoanNy*|h?dz^qie2QwJK`=TeN5)hO*&& zBP6I!e2!v~SI7TCZMXW!l*yA$jpe#%B;1Y`m5P+(-aF42iFm zx^ZOe6A35*{eUAFK$WiMH+QfE>~28mNQQ|SLJT8vQlh>_NcY{TQ>Um-;oZ7*lh&Fd z+-)*ozyMjhZk@naBa#doIyCO^xbR2vG}C0?P&kHXj=~YIqJeuyWh2rjIF0Al@2rqh z)OmdVy3Mh*sx#)QDHn!Z(`x%VDXZ8Pa@@&*>e807t6fhgMdK=S)0>~lW1lT_d@Xi- z4*qpd`SWoH$QuimOQohrZ&6$%e>`S?LlL?Dm(hM zqO^!M1QRGsSPNA_NS0!6?3PqjYtqWngiNcj=r(5bX!&&ZY}u|kE9|6w`}VT;$dPuZ zn*A7dF7_EaRzBB|z2Ov&VZ(+Q3ROIvg<@zVs7%`jCbXAOIKCZujDChf7g|G0H}4TB zzrHvZovCaz)4l7hrc+xg+8e9y=)#tYDr4mxEAUpqivJH=Gy;P|X}5Ki^jiLfe(}Zs z7gV5$n>Kas@36(x7F(pd5!ms${J>FimHM_|=Le4Vom-a4zh`_Q%Wc>^_W1%?wq?7_ zTDm%U$34$rB|hu>VLA%AvO9Tnk;0=_utF<_3Mq) z;}}zi4jrWbUVBNKHf`c+s!&%ki`5~>4J$O5)q9KLcSwl~K$7n<)0@VqG>`!W-M7L=REML*<8fu zN9HULguWWty`y}#V!b^1<)Vn?d6ERuK>(8iC?7R3#G_1QztEpj?GJ?nNFX-}tn%gw z9uyLA3xUi~JlsOdrTs{iqG1j<*$T%tO|QPaF~$1oZM)27qCR71qlG3gfwa_TPo8=* z)jQu7HMf$ZHpBuX5F-J^Sj<=Lxmp#DWCYTWvH7Or1!_iBQd8v$o8+cW@dXKx011#l zP7|QAkp;KE=MRZ_iaS97g=1^`UbgXaom?MS2BuN5gsL*H{pmrYq+f?NhKbo=^|JrT z#1V4WtZ&TId?tZrB>P7>JOX~Xy|v<1k? zd&xnQKvol=;*kZnH(gN{nsb8|2}`b-JWk-#aqOMX${SalD%Tx3UY_}Sv25O5l?8W^ zL&ydKh+0!v>(xg#jD}+%0TLhq5=bS1wq+%9sb(%NDYT!CBM8ntv%Z#15#~8#dv%s0 zhV+%Frj5d4HhQmIarM# zS=#IKm&-S6HkyaSKHHBjY;m4Ql)%J(-F4EuuOJhiRm`XnID^x9`rj9;Hpt)Ko}+2E zz1HG6^?sTr-zZlzsy}G*jk#+!$-mzHT&|xo!FXKx^m~q21jp@gbO{TQ011!)36KB@ zkU&ZZ96X?>{Pu|bBG$cJTV5hc5dTggn?Y~!dIep9^ zIr8?WWyRK=PA>~8*S>A395}w8#@j9pI&Qw%xwA?>_;jgk-PSw~ro%42Xtz^afN{OM z${Aw@3sQ#Pt=c~1_L*PGIpc=Px#Nb(eII{oC?n?B!abWQ-ou^eaS|W_5+DH*AOR9c z7l9Y&E|Dq!t!PpPphT4Fj>owVHU!l1>MqCZAJ*D-2RkEwKJGw4nV&uRwqfys1y;;; zmTwMC=!S)F?1Vg_yBSY>5gd5uR82pCIn~j_``IcvNN9G+V{Zoa1J)lmGxBqkcVRH1aeu7*b@hTpLg&xTlzmybtq+Pr6pr*F( zsM3TwYox#vy6X~DFo|#eapEDyABeR|uWrv>pxW}MKLqC-`SbAy7`r~deenaq_96sZ zdB4+i^@>huZ?-73peL;q&=CYiB#WzebD z=Pz@d*W}FwKge!xZkweA!Q&eeAOR8}feazAaN0q-PMCE$0+LC8TK&{yR+n*gVZwOT zq5DX+hAR9&)g+wA_nWrMub-VE$VC0nc}L5kw>{aE;B2|tWS#cE7oAiSQU*a&qDgwD zozZ%c!{_0+G3T0dq-9A_SPDC;YBT{w@bZ7y!Vyk*bUUM7w>*L*jb0AMUY_pf#VVIQ z{#Mvae*VRVNPq-LfCNY&(+OOl+Z>%*mKoa|>vvW-9?qkmEs*hj zyBap>x1T;)E`0cP$Jg2w#2gmeHPM91Ml?bxK98G(n6U#pwF_yfMD3wY88uK^mln&2 zZXM-4%Tj;H1w23kBtQZrKmsH{0wj>t1mM`}ud8>ws}Ts}n&;jZgseJg?*T?8oa}`afY(yiJhV%D+G&gK38+KO8)f(EW)Dn{{>;*hQ0~3^wSuTk2YxP8LDirX7ibnv9biCZBR~NZ*(pd30FnB{qfk^{<8Qt2d zAF4#f@2it_8-v3aGw_794AT`@+n?!?v$E%e2Lkx`W8la-3#f zaVjhI196>(WC{#r;Pl$Sa@93HS+Z=6)M#e1K!LM%L4Mo7Lyle}bIfTPyB88JP_v_o zScn8jfCNZ@1W14ca*)6g3qJlRQCoofKl#>BJgz-_KRI8+ea0hiFIr(Fe?xK*C?CgZ zmSrSDH6BLF!ODjrTKC9%xiv%WF1vv|}7&WRwUw$qy#u0F5V zCtu9{LAY5^2*`1Ge%-M`LS}i;u z6M+qyMP==d-HslKDjMk6*6r1@Vs*8w+u*l-tl#999Xo?}KVljX3zGl|kN^pg011!) z38aMpf)3sM_MC=fWl_?Wn;n7)*0LKf5m?)pf_jkE4<=SWJ!rIy@6**h74+;N5LHll z>l^NDSc&jG36KB@kU(?-S3hy`af#XjL~n&9NPq-LASVd4C@wP6eV3@^NS=CQx6)Jz zup&X84G0#WADOd2kS4(@FX&FkHR~F7I{tdXL4wra5cpVpyJk~Jlv6l)!yF#F8Sj1< zoa_v{B)!|Ul0lu@hh$^6wj0lTDjxArbK5megCQr{Z?w}WND*$A-(p7+>Vzp0{Nm8nNhY&x`4=T#5Sncwu@e1RQ`E1!8+p8m-R za`}Oy%)`br{4GY@G(G6pYwG0vvHJ8LuvZV`5p+`DAmY_)^Osr=8_5N$)HFX4$5MxL&4)Lk>I(O}6sr1_WSVXG0{gxHd>j%VA zPTj3s+kKd9=rS-;H+YT&NPq-LfCNZ@1W3Rk0pzooJh-lA! zrqxfib`0;SdDv0mF%lpF5+DH*AOR8}fk*_PebBBd5}_2FLmF{t8{e+oBr}(+YH9(7 zeCO>qOl~{vFxl@Pk2D>3`eIA_-bT@6Z+TGBaC!$=js!@61W14cNPq-Lz)FD1hLuQ) z-w*5B!D!)zS3Z&%i-SFOMt1LL#3w8Acp~nhh3jIn6%HyI;Yjm136KB@WG#X2hrV`B z>TChB78?gf0wmys0Au7jp_Ifj`Hddfb{SU8V-7NshwTxRkClB!>-oeyj;VBnMa&Z< zKmsH{0wh2JBtQZV32>LgA*f^(3yAg$=_BtQZr zKmsI?2?VHkG}B!g(5a17c!QHyv`|le$lg6VHTiNNf-&OeBHkH=1W14cNPq-LfCNZ@ z1Tu*Lm5pY)TeqHaXxNtD{Fw&uwa8JoKb?~FkCtZmmIO$E1W14cNFZwo+KmsH{0wh2JBtQZrkV^z;SCvb5n`0#bO$u3; zm%cTI{JJ_aCjk;50TLhq5=b$DeLfsjn+di6R6J7rL16VH;06MIk4L;kMS|!0Na-k( z+}ZrPdMWVvq{v$%c?kGSApsH~0TLhq638h6R5mjFcGnaaXZQ%x+x_C2n)Ei<+^y-Z zMs-oK)T*M<98#B`r%Fzt)D{*HG(VXrPOH75Gb38khac2lZKOQjSFN3HJqQb-_>z=OLko|Fl;0D*nxYZ4#< z5+H$?38c@cJLazO0umsBL^FsUz9fRu`8@T|U2dlFgO%kpKyh011$QTM6)2 z->r9s{UU)x2&gGlO|koS!0NSM-a_~FD-qPZmIO$E1W14cNFY1`DjVV1I6Sk}|Mjc1 z1#k$9#hQVDKhNXZ-2|Wdbx=rv1W14cNPq-#g8+Z^bK^d9l#K*n&XcGw2d+=Fj#S@} zI*)tT6yVo^`@qn-ss0qOei9%75+DH*Xnq1zHk$t)um@=*P+3-%cxxKj)l*-m4$xjH zto6BG@ko5YDX2kBt0?Iu040w>iPOuRtu+wnd!_G1+5*JzY3Mg~>9iP%vuKtR$j4WA zmVJ9u`^fSSEr*u^0y=Z{Yc@TDQGDED&0QM%+o|2l=Kon*f1ny zp^yLxkN^p|mq3f%yT$Fl;jz+lukl5!BVlW?hG0(Y&HJMBBmiQx_H zD=c(Xmb)TNv0jx72bLt>U?@R_UMd?&M9Qj2fCNZ@1W3Tu1ZYy_>f6K4WfFnf0(a@| z1-i3QtO|!)1ZGqvns%J#RBpK=><0;u00|^Qz;nvK2gPa&kVt#HHX8`IYo?S9L^+0> zB~a%=r`#g1*O=(n6c-CjznV*6-c?*vt!7^C^)YEKHf)y!NPq-LfCRFc0F{kwzPlV- zS_!x{?owfGtrYlt8d0lI&Grh_{7d6sK=DX%hY@AN|UqQ7JwfH5+DH*AOR9cBLVtyq!Bio%5DPr zYBoe60TLhq5+DH*AORBCLx9T09u|B>0tpc)Qqv_036KB@kN^pg010F~fmPp}es3yl z0hUecJC)i;#(z9G05=fusM7*Gq$wmo0wh2JBtQZrKmvORP}$hSg0Dy*E&@pLU8=EP zDI`DwBtQZrKmsH{0>K36Js!-1??@mf0tf-cBoi?qV__0VErInvTX1SBYync+Yt~Kz zBtQZ=L4b-!PTXb=(j<_K&{9V1*T%T7$hJoz0TLhq5+DH*AORAvCqQMxo(qp=DS`5e z3Z}iz(tFEckpKyh011!)3Am2{{Wn~7Plg{d@UOH5aMdkgXGnkqNPq-LfCNZ@1d>94 zibql~vJw&?0TLhq5+DH*AOR8}0V@G28&)EGPXZ)B0wh2JB;ZN{>u2@oPFnz1-Wqm_ z1RN2d;^7Dr3y}Z`kN^pg011!)36KB@#7cn5My$kGo&-pM1W14cNPq-LfCNau5dkV2 zjxezh2_%8Qk;nGjNLzsTKu)Wl8sCMyiUdf21W14cNWfJDsC~HVcCa%fKmsH{0wh2J zBtQZrkUa#LkRyBUB}YO6jtJD}BY$C@qp}GVs;`%PeNae%1W14cNFXwSYbV@&4Q&A; zGs=#$R6MfdK5`Te3DgvqXvxGT!$@83(RAYleqW+hIqY@f#rz(Rc#Dbz&-Hm}P&yjB zoL^Tb1)8G0$m?YW#>8(UYajsZRCQBZalK ziF7ID*VYsli?^^y(y_5&9ZX$*zMR1gLmq?!9BrT|%I?uuv!W#V+a1o^JTGU+9~g1d0%LJ}4wW0wh2JBtQbW zL4d#dxpAL4$`li*QIjFJO`>bCyWui{?}*oB3XsbXu{$I{0wh2JB#>1E@?YJ3>sz!1 z$f|qF!8A7kH6Mb%f?LGz$#=&O&fCNZ@1W14cl23rU9LZhk#KNpX#U_3y}Z`(?$EjTCc16R8&(V;` zMiL+a5+DH*AORA{9Rjbuyz!zqZ2@uzQq+-7tDhRROgsgseZ)hJ*N^}SkN^pg011!) z36KB@L?u9FBPt>mAOR8}0TLhq5+DH*AORAHhX9q0c&PCj5+DH*NDYCW4_)y+Z2?ll zmbH=q36KB@G&2Dz9?g6M*gOf4011!)36KB@kN^o}D*-AS*?MO=E)pOC5+DH*AOR8} z0TO6t0!dXi#yxuJZrTDg^BraLBtQZrKmsH{0wh2JB;Y~u)g!7)YR^ygcx2e6KZYxu?!Ag|)T%@M#^1r7@4M z6TFH9NFehFtnBw;U)lm>-hE?_Nq_{BMSzM&vPiO?+#!%B;`exrhqtg!N^7bl-(QzI znmE=`NGeiRmZb!!cx35~<*?FEfKDChXU-N$fCNZ@1W14c+)aSWhP&?% zdzV23l9lWugNDqmk^l*i011!)36MZU0<-~%h$np~;cT22M_z~eh+fL+>pxq<{fpxU zx&hpP_kVFEZ2{bHC)fuPAOR8}0TRe+0#rO)a(g{~zqHuBE9TzpYS}X8Yj{Cw2rwx} zYPhmi5+DH*AOR8}fy^a9ld8#2o(9eF6$AyLjoi~0wh2JB#OIxkpKyh011!)36OyM3DAGT{r8AHbTI+=-IP{UWvi`lYk$ssVZ|MM+q3y>U^ zsAXFH)Z|shnn-{INFWypQ2WTmyUsBuL;&HbU`JJC*i!its*4ws011!)36KB@kU%yQ zpt6z8cb8)eA<$y?Zf;A2P~ia*AOR8}0TLhq5=adJ*E(^`y1x4a+5)8Jrn6QOAOR8} z0TLhq5+DH*NH+oQa-^GhQrpXu0(B^$WEla??&$OIPcyH+PCWj)EW5cJ4hfI|36KB@ zp%j zg-L(}nw@}a&8wQ73_CyqBtQZrKmsH{0wh2JB#;mRDjNymAaLP3#Tu}%k4%|7>D25PkqbsKt$wNtdciIz z0cs!o7?1!7kN^pg011!)36KB@q=W#Kjg(+zr6fQCBtQZrKmsH{0wh2JR5th-AOR9c z9)V3&cg?0PK=OF9CK4b45+DH*$RYw%JhJF!awsG~0wh2JBtQZrKmsJ-CIVD8+;lhC z7ZM-=5+DH*AOR8}fovtv`O;Gk%4u5wDjwPT(dM{FfCNZ@1W14cNPq-LpqU9!*=Xh) zz~)JS1W14cNPq-LfCNY&TM1Cv$ksc{aghKCkU)|N+&Xdo)||ElNHR}WM*<{30=Y(@ zAe#rgVZ#R5v13PChPZ3}8mZg1QSw@}k-Wm9w6w`aNPq-Lz$t-ZuUG2wG`ARq1W14c zNWirO@}%CieT%naS*>hbcinX|efsox*~E2S*@+iR;a*I{tY81(iR}h#~`7@)9R-tbUiO70TLhq5+DH*AORA{6#}`V zedNj=I%)oA*aL>E61<0A( z&4H2t36MZ~2zc^S6G!@%QzprbE6L?WVh2fp1W14cNPq-V zO`vc4)>2a7k#22TB)zHoKKWKY{C>4O`}N|a*B!OOoe#`Dm9_v;F|j~;2+)@!J)GGJ z36KB@kN^pgKn4&# z^;>M~9n-V3%v-xzyncVs`MtY$lEoXhN>!~d=s4bVYFQ>Ni;HCG=Iue}+Lo0_XFa!Q z!`7hV=9{s-I?LQOo21rXXC6xU8GRYrrF~O9{`z{OjU81rO~(RX4DQ@cHtedDZI#u= z?!>r0T_ry+&lu+yD>sO*E_~lg3k%f5sk0P$@}<)2lP^}Sm;4C68-ul9>v!yy?KXWG z*r}~-si@XRRbcmw*X@+!$nG7D*Y)}@n4!OsWN6p+vU=Mt*;(z?{&{4Knqrk`e=*i@ z_uj0pytq(C_2?vp`3*)aTeU5;nJU5fhIQ*89_4Pzw#pj$X3a+PZOAihI3ZA_v|BOpI%+0 z`|s}zIt6d%V~6i0y|2GpXXmv+=MLDbhrDw6$@06GK9oB?_$ugJB5&@|z8o^3XHXTp zYrJyagRjYZi&q96hb`2HzdBo9pTA75dH#KQ?84&&8j&eWHf@vR?|NR=sm5TH@qN3= zL+2fBXkO;I1?#rR!VUIXUCWXp`AFM(?wiGO@uP2=ml)3lI?Mk+&&^!2O3t|NrJ!?o z^DpIi(%u7_jz6b1Ru?_;M$>!i3ut{GUUjxS{pBKgM$6uE@+9fiu2oYpT>lg8L!D}7 zRsp;F&z&OB2mezRvjNSBdd)3%LB4<>rU%o>DZ!F9ym&|>GJ#TD z(C{?S?h@P{omi7BcH8UZ!CXfIlNrA zV17Jdgxq%eVKV&B(}Su*pV7aeT|3CTSDqm~+O!l*s=w8(p1nG>k>f_}CD$FXpZwx! z4Qm)6gLHf3g-cE}#{AIi`LbEJCkE+O#WAW_VT-{|uzi9MRvlZEg`6(X<4}BBD_nWp$3*Caa{J>GhnC!}#$1rh6TPLW3a;8q^p;SEa`9fK*+Z>Yz^;TtbpgjN0 z5_$RCrRF8ZGqzKn`pNOeR?q$FJc4Z!2o$Ts^tz{he7qca`_sm;Wc+j8k>lms!^cTQ zO|ATQ=2uef^NFedV9N(vHCBP*`|QOhN^e!3pqzcNYJ=3(H7HH^_ocbtTQA}JEFnN; zBTH^1hd}})KmsI?-2`fU4fax)+(IyEtI%DC&AY3ltZ{X@y0MH&!rS=XR62|adY2AO zb}z6mva`3UZnVKFD=IXq>Z6|3yL1AJiKMxbZI&_HO7!{TWorbFKpCzI0CrNayMXph z$!oOZ!2}j^>wBNejjw-fOn5QIO*j8cpjcStjIo1_{{QOP8G=cmq=os#eKnU%g>&A$G9HXe7bzCJaN%+ zGE5aLtFm#nhS`D*)}@cXB~N~_sHq7kr!QW5lI*`%cZ0`wVeI6ZE^zST6jgYjXsp#; zqtkUa2ey3C1mk|v>Fq_n#V9?!t+O=}a32AzBtuMPVCGe4>7;Ly9DC<;QrM`BSd{@&sVFaM z___bd#F6sX6AqFo|9wV2(TOZ}Gt4r9Bd)G(RB{R%lpL58&0euiPQK>_V^tqzD{UM^ z0_%w7HJnvWA2Z09pqtm#>#})=#+?NyR)LC#Sq2UzS3UclJThm2dB|`w!F#Z=3U(VH z-&)Syrn?dSba%uqVMdL97hwh8RNkz0#`4}&ETUaA`-gRUU!M(fn1PZBG4Fq19p)~J z)p-L>Eeq5E2#N=Gtdb$vHGy34(Cdzr4wQwG3?=dHMJwbFFVDguN)8dAvXMhKn?ubI z0{Bs@j_h5SAtPZoNq_{BNWfG8pddit^^Hk7q-mFOZDARB_`jh*{ozF4bL( z-2uA?=5?lGQ_^UX1+QdtvTCmOo4YRNHG$7CYx?_12dmkW$JiOUOjqB-m50y53^j|w zPD(hjn_#0Xc0~de4fA^J4%kUvlfQE4Sv${gl!BsBql#Whqv92MWn&@R6Y(9yZj#kA z-&7Tbw#(i9GtL&kMTE`W2u#AEe85u2G<7m3=2ey*QgbK5R7}kB zrt*U0Fwe3|m4biRh!8cd$TiRvbfB#L!a#5zpg=6#4**yg0^YhiOr zju_l0$Z7w&mp-uFRl>E{eTr4$xFpslSl-11!vE#CxYO(!36KB@kN^qf8UfP|e;Wbw6@f8bQBND)EjnyaFTAvsDm7=aDm-`(=k!#Fl^@#Jv&#`Wbu-Ju zcf%^??uNfUe9U0;dh=TAd-FJ+H>wjm1WxRKduz7wI@&*Q(D&+(p{bW*XU=G zk~;)KKe4$pc8-(;NPq-LfCMszz&ne6Fw9&|8#7oZY%K)gVzHwEe~b?5n7n>xMTlPl z2KV;D6>{N(;qs(LDuM$uLZd=Cx%14!jj_L@HuY9{Tib%1t&x&odk^1<7HY!vw-XOC zB0@od374z`*S$J#nc)wCNLENBv3yH#!Vi=&rK-^k3~9ejWdr70ztxHSkJKR=63%xb z)>U>hp~dfynjq&r^qOJf1BLBzb;vfI(9M1YJ|oFQ|4wbBORI8&Gs4gQ`_x05%)rcO zOM%xU`?bl*Zii*z`i8_QHyk}te*DPmO;M^Q_UkUEj2>v1vn|`aBW#zT=(Q>-HX>v# z-?GDqJ{7L;-Lt+iB52|MbZ%KL*S;__Bngc5T9{J(SHma6LI2%foGxcif2GMG-ddLL zvyT9kjdt1?K_S1YH z2-ymGbHNYBbGU!ti!jY5_lI5i7qs8MoHSPcqhW9{nTNpi$XuPrd((deuOa3ty!qU4 zH2S}f&RHl&58F!)AKY7({qYi`9)zQXKyia&VOQtVUwx;(4ijbJ^*?U92JB!42Ia`y zJ+bxx*ZlOL(M@AHbYO4Uc=HvyW8s$*?|wdH@@=+_9RaJDyBwxDT;TcteLPopyZRX8 zTJfjLn#O>!q0V1E^Ik+h3Mi7-J4}s^vXgIn(g?W=g%-~CX20=_dw;+!kA;}(A@%yM z>KggokrU)S_1!VY2zGdV%e$Y+%^D(^l6?fIcx2x_<%mdt1W3U31kThjr9at!q%lc( zO;^PK9gwWf^__3V4kEY1uMXQsuwwnckLDVV<8ab0FDjA;KAmUVv-dUc0d_Jvwk(xD zz5a<2b`{}Cf3)uqK{O=0)fqWOHkHZ_P(0rzmv93I1Y{-rZazMb>134XH5u{{4>;9MJszb-h5kvaw zSUkoK4)St9!TODceYM-Y`S*;^jg|ZpM-DK2XHXU)dBG364xek9b34Jr94X(e4(ss6 zuuH^;iTWE0mJ1X_t6-<#m3d2Lh9wCi%0Ur1`JNZ$>dE^WW^blKvSC+++^)GY?5?ra zAItj{>Rf*Kf1Z**Oqn42_UbAHo(6>t3K&wIn?Vk&b^ha>&t#s4{k`mfy#)aS%zF(b zW%c&ZB#qYV`93oVh^76%>uOVP`pEplE?eel#H_y-;#BOIDfpZ^xH7Gn$)(wyYA^{R0fgC45Wh2Kw z033cWftu11k@AMHDLA8hfr2b_1$BP$sL`#bUNd4*0SSi2cO;Mi0eJ3?@7v8$4M?Gl z6wVY9AOR8}fy^O5Wg~O$6?>X`0vFx){99pd0qWJ-)UTPaRP_+ijIH^a!nIHX-s1tK zghB!&kQf1k*@;EY(r`Dg&tDeHB`iwNMDSO>wb!)l;*m zrbdc#&s!Y0D?5nE@ zz^?;R^NdV@JFEHkZIbT9+0}pZ2ZgkO4i7>o)GHlY1#sWoa6x#AORA{aRQSE_m)>LKSjE=Zjm$+ zM-A;OZ(MPzbZk)?Q=L>iV#3A3B;cF?l#W^^>TnK>C3A{EnR+Wi;I-&P%yMN#v;=m- zi)BcF1agZqL7u69Gxc@)jBsB1tUU0VVj-aH{K~Q}zo3Dw$u$yN<(j8_!PryhGeV|iGF$wh zT|Z@_{QB7$vU>Y23m|;FEFZ9t5vVe3=2{)}P^-wp(ef4p}W zTV*4wZaxP?0wh2JNhE;p&Li}ZF8SlJ2gpm`E|p4efA1-$&~qQ?XP}pD7i&o-o*!Z7wIEYCQmvww?@v_# z9Pg_csVF2s0wh2J_5_gW58-grEfvr*K)Cq54u}*}W zucz>NsV*rNZ(WfTd#j}YEA#G07ZDh`?=3Dib~VzeWLP_e9TmT)RJ=uX%GPV_ept(- z`~3|^Pqc0Fw@2(Jcu+`y1W14cq7yi2?*Y*ZrJzLrj%@^fck|Y64qH2SHo~Ie2@*)0 zz}lmG{oQH{0AC1?`YyOt{2JM!y0}ywmYY%Iu)slIs!B?YmHFc8YQy2&QbgZQtDhQr z(0xa+eN$6XBKRP+x>-E=zg)FJ?)vcSn2{`8za{2tcmWBJ010F} z0jG(MQo#XjQ~O8-S=LDcu@G?4L^H74*wTQe;Y&f#?!a~ud5z`y%Cd4PuB}mftIV;r z!j?w0PUxX*xK(_rY5F|M*4xN7jUAo2WR>7SApsJIj{stzA}PX_imHee^k~ykkjQ3d zwO7{ftOzPwq~EWtN{eLk?#OkNXj@34v$e81=z8lLB!DQ@$F?Zttk(s;M~drGZEM|* z-GPUrzQ$O3wrwe%#+5USDO!J{R@ztqWACn6^uqm=Z_>7`W5&Jd*1FuN;rmV7rOvYI zh*-88c2$Vwu+uIDmBW~kD5JS#@9v!AJHXdu;V5>G?P>M>clx;G8`C{!uj+Z}u z%%G4=TJ>s7==IeZ3YlGe;6P-UWF0-cpLq21_Uim)QXRobXP;hOwB~yGV&#UwDvZ~d zFqG<~^wZ^QgU$`^+)gI;QxEPiQtPjiXTM$?60YW~eTEnl&%n#jc8!Klo3VIR%#+E$ zdiYx8uCtDi!!@1s;2ZCk-8H%p6GraVa{KSq-3a|Q>Msw9FCZrQKTkeHP8~f^_L_RH zR7LpQ+;`4Va=>0aWYCTGMf`l-ujR(|?kfGK-5aJi0f&dzbUfH<3lMPn=ZB1uscLsw zpgXb8f3rk>^5`4l*ZAMj1fiP1rd?S{Q_<~JHPZKnyOT1pfAXT^gZ1Vz!yYGnT$00SQ)T?W9^K>E}G~ioCOEh23dEc(0c(JE_Uje(su0a`=CqGA8$S z7d&v@6oH~J`mYa$oWAqS!=;mkp0Q3Ep>*7G%A}C;fd?>|ef;x)3&528ffbg?Zdjp9U|;pKna5h(E>H`tk>W7MH{vT zb}8ibPY)awq-=l@)}`WQWo@9f?hG}jv<*=5?D|1v!;T7%kwB6Oq)3NFanjo-VPtB}^6k39ze? z4FYfS5_NPg)MOLsmrvfU{c#vu0R54zHo{V6uF_%hG!(lMRqRyxNVQ@&WmrGCTvz`- z_3aw(seZ8lAj9G<$wXb7yJ4`-F)Pj7i?rlgAk*Cs#lBzUoE+%SQe9{yJCeK^c|VhuQtNz6Y^Iz-^Z z0Gnw%@WYP@)Whc=BbfA#>DjsI9L}Gq6Z|U<8Xee`W_|5xRFbTnI8AprFh2NEEK&0j zINClmd!BSv2UmU20|$Z0*fTn?XKnA^R%@CU;TZgh{%QE}Wg zt05}fPi;Qik#;#?x&@{2Z8dM|rk^b*4$9G1y{(;v(hTS4f4noN$-x>65ZK+gX7avG z3k8AKovBL5@gw^SBHms1V(<=OV7cag{mQ(hvTEBd^_dwYFkwrDARs`vouE{-XRUMP zww=~PLGP(-1mWNt5=cIQd|k0@ZZQ&VWU#4EBz~9_`096X!kENgb?8`u`BWr9 z5dGQLoD;JG7pYF`IdEKf{Y62K?{4_9AQI8bVa%|sb?|*K0j%U>S7_GvtDDTD0(T#R zOYeF2nmQ}5*t%0r&W(bszWD3$%pQaP>Wt+FlGhcnz zR5khvGp1hZh>XApR)IMoXRZA+-^1ka-jBX9oVkBIain=Tsn77qTD)Of zQtOPRGP`iNPu_N}+K=^-DU&Ci8dw(T^T^~rh<-wK~i|KQAxT_8Bh!VJX}>~z4<7CRjQkj9mdIiU<5|?w<9Hv15RpLni`15y)E$R~S1gczx8chGTf03>m6E4%p#% zW!|!oh*bD|Lq_ZFqgC4KcM}9Vd~Oyj8$76sCzWdQc+rFr#)5$PtrTf@Db_K4uyl2- zmn5zHhpGfel7dJld5#1~ATa{@x*hDPYrb6$zeeqVxlgu>k@rF!p35pLG`wqiMw?4) z;Y^+mwpi_)u1W?b(m!n3A?H5$ntZ!u#rRG$~HvyBPu|2yO5s#wf z)PNtv?~dG0E*L*t{{Hrys12Gs8gHpm14S0A*)UH;Py_2iL11MKO1`r%V>7fba#L8P zYpaIcMmzrm>t_|$Uwim|8vG!?$t(+gO>QvV+o9okA6NggA5R=1r)W50Gk;3p-=1~s zvA%yvBb8ltz}~T5mT-BPbA7&YeZ+dWvk?&wPm(}l1PrHUXWzGpHDq%&nyprIBTU3| zL9qIsF2%}FZq%VV%%XYL;Rf3pO%Rz%F(NHpb(W!Mz$X2We?JkVXqZ3B$yN@mq5X~e z&+XFnK08~0R00U78{tmPi9McSr;c3;II&-;yF6Agb9Ic-NuX6QF@&s&Vqa~&F4}vT zx?J__dtpuDB3=Ktw`QxeewE=PV>*matDhR_5}pf30MQgC_UmRi2w+wXTdB`htT(*J z4!TXbKN4?-ov5?yvR+l<8Gdd z_4$+KYh>}p^l$rM8^@aE(Asxyby$h;JqaX<05Yl-)z5|v2DyH z2YqVimx%XU+k-K46rM#_LoD(rMyJ%%U(l+hFPDQ&8=aKRnTO9v6 z{V-$O1KH3hu@jgypqF8ngRQOw>L?Q^*aER8MF~7_evn{W1-q(Cby*xv6XE1ARVZNZ zga`%JwqV=!bb#|kxH_C1hu8Bh?|yDXhxpZD`!t2ZI&tIxL9z|lR@qIWt)E*soM8J6 zwupC}akvpB!b$t-Dg*nkcfMO;WN$oBW9%kd@ZELGyPp|Z5R+YZxcZ>H-mOYhG;xp0 z7pQ&enntuJ(Wzhrd`3! zCmw7}KJlY<+W%fMY(1)E@h|;x4wP`^1Iwi1HSE05 zr@#kn%d9NSwy{c$P(E;axkkhMm~!Q$u}1QQf4}#+5z5Fs-dxYfjAja)OR$QKc*vN@ zQer1CrO|-`30U9_Za$!_9;z{sP3NdcHN#8$P0ip32OexOh7;3%YlcQxFx$a1?4Ypq z21O)U^2nS8#_q;BQE%E)xUYHnBS~hV|HTqw+eed{0&t##!e6T_}NvF_G5Q^ z@RiZ#`D)je3VBLn3R`0yr=lIR&YRUX4@q~T36+g#gfi*;qQ%ElG@~s*CZS4aUp=~N zQ>30^%_90+;6+ODAbMjm>#RQ+^j5o+!FqsGhm~8q;Bge)}UF+_s8M_cLXY<&E_(`OPwFQMaXQpQ51!V({N{Eb*h(G~2 zO(#s4aN3263atCc+n-k5IWMY>aNsrOYfQ#_{QfS-!Rj}vDN^t2?+yu>W7e0d=Xh3Q zVTytT1~4D_=gEg?C>)=Z=w$BSn&umP?F32}LQWw=;4#DZGK!q1$vZrHA231tdi6%T z%Zy`4T!O?5R;kr~VAb5tOV?`GJx9IWpZs!>k@V!^{r47g)z?}l--i-FA`DD!VPa(c zn?cwrq?^Z12h8mP#oP@Dv__nzVT}-(0pZH*1lcy*mzN3_1+4cFZ6TZHp@Vt}?x&p~ zF0!3qcXRNK_l5KyWgnisKz?@c7{h@HPE7bb!TBeg{O{wrf>_%xTzZmi5q!3=8<-4v z;-cdW9*4j3o4t;H{Yq_*X5dmefl|5 z48NXa)@|0cM*VFrc=&b4e5q`htXZ*=H9?8iFY`QBbxr>i^O)0TcyS}v zrfH&tRnrF|>}ve}r4QwuUz{#C96ix!0{oG50kMc7zm1SZ4dIROuoFyftiA(Yoh*87 z6#i+d3>~Wp7T`mWW@W>F(#bDmg)IR5E}qbPk3 z-E?oUBM>OdH}5bKW8BfW8xnZ#wfW2CF?Bx2ioI2^^1Y?P-rNQ|c~}St_xF$f9ed|< znsjC#-32I?|LHjGd^pTDk=zA~7f@1K1pEdf83y_`{gd!JeP$g_pI={~@q5Fp4q#mP z8?*|X?2X-Ay*GjKl-@&_t`+KzPO9SjVs{9R|F@oUXwsDr?4q7@&kM$GYTпk4W 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 literal 0 HcmV?d00001 diff --git a/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino b/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino new file mode 100644 index 0000000..b4a5481 --- /dev/null +++ b/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino @@ -0,0 +1,56 @@ +#include + +IRSender irSender(3); // IR led on Duemilanove digital pin 3 + +HisenseHeatpumpIR *heatpumpIR; + +void setup() +{ + + Serial.begin(9600); + delay(500); + heatpumpIR = new HisenseHeatpumpIR(); + Serial.println(F("Starting")); + + + +} + +void loop() +{ + int i = 0; + prog_char* buf; + + + // Send the same IR command to all supported heatpumps + Serial.print(F("Sending IR to ")); + // Print the model + buf = (prog_char*)heatpumpIR->model(); + // 'model' is a PROGMEM pointer, so need to write a byte at a time + while (char modelChar = pgm_read_byte(buf++)) + { + Serial.print(modelChar); + } + Serial.print(F(", info: ")); + // Print the info + buf = (prog_char*)heatpumpIR->info(); + // 'info' is a PROGMEM pointer, so need to write a byte at a time + while (char infoChar = pgm_read_byte(buf++)) + { + Serial.print(infoChar); + } + Serial.println(); + + + heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_3, 30, VDIR_AUTO, HDIR_AUTO); + delay(1500); + heatpumpIR->send(irSender, POWER_ON, MODE_DRY, FAN_2, 20, VDIR_AUTO, HDIR_AUTO); + delay(1500); + heatpumpIR->send(irSender, POWER_ON, MODE_FAN, FAN_AUTO, 18, VDIR_AUTO, HDIR_AUTO); + delay(1500); + heatpumpIR->send(irSender, POWER_ON, MODE_COOL, FAN_1, 18, VDIR_AUTO, HDIR_AUTO); + delay(2500); + heatpumpIR->send(irSender, POWER_OFF, NULL, NULL, NULL, NULL, NULL); + delay(1500); + +} diff --git a/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino b/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino new file mode 100644 index 0000000..efd577d --- /dev/null +++ b/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino @@ -0,0 +1,92 @@ +#include // https://github.com/ToniA/arduino-heatpumpir +#include // http://playground.arduino.cc/Code/Button + +/* + +This is a project I did for my my friend's summer cottage. He has an alarm system +with some extra relays which can be controlled via GSM SMS messages. With two relays +we can have four heatpump states: +* relay 1 OFF and relay 2 in any state: heatpump OFF +* relay 1 ON and relay 2 OFF: heatpump ON, normal heating state +* relay 1 ON and relay 2 ON: heatpump ON, maintenance heating (FP mode on Midea's remote) + +The heatpump is 'Ultimate 12 Pro Plus Inverter', sold by ultimatemarket.com: +Midea MSR1U-12HRDN1-QRC4W + MOB-12HFN1-QRC4W, with remote control RG51I20/BGE + +This same sketch would also control the older 'Ultimate Pro Plus 13FP': +Midea MSR1-12HRN1-QC2 + MOA1-12HN1-QC2 heatpump control, with remote control RG51M1/E + +... and very likely many other Midea heatpumps... + + +The Midea 'FP', or 'maintenance mode', is not really a mode even if the HeatpumpIR library +interface makes it look like a mode. Instead it's a switch between the 'normal' and 'FP' +mode, so to be absolutely sure about the operating mode, we need to turn the heatpump OFF +first, and then turn it on (goes into 'normal' mode), and finally switch into 'FP'. + +The IR led is driven by a transistor amplifier. I used 2 ohms as the resistor. I might sound +very small, but also the led operates on very short bursts. For example Panasonic uses 2 ohm +resistor for the IR led on its remotes. +*/ + +// Heatpump states +const byte heatpumpOff = 0; +const byte heatpumpNormal = 1; +const byte heatpumpMaintenance = 2; + +Button relay1 = Button(11,PULLUP); // Heatpump ON-OFF state +Button relay2 = Button(12,PULLUP); // FP mode (maintenance heating at 8 degrees C) ON-OFF state + +IRSender irSender(9); // IR led on Duemilanove digital pin 9 + +byte heatpumpState = heatpumpOff; + +HeatpumpIR *heatpumpIR = new MideaHeatpumpIR(); + +void setup(){ + Serial.begin(9600); + delay(500); + Serial.println(F("Starting...")); +} + +void loop(){ + + if (relay1.isPressed()) { + // Normal heating operation + if (!relay2.isPressed() && heatpumpState != heatpumpNormal) { + Serial.println(F("Normal heating operation")); + + Serial.println(F("* Sending power OFF")); + heatpumpIR->send(irSender, POWER_OFF, MODE_HEAT, FAN_2, 22, VDIR_UP, HDIR_AUTO); + delay(15 * 1000); // 15 seconds, to allow full shutdown + + Serial.println(F("* Sending normal heat command")); + heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_2, 22, VDIR_UP, HDIR_AUTO); + heatpumpState = heatpumpNormal; + + // Maintenance heating operation + } else if (relay2.isPressed() && heatpumpState != heatpumpMaintenance) { + Serial.println(F("Maintenance heating operation")); + + Serial.println(F("* Sending power OFF")); + heatpumpIR->send(irSender, POWER_OFF, MODE_HEAT, FAN_2, 22, VDIR_UP, HDIR_AUTO); + delay(15 * 1000); // 15 seconds, to allow full shutdown + + Serial.println(F("* Sending normal heat command")); + heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_2, 22, VDIR_UP, HDIR_AUTO); + delay(5 * 1000); // 15 seconds, to allow full start before switching to maintenance + + Serial.println(F("* Switching to maintenance heating")); + heatpumpIR->send(irSender, POWER_ON, MODE_MAINT, FAN_3, 10, VDIR_UP, HDIR_AUTO); + + heatpumpState = heatpumpMaintenance; + } + // Power OFF + } else if (!relay1.isPressed() && heatpumpState != heatpumpOff) { + heatpumpState = heatpumpOff; + Serial.println(F("Sending power OFF")); + heatpumpIR->send(irSender, POWER_OFF, MODE_HEAT, FAN_2, 22, VDIR_UP, HDIR_AUTO); + } + + delay(500); // 0.5 seconds delay +} diff --git a/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino b/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino new file mode 100644 index 0000000..366e79c --- /dev/null +++ b/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino @@ -0,0 +1,204 @@ +// HeatpumpIR libraries +#include +#include +#include +#include +#include +#include +#include + +// Timer library, https://github.com/ToniA/Timer +#include + +// MySensors libraries +#include +#include + +// This MySensors node has three childs, with ID's 1, 2 & 3 +#define CHILD_1 1 +#define CHILD_2 2 +#define CHILD_3 3 + +// MySensors definitions +MySensor gw; +MyMessage irMsg(CHILD_1, V_IR_RECEIVE); +// Domoticz does not yet properly support the S_IR sensor type +MyMessage textMsg(CHILD_2, V_TEXT); +MyMessage sendMsg(CHILD_3, V_LIGHT); + +// Array with all supported heatpump models +HeatpumpIR *heatpumpIR[] = { new PanasonicCKPHeatpumpIR(), // 0, keep this if you don't remove the timer for cancelling Panasonic CKP messages + new PanasonicDKEHeatpumpIR(), // 1 + new PanasonicJKEHeatpumpIR(), // 2 + new PanasonicNKEHeatpumpIR(), // 3 + new CarrierHeatpumpIR(), // 4 + new MideaHeatpumpIR(), // 5 + new FujitsuHeatpumpIR(), // 6 + new MitsubishiFDHeatpumpIR(), // 7 + new MitsubishiFEHeatpumpIR(), // 8 + new SamsungHeatpumpIR() // 9 + }; + +// IR led on PWM output-capable digital pin 3 +IRSender irSender(3); + +// Timer for sending Panasonic CKP series timer cancellation commands +Timer timer; +int8_t panasonicCKPTimer = 0; + +// Number of supported models +byte models = 0; + +void setup() +{ + Serial.begin(115200); + delay(1000); + Serial.println(F("HeatpumpIR sensor starting up...")); + + models = sizeof(heatpumpIR) / sizeof(HeatpumpIR*); + Serial.print(F("Number of supported models: ")); Serial.println(models); + + // Auto-assign the node ID, do not forward messages + gw.begin(incomingMessage, AUTO, false); + + // Send the sketch version information to the gateway and Controller + gw.sendSketchInfo("Heatpump Sensor", "1.0"); + + // Register a sensors to the MySensors Gateway + gw.present(CHILD_1, S_IR, "IR sender"); + + // For Domoticz + gw.present(CHILD_2, S_INFO, "IR data"); + gw.present(CHILD_3, S_LIGHT, "IR send"); + // The TEXT sensor is not created in Domoticz before data is sent + gw.send(textMsg.setSensor(CHILD_2).set("00000000")); +} + + +void loop() +{ + gw.process(); + timer.update(); +} + + +// Handle incoming messages from the MySensors Gateway +void incomingMessage(const MyMessage &message) { + + const char *irData; + long irCommand; + + // V_IR type message + if (message.type==V_IR_SEND) { + Serial.println(F("Received IR send command...")); + irData = message.getString(); + Serial.print(F("Code: 0x")); + Serial.println(irData); + sendHeatpumpIRCommand(irData); + } + // Domoticz + else if (message.type==V_LIGHT) { + // When the button is pressed on Domoticz, request the value of the TEXT sensor + Serial.println(F("Requesting IR code from Domoticz...")); + gw.request(CHILD_2, V_TEXT, 0); + } else if (message.type==V_TEXT) { + // TEXT sensor value is received as a result of the previous step + Serial.println(F("IR code received from Domoticz...")); + Serial.print(F("Code: 0x")); + + irData = message.getString(); + Serial.println(irData); + + sendHeatpumpIRCommand(irData); + + // Set the Domoticz switch back to 'OFF' state + gw.send(sendMsg.setSensor(CHILD_3).set(0)); + } +} + + +// Decode the IR command and send the IR command to the heatpump +void sendHeatpumpIRCommand(const char *irCommandString) { + + // irCommandString is an 8-digit hex digit + long irCommand = 0; + int sscanfStatus = sscanf(irCommandString, "%lx", &irCommand); + + if (sscanfStatus == 1) { + Serial.print(F("IR code conversion OK: 0x")); + Serial.println(irCommand, HEX); + } else { + Serial.println(F("Failed to convert IR hex code to number")); + } + +/* +The heatpump command is packed into a 32-bit hex number, see +libraries\HeatpumpIR\HeatpumpIR.h for the constants + +12345678 + 3 MODEL + 4 POWER + 5 OPERATING MODE + 6 FAN SPEED + 78 TEMPERATURE IN HEX + +00213416 (as an example of a valid code) + 2 = PanasonicJKE + 1 = Power ON + 3 = COOL + 4 = FAN 4 + 16 = Temperature 22 degrees (0x16 = 22) + */ + + byte model = (irCommand & 0x00F00000) >> 20; + byte power = (irCommand & 0x00010000) >> 16; + byte mode = (irCommand & 0x0000F000) >> 12; + byte fan = (irCommand & 0x00000F00) >> 8; + byte temp = (irCommand & 0x000000FF); + + prog_char* buf; + Serial.print(F("Model: ")); + + buf = (prog_char*)heatpumpIR[model]->model(); + // 'model' is a PROGMEM pointer, so need to write a byte at a time + while (char modelChar = pgm_read_byte(buf++)) + { + Serial.print(modelChar); + } + Serial.println(); + + Serial.print(F("Model #: ")); Serial.println(model); + Serial.print(F("Power: ")); Serial.println(power); + Serial.print(F("Mode: ")); Serial.println(mode); + Serial.print(F("Fan: ")); Serial.println(fan); + Serial.print(F("Temp: ")); Serial.println(temp); + + // Heatpump models start from 0, i.e. model number is always less than the number of different models + if (model < models) { + // This is a supported model + + // Cancel the timer on Panasonic CKP heatpump + if (model == 0) { + Serial.println(F("Cancelling timer on Panasonic CKP heatpump...")); + timer.stop(panasonicCKPTimer); + } + + Serial.println(F("All OK - sending IR command to heatpump...")); + heatpumpIR[model]->send(irSender, power, mode, fan, temp, VDIR_UP, HDIR_AUTO); + + if (model == 0) { + Serial.println(F("Scheduling timer cancellation on Panasonic CKP heatpump...")); + panasonicCKPTimer = timer.after(120000, panasonicCancelTimer, (void *)NULL); // Called after 2 minutes + } + } +} + + +// Cancel the timer on the Panasonic CKP heatpump +void panasonicCancelTimer(void *context) +{ + PanasonicCKPHeatpumpIR *panasonicCKPHeatpumpIR = new PanasonicCKPHeatpumpIR(); + + panasonicCKPHeatpumpIR->sendPanasonicCKPCancelTimer(irSender); + Serial.println(F("The TIMER led should now be OFF")); +} diff --git a/libraries/HeatpumpIR/examples/MySensorsNode/README.md b/libraries/HeatpumpIR/examples/MySensorsNode/README.md new file mode 100644 index 0000000..2f53ec8 --- /dev/null +++ b/libraries/HeatpumpIR/examples/MySensorsNode/README.md @@ -0,0 +1,73 @@ +# MySensors example + +This example shows how HeatpumpIR could be integrated with the MySensors sensor network. As this sensor sends infrared signals, +the natural sensor type is 'S_IR'. The heatpump/air conditioner signals are really long, so the signals need to be encoded somehow. + +In this example the idea is to encode just this information, using 24 bits of data +* model code (see the index of the array heatpumpIR in the code) +* power state (for the rest, see the HeatpumpIR.h file for the constants) +* operating mode +* fan speed +* temperature (the temperature is directly the temperature in HEX) + +``` +An example: +00213416 (as an example of a valid code) + 2 = PanasonicJKE + 1 = Power ON + 3 = COOL + 4 = FAN 4 + 16 = Temperature 22 degrees (0x16 = 22) +``` + +This sketch creates three sensors: +* S_IR +* S_INFO +* S_LIGHT + +The S_INFO and S_LIGHT sensors are for Domoticz, as it does not currently (as of V2.3093) support infrared code sending through the S_IR sensor type. + +## Sending the code +### MYSController + +* Select the IR sensor on the 'Heatpump Sensor 1.0' node +* Select 'IR_SEND' as the subtype +* Enter for example '00213416' as the payload + +### Domoticz + +As Domoticz does not really support sending IR code yet, the workaround is to use two sensors. When the node is connected, Domoticz should find two new devices +* A 'Lighting 2 / AC' type sensor with name 'IR send' +* A 'General / Text' type sensor with name 'Unknown' and Data '00000000' + +To send a command, first add the sensors. Then update the data of the text sensor to contain the IR code to be sent, and then flip the switch to trigger the sending. +The data can be updated through the JSON REST API, or through the Lua event scripts. + +REST API example (assuming that the idx of the text sensor is 105 and the switch is 104, and Domoticz is at 192.168.0.4:8080): +``` +http://192.168.0.4:8080/json.htm?type=command¶m=udevice&idx=105&nvalue=0&svalue=00213416 +http://192.168.0.4:8080/json.htm?type=command¶m=switchlight&idx=104&switchcmd=On +``` + +Lua example: +* Seems that the text sensor can't be updated through the commandArray... +* So now when a device called 'trigger' changes, the text sensor value is first updated to '00213416', and then the IR signal is sent + +``` +commandArray = {} + +for key, value in pairs(devicechanged) do + if (key == 'trigger') then + + print("Heatpump script") + commandArray['OpenURL']='http://192.168.0.4:8080/json.htm?type=command¶m=udevice&idx=105&nvalue=0&svalue=00213416' + commandArray['IR send']='On' + end +end + +return commandArray +``` + +## Hardware + +Use any ATMega328-based Arduino, connect the radio, and finally connect an infrared led (in series with a resistor, like 100 Ohm) between digital pin 3 and GND. \ No newline at end of file diff --git a/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino b/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino new file mode 100644 index 0000000..680026c --- /dev/null +++ b/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino @@ -0,0 +1,56 @@ +#include +#include + +/* + This schema demonstrates how to control the Panasonic CKP power state change. The CKP does not have discrete + 'ON' and 'OFF' commands, but only a state switch. So, if the initial power state is now known, switching the + state does not help much. + + Luckily this can be implemented by using the timer: + * The 'send' command will send all the settings AND program the timer so that the pump will turn ON in a minute + * The 'sendPanasonicCKPCancelTimer' will cancel the timer + + The 'turn OFF' must be implemented the same way. + + Of course you can choose to not turn off the timer, but that means that the heatpump will attempt to switch ON + (or OFF) every day at the same time, as the timer will still be active. +*/ + + +IRSender irSender(3); // IR led on Duemilanove digital pin 3 + +PanasonicCKPHeatpumpIR *heatpumpIR; + +Timer timer; + +void setup() +{ + Serial.begin(9600); + delay(500); + + heatpumpIR = new PanasonicCKPHeatpumpIR(); + + Serial.println("Turning the Panasonic CKP heatpump ON by using the timer"); + heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_2, 24, VDIR_UP, HDIR_AUTO); + Serial.println("The heatpump should have beeped, and the TIMER led should be ON"); + + timer.after(60000, panasonicIsOn, NULL); // Called after 1 minute + timer.after(120000, panasonicCancelTimer, NULL); // Called after 2 minutes + +} + +void loop() +{ + timer.update(); +} + +void panasonicIsOn(void *context) +{ + Serial.println("The heatpump should should turn ON by now, the TIMER led is still ON"); +} + +void panasonicCancelTimer(void *context) +{ + heatpumpIR->sendPanasonicCKPCancelTimer(irSender); + Serial.println("The TIMER led should now be OFF"); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/examples/simple/simple.ino b/libraries/HeatpumpIR/examples/simple/simple.ino new file mode 100644 index 0000000..b5d0808 --- /dev/null +++ b/libraries/HeatpumpIR/examples/simple/simple.ino @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include +#include + +IRSender irSender(3); // IR led on Duemilanove digital pin 3 + +// Array with all supported heatpumps +HeatpumpIR *heatpumpIR[] = {new PanasonicCKPHeatpumpIR(), new PanasonicDKEHeatpumpIR(), new PanasonicJKEHeatpumpIR(), + new PanasonicNKEHeatpumpIR(), new CarrierHeatpumpIR(), new MideaHeatpumpIR(), + new FujitsuHeatpumpIR(), new MitsubishiFDHeatpumpIR(), new MitsubishiFEHeatpumpIR(), + new SamsungHeatpumpIR(), NULL}; + +void setup() +{ + Serial.begin(9600); + delay(500); + + Serial.println(F("Starting")); +} + +void loop() +{ + int i = 0; + prog_char* buf; + + do { + // Send the same IR command to all supported heatpumps + Serial.print(F("Sending IR to ")); + + buf = (prog_char*)heatpumpIR[i]->model(); + // 'model' is a PROGMEM pointer, so need to write a byte at a time + while (char modelChar = pgm_read_byte(buf++)) + { + Serial.print(modelChar); + } + Serial.print(F(", info: ")); + + buf = (prog_char*)heatpumpIR[i]->info(); + // 'info' is a PROGMEM pointer, so need to write a byte at a time + while (char infoChar = pgm_read_byte(buf++)) + { + Serial.print(infoChar); + } + Serial.println(); + + // Send the IR command + heatpumpIR[i]->send(irSender, POWER_ON, MODE_HEAT, FAN_2, 24, VDIR_UP, HDIR_AUTO); + delay(500); + } + while (heatpumpIR[++i] != NULL); + + delay(2000); +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/keywords.txt b/libraries/HeatpumpIR/keywords.txt new file mode 100644 index 0000000..b7e0632 --- /dev/null +++ b/libraries/HeatpumpIR/keywords.txt @@ -0,0 +1,26 @@ +HeatpumpIR KEYWORD1 +PanasonicHeatpumpIR KEYWORD1 +PanasonicCKPHeatpumpIR KEYWORD1 +PanasonicDKEHeatpumpIR KEYWORD1 +PanasonicJKEHeatpumpIR KEYWORD1 +PanasonicNKEHeatpumpIR KEYWORD1 +CarrierHeatpumpIR KEYWORD1 +MideaHeatpumpIR KEYWORD1 +FujitsuHeatpumpIR KEYWORD1 +MitsubishiHeatpumpIR KEYWORD1 +MitsubishiFDHeatpumpIR KEYWORD1 +MitsubishiFEHeatpumpIR KEYWORD1 +SamsungHeatpumpIR KEYWORD1 + +IRSender KEYWORD1 + +setFrequency KEYWORD2 +sendIRByte KEYWORD2 +bitReverse KEYWORD2 +space KEYWORD2 +mark KEYWORD2 + +send KEYWORD2 +model KEYWORD2 +info KEYWORD2 +sendPanasonicCKPCancelTimer KEYWORD2 diff --git a/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino b/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino new file mode 100644 index 0000000..7ef8f5c --- /dev/null +++ b/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino @@ -0,0 +1,209 @@ +// HeatpumpIR libraries, https://github.com/ToniA/arduino-heatpumpir +#include +#include +#include +#include +#include +#include +#include + +// Timer library, https://github.com/JChristensen/Timer +#include + +// MySensors libraries +#include +#include + +// Child ID's of this node +#define CHILD_1 1 + +// MySensors definitions +MySensor gw; +MyMessage irMsg(CHILD_1, V_IR_RECEIVE); + +// -- Hacks for Domoticz start +#define CHILD_2 2 +#define CHILD_3 3 +// Domoticz does not yet properly support the S_IR sensor type +MyMessage textMsg(CHILD_2, V_TEXT); +MyMessage sendMsg(CHILD_3, V_LIGHT); +// -- Hacks for Domoticz end + +// Array with all supported heatpump models +HeatpumpIR *heatpumpIR[] = { new PanasonicCKPHeatpumpIR(), // 0, keep this if you don't remove the timer for cancelling Panasonic CKP messages + new PanasonicDKEHeatpumpIR(), // 1 + new PanasonicJKEHeatpumpIR(), // 2 + new PanasonicNKEHeatpumpIR(), // 3 + new CarrierHeatpumpIR(), // 4 + new MideaHeatpumpIR(), // 5 + new FujitsuHeatpumpIR(), // 6 + new MitsubishiFDHeatpumpIR(), // 7 + new MitsubishiFEHeatpumpIR(), // 8 + new SamsungHeatpumpIR() // 9 + }; + +// IR led on PWM output-capable digital pin 3 +IRSender irSender(3); + +// Timer for sending Panasonic CKP series timer cancellation commands +Timer timer; +int8_t panasonicCKPTimer = 0; + +// Number of supported models +byte models = 0; + +void setup() +{ + Serial.begin(115200); + delay(1000); + Serial.println(F("HeatpumpIR sensor starting up...")); + + models = sizeof(heatpumpIR) / sizeof(HeatpumpIR*); + Serial.print(F("Number of supported models: ")); Serial.println(models); + + // Auto-assign the node ID, do not forward messages + gw.begin(incomingMessage, AUTO, false); + + // Send the sketch version information to the gateway and Controller + gw.sendSketchInfo("Heatpump Sensor", "1.0"); + + // Register a sensors to the MySensors Gateway + gw.present(CHILD_1, S_IR, "IR sender"); + + // -- Hacks for Domoticz start + gw.present(CHILD_2, S_INFO, "IR data"); + gw.present(CHILD_3, S_LIGHT, "IR send"); + // The TEXT sensor is not created in Domoticz before data is sent + gw.send(textMsg.setSensor(CHILD_2).set("00000000")); + // -- Hacks for Domoticz end +} + + +void loop() +{ + gw.process(); + timer.update(); +} + + +// Handle incoming messages from the MySensors Gateway +void incomingMessage(const MyMessage &message) { + + const char *irData; + long irCommand; + + // V_IR type message + if (message.type==V_IR_SEND) { + Serial.println(F("Received IR send command...")); + irData = message.getString(); + Serial.print(F("Code: 0x")); + Serial.println(irData); + sendHeatpumpIRCommand(irData); + } + // -- Hacks for Domoticz start + else if (message.type==V_LIGHT) { + // When the button is pressed on Domoticz, request the value of the TEXT sensor + Serial.println(F("Requesting IR code from Domoticz...")); + gw.request(CHILD_2, V_TEXT, 0); + } else if (message.type==V_TEXT) { + // TEXT sensor value is received as a result of the previous step + Serial.println(F("IR code received from Domoticz...")); + Serial.print(F("Code: 0x")); + + irData = message.getString(); + Serial.println(irData); + + sendHeatpumpIRCommand(irData); + + // Set the Domoticz switch back to 'OFF' state + gw.send(sendMsg.setSensor(CHILD_3).set(0)); + } + // -- Hacks for Domoticz end +} + + +// Decode the IR command and send the IR command to the heatpump +void sendHeatpumpIRCommand(const char *irCommandString) { + + // irCommandString is an 8-digit hex digit + long irCommand = 0; + int sscanfStatus = sscanf(irCommandString, "%lx", &irCommand); + + if (sscanfStatus == 1) { + Serial.print(F("IR code conversion OK: 0x")); + Serial.println(irCommand, HEX); + } else { + Serial.println(F("Failed to convert IR hex code to number")); + } + +/* +The heatpump command is packed into a 32-bit hex number, see +libraries\HeatpumpIR\HeatpumpIR.h for the constants + +12345678 + 3 MODEL + 4 POWER + 5 OPERATING MODE + 6 FAN SPEED + 78 TEMPERATURE IN HEX + +00213416 (as an example of a valid code) + 2 = PanasonicJKE + 1 = Power ON + 3 = COOL + 4 = FAN 4 + 16 = Temperature 22 degrees (0x16 = 22) + */ + + byte model = (irCommand & 0x00F00000) >> 20; + byte power = (irCommand & 0x00010000) >> 16; + byte mode = (irCommand & 0x0000F000) >> 12; + byte fan = (irCommand & 0x00000F00) >> 8; + byte temp = (irCommand & 0x000000FF); + + prog_char* buf; + Serial.print(F("Model: ")); + + buf = (prog_char*)heatpumpIR[model]->model(); + // 'model' is a PROGMEM pointer, so need to write a byte at a time + while (char modelChar = pgm_read_byte(buf++)) + { + Serial.print(modelChar); + } + Serial.println(); + + Serial.print(F("Model #: ")); Serial.println(model); + Serial.print(F("Power: ")); Serial.println(power); + Serial.print(F("Mode: ")); Serial.println(mode); + Serial.print(F("Fan: ")); Serial.println(fan); + Serial.print(F("Temp: ")); Serial.println(temp); + + // Heatpump models start from 0, i.e. model number is always less than the number of different models + if (model < models) { + // This is a supported model + + // Cancel the timer on Panasonic CKP heatpump + if (model == 0) { + Serial.println(F("Cancelling timer on Panasonic CKP heatpump...")); + timer.stop(panasonicCKPTimer); + } + + Serial.println(F("All OK - sending IR command to heatpump...")); + heatpumpIR[model]->send(irSender, power, mode, fan, temp, VDIR_UP, HDIR_AUTO); + + if (model == 0) { + Serial.println(F("Scheduling timer cancellation on Panasonic CKP heatpump...")); + panasonicCKPTimer = timer.after(120000, panasonicCancelTimer); // Called after 2 minutes + } + } +} + + +// Cancel the timer on the Panasonic CKP heatpump +void panasonicCancelTimer() +{ + PanasonicCKPHeatpumpIR *panasonicCKPHeatpumpIR = new PanasonicCKPHeatpumpIR(); + + panasonicCKPHeatpumpIR->sendPanasonicCKPCancelTimer(irSender); + Serial.println(F("The TIMER led should now be OFF")); +} \ No newline at end of file diff --git a/libraries/MySensors/examples/HeatpumpIRController/README.md b/libraries/MySensors/examples/HeatpumpIRController/README.md new file mode 100644 index 0000000..2f53ec8 --- /dev/null +++ b/libraries/MySensors/examples/HeatpumpIRController/README.md @@ -0,0 +1,73 @@ +# MySensors example + +This example shows how HeatpumpIR could be integrated with the MySensors sensor network. As this sensor sends infrared signals, +the natural sensor type is 'S_IR'. The heatpump/air conditioner signals are really long, so the signals need to be encoded somehow. + +In this example the idea is to encode just this information, using 24 bits of data +* model code (see the index of the array heatpumpIR in the code) +* power state (for the rest, see the HeatpumpIR.h file for the constants) +* operating mode +* fan speed +* temperature (the temperature is directly the temperature in HEX) + +``` +An example: +00213416 (as an example of a valid code) + 2 = PanasonicJKE + 1 = Power ON + 3 = COOL + 4 = FAN 4 + 16 = Temperature 22 degrees (0x16 = 22) +``` + +This sketch creates three sensors: +* S_IR +* S_INFO +* S_LIGHT + +The S_INFO and S_LIGHT sensors are for Domoticz, as it does not currently (as of V2.3093) support infrared code sending through the S_IR sensor type. + +## Sending the code +### MYSController + +* Select the IR sensor on the 'Heatpump Sensor 1.0' node +* Select 'IR_SEND' as the subtype +* Enter for example '00213416' as the payload + +### Domoticz + +As Domoticz does not really support sending IR code yet, the workaround is to use two sensors. When the node is connected, Domoticz should find two new devices +* A 'Lighting 2 / AC' type sensor with name 'IR send' +* A 'General / Text' type sensor with name 'Unknown' and Data '00000000' + +To send a command, first add the sensors. Then update the data of the text sensor to contain the IR code to be sent, and then flip the switch to trigger the sending. +The data can be updated through the JSON REST API, or through the Lua event scripts. + +REST API example (assuming that the idx of the text sensor is 105 and the switch is 104, and Domoticz is at 192.168.0.4:8080): +``` +http://192.168.0.4:8080/json.htm?type=command¶m=udevice&idx=105&nvalue=0&svalue=00213416 +http://192.168.0.4:8080/json.htm?type=command¶m=switchlight&idx=104&switchcmd=On +``` + +Lua example: +* Seems that the text sensor can't be updated through the commandArray... +* So now when a device called 'trigger' changes, the text sensor value is first updated to '00213416', and then the IR signal is sent + +``` +commandArray = {} + +for key, value in pairs(devicechanged) do + if (key == 'trigger') then + + print("Heatpump script") + commandArray['OpenURL']='http://192.168.0.4:8080/json.htm?type=command¶m=udevice&idx=105&nvalue=0&svalue=00213416' + commandArray['IR send']='On' + end +end + +return commandArray +``` + +## Hardware + +Use any ATMega328-based Arduino, connect the radio, and finally connect an infrared led (in series with a resistor, like 100 Ohm) between digital pin 3 and GND. \ No newline at end of file diff --git a/libraries/Timer/Event.cpp b/libraries/Timer/Event.cpp new file mode 100644 index 0000000..6ef838d --- /dev/null +++ b/libraries/Timer/Event.cpp @@ -0,0 +1,65 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Code by Simon Monk + http://www.simonmonk.org +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// For Arduino 1.0 and earlier +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Event.h" + +Event::Event(void) +{ + eventType = EVENT_NONE; +} + +void Event::update(void) +{ + unsigned long now = millis(); + update(now); +} + +void Event::update(unsigned long now) +{ + if (now - lastEventTime >= period) + { + switch (eventType) + { + case EVENT_EVERY: + (*callback)(); + break; + + case EVENT_OSCILLATE: + pinState = ! pinState; + digitalWrite(pin, pinState); + break; + } + lastEventTime = now; + count++; + } + if (repeatCount > -1 && count >= repeatCount) + { + eventType = EVENT_NONE; + } +} diff --git a/libraries/Timer/Event.h b/libraries/Timer/Event.h new file mode 100644 index 0000000..2f64d3f --- /dev/null +++ b/libraries/Timer/Event.h @@ -0,0 +1,49 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Code by Simon Monk + http://www.simonmonk.org +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef Event_h +#define Event_h + +#include + +#define EVENT_NONE 0 +#define EVENT_EVERY 1 +#define EVENT_OSCILLATE 2 + +class Event +{ + +public: + Event(void); + void update(void); + void update(unsigned long now); + int8_t eventType; + unsigned long period; + int repeatCount; + uint8_t pin; + uint8_t pinState; + void (*callback)(void); + unsigned long lastEventTime; + int count; +}; + +#endif diff --git a/libraries/Timer/ReadMe.txt b/libraries/Timer/ReadMe.txt new file mode 100644 index 0000000..5b08895 --- /dev/null +++ b/libraries/Timer/ReadMe.txt @@ -0,0 +1,29 @@ +1.0 by Simon Monk +Library as downloaded 02Feb2012 22:55 UTC from http://srmonk.blogspot.com/2012/01/arduino-timer-library.html + +1.1 by Jack Christensen +Changed data types of variables and functions: + o event types and indexes changed from int to int8_t. + o periods and durations changed from lon to unsigned long. + o update() and stop() functions typed as void, since they return nothing. + o pin numbers and pin values changed from int to uint8_t, this agrees with digitalWrite(). + o added return values to Timer::pulse() and Timer::oscillate(uint8_t, unsigned long, uint8_t). + o changed test in Event::update() to use subtraction to avoid rollover issues. + o Updated keywords.txt file to include all functions. + +1.2 by Damian Philipp + o Added a range check to Timer::stop() to avoid memory corruption. + o Added constants to : + - NO_TIMER_AVAILABLE: Signals that while an event was to be queued, no free timer could be found. + - TIMER_NOT_AN_EVENT: Can be used to flag a variable that *might* contain a timer ID as + *not* containing a timer ID + o Replaced a bunch of magic numbers in by the above constants + o Added several comments + o Added Timer::pulseImmediate(). pulseImmediate sets the pin to the specified value for the given + duration. After the duration, the pin is set to !value. + +1.3 by Jack Christensen + o Added "blink2" example illustrating flashing two LEDs at different rates. + o 19Oct2013: This is the last v1.x release. It will continue to be available on GitHub + as a branch named v1.3. Future development will continue with Sandy Walsh's v2.0 which + can pass context (timer ID, etc.) to the callback functions. \ No newline at end of file diff --git a/libraries/Timer/Timer.cpp b/libraries/Timer/Timer.cpp new file mode 100644 index 0000000..9c8b889 --- /dev/null +++ b/libraries/Timer/Timer.cpp @@ -0,0 +1,138 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Code by Simon Monk + http://www.simonmonk.org +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// For Arduino 1.0 and earlier +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include "Timer.h" + +Timer::Timer(void) +{ +} + +int8_t Timer::every(unsigned long period, void (*callback)(), int repeatCount) +{ + int8_t i = findFreeEventIndex(); + if (i == -1) return -1; + + _events[i].eventType = EVENT_EVERY; + _events[i].period = period; + _events[i].repeatCount = repeatCount; + _events[i].callback = callback; + _events[i].lastEventTime = millis(); + _events[i].count = 0; + return i; +} + +int8_t Timer::every(unsigned long period, void (*callback)()) +{ + return every(period, callback, -1); // - means forever +} + +int8_t Timer::after(unsigned long period, void (*callback)()) +{ + return every(period, callback, 1); +} + +int8_t Timer::oscillate(uint8_t pin, unsigned long period, uint8_t startingValue, int repeatCount) +{ + int8_t i = findFreeEventIndex(); + if (i == NO_TIMER_AVAILABLE) return NO_TIMER_AVAILABLE; + + _events[i].eventType = EVENT_OSCILLATE; + _events[i].pin = pin; + _events[i].period = period; + _events[i].pinState = startingValue; + digitalWrite(pin, startingValue); + _events[i].repeatCount = repeatCount * 2; // full cycles not transitions + _events[i].lastEventTime = millis(); + _events[i].count = 0; + return i; +} + +int8_t Timer::oscillate(uint8_t pin, unsigned long period, uint8_t startingValue) +{ + return oscillate(pin, period, startingValue, -1); // forever +} + +/** + * This method will generate a pulse of !startingValue, occuring period after the + * call of this method and lasting for period. The Pin will be left in !startingValue. + */ +int8_t Timer::pulse(uint8_t pin, unsigned long period, uint8_t startingValue) +{ + return oscillate(pin, period, startingValue, 1); // once +} + +/** + * This method will generate a pulse of startingValue, starting immediately and of + * length period. The pin will be left in the !startingValue state + */ +int8_t Timer::pulseImmediate(uint8_t pin, unsigned long period, uint8_t pulseValue) +{ + int8_t id(oscillate(pin, period, pulseValue, 1)); + // now fix the repeat count + if (id >= 0 && id < MAX_NUMBER_OF_EVENTS) { + _events[id].repeatCount = 1; + } + return id; +} + + +void Timer::stop(int8_t id) +{ + if (id >= 0 && id < MAX_NUMBER_OF_EVENTS) { + _events[id].eventType = EVENT_NONE; + } +} + +void Timer::update(void) +{ + unsigned long now = millis(); + update(now); +} + +void Timer::update(unsigned long now) +{ + for (int8_t i = 0; i < MAX_NUMBER_OF_EVENTS; i++) + { + if (_events[i].eventType != EVENT_NONE) + { + _events[i].update(now); + } + } +} +int8_t Timer::findFreeEventIndex(void) +{ + for (int8_t i = 0; i < MAX_NUMBER_OF_EVENTS; i++) + { + if (_events[i].eventType == EVENT_NONE) + { + return i; + } + } + return NO_TIMER_AVAILABLE; +} diff --git a/libraries/Timer/Timer.h b/libraries/Timer/Timer.h new file mode 100644 index 0000000..dd5ce93 --- /dev/null +++ b/libraries/Timer/Timer.h @@ -0,0 +1,67 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Code by Simon Monk + http://www.simonmonk.org +* * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef Timer_h +#define Timer_h + +#include +#include "Event.h" + +#define MAX_NUMBER_OF_EVENTS (10) + +#define TIMER_NOT_AN_EVENT (-2) +#define NO_TIMER_AVAILABLE (-1) + +class Timer +{ + +public: + Timer(void); + + int8_t every(unsigned long period, void (*callback)(void)); + int8_t every(unsigned long period, void (*callback)(void), int repeatCount); + int8_t after(unsigned long duration, void (*callback)(void)); + int8_t oscillate(uint8_t pin, unsigned long period, uint8_t startingValue); + int8_t oscillate(uint8_t pin, unsigned long period, uint8_t startingValue, int repeatCount); + + /** + * This method will generate a pulse of !startingValue, occuring period after the + * call of this method and lasting for period. The Pin will be left in !startingValue. + */ + int8_t pulse(uint8_t pin, unsigned long period, uint8_t startingValue); + + /** + * This method will generate a pulse of pulseValue, starting immediately and of + * length period. The pin will be left in the !pulseValue state + */ + int8_t pulseImmediate(uint8_t pin, unsigned long period, uint8_t pulseValue); + void stop(int8_t id); + void update(void); + void update(unsigned long now); + +protected: + Event _events[MAX_NUMBER_OF_EVENTS]; + int8_t findFreeEventIndex(void); + +}; + +#endif diff --git a/libraries/Timer/examples/blink2/blink2.ino b/libraries/Timer/examples/blink2/blink2.ino new file mode 100644 index 0000000..e9080a4 --- /dev/null +++ b/libraries/Timer/examples/blink2/blink2.ino @@ -0,0 +1,29 @@ +//Flash two LEDs at different rates using Simon Monk's Timer library +//http://www.doctormonk.com/2012/01/arduino-timer-library.html +// +//Jack Christensen 30Sep2013 +// +//Beerware license: Free for any and all purposes, but if you find it +//useful AND we actually meet someday, you can buy me a beer! + +#include "Timer.h" //http://github.com/JChristensen/Timer + +const int LED1 = 8; //connect one LED to this pin (with appropriate current-limiting resistor of course) +const int LED2 = 9; //connect another LED to this pin (don't forget the resistor) +const unsigned long PERIOD1 = 1000; //one second +const unsigned long PERIOD2 = 10000; //ten seconds +Timer t; //instantiate the timer object + +void setup(void) +{ + pinMode(LED1, OUTPUT); + pinMode(LED2, OUTPUT); + t.oscillate(LED1, PERIOD1, HIGH); + t.oscillate(LED2, PERIOD2, HIGH); +} + +void loop(void) +{ + t.update(); +} + diff --git a/libraries/Timer/examples/kitchen_sink/kitchen_sink.pde b/libraries/Timer/examples/kitchen_sink/kitchen_sink.pde new file mode 100644 index 0000000..991bcb4 --- /dev/null +++ b/libraries/Timer/examples/kitchen_sink/kitchen_sink.pde @@ -0,0 +1,42 @@ +#include "Timer.h" + +Timer t; + +int ledEvent; + +void setup() +{ + Serial.begin(9600); + int tickEvent = t.every(2000, doSomething); + Serial.print("2 second tick started id="); + Serial.println(tickEvent); + + pinMode(13, OUTPUT); + ledEvent = t.oscillate(13, 50, HIGH); + Serial.print("LED event started id="); + Serial.println(ledEvent); + + int afterEvent = t.after(10000, doAfter); + Serial.print("After event started id="); + Serial.println(afterEvent); + +} + +void loop() +{ + t.update(); +} + +void doSomething() +{ + Serial.print("2 second tick: millis()="); + Serial.println(millis()); +} + + +void doAfter() +{ + Serial.println("stop the led event"); + t.stop(ledEvent); + t.oscillate(13, 500, HIGH, 5); +} diff --git a/libraries/Timer/examples/pin_high_10_mins/pin_high_10_mins.pde b/libraries/Timer/examples/pin_high_10_mins/pin_high_10_mins.pde new file mode 100644 index 0000000..3700d48 --- /dev/null +++ b/libraries/Timer/examples/pin_high_10_mins/pin_high_10_mins.pde @@ -0,0 +1,17 @@ +#include "Timer.h" + +Timer t; +int pin = 13; + +void setup() +{ + pinMode(pin, OUTPUT); + t.pulse(pin, 10 * 1000, HIGH); // 10 seconds + // t.pulse(pin, 10 * 60 * 1000, HIGH); // 10 minutes +} + +void loop() +{ + t.update(); +} + diff --git a/libraries/Timer/examples/read_A0_flashLED/read_A0_flashLED.pde b/libraries/Timer/examples/read_A0_flashLED/read_A0_flashLED.pde new file mode 100644 index 0000000..e103e37 --- /dev/null +++ b/libraries/Timer/examples/read_A0_flashLED/read_A0_flashLED.pde @@ -0,0 +1,22 @@ +#include "Timer.h" + +Timer t; +int pin = 13; + +void setup() +{ + Serial.begin(9600); + pinMode(pin, OUTPUT); + t.oscillate(pin, 100, LOW); + t.every(1000, takeReading); +} + +void loop() +{ + t.update(); +} + +void takeReading() +{ + Serial.println(analogRead(0)); +} diff --git a/libraries/Timer/keywords.txt b/libraries/Timer/keywords.txt new file mode 100644 index 0000000..a5107b8 --- /dev/null +++ b/libraries/Timer/keywords.txt @@ -0,0 +1,31 @@ +####################################### +# Syntax Coloring Map For Timer Library +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +Timer KEYWORD1 +Event KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +every KEYWORD2 +after KEYWORD2 +oscillate KEYWORD2 +pulse KEYWORD2 +pulseImmediate KEYWORD2 +stop KEYWORD2 +update KEYWORD2 +findFreeEventIndex KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### From f58fc601a408e500264feb7ec15716d6ac181d99 Mon Sep 17 00:00:00 2001 From: ToniA Date: Wed, 30 Sep 2015 08:30:37 +0300 Subject: [PATCH 33/35] Fix compiler warnings about PROGMEM use --- libraries/HeatpumpIR/CarrierHeatpumpIR.cpp | 4 ++-- libraries/HeatpumpIR/HeatpumpIR.h | 4 ++-- libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp | 2 +- .../examples/HeatpumpIRController/HeatpumpIRController.ino | 7 +++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp index a4d6d70..55fe332 100644 --- a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp @@ -145,7 +145,7 @@ void CarrierHeatpumpIR::sendCarrier(IRSender& IR, byte operatingMode, byte fanSp IR.space(CARRIER_AIRCON1_HDR_SPACE); // Payload - for (int i=0; i> 8; byte temp = (irCommand & 0x000000FF); - prog_char* buf; + const char* buf; Serial.print(F("Model: ")); - buf = (prog_char*)heatpumpIR[model]->model(); + buf = heatpumpIR[model]->model(); // 'model' is a PROGMEM pointer, so need to write a byte at a time while (char modelChar = pgm_read_byte(buf++)) { From 1b120d7520c4499e267a06be810ae51347e695ae Mon Sep 17 00:00:00 2001 From: ToniA Date: Wed, 30 Sep 2015 08:45:17 +0300 Subject: [PATCH 34/35] Fix compiler warnings about unused parameters --- libraries/HeatpumpIR/CarrierHeatpumpIR.cpp | 3 +++ libraries/HeatpumpIR/HeatpumpIR.cpp | 2 +- libraries/HeatpumpIR/HisenseHeatpumpIR.cpp | 10 ++++++++-- libraries/HeatpumpIR/MideaHeatpumpIR.cpp | 3 +++ libraries/HeatpumpIR/SamsungHeatpumpIR.cpp | 3 +++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp index 55fe332..026c1aa 100644 --- a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp @@ -12,6 +12,9 @@ CarrierHeatpumpIR::CarrierHeatpumpIR() : HeatpumpIR() void CarrierHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) { + (void)swingVCmd; + (void)swingHCmd; + // Sensible defaults for the heat pump mode byte operatingMode = CARRIER_AIRCON1_MODE_HEAT; diff --git a/libraries/HeatpumpIR/HeatpumpIR.cpp b/libraries/HeatpumpIR/HeatpumpIR.cpp index a741ea4..7f7f60a 100644 --- a/libraries/HeatpumpIR/HeatpumpIR.cpp +++ b/libraries/HeatpumpIR/HeatpumpIR.cpp @@ -5,7 +5,7 @@ HeatpumpIR::HeatpumpIR() } // This is a virtual function, i.e. never called -void HeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +void HeatpumpIR::send(IRSender&, byte, byte, byte, byte, byte, byte) { } diff --git a/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp index 542ceb9..5f7dd85 100644 --- a/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp @@ -12,6 +12,9 @@ HisenseHeatpumpIR::HisenseHeatpumpIR() : HeatpumpIR() void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd , byte fanSpeedCmd , byte temperatureCmd , byte swingVCmd , byte swingHCmd ) { + (void)swingVCmd; + (void)swingHCmd; + // Sensible defaults for the heat pump mode byte powerMode = HISENSE_AIRCON1_POWER_ON; @@ -47,7 +50,7 @@ void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingMode operatingMode = HISENSE_AIRCON1_MODE_FAN; if ( fanSpeedCmd == FAN_AUTO ) { fanSpeedCmd = FAN_1; // Fan speed cannot be 'AUTO' in FAN mode - temperatureCmd = 25; // Fixed temperature FAN mode + temperatureCmd = 25; // Fixed temperature FAN mode } break; } @@ -66,7 +69,7 @@ void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingMode break; case FAN_3: fanSpeed = HISENSE_AIRCON1_FAN3; - break; + break; } if ( temperatureCmd > 17 && temperatureCmd < 33) @@ -81,6 +84,9 @@ void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingMode // Send the Hisense code void HisenseHeatpumpIR::sendHisense(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV ,byte swingH) { + (void)swingV; + (void)swingH; + byte HisenseTemplate[] = { 0x87, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // Header byte 0-1 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 }; // diff --git a/libraries/HeatpumpIR/MideaHeatpumpIR.cpp b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp index faec8ed..04e709d 100644 --- a/libraries/HeatpumpIR/MideaHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp @@ -12,6 +12,9 @@ MideaHeatpumpIR::MideaHeatpumpIR() : HeatpumpIR() void MideaHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) { + (void)swingVCmd; + (void)swingHCmd; + // Sensible defaults for the heat pump mode byte operatingMode = MIDEA_AIRCON1_MODE_HEAT; byte fanSpeed = MIDEA_AIRCON1_FAN_AUTO; diff --git a/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp b/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp index 274504f..b932baf 100644 --- a/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/SamsungHeatpumpIR.cpp @@ -12,6 +12,9 @@ SamsungHeatpumpIR::SamsungHeatpumpIR() : HeatpumpIR() void SamsungHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) { + (void)swingVCmd; + (void)swingHCmd; + // Sensible defaults for the heat pump mode byte powerMode = SAMSUNG_AIRCON1_MODE_ON; From c4f706fd3e298944ceffd83a18c8d9a9301eab9a Mon Sep 17 00:00:00 2001 From: ToniA Date: Thu, 1 Oct 2015 10:39:59 +0300 Subject: [PATCH 35/35] Updated HeatpumpIR, to a version which uses the native Arduino IDE 1.6 PROGMEM definitions --- libraries/HeatpumpIR/CarrierHeatpumpIR.cpp | 20 +++---- libraries/HeatpumpIR/CarrierHeatpumpIR.h | 4 +- libraries/HeatpumpIR/FujitsuHeatpumpIR.cpp | 46 +++++++-------- libraries/HeatpumpIR/FujitsuHeatpumpIR.h | 8 +-- libraries/HeatpumpIR/HeatpumpIR.cpp | 2 +- libraries/HeatpumpIR/HeatpumpIR.h | 3 +- libraries/HeatpumpIR/HisenseHeatpumpIR.cpp | 26 ++++----- libraries/HeatpumpIR/HisenseHeatpumpIR.h | 4 +- libraries/HeatpumpIR/IRSender.cpp | 10 ++-- libraries/HeatpumpIR/IRSender.h | 8 +-- libraries/HeatpumpIR/MideaHeatpumpIR.cpp | 38 ++++++------ libraries/HeatpumpIR/MideaHeatpumpIR.h | 6 +- libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp | 22 +++---- libraries/HeatpumpIR/MitsubishiHeatpumpIR.h | 6 +- .../HeatpumpIR/PanasonicCKPHeatpumpIR.cpp | 58 +++++++++---------- libraries/HeatpumpIR/PanasonicCKPHeatpumpIR.h | 6 +- libraries/HeatpumpIR/PanasonicHeatpumpIR.cpp | 32 +++++----- libraries/HeatpumpIR/PanasonicHeatpumpIR.h | 6 +- libraries/HeatpumpIR/README.md | 9 +-- libraries/HeatpumpIR/SamsungHeatpumpIR.cpp | 40 ++++++------- libraries/HeatpumpIR/SamsungHeatpumpIR.h | 4 +- .../examples/HisenseTest/HisenseTest.ino | 26 +++------ .../MideaRelayControl/MideaRelayControl.ino | 5 +- .../examples/MySensorsNode/MySensorsNode.ino | 13 +++-- .../PanasonicCKPTimer/PanasonicCKPTimer.ino | 12 ++-- .../HeatpumpIR/examples/simple/simple.ino | 8 ++- .../HeatpumpIRController.ino | 2 + 27 files changed, 212 insertions(+), 212 deletions(-) diff --git a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp index 026c1aa..244f03d 100644 --- a/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/CarrierHeatpumpIR.cpp @@ -10,16 +10,16 @@ CarrierHeatpumpIR::CarrierHeatpumpIR() : HeatpumpIR() } -void CarrierHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +void CarrierHeatpumpIR::send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd) { (void)swingVCmd; (void)swingHCmd; // Sensible defaults for the heat pump mode - byte operatingMode = CARRIER_AIRCON1_MODE_HEAT; - byte fanSpeed = CARRIER_AIRCON1_FAN_AUTO; - byte temperature = 23; + uint8_t operatingMode = CARRIER_AIRCON1_MODE_HEAT; + uint8_t fanSpeed = CARRIER_AIRCON1_FAN_AUTO; + uint8_t temperature = 23; if (powerModeCmd == POWER_OFF) { @@ -82,12 +82,12 @@ void CarrierHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingMode // Send the Carrier code // Carrier has the LSB and MSB in different format than Panasonic -void CarrierHeatpumpIR::sendCarrier(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature) +void CarrierHeatpumpIR::sendCarrier(IRSender& IR, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature) { - byte sendBuffer[9] = { 0x4f, 0xb0, 0xc0, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00 }; // The data is on the last four bytes + uint8_t sendBuffer[9] = { 0x4f, 0xb0, 0xc0, 0x3f, 0x80, 0x00, 0x00, 0x00, 0x00 }; // The data is on the last four uint8_ts - static const prog_uint8_t temperatures[] PROGMEM = { 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b }; - byte checksum = 0; + static const uint8_t temperatures[] PROGMEM = { 0x00, 0x08, 0x04, 0x0c, 0x02, 0x0a, 0x06, 0x0e, 0x01, 0x09, 0x05, 0x0d, 0x03, 0x0b }; + uint8_t checksum = 0; // PROGMEM arrays cannot be addressed directly, see http://forum.arduino.cc/index.php?topic=106603.0 sendBuffer[5] = pgm_read_byte(&(temperatures[(temperature-17)])); @@ -149,7 +149,7 @@ void CarrierHeatpumpIR::sendCarrier(IRSender& IR, byte operatingMode, byte fanSp // Payload for (size_t i=0; i #include @@ -61,7 +60,7 @@ class HeatpumpIR const char * _info; public: - virtual void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + virtual void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd); const char PROGMEM* model(); const char PROGMEM* info(); }; diff --git a/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp index 5f7dd85..e01a092 100644 --- a/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/HisenseHeatpumpIR.cpp @@ -10,19 +10,19 @@ HisenseHeatpumpIR::HisenseHeatpumpIR() : HeatpumpIR() } -void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd , byte fanSpeedCmd , byte temperatureCmd , byte swingVCmd , byte swingHCmd ) +void HisenseHeatpumpIR::send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd , uint8_t fanSpeedCmd , uint8_t temperatureCmd , uint8_t swingVCmd , uint8_t swingHCmd ) { (void)swingVCmd; (void)swingHCmd; // Sensible defaults for the heat pump mode - byte powerMode = HISENSE_AIRCON1_POWER_ON; - byte operatingMode = HISENSE_AIRCON1_MODE_HEAT; - byte fanSpeed = HISENSE_AIRCON1_FAN_AUTO; - byte temperature = 21; - byte swingV=0; - byte swingH=0; + uint8_t powerMode = HISENSE_AIRCON1_POWER_ON; + uint8_t operatingMode = HISENSE_AIRCON1_MODE_HEAT; + uint8_t fanSpeed = HISENSE_AIRCON1_FAN_AUTO; + uint8_t temperature = 21; + uint8_t swingV=0; + uint8_t swingH=0; if (powerModeCmd == POWER_OFF) { @@ -82,21 +82,21 @@ void HisenseHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingMode } // Send the Hisense code -void HisenseHeatpumpIR::sendHisense(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV ,byte swingH) +void HisenseHeatpumpIR::sendHisense(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV ,uint8_t swingH) { (void)swingV; (void)swingH; - byte HisenseTemplate[] = { 0x87, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // Header byte 0-1 + uint8_t HisenseTemplate[] = { 0x87, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, // Header uint8_t 0-1 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 }; // - byte i; + uint8_t i; // Set the Fan speed, On/Off. HisenseTemplate[2] = fanSpeed | powerMode; HisenseTemplate[3] = (((temperature - 18) << 4) | operatingMode ) ; - // Calculate the byte checksum EXOR byte 2 to 12 + // Calculate the uint8_t checksum EXOR uint8_t 2 to 12 HisenseTemplate[13] = HisenseTemplate[2]; for (i=3; i<13; i++) { HisenseTemplate[13]= HisenseTemplate[i] ^ HisenseTemplate[13]; @@ -111,7 +111,7 @@ void HisenseHeatpumpIR::sendHisense(IRSender& IR, byte powerMode, byte operating // Payload header part for (i=0; i<7; i++) { - IR.sendIRByte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); + IR.sendIRbyte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); } // Mesage space @@ -120,7 +120,7 @@ void HisenseHeatpumpIR::sendHisense(IRSender& IR, byte powerMode, byte operating // Payload message part for (; i<14; i++) { - IR.sendIRByte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); + IR.sendIRbyte(HisenseTemplate[i], HISENSE_AIRCON1_BIT_MARK, HISENSE_AIRCON1_ZERO_SPACE, HISENSE_AIRCON1_ONE_SPACE); } // End mark diff --git a/libraries/HeatpumpIR/HisenseHeatpumpIR.h b/libraries/HeatpumpIR/HisenseHeatpumpIR.h index 4cc2b01..eb2e69c 100644 --- a/libraries/HeatpumpIR/HisenseHeatpumpIR.h +++ b/libraries/HeatpumpIR/HisenseHeatpumpIR.h @@ -64,10 +64,10 @@ class HisenseHeatpumpIR : public HeatpumpIR { public: HisenseHeatpumpIR(); - void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd); private: - void sendHisense(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH); + void sendHisense(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV, uint8_t swingH); }; #endif diff --git a/libraries/HeatpumpIR/IRSender.cpp b/libraries/HeatpumpIR/IRSender.cpp index 8daffe6..fdf8b58 100644 --- a/libraries/HeatpumpIR/IRSender.cpp +++ b/libraries/HeatpumpIR/IRSender.cpp @@ -6,7 +6,7 @@ // // For PWM on Arduino, see http://playground.arduino.cc/Main/TimerPWMCheatsheet -IRSender::IRSender(byte pin) +IRSender::IRSender(uint8_t pin) { _pin = pin; } @@ -72,8 +72,8 @@ void IRSender::setFrequency(int frequency) } } -// Send a byte (8 bits) over IR -void IRSender::sendIRByte(byte sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength) +// Send a uint8_t (8 bits) over IR +void IRSender::sendIRbyte(uint8_t sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength) { for (int i=0; i<8 ; i++) { @@ -93,8 +93,8 @@ void IRSender::sendIRByte(byte sendByte, int bitMarkLength, int zeroSpaceLength, } // The Carrier IR protocol has the bits in a reverse order (compared to the other heatpumps) -// See http://www.nrtm.org/index.php/2013/07/25/reverse-bits-in-a-byte/ -byte IRSender::bitReverse(byte x) +// See http://www.nrtm.org/index.php/2013/07/25/reverse-bits-in-a-uint8_t/ +uint8_t IRSender::bitReverse(uint8_t x) { // 01010101 | 10101010 x = ((x >> 1) & 0x55) | ((x << 1) & 0xaa); diff --git a/libraries/HeatpumpIR/IRSender.h b/libraries/HeatpumpIR/IRSender.h index b0028ac..c99bb82 100644 --- a/libraries/HeatpumpIR/IRSender.h +++ b/libraries/HeatpumpIR/IRSender.h @@ -9,15 +9,15 @@ class IRSender { public: - IRSender(byte pin); + IRSender(uint8_t pin); void setFrequency(int frequency); - void sendIRByte(byte sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength); - byte bitReverse(byte x); + void sendIRbyte(uint8_t sendByte, int bitMarkLength, int zeroSpaceLength, int oneSpaceLength); + uint8_t bitReverse(uint8_t x); void space(int spaceLength); void mark(int markLength); private: - byte _pin; + uint8_t _pin; }; #endif diff --git a/libraries/HeatpumpIR/MideaHeatpumpIR.cpp b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp index 04e709d..bf58bab 100644 --- a/libraries/HeatpumpIR/MideaHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/MideaHeatpumpIR.cpp @@ -10,15 +10,15 @@ MideaHeatpumpIR::MideaHeatpumpIR() : HeatpumpIR() } -void MideaHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +void MideaHeatpumpIR::send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd) { (void)swingVCmd; (void)swingHCmd; // Sensible defaults for the heat pump mode - byte operatingMode = MIDEA_AIRCON1_MODE_HEAT; - byte fanSpeed = MIDEA_AIRCON1_FAN_AUTO; - byte temperature = 23; + uint8_t operatingMode = MIDEA_AIRCON1_MODE_HEAT; + uint8_t fanSpeed = MIDEA_AIRCON1_FAN_AUTO; + uint8_t temperature = 23; switch (powerModeCmd) { @@ -48,8 +48,8 @@ void MideaHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCm break; case MODE_MAINT: // Maintenance mode ('FP' on the remote) is a special mode on Midea - // Also, this is a switch between 'normal' operation and 'maintenance' operation, - // i.e. if already running on maintenance, the heatpump will go back to normal operation + // Also, this is a switch between 'normal' operation and 'maintenance' operation, + // i.e. if already running on maintenance, the heatpump will go back to normal operation operatingMode = MIDEA_AIRCON1_MODE_FP; break; } @@ -79,14 +79,14 @@ void MideaHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCm } // Send the Midea code -void MideaHeatpumpIR::sendMidea(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature) +void MideaHeatpumpIR::sendMidea(IRSender& IR, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature) { - byte sendBuffer[3] = { 0x4D, 0x00, 0x00 }; // First byte is always 0x4D + uint8_t sendBuffer[3] = { 0x4D, 0x00, 0x00 }; // First uint8_t is always 0x4D - static const prog_uint8_t temperatures[] PROGMEM = {0, 8, 12, 4, 6, 14, 10, 2, 3, 11, 9, 1, 5, 13 }; + static const uint8_t temperatures[] PROGMEM = {0, 8, 12, 4, 6, 14, 10, 2, 3, 11, 9, 1, 5, 13 }; - static const prog_uint8_t OffMsg[] PROGMEM = {0x4D, 0xDE, 0x07 }; - static const prog_uint8_t FPMsg[] PROGMEM = {0xAD, 0xAF, 0xB5 }; + static const uint8_t OffMsg[] PROGMEM = {0x4D, 0xDE, 0x07 }; + static const uint8_t FPMsg[] PROGMEM = {0xAD, 0xAF, 0xB5 }; if (operatingMode == MIDEA_AIRCON1_MODE_OFF) { @@ -115,7 +115,7 @@ void MideaHeatpumpIR::sendMidea(IRSender& IR, byte operatingMode, byte fanSpeed, } // Send the Midea raw code -void MideaHeatpumpIR::sendMidearaw(IRSender& IR, byte sendBuffer[]) +void MideaHeatpumpIR::sendMidearaw(IRSender& IR, uint8_t sendBuffer[]) { // 40 kHz PWM frequency IR.setFrequency(40); @@ -124,24 +124,24 @@ void MideaHeatpumpIR::sendMidearaw(IRSender& IR, byte sendBuffer[]) IR.mark(MIDEA_AIRCON1_HDR_MARK); IR.space(MIDEA_AIRCON1_HDR_SPACE); - // Six bytes, every second byte is a bitwise not of the previous byte + // Six uint8_ts, every second uint8_t is a bitwise not of the previous uint8_t for (int i=0; i<3; i++) { - IR.sendIRByte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); - IR.sendIRByte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRbyte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRbyte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); } // Pause IR.mark(MIDEA_AIRCON1_BIT_MARK); IR.space(MIDEA_AIRCON1_MSG_SPACE); - // Header, two last bytes repeated + // Header, two last uint8_ts repeated IR.mark(MIDEA_AIRCON1_HDR_MARK); IR.space(MIDEA_AIRCON1_HDR_SPACE); - // Six bytes, every second byte is a bitwise not of the previous byte + // Six uint8_ts, every second uint8_t is a bitwise not of the previous uint8_t for (int i=0; i<3; i++) { - IR.sendIRByte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); - IR.sendIRByte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRbyte(sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); + IR.sendIRbyte(~sendBuffer[i], MIDEA_AIRCON1_BIT_MARK, MIDEA_AIRCON1_ZERO_SPACE, MIDEA_AIRCON1_ONE_SPACE); } // End mark diff --git a/libraries/HeatpumpIR/MideaHeatpumpIR.h b/libraries/HeatpumpIR/MideaHeatpumpIR.h index d3490fe..20602a3 100644 --- a/libraries/HeatpumpIR/MideaHeatpumpIR.h +++ b/libraries/HeatpumpIR/MideaHeatpumpIR.h @@ -37,11 +37,11 @@ class MideaHeatpumpIR : public HeatpumpIR { public: MideaHeatpumpIR(); - void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd); private: - void sendMidea(IRSender& IR, byte operatingMode, byte fanSpeed, byte temperature); - void sendMidearaw(IRSender& IR, byte sendBuffer[]); + void sendMidea(IRSender& IR, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature); + void sendMidearaw(IRSender& IR, uint8_t sendBuffer[]); }; #endif diff --git a/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp b/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp index c9922dc..7823e57 100644 --- a/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp +++ b/libraries/HeatpumpIR/MitsubishiHeatpumpIR.cpp @@ -31,16 +31,16 @@ MitsubishiFEHeatpumpIR::MitsubishiFEHeatpumpIR() : MitsubishiHeatpumpIR() } -void MitsubishiHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd) +void MitsubishiHeatpumpIR::send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd) { // Sensible defaults for the heat pump mode - byte powerMode = MITSUBISHI_AIRCON1_MODE_ON; - byte operatingMode = MITSUBISHI_AIRCON1_MODE_HEAT; - byte fanSpeed = MITSUBISHI_AIRCON1_FAN_AUTO; - byte temperature = 23; - byte swingV = MITSUBISHI_AIRCON1_VS_AUTO; - byte swingH = MITSUBISHI_AIRCON1_HS_SWING; + uint8_t powerMode = MITSUBISHI_AIRCON1_MODE_ON; + uint8_t operatingMode = MITSUBISHI_AIRCON1_MODE_HEAT; + uint8_t fanSpeed = MITSUBISHI_AIRCON1_FAN_AUTO; + uint8_t temperature = 23; + uint8_t swingV = MITSUBISHI_AIRCON1_VS_AUTO; + uint8_t swingH = MITSUBISHI_AIRCON1_HS_SWING; if (powerModeCmd == 0) { @@ -155,12 +155,12 @@ void MitsubishiHeatpumpIR::send(IRSender& IR, byte powerModeCmd, byte operatingM sendMitsubishi(IR, powerMode, operatingMode, fanSpeed, temperature, swingV, swingH); } -void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV, byte swingH) +void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV, uint8_t swingH) { - byte MitsubishiTemplate[] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x48, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00 }; + uint8_t MitsubishiTemplate[] = { 0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x48, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00 }; // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - byte checksum = 0x00; + uint8_t checksum = 0x00; // Set the operatingmode on the template message MitsubishiTemplate[5] = powerMode; @@ -198,7 +198,7 @@ void MitsubishiHeatpumpIR::sendMitsubishi(IRSender& IR, byte powerMode, byte ope // Data for (unsigned int i=0; i>= 1; + Samsunguint8_t >>= 1; } } @@ -142,7 +142,7 @@ void SamsungHeatpumpIR::sendSamsung(IRSender& IR, byte powerMode, byte operating // Payload header part for (int i=0; i<7; i++) { - IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + IR.sendIRbyte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); } // Pause + new header @@ -154,7 +154,7 @@ void SamsungHeatpumpIR::sendSamsung(IRSender& IR, byte powerMode, byte operating // Payload power message part for (int i=7; i<14; i++) { - IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + IR.sendIRbyte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); } // Pause + new header @@ -166,7 +166,7 @@ void SamsungHeatpumpIR::sendSamsung(IRSender& IR, byte powerMode, byte operating // Payload data message part for (int i=14; i<21; i++) { - IR.sendIRByte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); + IR.sendIRbyte(SamsungTemplate[i], SAMSUNG_AIRCON1_BIT_MARK, SAMSUNG_AIRCON1_ZERO_SPACE, SAMSUNG_AIRCON1_ONE_SPACE); } // End mark diff --git a/libraries/HeatpumpIR/SamsungHeatpumpIR.h b/libraries/HeatpumpIR/SamsungHeatpumpIR.h index aab79fd..c33e661 100644 --- a/libraries/HeatpumpIR/SamsungHeatpumpIR.h +++ b/libraries/HeatpumpIR/SamsungHeatpumpIR.h @@ -33,10 +33,10 @@ class SamsungHeatpumpIR : public HeatpumpIR { public: SamsungHeatpumpIR(); - void send(IRSender& IR, byte powerModeCmd, byte operatingModeCmd, byte fanSpeedCmd, byte temperatureCmd, byte swingVCmd, byte swingHCmd); + void send(IRSender& IR, uint8_t powerModeCmd, uint8_t operatingModeCmd, uint8_t fanSpeedCmd, uint8_t temperatureCmd, uint8_t swingVCmd, uint8_t swingHCmd); private: - void sendSamsung(IRSender& IR, byte powerMode, byte operatingMode, byte fanSpeed, byte temperature, byte swingV); + void sendSamsung(IRSender& IR, uint8_t powerMode, uint8_t operatingMode, uint8_t fanSpeed, uint8_t temperature, uint8_t swingV); }; #endif diff --git a/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino b/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino index b4a5481..3215480 100644 --- a/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino +++ b/libraries/HeatpumpIR/examples/HisenseTest/HisenseTest.ino @@ -1,47 +1,40 @@ +#include #include IRSender irSender(3); // IR led on Duemilanove digital pin 3 -HisenseHeatpumpIR *heatpumpIR; +HisenseHeatpumpIR *heatpumpIR; void setup() { - Serial.begin(9600); delay(500); heatpumpIR = new HisenseHeatpumpIR(); Serial.println(F("Starting")); - - - } void loop() { - int i = 0; - prog_char* buf; + const char* buf; - - // Send the same IR command to all supported heatpumps Serial.print(F("Sending IR to ")); // Print the model - buf = (prog_char*)heatpumpIR->model(); + buf = heatpumpIR->model(); // 'model' is a PROGMEM pointer, so need to write a byte at a time while (char modelChar = pgm_read_byte(buf++)) { Serial.print(modelChar); } Serial.print(F(", info: ")); - // Print the info - buf = (prog_char*)heatpumpIR->info(); + // Print the info + buf = heatpumpIR->info(); // 'info' is a PROGMEM pointer, so need to write a byte at a time while (char infoChar = pgm_read_byte(buf++)) { Serial.print(infoChar); } - Serial.println(); + Serial.println(); - heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_3, 30, VDIR_AUTO, HDIR_AUTO); delay(1500); heatpumpIR->send(irSender, POWER_ON, MODE_DRY, FAN_2, 20, VDIR_AUTO, HDIR_AUTO); @@ -50,7 +43,6 @@ void loop() delay(1500); heatpumpIR->send(irSender, POWER_ON, MODE_COOL, FAN_1, 18, VDIR_AUTO, HDIR_AUTO); delay(2500); - heatpumpIR->send(irSender, POWER_OFF, NULL, NULL, NULL, NULL, NULL); + heatpumpIR->send(irSender, POWER_OFF, MODE_COOL, FAN_1, 18, VDIR_AUTO, HDIR_AUTO); delay(1500); - -} +} \ No newline at end of file diff --git a/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino b/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino index efd577d..80a5bdb 100644 --- a/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino +++ b/libraries/HeatpumpIR/examples/MideaRelayControl/MideaRelayControl.ino @@ -1,3 +1,4 @@ +#include #include // https://github.com/ToniA/arduino-heatpumpir #include // http://playground.arduino.cc/Code/Button @@ -34,8 +35,8 @@ const byte heatpumpOff = 0; const byte heatpumpNormal = 1; const byte heatpumpMaintenance = 2; -Button relay1 = Button(11,PULLUP); // Heatpump ON-OFF state -Button relay2 = Button(12,PULLUP); // FP mode (maintenance heating at 8 degrees C) ON-OFF state +Button relay1 = Button(11, INPUT_PULLUP); // Heatpump ON-OFF state +Button relay2 = Button(12, INPUT_PULLUP); // FP mode (maintenance heating at 8 degrees C) ON-OFF state IRSender irSender(9); // IR led on Duemilanove digital pin 9 diff --git a/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino b/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino index 366e79c..a5b303b 100644 --- a/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino +++ b/libraries/HeatpumpIR/examples/MySensorsNode/MySensorsNode.ino @@ -1,3 +1,5 @@ +#include + // HeatpumpIR libraries #include #include @@ -85,8 +87,7 @@ void loop() // Handle incoming messages from the MySensors Gateway void incomingMessage(const MyMessage &message) { - const char *irData; - long irCommand; + const char *irData; // V_IR type message if (message.type==V_IR_SEND) { @@ -156,10 +157,10 @@ libraries\HeatpumpIR\HeatpumpIR.h for the constants byte fan = (irCommand & 0x00000F00) >> 8; byte temp = (irCommand & 0x000000FF); - prog_char* buf; + const char* buf; Serial.print(F("Model: ")); - buf = (prog_char*)heatpumpIR[model]->model(); + buf = heatpumpIR[model]->model(); // 'model' is a PROGMEM pointer, so need to write a byte at a time while (char modelChar = pgm_read_byte(buf++)) { @@ -188,14 +189,14 @@ libraries\HeatpumpIR\HeatpumpIR.h for the constants if (model == 0) { Serial.println(F("Scheduling timer cancellation on Panasonic CKP heatpump...")); - panasonicCKPTimer = timer.after(120000, panasonicCancelTimer, (void *)NULL); // Called after 2 minutes + panasonicCKPTimer = timer.after(120000, panasonicCancelTimer); // Called after 2 minutes } } } // Cancel the timer on the Panasonic CKP heatpump -void panasonicCancelTimer(void *context) +void panasonicCancelTimer() { PanasonicCKPHeatpumpIR *panasonicCKPHeatpumpIR = new PanasonicCKPHeatpumpIR(); diff --git a/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino b/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino index 680026c..dcacf44 100644 --- a/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino +++ b/libraries/HeatpumpIR/examples/PanasonicCKPTimer/PanasonicCKPTimer.ino @@ -1,5 +1,7 @@ +#include + #include -#include +#include // https://github.com/JChristensen/Timer /* This schema demonstrates how to control the Panasonic CKP power state change. The CKP does not have discrete @@ -34,8 +36,8 @@ void setup() heatpumpIR->send(irSender, POWER_ON, MODE_HEAT, FAN_2, 24, VDIR_UP, HDIR_AUTO); Serial.println("The heatpump should have beeped, and the TIMER led should be ON"); - timer.after(60000, panasonicIsOn, NULL); // Called after 1 minute - timer.after(120000, panasonicCancelTimer, NULL); // Called after 2 minutes + timer.after(60000, panasonicIsOn); // Called after 1 minute + timer.after(120000, panasonicCancelTimer); // Called after 2 minutes } @@ -44,12 +46,12 @@ void loop() timer.update(); } -void panasonicIsOn(void *context) +void panasonicIsOn() { Serial.println("The heatpump should should turn ON by now, the TIMER led is still ON"); } -void panasonicCancelTimer(void *context) +void panasonicCancelTimer() { heatpumpIR->sendPanasonicCKPCancelTimer(irSender); Serial.println("The TIMER led should now be OFF"); diff --git a/libraries/HeatpumpIR/examples/simple/simple.ino b/libraries/HeatpumpIR/examples/simple/simple.ino index b5d0808..229c936 100644 --- a/libraries/HeatpumpIR/examples/simple/simple.ino +++ b/libraries/HeatpumpIR/examples/simple/simple.ino @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -25,13 +27,13 @@ void setup() void loop() { int i = 0; - prog_char* buf; + const char* buf; do { // Send the same IR command to all supported heatpumps Serial.print(F("Sending IR to ")); - buf = (prog_char*)heatpumpIR[i]->model(); + buf = heatpumpIR[i]->model(); // 'model' is a PROGMEM pointer, so need to write a byte at a time while (char modelChar = pgm_read_byte(buf++)) { @@ -39,7 +41,7 @@ void loop() } Serial.print(F(", info: ")); - buf = (prog_char*)heatpumpIR[i]->info(); + buf = heatpumpIR[i]->info(); // 'info' is a PROGMEM pointer, so need to write a byte at a time while (char infoChar = pgm_read_byte(buf++)) { diff --git a/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino b/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino index 84ffd71..79e153f 100644 --- a/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino +++ b/libraries/MySensors/examples/HeatpumpIRController/HeatpumpIRController.ino @@ -1,3 +1,5 @@ +#include + // HeatpumpIR libraries, https://github.com/ToniA/arduino-heatpumpir #include #include