Homewizard climate local MQTT control!

Hey Guys,

I bought a princess smart panel heater like this:

It is wifi enabled and controllable with the homewizard climat app. I did send a mail to support to ask for an open (local) API, you can guess the answer. You need to stay connected to the internet for the smart functions and HA integration is not possible…

So I started to investigate with tcpdump and nmap, but the traffic was TLS encrypted and no open ports on the heater.

My second option was to MITM the android app with an SSL proxy and custom CA and with great success, i got the ins and outs of the android app (websockets) and created a python bridge (HomewizardWebsocketsToMQTT). There were 2 problems: Every 30min a reconnect and we still needed the homewizard cloud connection. After all, the bridge was merely an android app emulation.

So back to the drawing board…

The heater does a DNS lookup for: m.cloud.homewizard.com and creates a connection to 443 (TLS). I changed my DNS server to point m.cloud.homewizard.com to my SSL proxy, but no cigar.
So maybe the subdomain m stands for mqtt… I knew I needed a TLS connection, and my best guess was MQTT over websockets. So I created a self-signed cert, opened a 443 TLS enable websocket and pointed the m.cloud.homewizard.com domain to my mosquitto server in verbose mode and bingo:

1613210905: New client connected from ::ffff:10.20.30.40 as ESP32_217CC0 (p2, c1, k10).

It immediately started to subscribe to topics and pushed some retained messages!! I disabled the internet of the heater in the firewall, and now I have full local MQTT control!

How TO:

  • Connect the heater to the Wi-Fi like original intended (maybe you can sniff the setup stuff and keep it local, but I didn’t, my heater was already on the network with cloud access)
  • Block the internet (so no FW updates are possible)
  • Redirect m.cloud.homewizard.com to your MQTT server
  • Create a self-signed certificate:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 3650 -out certificate.pem
Country Name (2 letter code) [XX]:US
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:Let's Encrypt
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:m.cloud.homewizard.com
Email Address []:
  • copy the files (key.pem and certificate.pem) to your MQTT broker. (i used mosquitto v1.6.12)
  • Create the MQTT websocket listener on port 443
vi /etc/mosquitto/mosquitto.conf

add:

port 443
protocol websockets
certfile /etc/mosquitto/certs/m.cloud.homewizard.certificate.pem
keyfile /etc/mosquitto/certs/m.cloud.homewizard.key.pem

listener 1883
protocol mqtt

Be sure to remove any authentication. I also use listener 1883 with protocol MQTT for non SSL local MQTT stuff.

Restart the broker and enjoy your MQTT local controllable heater.

When the heater connects to the broker, it sends the following retained messages:

appliance/heater/macadressheater/state {"power_on":"false","lock":"false","target_temperature":"25","current_temperature":"12","timer":"0","mode":"low"}
appliance/heater/macadressheater/state/power_on false
appliance/heater/macadressheater/state/lock false
appliance/heater/macadressheater/state/target_temperature 25
appliance/heater/macadressheater/state/timer 0
appliance/heater/macadressheater/state/mode low
appliance/heater/macadressheater/state/current_temperature 12
appliance/heater/macadressheater/$online true
appliance/heater/macadressheater/$version 1.07
appliance/heater/macadressheater/$properties [{"name":"power_on","type":"bool","readonly":false},{"name":"lock","type":"bool","readonly":false},{"name":"target_temperature","type":"int","readonly":false},{"name":"current_temperature","type":"int","readonly":true},{"name":"timer","type":"int","readonly":false},{"name":"mode","type":"enum","values":["high","low"],"readonly":false}]
appliance/heater/macadressheater/$name Heater
appliance/heater/macadressheater/$type heater

And you can control it with:

appliance/heater/macadressheater/$online (true/false) < last will
appliance/heater/macadressheater/$update/set (??)
appliance/heater/macadressheater/$beep (??)
appliance/heater/macadressheater/$wifi_led (??)
appliance/heater/macadressheater/state/power_on/set (true/false)
appliance/heater/macadressheater/state/lock/set (true/false)
appliance/heater/macadressheater/state/target_temperature/set (int)
appliance/heater/macadressheater/state/timer/set (int)
appliance/heater/macadressheater/state/mode/set (low/high)
appliance/heater/macadressheater/$token (??)

