MQTT JSON Mitsubishi Heatpump

I assume you are working with swicagoā€™s mqtt example? There was an issue I ran into where his library used the default payload size which was too small for topics over a certain length. Basically if you changed your topic from the default of the example it wouldnā€™t work.

The solution was to edit the .h file or something to increase the buffer.

If this sounds at all correct, check out this: https://github.com/SwiCago/HeatPump/issues/53

If not, give more details (sketch, mosquitto pub commands and results etc.) and Iā€™ll try to dust this off in my brain. I havenā€™t been working with OH or the HP stuff for 8 months or so.

Yes exactly Iā€™m using that library Ill try to look into what you are writing :slight_smile:

I was wondering if you would share your example of Items, Sitemaps and TransformMAP ?

Thanks in advanced :slight_smile:

Yeah, basically, open pubsubclient.h and edit MQTT_MAX_PACKET_SIZE to 256, recompile and upload to your micro controller.

Iā€™ll try to dig up my stuff. I will admit that it was a major project to get the transforms and junk correct. IIRCā€¦ OH doesnā€™t like feeding certain characters out via json which caused major headache.

On the bright side, the system worked perfectly all summer for 5 HPā€™s.

Hi #Moxified

I have been reading your post, and I can see itā€™s a huge project - hope you would share with me :slight_smile:

I tried to increase the Size to 256, but I get the same error !root.success(): invalid JSON on heatpump_set_topicā€¦

Topic: heatpump/set
Payload: ā€œmodeā€:ā€œHEATā€

Ok, sounds like more is going on. do a paste bin of your sketch files and also let me know the exact commands you are sending and from what mqtt client. Iā€™ll see if I can notice anything out of order.

Great :slight_smile:

mitshibishi_heatpump_mqtt_esp8266

#include <ArduinoJson.h>


#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;

// 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 = true;


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

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    // wait 500ms, flashing the blue LED to indicate WiFi connecting...
    digitalWrite(blueLedPin, LOW);
    delay(250);
    digitalWrite(blueLedPin, HIGH);
    delay(250);
  }

  // startup mqtt connection
  mqtt_client.setServer(mqtt_server, mqtt_port);
  mqtt_client.setCallback(mqttCallback);
  mqttConnect();

  // 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();
}

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"] = currentSettings.temperature;
  root["fan"]         = currentSettings.fan;
  root["vane"]        = currentSettings.vane;
  root["wideVane"]    = currentSettings.wideVane;
  //root["iSee"]        = currentSettings.iSee;

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

  bool retain = true;
  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["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);
    }

    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);
    }
    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);
    }
  }
}

void loop() {
  if (!mqtt_client.connected()) {
    mqttConnect();
  }

  hp.sync();

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

  mqtt_client.loop();
  
#ifdef OTA
   ArduinoOTA.handle();
#endif
}

mitsubishi_heatpump_mqtt_esp8266.h

//#define OTA

// wifi settings
const char* ssid     = "xxx";
const char* password = "xxx";

// mqtt server settings
const char* mqtt_server   = "xxx";
const int mqtt_port       = 1883;
const char* mqtt_username = "xxx";
const char* mqtt_password = "xxx";

// mqtt client settings
// Note PubSubClient.h has a MQTT_MAX_PACKET_SIZE of 128 defined, so either raise it to 256 or use short topics
const char* client_id                   = "heatpump-controller-1"; // Must be unique on the MQTT network
const char* heatpump_topic              = "heatpump";
const char* heatpump_set_topic          = "heatpump/set";
const char* heatpump_status_topic       = "heatpump/status";
const char* heatpump_timers_topic       = "heatpump/timers";

const char* heatpump_debug_topic        = "heatpump/debug";
const char* heatpump_debug_set_topic    = "heatpump/debug/set";

// pinouts
const int redLedPin  = 0; // Onboard LED = digital pin 0 (red LED on adafruit ESP8266 huzzah)
const int blueLedPin = 2; // Onboard LED = digital pin 0 (blue LED on adafruit ESP8266 huzzah)

// sketch settings
const unsigned int SEND_ROOM_TEMP_INTERVAL_MS = 60000;

I use MQTT.fx Client for testing

heatpump/set

ā€œmodeā€:ā€œHEATā€

OK, give me a little bit. Iā€™m at work. I may have time to look this over later but it may take me a day if I get bogged. I will look at it though.

I also tried to fire the the command from command line

mosquitto_pub -h xxx -t heatpump/set -m ā€œmodeā€:ā€œHEATā€ with the same error

Great thanks alot :slight_smile:

I donā€™t think that is the correct format. Try:

mosquitto_pub -d -t heatpump/set -m ā€˜{ā€œmodeā€: ā€œHEATā€}ā€™

I wish there were others like you :slight_smile:

Thanks a million it works PERFECT nowā€¦ WOW

