[Guide]Millheat hardware modification, making it open source

Tags: #<Tag:0x00007f7804906928> #<Tag:0x00007f7804905fa0> #<Tag:0x00007f7804905780>

I will try to make a guide on how to modify a millheat heater so you can have full local control off the device. So no need to rely on Mill’s servers or API.

I have a Mill Glass https://www.millheat.com/mill-glass/mb600dn
but I think this could work on most models, since the wifi device basically is only turning on and off a relay. (Open up your device, look for labels and do some measuring)

NOTE I: If you do it like I did, there is nothing that breaks, and you can revert the modification if the heater malfunctions and you need to send it in for warranty. But still, do this on your own risk, I can’t stop you from doing mistakes that can cause a fire or hurt yourself.

NOTE II: The button panel does not work when doing this modification. I have not put to much time trying to implement this since I use Home Assistant to control the heater. (And I don’t want my wife to turn up the heat to much :slight_smile: )

NOTE III: I have tried to reverse engineer the wifi controller and the button/display controller, but I couldn’t figure out anything there. Atleast not with the posibility to revert to stock.

NOTE IV: Let me know if something is unclear or if you have suggestions on improvement.

NOTE V: The safeguard has not been tested for a long periode of time, so it could be that it creates false positives. Let me know if this happens.

Note VI: @Blern_Jalkeby Has tested this succesfully on a Mill Steel Wifi heater, he only needed to move the relay to GPIO3 and the DHT to GPIO0.

Tools and equipment required:
1 esp8266 (I am using an ESP-01)
1 DHT22 (You could use the built in temperature sensor, more on that later)
Wire wrapping tool and Wire wrapping wire (If you don’t want to solder)
Small bolt with nut, or just some hot glue.

First step, figuring out what does what
This is a picture of the heater I have.


I opened it up and checked how it looked inside.
Here you see a overview of the inside. The top is the controller box, then two black wires goes down to the analog temperature sensor.

I opened the box up and this is how it looks inside.


There you have a relay[1], wifi controller[2], pinouts[3], button/display board[4], temp sensor connection [5] and antenna [6].
Beside the pinout there is printed some labels on the board, TX,RX,PT?,AD,GND and PWR. (Not easy to see)
By measuring these while the device was in operation I figured out that it gave 3.3VDC and the PT? is for signaling the relay.

Was easier to measure on the backside of the board.

Removing things

Since the ESP8266 I am using doesn’t have the ADC broken out, I chose to change the temperature sensor.

I also removed the antenna for the wifi controller since this won’t be used anymore. (I also reset the device so there is no chance it can connect to internet)
Then I unplugged the pinout ribbon and put it aside.

The wifi controller is soldered so I did not bother removing it.

The code

I am using ESPHOME to control my ESP8266 and integrate them with Home Assistant. You can use what you want. The code is not that special.
Be carefull when copy paste, YAML is extremely picky with indentations.
Also, if you have any questions about the code, please read up a bit in the docs before asking. https://esphome.io/

This config has the Bang Bang climate setup, so no need to do anything in HA (other that integrate the device)
Remember to set the temperatures to a default value you want. (One in globals also)

substitutions:
  devicename: esp01_mill04

esphome:
  name: $devicename
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret ssid
  password: !secret wpa

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret api_password

ota:
  password: !secret ota_password
  
web_server:
  port: 80

globals:
  - id: counter
    type: int
    restore_value: no
    initial_value: '0'
  - id: check_counter
    type: int
    restore_value: no
    initial_value: '0'
  - id: error_bool
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: max_temp
    type: int
    restore_value: no
    initial_value: '27'

switch:
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF
    pin: GPIO0
    id: relay1
    name: $devicename " Relay"
    
sensor:
  - platform: dht
    pin: GPIO3
    model: AM2302
    temperature:
      id: temp1
      name: $devicename " Temperature"
      on_value:
        then:
          - lambda: !lambda |-
              //If there has been an error before
              if (id(counter) != id(check_counter)) { 
                // Reset counters, since everything is back to normal 
                id(check_counter) = 0;
                id(counter) = 0;
              }
              
              id(counter) += 1;

    humidity:
      name: $devicename " Humidity"
    update_interval: 10s
    
script:
  - id: test1
    then:
      - lambda: !lambda |-
          // Increase counter to be used in error checks
          id(check_counter) += 1; 
          // Check if any difference (If there is a problem with the sensor the counter won't be increased) 
          if (id(counter) != id(check_counter)) {
            // Put a message in the logs //
            ESP_LOGE("main", "Something is wrong with the sensor, turning off heater");
            // Set the heater in OFF mode //
            auto call = id(my_climate).make_call();
            call.set_mode("OFF");
            call.perform();
            
            id(error_bool) = true;
          }
          // Just as an absolute safeguard, not really necesarry
          else if (id(temp1).state > id(max_temp)) {
            // Put a message in the logs //
            ESP_LOGE("main", "It is too hot here, turning off heater");
            // Set the heater in OFF mode 
            auto call = id(my_climate).make_call();
            call.set_mode("OFF");
            call.perform();
            
            id(error_bool) = true;
          }
          else if (id(error_bool)) {
            // Reset the error bool 
            id(error_bool) = false;
            // Set the heater back in AUTO mode 
            auto call = id(my_climate).make_call();
            call.set_mode("AUTO");
            call.perform();
          }
          
          // Reset variables once in a while
          if (id(check_counter) > 1000) {
            id(check_counter) = 0;
            id(counter) = 0;
          }