HA example:
heater

Yaml in config:
***** SEE EDIT2 FOR THE NEW FORMAT ****

switch:
  - platform: mqtt
    name: "Badkamer vuurtje lock"
    state_topic: "appliance/heater/macadressheater/state/lock"
    command_topic: "appliance/heater/macadressheater/state/lock/set"
    payload_on: "true"
    payload_off: "false"
    state_on: "true"
    state_off: "false"

  - platform: mqtt
    name: "Badkamer vuurtje mode"
    state_topic: "appliance/heater/macadressheater/state/mode"
    command_topic: "appliance/heater/macadressheater/state/mode/set"
    payload_on: "high"
    payload_off: "low"
    state_on: "high"
    state_off: "low"

climate:
  - platform: mqtt
    name: Badkamer
    fan_modes:
      - "high"
      - "low"
    modes: ["off", "heat"]
    mode_command_topic: "appliance/heater/macadressheater/state/power_on/set" 
    mode_state_topic: "appliance/heater/macadressheater/state/power_on"
    mode_state_template: >-
      {% if value == "false" %}
        off
      {% elif value == "true" %}
        heat
      {% endif %}
    mode_command_template: >-
      {% if value == "off" %}
        false
      {% elif value == "heat" %}
        true
      {% endif %}
    min_temp: 10   
    max_temp: 35   
    current_temperature_topic: "appliance/heater/macadressheater/state/current_temperature"
    temperature_command_topic: "appliance/heater/macadressheater/state/target_temperature/set"
    temperature_state_topic: "appliance/heater/macadressheater/state/target_temperature"
    fan_mode_command_topic: "appliance/heater/macadressheater/state/mode/set"
    fan_mode_state_topic: "appliance/heater/macadressheater/state/mode"
    precision: 1.0

Lovelace yaml:

- type: vertical-stack
        title: Badkamer
        cards:
          - type: thermostat
            entity: climate.badkamer
          - type: entities
            entities:
              - entity: switch.badkamer_vuurtje_mode
                name: mode (low/high)
              - entity: switch.badkamer_vuurtje_lock
                name: lock (OFF/ON)
            show_header_toggle: false

I only own this exact model, so I don’t know if the other homewizard climat devices acting the same.
I really enjoyed the quest for local control and I hope it can be of use for some of you!

Greets,

Edit: Also check the edit here: Homewizard climate local MQTT control! - #6 by a1ad

Edit2: yml config in the new format:

mqtt:
  climate:
    - name: Badkamer
      fan_modes:
        - "high"
        - "low"
      modes: ["off", "heat"]
      mode_command_topic: "appliance/heater/3c39e7217cc0/state/power_on/set" 
      mode_state_topic: "appliance/heater/3c39e7217cc0/state/power_on"
      mode_state_template: >-
        {% if value == "false" %}
          off
        {% elif value == "true" %}
          heat
        {% endif %}
      mode_command_template: >-
        {% if value == "off" %}
          false
        {% elif value == "heat" %}
          true
        {% endif %}
      min_temp: 10   
      max_temp: 35   
      current_temperature_topic: "appliance/heater/3c39e7217cc0/state/current_temperature"
      temperature_command_topic: "appliance/heater/3c39e7217cc0/state/target_temperature/set"
      temperature_state_topic: "appliance/heater/3c39e7217cc0/state/target_temperature"
      fan_mode_command_topic: "appliance/heater/3c39e7217cc0/state/mode/set"
      fan_mode_state_topic: "appliance/heater/3c39e7217cc0/state/mode"
      precision: 1.0

