ESP8266 - Air Conditioner Command and Control Unit



This is part 2 of a series of posts where I'll try to show how I am trying to set up a home control and monitoring system using open source tools and network enabled sensors.
In Part 1 I illustrated how I decoded my Toshiba Air Conditioner AC signal, in this part I'll build on that to show how I built and ESP8266 based unit that is able to control five separate AC units in (hopefully in the near future) different rooms by receiving commands from a central server. The ESP8266 unit has also five DS18B20 temperature sensors that will be used to monitor real temperatures in the different rooms


Parts List:


  • one ESP8266 based dev board, powered from USB, it has seven output pins available to control various sensors/modules, wifi capabilities and the NodeMcu firmware that allows normal arduino sketches to run on it




  • SendIR module: an arduino/esp8266 controllable module that has two high power/high range IR emitters, used to control AC units in different rooms Each module needs one pin to be controlled


  • 5 DS18B20 temperature sensors: used to monitor temperatures in the rooms where the AC units will be installed

The photos are showing the dev board, with all sensors/modules connected locally, once the software side will be deemed stable they will be moved near the ac units from which I have ran cat5 cables that will be used to connect the sensors/shields to the ESP8266 board

Software:

  • Arduino and NodeMcu firmware for the ESP8266 module
  • Mosquitto: an open source MQTT broker. MQTT provides a lightweight method of carrying out messaging using a publish/subscribe model and it allows to connect different softwares with a standard approach
  • openHAB: an open source home automation software, used to provide a web based interface to monitor and control the status of multiple sensors/units in a smart home





ESP8266 - One module to control it all

The ESP8266 module provides power to the five SendIr shields using the Vin pin (5v) and controls them using pins D0-D4. It also handles five temperature sensors using one single pin (SD2) and the Onewire library


The ESP8266 sends data and receive commands through a single connection to a MQTT server running on a Synology NAS, that also happens to run an instance of the home automation software (and an instance of an energy monitoring system, but that will be part 3 and 4 of the series) through Wifi.

The publisher/subscriber model of an MQTT server is pretty straightforward, the server allows clients to subscribe to topics and to send messages to specific topics:



What this allows to do in our case is for the ESP8266 to receive commands by other clients by subscribing to specific topics on the MQTT server, and also to send feedback about the state of the various AC units and temperatures to other MQTT topics, that will be consumed (read) by interested clients
This allows the home automation software to generate commands to control the AC units in different rooms without knowing anything about IR signals and IR protocols, the only thing that it knows is that in order for, say, turn on an AC unit in room1 it is necessary to send the message "1" to the topic "esp8266/01/in/00/00". This message will be received by the ESP8266 unit that will convert the information into the appropriate IR signal that will be sent to the appropriate AC unit

