HeatpumpIR library + sensors example to send IR commands to heatpumps

This commit is contained in:
ToniA
2015-09-29 20:21:42 +03:00
parent 1b8c87704c
commit c9ffef8da3
42 changed files with 4004 additions and 0 deletions
@@ -0,0 +1,56 @@
#include <HisenseHeatpumpIR.h>
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);
}
@@ -0,0 +1,92 @@
#include <MideaHeatpumpIR.h> // https://github.com/ToniA/arduino-heatpumpir
#include <Button.h> // 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
}
@@ -0,0 +1,204 @@
// HeatpumpIR libraries
#include <FujitsuHeatpumpIR.h>
#include <PanasonicCKPHeatpumpIR.h>
#include <PanasonicHeatpumpIR.h>
#include <CarrierHeatpumpIR.h>
#include <MideaHeatpumpIR.h>
#include <MitsubishiHeatpumpIR.h>
#include <SamsungHeatpumpIR.h>
// Timer library, https://github.com/ToniA/Timer
#include <Timer.h>
// MySensors libraries
#include <MySensor.h>
#include <SPI.h>
// 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"));
}
@@ -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&param=udevice&idx=105&nvalue=0&svalue=00213416
http://192.168.0.4:8080/json.htm?type=command&param=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&param=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.
@@ -0,0 +1,56 @@
#include <PanasonicCKPHeatpumpIR.h>
#include <Timer.h>
/*
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");
}
@@ -0,0 +1,57 @@
#include <FujitsuHeatpumpIR.h>
#include <PanasonicCKPHeatpumpIR.h>
#include <PanasonicHeatpumpIR.h>
#include <CarrierHeatpumpIR.h>
#include <MideaHeatpumpIR.h>
#include <MitsubishiHeatpumpIR.h>
#include <SamsungHeatpumpIR.h>
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);
}