MQTT JSON Mitsubishi Heatpump

Hi Again :slight_smile:

I was also playing around with the " sign yesterday and found out it worked - Happy to see you are doing it the same way.

I was trying to do mappings with the setpoint DOH stupid me.

And again thanks, it all works like a charm - finally I can take control of my pretty “stupid” HeatPump

Can you explain what the RemoteTemp function is used for?

Glad to hear it is up and running. I did a lot of research before choosing these HP’s as I couldn’t see myself putting in AC that I couldn’t control. I was basically offered fujitsu, LG and mitsubishi. They all had some over priced cloud based wifi app which is better than nothing but I found the swicago’s library and knew that was the proper solution. I looked at their Kumo cloud offering which is like $150 USD per HP to outfit plus install. I setup all 5 of my HP’s with Huzzah’s for about $50 total. AND it works with OH. Kumo didn’t.

I digress.

As for remote temp. The HP’s have a built in temp sensor. It uses this sensor to approximate room temperature for its cycling on and off. The problem is that the sensor is influenced heavily by the temperature of the unit itself. So as the AC kicks on, it will quickly cool the sensor down making the unit think the room is too cold and switch cooling off and reduce the fan speed. A short time later (sometimes a matter of seconds) it warms back up without that cold air and resumes cooling and higher fan speeds. I found that the power consumption of my condenser was spiking up and down constantly as it did this. Additionally, you would hear the fan speed up and slow down every 30 seconds
 it irritated me for one.