switch:
  - name: "Badkamer vuurtje"
    state_topic: "appliance/heater/3c39e7217cc0/state/power_on"
    command_topic: "appliance/heater/3c39e7217cc0/state/power_on/set"
    payload_on: "true"
    payload_off: "false"
    state_on: "true"
    state_off: "false"

  - name: "Badkamer vuurtje lock"
    state_topic: "appliance/heater/3c39e7217cc0/state/lock"
    command_topic: "appliance/heater/3c39e7217cc0/state/lock/set"
    payload_on: "true"
    payload_off: "false"
    state_on: "true"
    state_off: "false"

  - name: "Badkamer vuurtje mode"
    state_topic: "appliance/heater/3c39e7217cc0/state/mode"
    command_topic: "appliance/heater/3c39e7217cc0/state/mode/set"
    payload_on: "high"
    payload_off: "low"
    state_on: "high"
    state_off: "low"

sensor:
  - platform: mqtt
    state_topic: "homewiz/heater/3c39e7217cc0/state/current_temperature"
    name: Temperatuur Vuurtje badkamer

  - platform: mqtt
    state_topic: "homewiz/heater/3c39e7217cc0/state/target_temperature"
    name: Target temp Vuurtje badkamer
12 Likes

Thx for figuring all of this out. I have it working ! It was not easy. Especially because I could not get mosquitto to work on 443 with sockets and a self-signed certificate. In the end it worked when I used the exact same mosquitto version as you (1.6.12). It is working super well now though.

Ah yes, that’s why i put the version number in the post. I am glad you got it working. Its really rock solid now for me.

Many thanks for this manual. Indeed not easy but I was able to set everything up.

Currently I am using the Home Assistant Mosquitto broker Addon, which I didn’t want to change.

I have setup a second Mosquitto broker (using 1.6.13 which also works) for this heater which I have bridged to my Home Assistant Mosquitto broker.
I only want the heater topics to be shared between the brokers, see configuration.

If someone is interested the configuration for the heater mosquitto broker I use looks like this:

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

port 443
protocol websockets
certfile /mosquitto/data/certs/certificate.pem
keyfile /mosquitto/data/certs/key.pem

# External MQTT Broker (HA Mosquitto broker)
connection external-bridge
address <ipaddress, example 10.1.2.3 without port>
topic appliance/heater/# both 0
remote_username <username HA broker>
remote_password <password HA broker>
2 Likes

Just updated mosquitto to 2.0.9 and binding to 443 isnt working anymore.

I didn’t investigate why yet. So be carefull.

Guys,

Because i dont want to break something after every server upgrade on my main mqtt server i installed a mosquitto docker for the heater only and like @heinoldenhuis bridged it to my main mqtt broker.

 sudo docker run -itd \
--name=mqtt \
--restart=always \
-p 443:8084 \
-v /mnt/docker/config/dock-mosquitto/mosquitto/config:/mosquitto/config:ro \
-v /mnt/docker/config/dock-mosquitto/mosquitto/data:/mosquitto/data \
-v /mnt/docker/config/dock-mosquitto/mosquitto/log:/mosquitto/log \
eclipse-mosquitto:1.6.12-openssl

Config:

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

listener 8084
protocol websockets
certfile  /mosquitto/config/m.cloud.homewizard.certificate.pem
keyfile /mosquitto/config/m.cloud.homewizard.key.pem

connection internal_mqtt
address <ip main mqtt server> 
topic appliance/heater/# both 0
remote_username ***
remote_password ***

And changed the DNS to the IP of the docker host.

Now i can update the main server without worry and the mosquitto version for the heater stays the same. I like this setup better because now i still can use password based auth on the main mqtt server.

1 Like

Big thanks for the great work and info on this! I utilized the same scheme to set up a Princess Smart Dehumidifier. The Mosquitto version I had readily available was slightly older than your, but worked fine with same config. The dehumidifier has slightly different commands, but follows essentially the same scheme.

Nice one! It’s a lot more stable with local MQTT :slight_smile:

I purchased a portable air conditioner that uses the HomeWizard climate app, I set up a mosquitto broker on docker with port mapped to 4443 externally pointing to port 443 internally in the container, the mosquitto config is set up accordingly with port 443 as websockets. I also generated the self-signed certificate and placed in the config.
I use a Unifi Dream Machine so I port forwarded requests originating from the IP of my air conditioner to the Mosquitto broker I set up just for the aircon, forwarding traffic from 443 to 4443 so that it communicates with the broker.
I also use pihole so I created a DNS rule to forward all traffic to m.cloud.homewizard.com to the IP of my server where the mosquitto broker for the aircon is running, although there is no way to set up a port I figured that the port forwarding rule on the UDM would take care of that. However, it’s not working. My aircon is not subscribing to the broker and I cannot see any topics. Any help appreciated, thanks.

Edit:
I changed the setup. Made sure to run the same mosquitto version as the author. I also created a macvlan net and now the container can be pinged from my computer, ports are 443 and 1883 as the author’s. I also added to my pihole to forward m.cloud.homewizard.com to the mosquitto IP, and I can see that pihole is picking up the queries. Unfortunately nothing shows on mosquitto (I can connect via MQTT Explorer, and there’s no authentication like the author’s setup). So unfortunately this either stopped working or I am doing something wrong.

Yeah, I’ve been fiddling around with a new Princess Smart Fan all day, and not been able to get mqtt to work. I have managed to divert all calls to *.homewizard.com and *.homewizardeasyonline.com to my nginx reverse proxy server, using adguard for the dns-rewrites. From there it changes the port and ip to my internal mqtt with tls, but I am getting no messages. They also seem to have changed the addresses as well, as I never saw a call to m.cloud… (plenty to app-ws/provisioning/bridge/mailing/cloud.homewizard.com and 0/1.homewizard.pool.ntp.org). I did test my network by connecting to cloud.homewizard.com using port 443 and tls turned on, and it connected directly to my internal mqtt server.
Maybe there’s some handshake, new authentication or change of urls we aren’t privy to. Although the alexa integration works great for now, very responsive and even oscillates the fan now. Just would have been nice to at least have some security in case homewizard/princess disappears.
Fingers crossed someone can come up with some suggestions.

Time to flash the onboard ESP with custom firmware :slight_smile:
My heater is still working great.

1 Like

In redirecting the queries you probably also blocked a firmware update. Either that, or the fan or air conditioner operates differently than your heater.

I wouldn’t mind flashing if it’s indeed a ESP32 or ESP8266 but I would need the full code and retail all functionality. I am not bothered about voice assistants, this could be exposed to Google Assistant via Home Assistant integration on Google Home.