OK, Iā€™ll drum up my items and maps. The maps are the gold that makes OH able to send the json format needed by swicagoā€™s mqtt example.

That would really REALLY make my day - you will defiantly be on my Christmas gift list this year :slight_smile:

OK, here they are. You need to make a different map file for each of the listed map sections below. Word of warningā€¦ I use F as Iā€™m US based and C makes no sense to me :slight_smile: Youā€™ll need to adapt the mappings to go lower to handle C probably if you are not using F. I have multiple HPā€™s so just adapt each HP to have a slightly different topic to differentiate and then update the .items stuff but the maps work for all.

Oh and you may notice the temp maps have **.0 and **.00. If you use basicUI, it puts .00 and the IOS app puts .0 so you have to have both to get both to work. I could have those backwards too but you get the idea.

And lastly, I send remotetemp data back to my HP as I use my zwave wall mounted thermostats as the room temp. I only use my HP as AC as I have a nice hydronic forced hot water system. I live in New Hampshire where HPā€™s arenā€™t as efficient for heating purposes. You can omit that if you donā€™t need that. I find that the HP fans donā€™t seek around so much (and the compressor inverter power graph is more linear with less peaks and dips) when they use external temp translating to better energy and noise control.

Switch Living_HP_Power			"Power"					{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACPower.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.power)]" }
String Living_HP_Mode			"Mode"		<text>			{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACMode.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.mode)]" }
Number Living_HP_Temp			"Set Temp: [%d]"	<temperature>	{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACTemp.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.temperature)]" }
String Living_HP_Fan			"Fan: [%s]"		<fan>			{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACFan.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.fan)]" }
String Living_HP_Vane			"Vane: [%s]"		<flow>			{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACVane.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.vane)]" }
String Living_HP_WideVane		"WideVane: [%s]"	<flow>			{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACWideVane.map)],<[broker:home/livingroom/heatpump:state:JSONPATH($.wideVane)]" }
Number Living_HP_RemoteTemp					<temperature>	{ mqtt=">[broker:home/livingroom/heatpump/set:command:*:MAP(ACRemoteTemp.map)]" }
String Living_HP_Status			"Operating: [%s]"	<text>			{ mqtt="<[broker:home/livingroom/heatpump/status:state:JSONPATH($.operating)]" }
Number Living_HP_RoomTemp		"RoomTemp: [%d]"	<temperature>	{ mqtt="<[broker:home/livingroom/heatpump/status:state:JSONPATH($.roomTemperature)]" }
ACFan.map

1={"fan":"1"}
2={"fan":"2"}
3={"fan":"3"}
4={"fan":"4"}
AUTO={"fan":"AUTO"}
QUIET={"fan":"QUIET"}

ACMode.map

COOL={"mode":"COOL"}
HEAT={"mode":"HEAT"}
DRY={"mode":"DRY"}

ACPower.map

ON={"power":"ON"}
OFF={"power":"OFF"}

ACRemoteTemp.map

50.0={"remoteTemp":50}
51.0={"remoteTemp":51}
52.0={"remoteTemp":52}
53.0={"remoteTemp":53}
54.0={"remoteTemp":54}
55.0={"remoteTemp":55}
56.0={"remoteTemp":56}
57.0={"remoteTemp":57}
58.0={"remoteTemp":58}
59.0={"remoteTemp":59}
60.0={"remoteTemp":60}
61.0={"remoteTemp":61}
62.0={"remoteTemp":62}
63.0={"remoteTemp":63}
64.0={"remoteTemp":64}
65.0={"remoteTemp":65}
66.0={"remoteTemp":66}
67.0={"remoteTemp":67}
68.0={"remoteTemp":68}
69.0={"remoteTemp":69}
70.0={"remoteTemp":70}
71.0={"remoteTemp":71}
72.0={"remoteTemp":72}
73.0={"remoteTemp":73}
74.0={"remoteTemp":74}
75.0={"remoteTemp":75}
76.0={"remoteTemp":76}
77.0={"remoteTemp":77}
78.0={"remoteTemp":78}
79.0={"remoteTemp":79}
80.0={"remoteTemp":80}
81.0={"remoteTemp":81}
82.0={"remoteTemp":82}
83.0={"remoteTemp":83}
84.0={"remoteTemp":84}
85.0={"remoteTemp":85}
86.0={"remoteTemp":86}
87.0={"remoteTemp":87}
88.0={"remoteTemp":88}
89.0={"remoteTemp":89}
90.0={"remoteTemp":90}
91.0={"remoteTemp":91}
92.0={"remoteTemp":92}
93.0={"remoteTemp":93}
94.0={"remoteTemp":94}
95.0={"remoteTemp":95}
96.0={"remoteTemp":96}
97.0={"remoteTemp":97}
98.0={"remoteTemp":98}
99.0={"remoteTemp":99}
100.0={"remoteTemp":100}
50.00={"remoteTemp":50}
51.00={"remoteTemp":51}
52.00={"remoteTemp":52}
53.00={"remoteTemp":53}
54.00={"remoteTemp":54}
55.00={"remoteTemp":55}
56.00={"remoteTemp":56}
57.00={"remoteTemp":57}
58.00={"remoteTemp":58}
59.00={"remoteTemp":59}
60.00={"remoteTemp":60}
61.00={"remoteTemp":61}
62.00={"remoteTemp":62}
63.00={"remoteTemp":63}
64.00={"remoteTemp":64}
65.00={"remoteTemp":65}
66.00={"remoteTemp":66}
67.00={"remoteTemp":67}
68.00={"remoteTemp":68}
69.00={"remoteTemp":69}
70.00={"remoteTemp":70}
71.00={"remoteTemp":71}
72.00={"remoteTemp":72}
73.00={"remoteTemp":73}
74.00={"remoteTemp":74}
75.00={"remoteTemp":75}
76.00={"remoteTemp":76}
77.00={"remoteTemp":77}
78.00={"remoteTemp":78}
79.00={"remoteTemp":79}
80.00={"remoteTemp":80}
81.00={"remoteTemp":81}
82.00={"remoteTemp":82}
83.00={"remoteTemp":83}
84.00={"remoteTemp":84}
85.00={"remoteTemp":85}
86.00={"remoteTemp":86}
87.00={"remoteTemp":87}
88.00={"remoteTemp":88}
89.00={"remoteTemp":89}
90.00={"remoteTemp":90}
91.00={"remoteTemp":91}
92.00={"remoteTemp":92}
93.00={"remoteTemp":93}
94.00={"remoteTemp":94}
95.00={"remoteTemp":95}
96.00={"remoteTemp":96}
97.00={"remoteTemp":97}
98.00={"remoteTemp":98}
99.00={"remoteTemp":99}
100.00={"remoteTemp":100}