interval:
  - interval: 10s
    then:
      - script.execute: test1

climate:
  - platform: bang_bang
    name: test
    id: my_climate
    sensor: temp1
    default_target_temperature_low: 20 °C
    default_target_temperature_high: 22 °C
    visual:
      min_temperature: 5 °C
      max_temperature: 25 °C
      temperature_step: 0.5 °C

    heat_action:
      - switch.turn_on: relay1
    idle_action:
      - switch.turn_off: relay1

This config is if you want to use the generic thermostat in HA

substitutions:
  devicename: esp01_mill04

esphome:
  name: $devicename
  platform: ESP8266
  board: esp01_1m

wifi:
  ssid: !secret ssid
  password: !secret wpa

# Enable logging
logger:

# Enable Home Assistant API
api:
  password: !secret api_password
  id: api_server

ota:
  password: !secret ota_password

globals:
  - id: counter
    type: int
    restore_value: no
    initial_value: '0'
  - id: check_counter
    type: int
    restore_value: no
    initial_value: '0'
  - id: error_bool
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: max_temp
    type: int
    restore_value: no
    initial_value: '27'

switch:
  - platform: gpio
    restore_mode: RESTORE_DEFAULT_OFF
    pin: GPIO0
    id: relay1
    name: $devicename " Relay"

sensor:
  - platform: dht
    pin: GPIO3
    model: AM2302
    temperature:
      id: temp1
      name: $devicename " Temperature"
      on_value:
        then:
          - lambda: !lambda |-
              //If there has been an error before
              if (id(counter) != id(check_counter)) { 
                // Reset counters, since everything is back to normal 
                id(check_counter) = 0;
                id(counter) = 0;
              }
              id(counter) += 1;

    humidity:
      name: $devicename " Humidity"
    update_interval: 10s
    
script:
  - id: test1
    then:
      - lambda: !lambda |-
          // Increase counter to be used in error checks
          id(check_counter) += 1; 
          
          // Get ready for calling HA services, change the entity id to what you have
          esphome::api::ServiceCallResponse r,s;
          //esphome::api::ServiceCallResponse s;
          r.set_service("climate.turn_off");
          r.set_data({api::KeyValuePair("entity_id", "climate.spisestue")});
          s.set_service("climate.turn_on");
          s.set_data({api::KeyValuePair("entity_id", "climate.spisestue")});
          
          // Check if any difference (If there is a problem with the sensor the counter won't be increased) 
          if (id(counter) != id(check_counter)) {
            // Put a message in the logs //
            ESP_LOGE("main", "Something is wrong with the sensor, turning off heater");
            // Turn off the relay, set error and turn off generic thermostat
            id(relay1).turn_off();
            id(error_bool) = true;
            api_server->send_service_call(r);
          }
          // Safeguarding the temperature
          else if (id(temp1).state > id(max_temp)) {
            // Put a message in the logs //
            ESP_LOGE("main", "It is too hot here, turning off heater");
            // Turn off the relay, set error and turn off generic thermostat
            id(relay1).turn_off();
            id(error_bool) = true;
            api_server->send_service_call(r);
          }
          // Everything normal again, set error and turn on generic thermostat
          else if (id(error_bool)) {
            id(error_bool) = false;
            ESP_LOGW("main", "Everything ok again");
            api_server->send_service_call(s);
          }
          
          // Reset variables once in a while
          if (id(check_counter) > 1000) {
            id(check_counter) = 0;
            id(counter) = 0;
          }

interval:
  - interval: 10s
    then:
      - script.execute: test1



In home assistant I added a generic thermostat. https://www.home-assistant.io/components/generic_thermostat/
Remember to set the entity_id in the code similar to the name you set on the generic thermostat.

Wireing things

Don’t let the wire colors confuse you, I am just being cheap so I use what I have of the best lenght.

So on the ESP-01 I connected like this:
3V3 to PWR on pinout Orange
GND to GND on pinout Yellow
IO0 to PT? on pinout Purple
3V3 to VCC on DHT22 Red
GND to GND on DHT22 Black
RX to Data on DHT22 Green

Remember to test if you chose some other pins on the ESP8266, if you use the wrong pin the ESP8266 won’t boot.
Also, depending on the ESP-01 you might need to connect EN and 3V3 pin to boot normally.

The wires to the DHT22 goes the same way as the old wires, so I don’t think there would be a problem with heat.
As soon as I figure out a proper way I will put some safeguards in the code in case the temperature is to high or the readings are NAN.

I used a bolt and a nut to keep the DHT22 in place.