The ESP8266 Sketch:
 #include <ESP8266WiFi.h>  
 #include <OneWire.h>  
 #include <DallasTemperature.h>  
 #include <PubSubClient.h>  
 //Def  
 #define HVAC_TOSHIBA_DEBUG; // Un comment to access DEBUG information through Serial Interface  
 #define ONE_WIRE_BUS 10 // DS18B20 on arduino pin10 corresponds to SD3 on ESP8266 Dev Board  
 #define NUMROOMS 5  
 #define DELAY 60000 //Check temp every 10 sec  
 int ledPin = 2; // GPIO2  
 WiFiClient espClient;  
 PubSubClient client(espClient);  
 OneWire oneWire(ONE_WIRE_BUS);  
 DallasTemperature DS18B20(&oneWire);  
 float prevTemp = 0;  
 const char* MY_SSID = "WIFISSID";  
 const char* MY_PWD = "WIFIPWD";  
 int sent = 0;  
 char mqtt_user[] = "mqttuser";  
 char mqtt_password[] = "mqttpasswd";  
 char mqtt_id[]  = "esp8266-01";  
 const char* mqtt_server = "ipoftheserver";  
 const char * outTopic = "esp8266/01/out";  
 const char * pingTopic = "esp8266/01/ping";  
 char * statusTopic = "esp8266/01/status/00/00";  
 char * inTopic = "esp8266/01/in/#";  
 typedef struct roomHvac RoomHvac;  
 unsigned long nextPingOn = 0;  
 unsigned long pingDelay = 60000;  
 byte probes[][8] = {  
  { 0x28, 0x82, 0xEB, 0x35, 0x05, 0x00, 0x00, 0xCC },  
  { 0x28, 0xDD, 0x1F, 0x35, 0x05, 0x00, 0x00, 0x7F },  
  { 0x28, 0xB7, 0x8B, 0x34, 0x05, 0x00, 0x00, 0x54 },  
  { 0x28, 0x2B, 0x4C, 0x35, 0x05, 0x00, 0x00, 0x64 },  
  { 0x28, 0x4F, 0xC3, 0xA2, 0x04, 0x00, 0x00, 0x13 }  
 };  
 float drift[5] = {0, 0, 0, 0, 0};  
 // Reconnect to the MQTT server in case of disconnect  
 void reconnect() {  
  // Loop until we're reconnected  
  while (!client.connected()) {  
 #ifdef HVAC_TOSHIBA_DEBUG  
   Serial.println("Attempting MQTT reconnection...");  
 #endif  
   if (WiFi.status() != WL_CONNECTED)  
   {  
    connectWifi();  
   }  
   // Attempt to connect  
   if (client.connect(mqtt_id, mqtt_user, mqtt_password)) {  
 #ifdef HVAC_TOSHIBA_DEBUG  
    Serial.println("reconnected");  
 #endif  
    // Once connected, publish an announcement...  
    client.publish(pingTopic, "2");  
    // ... and resubscribe  
    client.subscribe(inTopic);  
   } else {  
 #ifdef HVAC_TOSHIBA_DEBUG  
    Serial.print("failed, rc=");  
    Serial.print(client.state());  
    Serial.println(" try again in 5 seconds");  
    // Wait 5 seconds before retrying  
 #endif  
    delay(15000);  
   }  
  }  
 }  
 int halfPeriodicTime;  
 int IRpin;  
 int khz;  
 typedef enum HvacMode {  
  HVAC_HOT,  
  HVAC_COLD,  
  HVAC_DRY,  
  HVAC_FAN, // used for Panasonic only  
  HVAC_AUTO  
 } HvacMode_t; // HVAC MODE  
 typedef enum HvacFanMode {  
  FAN_SPEED_1,  
  FAN_SPEED_2,  
  FAN_SPEED_3,  
  FAN_SPEED_4,  
  FAN_SPEED_5,  
  FAN_SPEED_AUTO,  
  FAN_SPEED_SILENT  
 } HvacFanMode_; // HVAC FAN MODE  
 typedef enum HvacVanneMode {  
  VANNE_AUTO,  
  VANNE_H1,  
  VANNE_H2,  
  VANNE_H3,  
  VANNE_H4,  
  VANNE_H5,  
  VANNE_AUTO_MOVE  
 } HvacVanneMode_; // HVAC VANNE MODE  
 typedef enum HvacWideVanneMode {  
  WIDE_LEFT_END,  
  WIDE_LEFT,  
  WIDE_MIDDLE,  
  WIDE_RIGHT,  
  WIDE_RIGHT_END  
 } HvacWideVanneMode_t; // HVAC WIDE VANNE MODE  
 typedef enum HvacAreaMode {  
  AREA_SWING,  
  AREA_LEFT,  
  AREA_AUTO,  
  AREA_RIGHT  
 } HvacAreaMode_t; // HVAC WIDE VANNE MODE  
 typedef enum HvacProfileMode {  
  NORMAL,  
  QUIET,  
  BOOST  
 } HvacProfileMode_t; // HVAC PANASONIC OPTION MODE  
 struct roomHvac  
 {  
  int temp;  
  int state; // 0 - OFF 1 - ON  
  HvacFanMode fanHvac;  
  HvacMode modeHvac;  
  int pin;  
  float probeTemp;  
  float oldTemp;  
 };  
 void sendHvacToshiba(  
  HvacMode        ,  
  int           ,  
  HvacFanMode       ,  
  int           ,  
  int  
 );  
 typedef struct roomHvac RoomHvac;  
 RoomHvac rooms[NUMROOMS];  
 void publishRoomStatus(int roomNo) {  
  char b[10];  
  statusTopic[19] = '0' + roomNo;  
  statusTopic[22] = '0';  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Sending update for room ");  
  Serial.println(roomNo);  
  Serial.println();  
 #endif  
  sprintf(b, "%d", rooms[roomNo].state);  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Publishing value ");  
  Serial.print(b);  
  Serial.print(" to Topic: ");  
  Serial.println(statusTopic);  
 #endif  
  client.publish(statusTopic, b);  
  statusTopic[22] = '1';  
  sprintf(b, "%d", rooms[roomNo].temp);  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Publishing value ");  
  Serial.print(b);  
  Serial.print(" to Topic: ");  
  Serial.println(statusTopic);  
 #endif  
  client.publish(statusTopic, b);  
  statusTopic[22] = '2';  
  sprintf(b, "%d", (int)rooms[roomNo].modeHvac);  
  client.publish(statusTopic, b);  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Publishing value ");  
  Serial.print(b);  
  Serial.print(" to Topic: ");  
  Serial.println(statusTopic);  
 #endif  
  statusTopic[22] = '3';  
  sprintf(b, "%d", (int)rooms[roomNo].fanHvac);  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Publishing value ");  
  Serial.print(b);  
  Serial.print(" to Topic: ");  
  Serial.println(statusTopic);  
 #endif  
  client.publish(statusTopic, b);  
  statusTopic[22] = '4';  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Publishing value ");  
  Serial.print(rooms[roomNo].probeTemp);  
  Serial.print(" to Topic: ");  
  Serial.println(statusTopic);  
 #endif  
  client.publish(statusTopic, f2s(rooms[roomNo].probeTemp, 2));  
 }  
 void callback(char* topic, byte* payload, unsigned int length) {  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Message arrived [");  
  Serial.print(topic);  
  Serial.print("] ");  
  for (int i = 0; i < length; i++) {  
   Serial.print((char)payload[i]);  
  }  
  Serial.println();  
 #endif  
  payload[length] = '\0';  
  int val = atoi((char *)payload);  
  client.publish(outTopic, "received");  
  int roomNo = (int)topic[15] - 48;  
  char commandRoom = topic[18];  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Received command for room:");  
  Serial.println(roomNo);  
  Serial.print("Received command:");  
  Serial.println(commandRoom);  
  Serial.print("Received val:");  
  Serial.println(val);  
 #endif  
  /*  
     Rooms:  
      0: Sala  
      1: Studio  
      2: Camera PT  
      3: Camera NORD PP  
      4: Camera SUD PP  
     Commands:  
      0: 0 = OFF 1 = ON  
      1: TEMP SETPOINT (17-30)  
      2: AC MODE: 0: Hot 1: Cold 2: Dry 4: Auto  
      3: FAN Mode: 0: 1 1: 2 2:3 3: 4 4: 5 5: AUTO  
  */  
  if (roomNo >= 0 && roomNo <= NUMROOMS - 1 ) {  
   switch (commandRoom) {  
    case '0':  
     if (val == 0 || val == 1) {  
      rooms[roomNo].state = val;  
     }  
     break;  
    case '1':  
     if (val < 17) {  
      val = 17;  
     }  
     if (val > 30) {  
      val = 30;  
     }  
     rooms[roomNo].temp = val;  
     break;  
    case '2':  
     if (val >= 0 && val <= 4) {  
      rooms[roomNo].modeHvac = (HvacMode) val;  
     }  
     break;  
    case '3':  
     if (val >= 0 && val <= 5) {  
      rooms[roomNo].fanHvac = (HvacFanMode)val;  
     }  
     break;  
    default:  
     break;  
   }  
 #ifdef HVAC_TOSHIBA_DEBUG  
   Serial.println("Room Data:");  
   Serial.print("State: ");  
   Serial.println(rooms[roomNo].state);  
   Serial.print("Temp: ");  
   Serial.println(rooms[roomNo].temp);  
   Serial.print("Mode: ");  
   Serial.println(rooms[roomNo].modeHvac);  
   Serial.print("Fan: ");  
   Serial.println(rooms[roomNo].fanHvac);  
 #endif  
   int offState = 0;  
   if ( rooms[roomNo].state == 0) {  
    offState = 1;  
   }  
   sendHvacToshiba(rooms[roomNo].modeHvac, rooms[roomNo].temp, rooms[roomNo].fanHvac, offState, rooms[roomNo].pin);  
   //const char * statusTopic="esp8266/01/status/00/00";  
   publishRoomStatus(roomNo);  
  }  
 }  
 // HVAC TOSHIBA_  
 #define HVAC_TOSHIBA_HDR_MARK  4400  
 #define HVAC_TOSHIBA_HDR_SPACE  4300  
 #define HVAC_TOSHIBA_BIT_MARK  560  
 #define HVAC_TOSHIBA_ONE_SPACE  1590  
 #define HVAC_MISTUBISHI_ZERO_SPACE 472  
 #define HVAC_TOSHIBA_RPT_MARK  440  
 #define HVAC_TOSHIBA_RPT_SPACE  7048 // Above original iremote limit  
 /****************************************************************************  
  /* Send IR command to Toshiba HVAC - sendHvacToshiba  
  /***************************************************************************/  
 void sendHvacToshiba(  
  HvacMode        HVAC_Mode,      // Example HVAC_HOT  
  int           HVAC_Temp,      // Example 21 (°c)  
  HvacFanMode       HVAC_FanMode,    // Example FAN_SPEED_AUTO  
  int           OFF,         // Example false  
  int           irPin  
 )  
 {  
 #define HVAC_TOSHIBA_DATALEN 9  
  IRpin = irPin;  
  byte mask = 1; //our bitmask  
  //F20D03FC0150000051  
  byte data[HVAC_TOSHIBA_DATALEN] = { 0xF2, 0x0D, 0x03, 0xFC, 0x01, 0x00, 0x00, 0x00, 0x00 };  
  // data array is a valid trame, only byte to be chnaged will be updated.  
  byte i;  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.println("Packet to send: ");  
  for (i = 0; i < HVAC_TOSHIBA_DATALEN; i++) {  
   Serial.print("_");  
   Serial.print(data[i], HEX);  
  }  
  Serial.println(".");  
 #endif  
  data[6] = 0x00;  
  // Byte 7 - Mode  
  switch (HVAC_Mode)  
  {  
   case HVAC_HOT:  data[6] = (byte) B00000011; break;  
   case HVAC_COLD: data[6] = (byte) B00000001; break;  
   case HVAC_DRY:  data[6] = (byte) B00000010; break;  
   case HVAC_AUTO: data[6] = (byte) B00000000; break;  
   default: break;  
  }  
  // Byte 7 - On / Off  
  if (OFF) {  
   data[6] = (byte) 0x07; // Turn OFF HVAC  
  } else {  
   // Turn ON HVAC (default)  
  }  
  // Byte 6 - Temperature  
  // Check Min Max For Hot Mode  
  byte Temp;  
  if (HVAC_Temp > 30) {  
   Temp = 30;  
  }  
  else if (HVAC_Temp < 17) {  
   Temp = 17;  
  }  
  else {  
   Temp = HVAC_Temp;  
  };  
  data[5] = (byte) Temp - 17 << 4;  
  // Byte 10 - FAN / VANNE  
  switch (HVAC_FanMode)  
  {  
   case FAN_SPEED_1:    data[6] = data[6] | (byte) B01000000; break;  
   case FAN_SPEED_2:    data[6] = data[6] | (byte) B01100000; break;  
   case FAN_SPEED_3:    data[6] = data[6] | (byte) B10000000; break;  
   case FAN_SPEED_4:    data[6] = data[6] | (byte) B10100000; break;  
   case FAN_SPEED_5:    data[6] = data[6] | (byte) B11000000; break;  
   case FAN_SPEED_AUTO:  data[6] = data[6] | (byte) B00000000; break;  
   case FAN_SPEED_SILENT: data[6] = data[6] | (byte) B00000000; break;//No FAN speed SILENT for TOSHIBA so it is consider as Speed AUTO  
   default: break;  
  }  
  // Byte 9 - CRC  
  data[8] = 0;  
  for (i = 0; i < HVAC_TOSHIBA_DATALEN - 1; i++) {  
   data[HVAC_TOSHIBA_DATALEN - 1] = (byte) data[i] ^ data[HVAC_TOSHIBA_DATALEN - 1]; // CRC is a simple bits addition  
  }  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.println("Packet to send: ");  
  for (i = 0; i < HVAC_TOSHIBA_DATALEN; i++) {  
   Serial.print("_"); Serial.print(data[i], HEX);  
  }  
  Serial.println(".");  
  for (i = 0; i < HVAC_TOSHIBA_DATALEN ; i++) {  
   Serial.print(data[i], BIN); Serial.print(" ");  
  }  
  Serial.println(".");  
 #endif  
  enableIROut(38); // 38khz  
  space(0);  
  for (int j = 0; j < 2; j++) { // For Toshiba IR protocol we have to send two time the packet data  
   // Header for the Packet  
   mark(HVAC_TOSHIBA_HDR_MARK);  
   space(HVAC_TOSHIBA_HDR_SPACE);  
   for (i = 0; i < HVAC_TOSHIBA_DATALEN; i++) {  
    // Send all Bits from Byte Data in Forward Order (MSB)  
    for (mask = 10000000; mask > 0; mask >>= 1) { //iterate through bit mask  
     if (data[i] & mask) { // Bit ONE  
      mark(HVAC_TOSHIBA_BIT_MARK);  
      space(HVAC_TOSHIBA_ONE_SPACE);  
     }  
     else { // Bit ZERO  
      mark(HVAC_TOSHIBA_BIT_MARK);  
      space(HVAC_MISTUBISHI_ZERO_SPACE);  
     }  
     //Next bits  
    }  
   }  
   // End of Packet and retransmission of the Packet  
   mark(HVAC_TOSHIBA_RPT_MARK);  
   space(HVAC_TOSHIBA_RPT_SPACE);  
   space(0); // Just to be sure  
  }  
 }  
 /****************************************************************************  
  /* enableIROut : Set global Variable for Frequency IR Emission  
  /***************************************************************************/  
 void enableIROut(int khz) {  
  // Enables IR output. The khz value controls the modulation frequency in kilohertz.  
  halfPeriodicTime = 500 / khz; // T = 1/f but we need T/2 in microsecond and f is in kHz  
 }  
 /****************************************************************************  
  /* mark ( int time)  
  /***************************************************************************/  
 void mark(int time) {  
  // Sends an IR mark for the specified number of microseconds.  
  // The mark output is modulated at the PWM frequency.  
  long beginning = micros();  
  while (micros() - beginning < time) {  
   digitalWrite(IRpin, HIGH);  
   delayMicroseconds(halfPeriodicTime);  
   digitalWrite(IRpin, LOW);  
   delayMicroseconds(halfPeriodicTime); //38 kHz -> T = 26.31 microsec (periodic time), half of it is 13  
  }  
 }  
 /****************************************************************************  
  /* space ( int time)  
  /***************************************************************************/  
 /* Leave pin off for time (given in microseconds) */  
 void space(int time) {  
  // Sends an IR space for the specified number of microseconds.  
  // A space is no output, so the PWM output is disabled.  
  digitalWrite(IRpin, LOW);  
  if (time > 0) delayMicroseconds(time);  
 }  
 /****************************************************************************  
  /* sendRaw (unsigned int buf[], int len, int hz)  
  /***************************************************************************/  
 void sendRaw (unsigned int buf[], int len, int hz)  
 {  
  enableIROut(hz);  
  for (int i = 0; i < len; i++) {  
   if (i & 1) {  
    space(buf[i]);  
   }  
   else {  
    mark(buf[i]);  
   }  
  }  
  space(0); // Just to be sure  
 }  
 /* Check the temperature data */  
 void tempTimer() {  
  //Getting the temperature  
  float temp = 0;  
  for (int r = 0; r < NUMROOMS; r++) {  
   rooms[r].oldTemp = rooms[r].probeTemp;  
   rooms[r].probeTemp = readTemp(probes[r]) - drift[r];  
 #ifdef HVAC_TOSHIBA_DEBUG  
   Serial.print("Room: ");  
   Serial.print(r);  
   Serial.print(" - Temperature: ");  
   Serial.println(rooms[r].probeTemp);  
 #endif  
   if (rooms[r].probeTemp != rooms[r].oldTemp) {  
    publishRoomStatus(r);  
   }  
  }  
 }  
 /* float to string  
   f is the float to turn into a string  
   p is the precision (number of decimals)  
   return a string representation of the float.  
 */  
 char *f2s(float f, int p) {  
  char * pBuff;             // use to remember which part of the buffer to use for dtostrf  
  const int iSize = 10;         // number of bufffers, one for each float before wrapping around  
  static char sBuff[iSize][20];     // space for 20 characters including NULL terminator for each float  
  static int iCount = 0;        // keep a tab of next place in sBuff to use  
  pBuff = sBuff[iCount];        // use this buffer  
  if (iCount >= iSize - 1) {      // check for wrap  
   iCount = 0;             // if wrapping start again and reset  
  }  
  else {  
   iCount++;              // advance the counter  
  }  
  return dtostrf(f, 0, p, pBuff);    // call the library function  
 }  
 void setup() {  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.begin(115200);  
  Serial.println("Sketch Started.");  
 #endif  
  IRpin = D0;  
  khz = 38;  
  halfPeriodicTime = 500 / khz;  
  pinMode(IRpin, OUTPUT);  
  for (int r = 0; r < NUMROOMS; r++) {  
   /*  
     struct roomHvac  
    {  
    int temp;  
    int state; // 0 - OFF 1 - ON  
    HvacFanMode fanHvac;  
    HvacMode modeHvac;  
    };  
   */  
   rooms[r].temp = 22;  
   rooms[r].state = 0;  
   rooms[r].fanHvac = FAN_SPEED_AUTO;  
   rooms[r].modeHvac = HVAC_AUTO;  
  }  
  rooms[0].pin = D0;  
  pinMode(D0, OUTPUT);  
  rooms[1].pin = D1;  
  pinMode(D1, OUTPUT);  
  rooms[2].pin = D2;  
  pinMode(D2, OUTPUT);  
  rooms[3].pin = D3;  
  pinMode(D3, OUTPUT);  
  rooms[4].pin = D4;  
  pinMode(D4, OUTPUT);  
  connectWifi();  
  client.setServer(mqtt_server, 1883);  
  client.setCallback(callback);  
  if (client.connect(mqtt_id, mqtt_user, mqtt_password)) {  
   client.publish(outTopic, "hello world");  
   client.subscribe(inTopic);  
  }  
  for (int r = 0; r < 5; r++) {  
   publishRoomStatus(r);  
  }  
 }  
 void loop() {  
  float temp;  
  if (WiFi.status() != WL_CONNECTED)  
  {  
   connectWifi();  
  }  
  if (!client.connected()) {  
   reconnect();  
  }  
  client.loop();  
  unsigned long time = millis();  
  if (time > nextPingOn) {  
   tempTimer();  
   client.publish(pingTopic, "1");  
   nextPingOn = time + pingDelay;  
  }  
 }  
 void connectWifi()  
 {  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Connecting to " + *MY_SSID);  
 #endif  
  WiFi.begin(MY_SSID, MY_PWD);  
  while (WiFi.status() != WL_CONNECTED) {  
   delay(1000);  
 #ifdef HVAC_TOSHIBA_DEBUG  
   Serial.print(".");  
 #endif  
  }  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.println("");  
  Serial.print("Connected to ");  
  Serial.println(MY_SSID);  
  Serial.print("IP address: ");  
  Serial.println(WiFi.localIP());  
 #endif  
 }//end connect  
 float readTemp(byte *addressDs1820) {  
  byte i;  
  byte present = 0;  
  byte type_s;  
  byte data[12];  
  byte *addr;  
  float celsius;  
  /*  
   if ( !ds.search(addr)) {  
    ds.reset_search();  
    delay(250);  
    return;  
   }  
  */  
  addr = addressDs1820;  
  if (OneWire::crc8(addr, 7) != addr[7]) {  
   return -1;  
  }  
  // the first ROM byte indicates which chip  
  switch (addr[0]) {  
   case 0x10:  
    type_s = 1;  
    break;  
   case 0x28:  
    type_s = 0;  
    break;  
   case 0x22:  
    type_s = 0;  
    break;  
   default:  
    return -1;  
  }  
  oneWire.reset();  
  oneWire.select(addr);  
  oneWire.write(0x44, 1);    // start conversion, with parasite power on at the end  
  delay(1000);   // maybe 750ms is enough, maybe not  
  // we might do a ds.depower() here, but the reset will take care of it.  
  present = oneWire.reset();  
  oneWire.select(addr);  
  oneWire.write(0xBE);     // Read Scratchpad  
  for ( i = 0; i < 9; i++) {      // we need 9 bytes  
   data[i] = oneWire.read();  
  }  
  // Convert the data to actual temperature  
  // because the result is a 16 bit signed integer, it should  
  // be stored to an "int16_t" type, which is always 16 bits  
  // even when compiled on a 32 bit processor.  
  int16_t raw = (data[1] << 8) | data[0];  
  if (type_s) {  
   raw = raw << 3; // 9 bit resolution default  
   if (data[7] == 0x10) {  
    // "count remain" gives full 12 bit resolution  
    raw = (raw & 0xFFF0) + 12 - data[6];  
   }  
  } else {  
   byte cfg = (data[4] & 0x60);  
   // at lower res, the low bits are undefined, so let's zero them  
   if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms  
   else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms  
   else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms  
   //// default is 12 bit resolution, 750 ms conversion time  
  }  
  celsius = (float)raw / 16.0;  
  // fahrenheit = celsius * 1.8 + 32.0;  
  return celsius;  
 }  

The sketch uses the following data structures:

roomHvac:
struct roomHvac  
 {  
  int temp;  
  int state; // 0 - OFF 1 - ON  
  HvacFanMode fanHvac;  
  HvacMode modeHvac;  
  int pin;  
  float probeTemp;  
  float oldTemp;  
 };  

structure that hold information about a room AC and temp status, I have defined a fixed size in function of the number of rooms/AC in my house

probes:

byte probes[][8] = {  
  { 0x28, 0x82, 0xEB, 0x35, 0x05, 0x00, 0x00, 0xCC },  
  { 0x28, 0xDD, 0x1F, 0x35, 0x05, 0x00, 0x00, 0x7F },  
  { 0x28, 0xB7, 0x8B, 0x34, 0x05, 0x00, 0x00, 0x54 },  
  { 0x28, 0x2B, 0x4C, 0x35, 0x05, 0x00, 0x00, 0x64 },  
  { 0x28, 0x4F, 0xC3, 0xA2, 0x04, 0x00, 0x00, 0x13 }  
 };  

This structure holds the physical addresses of the temperature sensors, detected by connecting them one at a time and running this sketch on the ESP8266:


#include             // OneWire-Bibliothek einbinden
#include   // DS18B20-Bibliothek einbinden

#define DS18B20_PIN 10   // Pin für DS18B20 definieren Arduino D2

OneWire  ds(DS18B20_PIN);  // Connect your 1-wire device to pin 3

void setup(void) {
  Serial.begin(115200);
  discoverOneWireDevices();
}

void discoverOneWireDevices(void) {
  byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];
  
  Serial.print("Looking for 1-Wire devices...\n\r");
  while(ds.search(addr)) {
    Serial.print("\n\rFound \'1-Wire\' device with address:\n\r");
    for( i = 0; i < 8; i++) {
      Serial.print("0x");
      if (addr[i] < 16) {
        Serial.print('0');
      }
      Serial.print(addr[i], HEX);
      if (i < 7) {
        Serial.print(", ");
      }
    }
    if ( OneWire::crc8( addr, 7) != addr[7]) {
        Serial.print("CRC is not valid!\n");
        return;
    }
  }
  Serial.print("\n\r\n\rThat's it.\r\n");
  ds.reset_search();
  return;
}