ACTemp.map

61={"temperature":61}
62={"temperature":62}
63={"temperature":63}
64={"temperature":64}
65={"temperature":65}
66={"temperature":66}
67={"temperature":67}
68={"temperature":68}
69={"temperature":69}
70={"temperature":70}
71={"temperature":71}
72={"temperature":72}
73={"temperature":73}
74={"temperature":74}
75={"temperature":75}
76={"temperature":76}
77={"temperature":77}
78={"temperature":78}
79={"temperature":79}
80={"temperature":80}
81={"temperature":81}
82={"temperature":82}
83={"temperature":83}
84={"temperature":84}
85={"temperature":85}
86={"temperature":86}
87={"temperature":87}
88={"temperature":88}

ACVane.map

1={"vane":"1"}
2={"vane":"2"}
3={"vane":"3"}
4={"vane":"4"}
5={"vane":"5"}
AUTO={"vane":"AUTO"}
SWING={"vane":"SWING"}

ACWideVane.map

<<={"wideVane":"<<"}
<={"wideVane":"<"}
|={"wideVane":"|"}
>={"wideVane":">"}
>>={"wideVane":">>"}
<>={"wideVane":"<>"}
SWING={"wideVane":"SWING"}

Thanks really REALLY THANKS - Amazing work, I would never have been able to do this, look forward to have this added to my OH :slight_smile:

Sure thing. I always intended to put together a little tutorial thing but Iā€™m not a very good blogger.

If you need help with site map, let me know. I didnā€™t put that as it would be all broken but can help give examples.

This is definitely better than Christmas :slight_smile:

I have the HP_Power and HP_Mode working

You sure saved me a lot of trouble with this project, I canā€™t thank you enough :slight_smile:

1 Like

Hi #Moxified

I do run into problems with the wideVane

Can you show me what your Siteman for this entry look like, I believe my problem is those different characters .

Also what do you use Selection Item or Setpoint Items for temperature? I manage to do the Selection Item, but would prefer Setpoint.

Best Nanna

Ah yes, the widevane was a tricky one iirc. I definitely use setpoint for temps and selections for the fan and vanes.

This is what I used for sitemap stuff for my livingroom HP. Itā€™s the only one I have with widevane. The rest are smaller units with only vane.

Switch item=Living_HP_Power
Switch item=Living_HP_Mode mappings=[COOL="Cool", HEAT="Heat", DRY="Dry"]
Setpoint item=Living_HP_Temp minValue=61 maxValue=88 step=1
Selection item=Living_HP_Fan mappings=[1="1", 2="2",3="3",4="4",AUTO="AUTO",QUIET="QUIET"]
Selection item=Living_HP_Vane mappings=[1="1", 2="2",3="3",4="4",5="5",AUTO="AUTO",SWING="SWING"]
Selection item=Living_HP_WideVane	mappings=["<<"="<<","<"="<","|"="|",">"=">",">>"=">>",SWING="SWING"]
Text item=Living_HP_Status
Text item=Living_HP_RoomTemp