Then just put everything back together and you now have a device you can fully control locally with Home Assistant or some other automation tool.

2 Likes

Nice guide, but I think something is lost in translation. The device you pointed to is not an oven - an oven is where you cook food. This is a heater :slight_smile:

Thank you :slight_smile:
Yes got a bit lost in translation. Should be corrected now !
Maybe I should do the same on a oven next time.

1 Like

Thanks for sharing. Will look into that. Ideally I want the thermostat logic handled by the ESP8266 in case HA goes down.
How much current does it need to activate the relay?

Happy to share. Hope it will be usefull.
Yes, that is the only thing missing I think.
Not 100% sure but a datasheet I found said 200mW. The Esp-01 handles it good.
But there is some extra circuitry that handles the relay, since the relay is 12vdc and the esp gives a 3.3vdc signal.

1 Like

After going through the docs for esphomelib, this look exactly like the generic thermostat in HA

Yes, with that config you don’t have to do much in HA if you want exactly that one.
Wonder if that handles the failsafe we are talking about. I will try to test during the next week.

I have tested now and it is a step further. But it is not handling a NAN reading. I will post an issue and see if it can be solved.

1 Like

Ok, I have now updated the code with safeguards.
It will now react to no temperature reading and it will also stop when the temperature is higher than the global variable max_temp.
It will work if the heater is turned on and wireless goes down while the heater is on.

I have choosen not to handle a faulty reading, for example if the sensor says it is 0degrees celsius when it is 15. This is because I have never heard of such faults with the sensor I use. (Or any other for that matter)

I also have an idea to make a notification in HA when there are some problems. But I will do that at a later time if I feel like it.

Let me know if anyone has some feedback.

Excellent guide! I’m trying to replicate this with my Mill Steel Wifi heater and having some problems. The ESP-01 won’t boot when the GPIO0 is connected to the heaters PTC pin. It does boot when it’s disconnected and the relay works as planned when I connect the GPIO0-PTC after the boot.

Any ideas what might be the problem?

Glad that you liked the guide, thank you.

It seems like the problem is that on the Mill Steel Wifi heater the PTC is held to ground when not active. So when the GPIO0 is held low on ESP01 when booting it will go into flash mode.

You can try this:
-Move the PTC to GPIO2 (This should also cause a boot failure, but test to be sure). PS, this will probably cause the relay to kick in, either just a moment or until you power off.
-If GPIO2 causes boot failure, connect PTC to GPIO3. Then try the DHT11 on GPIO0. PS, this will probably cause the relay to kick in a short moment during boot.
-If neither works and you absolutely want to use a ESP01 you need to solder on a pin directly on the esp8266 chip. (You need to be very steady on your hand to do this and you need a fine tip solder iron). But I would recommend getting a ESP-12 since it has more pins broken out.

1 Like

Moving the DHT to IO0 and PTC to IO3 worked! Aweome, might consider buying some more Mills for the country cottage now!

Great! Glad you got it working. Will make another note about this in the guide.
Yeah, when I managed to modify the heater this easy I am also thinking about buying more.

Btw, if you are using OTA, could you test if that is working?

Hi john-arvid. Have you opened up a Mill non WIFI heater?
I suppose that adding an ESP with temp sensor is doable as those heaters also have an electronic thermostat.

PK

Hi @Lucky70,

No I don’t have a non WIFI heater from Mill, but as you say, it should be doable.
The first thing that comes to mind is that it probably don’t have a DC circuit so you would have to add a power supply to it.
I would rather suggest using a smart plug from a vendor that uses the ESP8266 chip. Check this site for ideas: https://blakadder.github.io/templates/

Then you don’t have to bother with problems that might occur when implementing something totally different.

Hi @john-arvid ,
Im going to modify my mill heater with a faulty WIFI unit.
Afther some serching i found this thread and a thread in a norwagian forum.
My suggestions is that you check it out.
Short summary of what Jan-Tore in the norwegian thread did:
He remove the stock mill wifi controller and replace it with an EPS01. The EPS01 connects to the button/display board on serial.
Leaving the display / buttons wokring and removing the need for the DHT22.

1 Like

Hi @JDolven,
Thank you for bringing that to my attention. I will try to implement it in my code if I get my head around the code Jan-Tore wrote, since it looked like the code was only partial.
It looks exactly like what I need to make this implementation better.

2 Likes

To send commands to the button/display it’s possible to use the the function calls on line 5, 6 and 8. They work but reading the response don’t work. It is possible I connected the ESP01 wrong. I tried to replicate the connecting form the picture in the thread. Not sure how to convert form Arduino to ESPhome :stuck_out_tongue:

I was succesfull using parts of the code and a wemos D1 mini to replace the HF-LPT120A. Talking to home assistant using MQTT. I need to look more into ESPhome to make it a custom component. I can post the code to git or pastebin if your intrested.

1 Like

Great work!
Yes I would like to see the code.
I am currently not in a position to work and test any code (maybe in a month or so), but I can look and make some plans.

If you haven’t seen this already, this can help you start: https://esphome.io/custom/uart.html