void loop(void) {
  // nothing to see here
}


MQTT:

 char mqtt_user[] = "mqttuser";  
 char mqtt_password[] = "mqttpasswd";  
 char mqtt_id[]  = "esp8266-01";  
 const char* mqtt_server = "ipoftheserver";  
 const char * outTopic = "esp8266/01/out";  
 const char * pingTopic = "esp8266/01/ping";  
 char * statusTopic = "esp8266/01/status/00/00";  
 char * inTopic = "esp8266/01/in/#";  


these variables define the connection parameters to the MQTT server and the topics used to communicate with other clients, the ESP8266 will react to any message published on the topics with base address  "esp8266/01/in/#" and every time it will change any parameter for a specific room it will publish a status packet on the appropriate "esp8266/01/status/0x/0x"  topic. It will also publish a ping message once every minute (will be used to monitor for failures on the server side) 

The function where all logic conversion between messages published on the topics and commands sent to the ac units is the callback function:


void callback(char* topic, byte* payload, unsigned int length) {  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Message arrived [");  
  Serial.print(topic);  
  Serial.print("] ");  
  for (int i = 0; i < length; i++) {  
   Serial.print((char)payload[i]);  
  }  
  Serial.println();  
 #endif  
  payload[length] = '\0';  
  int val = atoi((char *)payload);  
  client.publish(outTopic, "received");  
  int roomNo = (int)topic[15] - 48;  
  char commandRoom = topic[18];  
 #ifdef HVAC_TOSHIBA_DEBUG  
  Serial.print("Received command for room:");  
  Serial.println(roomNo);  
  Serial.print("Received command:");  
  Serial.println(commandRoom);  
  Serial.print("Received val:");  
  Serial.println(val);  
 #endif  
  /*  
     Rooms:  
      0: Sala  
      1: Studio  
      2: Camera PT  
      3: Camera NORD PP  
      4: Camera SUD PP  
     Commands:  
      0: 0 = OFF 1 = ON  
      1: TEMP SETPOINT (17-30)  
      2: AC MODE: 0: Hot 1: Cold 2: Dry 4: Auto  
      3: FAN Mode: 0: 1 1: 2 2:3 3: 4 4: 5 5: AUTO  
  */  
  if (roomNo >= 0 && roomNo <= NUMROOMS - 1 ) {  
   switch (commandRoom) {  
    case '0':  
     if (val == 0 || val == 1) {  
      rooms[roomNo].state = val;  
     }  
     break;  
    case '1':  
     if (val < 17) {  
      val = 17;  
     }  
     if (val > 30) {  
      val = 30;  
     }  
     rooms[roomNo].temp = val;  
     break;  
    case '2':  
     if (val >= 0 && val <= 4) {  
      rooms[roomNo].modeHvac = (HvacMode) val;  
     }  
     break;  
    case '3':  
     if (val >= 0 && val <= 5) {  
      rooms[roomNo].fanHvac = (HvacFanMode)val;  
     }  
     break;  
    default:  
     break;  
   }  
 #ifdef HVAC_TOSHIBA_DEBUG  
   Serial.println("Room Data:");  
   Serial.print("State: ");  
   Serial.println(rooms[roomNo].state);  
   Serial.print("Temp: ");  
   Serial.println(rooms[roomNo].temp);  
   Serial.print("Mode: ");  
   Serial.println(rooms[roomNo].modeHvac);  
   Serial.print("Fan: ");  
   Serial.println(rooms[roomNo].fanHvac);  
 #endif  
   int offState = 0;  
   if ( rooms[roomNo].state == 0) {  
    offState = 1;  
   }  
   sendHvacToshiba(rooms[roomNo].modeHvac, rooms[roomNo].temp, rooms[roomNo].fanHvac, offState, rooms[roomNo].pin);  
   //const char * statusTopic="esp8266/01/status/00/00";  
   publishRoomStatus(roomNo);  
  }  
 }  

.. to be continued ...

Commenti

Post popolari in questo blog

Toshiba Air Conditioner IR signal Reverse Engineering

Arduino pulse counting with multiple Energy Meters and logging to Emoncms