SO, all that to say. If you send {“remoteTemp”:##} to the heat pump, it will ignore its internal sensor and use that number as the rooms temperature to do heating/cooling calculations off of. Of course you need to send that command every time the room’s temperature changes otherwise the HP will heat/cool indefinitely thinking it needs to.

In addition to the code provided by swicago, I added a function to the sketch to monitor for this command. If my HP sees the command, it starts a timer. If it does not receive the same command within say 5 minutes (can’t remember the number), it tells the HP to revert back to its internal sensor. I think that is by sending {“remoteTemp”:0} or something. I would have to look it up. I then setup an OH rule to send my zwave thermostat’s current temp every 4 minutes or when the thermostat’s temp changes any time the HP is powered on.

By doing this, if OH fails for whatever reason (unfortunately I still suffer from random rules engine stopping which nobody knows why), the HP will resume using its internal sensor instead of running full tilt indefinitely or until I intervene.

Mine is +8 Years old at that time I didn’t have any need to control it, but lucky me I choose Mitsubishi :slight_smile:

I’m running my sketch on a NodeMCU Lua 5 USD at Ebay - and your OH code this is a really cheap and powerful setup.

I’ll have to play a little around with the rempte temp, I have a Carlo Gavazzi setup with a Temperature sensor with feedback to OH via. modbus.

As for your sketch mods, I have no idea how to do so, but I would like to see if it’s possible to poll for the enhanced status eg. every 5-10 seconds. I believe it’s only the simple status update that polls every 60s.

I tweaked a few things in his mqtt example. His sketch wasn’t setup well for handling millis rollover, I added wifi and mqtt reconnect functions, calls to the Celsius to Fahrenheit conversion and the remote temp function I mentioned.

As for millis rollover, Arduino uses millis as a variable to time things. This variable increases 1 every millisecond. After about 50 days the variable overflows and resets to 0. If your code doesn’t know how to handle this, often it will fail to fire events until restarted. There are lots of write ups on this issue if you are interested. Since these HP’s will be powered on for months on end, I tweaked his code to be more resilient to rollover. Mine were up much longer than the rollover window with no issues this summer.

His code also tried to connect to wifi and the mqtt broker once at system boot. I found that if there was any interuption in my wifi or mqtt broker, I would have to power cycle each heatpump at the disconnect switch. I added code to dump back to reconnecting if it detects it is no longer connected.

Some of these things I knew to put in place ahead of time due to other arduino based automation projects I have in place already.

As for remote temp:

I added the following:

before setup:
unsigned long lastRemoteTemp; //holds last time a remote temp value has been received from OpenHAB

in setup:

lastRemoteTemp = millis();

Under void mqttCallback:

if(root.containsKey("remoteTemp")) {
  float remoteTemp = root["remoteTemp"];
  hp.setRemoteTemperature( hp.FahrenheitToCelsius(remoteTemp) ); //set usable F to HP's stupid C
  lastRemoteTemp = millis();
}

Someplace under void loop:

if ((unsigned long)(millis() - lastRemoteTemp) >= 300000) { //reset to local temp sensor after 5 minutes of no remote temp udpates
  hp.setRemoteTemperature(0); //sending 0 tells HP to revert to using internal temp sensor
  lastRemoteTemp = millis();
}

I can share more of the sketch if you need/want.

Hi

I don’t have enough understanding of sketch programming, but I would love if you would share it so I can learn and have a stable system?

Best Nanna

OK, I figured I would have to sanitize it
 there actually isn’t anything I can’t share. You know already but I have to say it for others later, this is mostly NOT my work, it’s swicago’s script that I modified:

#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <HeatPump.h>

#include "mitsubishi_heatpump_mqtt_esp8266.h"

#ifdef OTA
  #include <ESP8266mDNS.h>
  #include <ArduinoOTA.h>
#endif

// wifi, mqtt and heatpump client instances
WiFiClient espClient;
PubSubClient mqtt_client(espClient);
HeatPump hp;
unsigned long lastTempSend;
unsigned long lastRemoteTemp; //holds last time a remote temp value has been received from OpenHAB
unsigned long wifiMillis; //holds millis for counting up to hard reset for wifi reconnect


// debug mode, when true, will send all packets received from the heatpump to topic heatpump_debug_topic
// this can also be set by sending "on" to heatpump_debug_set_topic
bool _debugMode = false;
bool retain = true; //change to false to disable mqtt retain

void setup() {
  pinMode(redLedPin, OUTPUT);
  digitalWrite(redLedPin, HIGH);
  pinMode(blueLedPin, OUTPUT);
  digitalWrite(blueLedPin, HIGH);

  WIFIConnect(); //connect to wifi

  // configure mqtt connection
  mqtt_client.setServer(mqtt_server, mqtt_port);
  mqtt_client.setCallback(mqttCallback);
  //mqttConnect();  //this is now called during loop

  // connect to the heatpump. Callbacks first so that the hpPacketDebug callback is available for connect()
  hp.setSettingsChangedCallback(hpSettingsChanged);
  hp.setStatusChangedCallback(hpStatusChanged);
  hp.setPacketCallback(hpPacketDebug);

  #ifdef OTA
    ArduinoOTA.begin();
  #endif
  
  hp.connect(&Serial);

  lastTempSend = millis();
  lastRemoteTemp = millis();
}

void hpSettingsChanged() {
  const size_t bufferSize = JSON_OBJECT_SIZE(6);
  DynamicJsonBuffer jsonBuffer(bufferSize);

  JsonObject& root = jsonBuffer.createObject();

  heatpumpSettings currentSettings = hp.getSettings();

  root["power"]       = currentSettings.power;
  root["mode"]        = currentSettings.mode;
  root["temperature"] = hp.CelsiusToFahrenheit(currentSettings.temperature); //convert HP's C to usable F
  root["fan"]         = currentSettings.fan;
  root["vane"]        = currentSettings.vane;
  root["wideVane"]    = currentSettings.wideVane;
  root["iSee"]        = currentSettings.iSee;

  char buffer[512];
  root.printTo(buffer, sizeof(buffer));

  if(!mqtt_client.publish(heatpump_topic, buffer, retain)) {
    mqtt_client.publish(heatpump_debug_topic, "failed to publish to heatpump topic");
  }
}

void hpStatusChanged(heatpumpStatus currentStatus) {
  // send room temp and operating info
  const size_t bufferSizeInfo = JSON_OBJECT_SIZE(2);
  DynamicJsonBuffer jsonBufferInfo(bufferSizeInfo);
  
  JsonObject& rootInfo = jsonBufferInfo.createObject();
  //rootInfo["roomTemperature"] = currentStatus.roomTemperature;
  rootInfo["roomTemperature"] = hp.CelsiusToFahrenheit(currentStatus.roomTemperature); //convert HP's c to usable F
  rootInfo["operating"]       = currentStatus.operating;
  
  char bufferInfo[512];
  rootInfo.printTo(bufferInfo, sizeof(bufferInfo));

  if(!mqtt_client.publish(heatpump_status_topic, bufferInfo, true)) {
    mqtt_client.publish(heatpump_debug_topic, "failed to publish to room temp and operation status to heatpump/status topic");
  }

  // send the timer info
//  const size_t bufferSizeTimers = JSON_OBJECT_SIZE(5);
//  DynamicJsonBuffer jsonBufferTimers(bufferSizeTimers);
//  
//  JsonObject& rootTimers = jsonBufferTimers.createObject();
//  rootTimers["mode"]          = currentStatus.timers.mode;
//  rootTimers["onMins"]        = currentStatus.timers.onMinutesSet;
//  rootTimers["onRemainMins"]  = currentStatus.timers.onMinutesRemaining;
//  rootTimers["offMins"]       = currentStatus.timers.offMinutesSet;
//  rootTimers["offRemainMins"] = currentStatus.timers.offMinutesRemaining;

//  char bufferTimers[512];
//  rootTimers.printTo(bufferTimers, sizeof(bufferTimers));

//  if(!mqtt_client.publish(heatpump_timers_topic, bufferTimers, true)) {
//    mqtt_client.publish(heatpump_debug_topic, "failed to publish timer info to heatpump/status topic");
//  }
}

void hpPacketDebug(byte* packet, unsigned int length, char* packetDirection) {
  if (_debugMode) {
    String message;
    for (int idx = 0; idx < length; idx++) {
      if (packet[idx] < 16) {
        message += "0"; // pad single hex digits with a 0
      }
      message += String(packet[idx], HEX) + " ";
    }

    const size_t bufferSize = JSON_OBJECT_SIZE(1);
    DynamicJsonBuffer jsonBuffer(bufferSize);

    JsonObject& root = jsonBuffer.createObject();

    root[packetDirection] = message;

    char buffer[512];
    root.printTo(buffer, sizeof(buffer));

    if(!mqtt_client.publish(heatpump_debug_topic, buffer)) {
      mqtt_client.publish(heatpump_debug_topic, "failed to publish to heatpump/debug topic");
    }
  }
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
  // Copy payload into message buffer
  char message[length + 1];
  for (int i = 0; i < length; i++) {
    message[i] = (char)payload[i];
  }
  message[length] = '\0';

  if (strcmp(topic, heatpump_set_topic) == 0) { //if the incoming message is on the heatpump_set_topic topic...
    // Parse message into JSON
    const size_t bufferSize = JSON_OBJECT_SIZE(6);
    DynamicJsonBuffer jsonBuffer(bufferSize);
    JsonObject& root = jsonBuffer.parseObject(message);

    if (!root.success()) {
      mqtt_client.publish(heatpump_debug_topic, "!root.success(): invalid JSON on heatpump_set_topic...");
      return;
    }

    // Step 3: Retrieve the values
    if (root.containsKey("power")) {
      String power = root["power"];
      hp.setPowerSetting(power);
    }

    if (root.containsKey("mode")) {
      String mode = root["mode"];
      hp.setModeSetting(mode);
    }

    if (root.containsKey("temperature")) {
      float temperature = root["temperature"];
      //hp.setTemperature(temperature);
      hp.setTemperature( hp.FahrenheitToCelsius(temperature) ); //set usable F to HP's stupid C
    }

    if (root.containsKey("fan")) {
      String fan = root["fan"];
      hp.setFanSpeed(fan);
    }

    if (root.containsKey("vane")) {
      String vane = root["vane"];
      hp.setVaneSetting(vane);
    }

    if (root.containsKey("wideVane")) {
      String wideVane = root["wideVane"];
      hp.setWideVaneSetting(wideVane);
    }

    if(root.containsKey("remoteTemp")) {
      float remoteTemp = root["remoteTemp"];
      //hp.setRemoteTemperature(remoteTemp);
      hp.setRemoteTemperature( hp.FahrenheitToCelsius(remoteTemp) ); //set usable F to HP's stupid C
      lastRemoteTemp = millis();
    }
    else if (root.containsKey("custom")) {
      String custom = root["custom"];

      // copy custom packet to char array
      char buffer[(custom.length() + 1)]; // +1 for the NULL at the end
      custom.toCharArray(buffer, (custom.length() + 1));

      byte bytes[20]; // max custom packet bytes is 20
      int byteCount = 0;
      char *nextByte;

      // loop over the byte string, breaking it up by spaces (or at the end of the line - \n)
      nextByte = strtok(buffer, " ");
      while (nextByte != NULL && byteCount < 20) {
        bytes[byteCount] = strtol(nextByte, NULL, 16); // convert from hex string
        nextByte = strtok(NULL, "   ");
        byteCount++;
      }

      // dump the packet so we can see what it is. handy because you can run the code without connecting the ESP to the heatpump, and test sending custom packets
      hpPacketDebug(bytes, byteCount, "customPacket");

      hp.sendCustomPacket(bytes, byteCount);
    }
    else {
      bool result = hp.update();

      if (!result) {
        mqtt_client.publish(heatpump_debug_topic, "heatpump: update() failed");
      }
    }

  } else if (strcmp(topic, heatpump_debug_set_topic) == 0) { //if the incoming message is on the heatpump_debug_set_topic topic...
    if (strcmp(message, "on") == 0) {
      _debugMode = true;
      mqtt_client.publish(heatpump_debug_topic, "debug mode enabled");
    } else if (strcmp(message, "off") == 0) {
      _debugMode = false;
      mqtt_client.publish(heatpump_debug_topic, "debug mode disabled");
    }
  } else {
    mqtt_client.publish(heatpump_debug_topic, strcat("heatpump: wrong mqtt topic: ", topic));
  }
}

void mqttConnect() {
  // Loop until we're reconnected
  while (!mqtt_client.connected()) {
    // Attempt to connect
    if (mqtt_client.connect(client_id, mqtt_username, mqtt_password)) {
      mqtt_client.subscribe(heatpump_set_topic);
      mqtt_client.subscribe(heatpump_debug_set_topic);
    } else {
      // Wait 5 seconds before retrying
      delay(5000);
      if (WiFi.status() !=WL_CONNECTED) //reconnect wifi
      {
        WIFIConnect();
      }
    }
  }
}

void WIFIConnect() { //wifi reconnect
  WiFi.disconnect();
  //WiFi.mode(WIFI_STA);  //set to not broadcast ssid
  WiFi.begin(ssid, password);
  wifiMillis = millis(); //start "timer"
  while (WiFi.status() != WL_CONNECTED) { //sit here indefinitely trying to connect
    // wait 500ms, flashing the blue LED to indicate WiFi connecting...
    digitalWrite(blueLedPin, LOW);
    delay(250);
    digitalWrite(blueLedPin, HIGH);
    delay(250);
    if ((unsigned long)(millis() - wifiMillis) >= 20000) break;
    //if (millis() > wifiMillis + 20000) break; //call reset
  }
}

void loop() {

  if (WiFi.status() !=WL_CONNECTED) //reconnect wifi
  {
     WIFIConnect();
  } else {
  
    if (!mqtt_client.connected()) {
      mqttConnect();
    }
  
    hp.sync();
  
    //if (millis() > (lastTempSend + SEND_ROOM_TEMP_INTERVAL_MS)) { // only send the temperature every 60s
    if ((unsigned long)(millis() - lastTempSend) >= SEND_ROOM_TEMP_INTERVAL_MS) { //only send the temperature every 60s (default)  
      hpStatusChanged(hp.getStatus());
      lastTempSend = millis();
    }
  
    //if (millis() > (lastRemoteTemp + 300000)) { //reset to local temp sensor after 5 minutes of no remote temp
    if ((unsigned long)(millis() - lastRemoteTemp) >= 300000) { //reset to local temp sensor after 5 minutes of no remote temp udpates
      hp.setRemoteTemperature(0);
      lastRemoteTemp = millis();
    }
    
    mqtt_client.loop();
    
  #ifdef OTA
     ArduinoOTA.handle();
  #endif
  }
}

You did a lot of tweaking comparing those two code examples :slight_smile:

I’ll have to take a closer look to understand it all.

Great to have a fail safe if WiFi & MQTT connections fails and also get a chance to turn off all those debug sends.

If you would do a tweak on the status, to have the detailed status send every 60s like the “normal” status - how should I approach this?

Didn’t you have the same issues with the ACTemp.map as with the ACRemoteTemp.map ?

I needed 61={“temperature”:61} and 61.0={“temperature”:61} when working with the Android APP.

I think without testing, you need to call hpSettingsChanged instead of hpStatusChanged. So, towards the end of the sketch you see:

if ((unsigned long)(millis() - lastTempSend) >= SEND_ROOM_TEMP_INTERVAL_MS) { //only send the temperature every 60s (default)  
      hpStatusChanged(hp.getStatus());
      lastTempSend = millis();
    }

This calls the function that returns only temp and operating status.

You need to change that to:

if ((unsigned long)(millis() - lastTempSend) >= SEND_ROOM_TEMP_INTERVAL_MS) { //only send the temperature every 60s (default)  
      hpSettingsChanged(hp.getStatus());
      lastTempSend = millis();
    }

This function feeds back much more data about the heatpump.

As for the temp maps. I’m thinking I’m a little confused on the reasoning behind the multiple map entries. It may not have been the BasicUI and app. The more I think about it the more I think it was an issue of something in OH or my zwave stuff that was not consistent with decimal places. The maps I listed work perfectly for everything I do but there may be different behavior for some of your equipment. I watched the logs to see what it was trying to send and then created additional map entries to accommodate. I don’t use Android so I’m not certain how the app may differ.

Ill have I look at the code :slight_smile:

I needed to have multiple map entries to be able to set the temperature from Chrome, Android & Iphone

That’s unfortunate but at least a bandaid that can be applied. Ultimately this is an OH issue. It needs to be extended to support being able to send ALL characters via json. I cannot remember which character it didn’t like but it was ’ " or { I think. Until that gets extended
 maps it is and lots of them.

This works and it works well - that is all that matters :slight_smile:
Out of the box project aren’t as fun as the ones with a little effort to succeed .

I have problems with the hpSettingsChanged(hp.getStatus());

Do you know were the Mitsubishi commands can be found?

Try just hpSettingsChanged();

It looks like it doesn’t want any arguments.

i’m still trying to make this working - until now not succeeded .

The remoteTemp i’m I correct that this has to be published in the /set topic?

When I publish eg. “25” Celsius, the HP respond with a room temperature of 10, and obvious switch the HP to Operation=false as this is below the setpoint.

When I send 0 it goes back to the internal sensor, and also if I just wait 5 minutes your code change kicks in.

Hi,

after all the activity in this thread, I finally took the time to investigate if my old Mitsubishi HP has the desired connector - and it looks as it does! Incredible! :slight_smile: I have ordered the prebuilt cable, and meanwhile dug out a Wemo mini D1, installed arduino ide, and shamelessly copied your scetch, @Moxified, and built/downloaded to the ESP.

Now, while I have been working with ESP8266 before, I have never used the Arduino IDE, so I’m a real newbie on this. I can see that the ESP is up and running, blue LED is blinking, and if I do a wifi scan, I find AI-THINKER_021692 wlan. If I connect to it, with my phone, I get 192.168.4.2, and I guess the AP it self is 192.168.4.1. So I try to browse to this address with my phone, but I get a ‘Connection refused’ error when I do this.
So my extremely stupid question is - how is this supposed to work, how do I enter the setup page of the ESP?? I have tried to google this, but I arrive at very old pages giving different suggestions. :blush:

Maybe the phones will not work,maybe I have to go and grab my work laptop?

Edit:
Or - do I actually also need an Arduino for this? I only have the ESP8266.

Hi

The Mitsubishi MQTT code is connecting as a client to an existing AP.

You can find the rest of the code needed for @Moxified tweaked version.

Don’t forget that my code has F to C conversion going on. Sounds like maybe you didn’t strip it all out. It has two functions that need to be removed everywhere: hp.CelsiusToFahrenheit and hp.FahrenheitToCelsius. I would search for these and remove them like this:

hp.setRemoteTemperature( hp.FahrenheitToCelsius(remoteTemp) )

Should be:

hp.setRemoteTemperature( remoteTemp )

I would search for any hp.FahrenheitToCelsius and any hp.CelsiusToFahrenheit and remove them. Then try again.

@vespaman, I’m not “familiar” with that Wemo mini D1. If it is anything like the plain ole esp8266, out of the box, they don’t work directly with the arduino IDE. You have to jump through some hoops to remove the stock AT command interface. You may be past this already but wanted to make sure. Here is a walkthrough for that: ESP8266 - Easiest way to program so far (Using Arduino IDE)

As Nanna pointed out, you need to grab the rest of the bits for SwiCago’s library. My sketch is useless without the pubsubclient (mqtt library) and heatpump-master (swicago’s heatpump library) libraries or the .h file which contains referenced settings. pubsubclient you can get through the library manager. You will have to download the heatpump-master library from Nanna’s link and pull that into the IDE. I don’t recall off the top of my head the steps for that but Google will know :smiley:

Once you have that in, I would start with the included mqtt example. Copy that to a new project and open with IDE. Replace the contents of the first tab (the ino) with my bits if you like. Then on the second tab (.h) update to fit your needs. The .h is where the settings unique to your environment go like wifi ssid and password etc.

Hopefully this helps. I’m really rusty as I don’t work with this stuff every day and I figured this stuff all out 5 months ago and then moved on to building a garage. Studying building code is enough to make you forget your own first name


Oh and how Posh
 there were no prebuilt cables in my day and we liked it, we loved it!

No worries
 I shamelessly copied all of SwiCago’s work. This is all thanks to those guys. I’m just glad I can help others with my work.