When integrating my heater, I also saw requests to climate.cloud.homewizard.com (in addition to m.cloud.homewizard.com, so maybe you need to redirect that as well? I redirected both URL’s to my MQTT server, but created the SSL certificate with m.cloud.homewizard.com.

Also, I needed to change my Home Assistant configuration a bit, because of the state would only be published in a JSON object. Furthermore, I expanded it with the availability topic, so the entities become unavailable when the heater loses connection.

I use the following configuration:

switch:
  - platform: mqtt
    availability:
      - topic: "appliance/heater/<MAC-ADDRESS>/$online"
        payload_available: "true"
        payload_not_available: "false"
    name: "eHeater lock"
    state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    value_template: "{{ value_json.lock }}"
    command_topic: "appliance/heater/<MAC-ADDRESS>/state/lock/set"
    payload_on: "true"
    payload_off: "false"
    state_on: "true"
    state_off: "false"

  - platform: mqtt
    availability:
      - topic: "appliance/heater/<MAC-ADDRESS>/$online"
        payload_available: "true"
        payload_not_available: "false"
    name: "eHeater mode"
    state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    value_template: "{{ value_json.mode }}"
    command_topic: "appliance/heater/<MAC-ADDRESS>/state/mode/set"
    payload_on: "high"
    payload_off: "low"
    state_on: "high"
    state_off: "low"

  - platform: mqtt
    availability:
      - topic: "appliance/heater/<MAC-ADDRESS>/$online"
        payload_available: "true"
        payload_not_available: "false"
    name: "eHeater on/off"
    state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    value_template: "{{ value_json.power_on }}"
    command_topic: "appliance/heater/<MAC-ADDRESS>/state/power_on/set"
    payload_on: "true"
    payload_off: "false"
    state_on: "true"
    state_off: "false"

climate:
  - platform: mqtt
    availability:
      - topic: "appliance/heater/<MAC-ADDRESS>/$online"
        payload_available: "true"
        payload_not_available: "false"
    name: eHeater
    fan_modes:
      - "high"
      - "low"
    modes: ["off", "heat"]
    mode_command_topic: "appliance/heater/<MAC-ADDRESS>/state/power_on/set"
    mode_state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    mode_state_template: >-
      {% if value_json.power_on == "false" %}
        off
      {% elif value_json.power_on == "true" %}
        heat
      {% endif %}
    mode_command_template: >-
      {% if value == "off" %}
        false
      {% elif value == "heat" %}
        true
      {% endif %}
    min_temp: 10
    max_temp: 35
    current_temperature_topic: "appliance/heater/<MAC-ADDRESS>/state"
    current_temperature_template: "{{ value_json.current_temperature }}"
    temperature_command_topic: "appliance/heater/<MAC-ADDRESS>/state/target_temperature/set"
    temperature_state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    temperature_state_template: "{{ value_json.target_temperature }}"
    fan_mode_command_topic: "appliance/heater/<MAC-ADDRESS>/state/mode/set"
    fan_mode_state_topic: "appliance/heater/<MAC-ADDRESS>/state"
    fan_mode_state_template: "{{ value_json.mode }}"
    precision: 1.0

1 Like

So I’m enough of a newb to be dangerous, I want to clarify some things before I get started.

I have a Unifi Dream Machine, do you mean an NoT Vlan?

How do I do this redirect? I did try and Google how to do that, but I’m kind of overwhelmed by the results.

Are you creating two .pem files with the data above?

Sorry if this seems elementary.

1 Like

Great Guide :slight_smile:

I am using a Princess infrared heater.
Princess 343350 Smart Infrared Panel Heater 350

Since I am lacking a dedicated DNS server I used a little different approach using the homeassistant addons.

Docker instance for the mqtt SSL server as explained by a1ad post
With a slight change, since I am already using port 443 in my setup I had to use a different port.
port 443 is needed for the SSL traffic the device uses, I used the nginx addon to redirect the ssl traffic.

Addon:

  • AdGuard
  • Nginx Proxy Manager

In Adguard I created a DNS rewrite rule to point “m.cloud.homewizard.com” to my docker host.

In Nginx Proxy Manager
I imported the self signed certicates created for domain m.cloud.homewizard.com
created a proxy host to redirect domain m.cloud.homewizard.com to the docker host and the MQTT ssl instance port.

2 Likes

Is this still working for you guys? When i monitor the network traffic from the Infrared Heater i only see one connection when i DNS resolving that address it says bc.googleusercontent.com and not m.cloud.homewizard.com

Still working here.

Has anyone got the Princess Smart Fan 3500 working?

Looking at Wireshark it uses cleaner.cloud.homewizard.com and I’ve setup the DNS to redirect to that.

If I connect a Laptop to my IOT LAN then connect to the ws in MQTT Explorer it shows up fine, but the fan never registers.

Looks like the fan does a DNS lookup, but then ignores the response and talks to a Google Cloud IP Address 35.109.201.110

Fan is 192.168.9.29 and DNSMasq Server is 192.168.6.83

Note to anyone else when the DNSMasq server doesn’t respond to a different VLAN you may need to edit the listen-address to include the IP Address the DNS Server is accessible on from the VLAN - e.g.
listen-address=192.168.6.83,127.0.0.1

Wireshark set of requests:-

Anyone any ideas?

Drat, Fresh eyes this morning - and I notice it’s talking on port 433 to my Mosquito sever as well as trying to talk to the Google Cloud address.

Given I have a self signed SSL Certificate on the server, I can decode the request - looks like it’s a https request to /tunnel/challenge

Now to work out how to respond…

Maybe they don’t use MQTT anymore?