Guide - Medify MA-40 with ESPHome

Just wanted to contribute something simple. I had been getting frustrated by my Medify Air Purifiers having absolutely zero smarts, so I decided to rip my MA-40s and my MA-112 open to get them integrated into home assistant while making no permanent changes to the devices. This guide is very basic, but gets the job done. These changes will disable the front panel on the device, making it 100% managed via Home Assistant/ESPHome. This adds a filter life field as well. I plan to do a guide for the MA-112 as well since it uses an analogue output for controlling fan speed, and also includes a tachometer for fan speed feedback.

This guide has more images to it, but I am limited to one image for this post :frowning:

Start by opening the top of the MA-40 unit, exposing the IO Panel and the Control Board Box. There should be a couple screws near the top holding this one, and the full panel snaps on/off.

Open the Control Board Box to expose the Control Board.

Unplug the cable connecting the Interface Panel and Control Board from the Control Board. We will be using the following pins on this header:

5V - ESP32 Dev Board Power
GND - ESP32 Dev Board GND
ION - DIO for ION control
H - DIO for Fan High
M - DIO for Fan Medium
L - DIO for Fan Low

Plug Jumper Cables into this header for the listed pins:

Lastly, close up the Control Board Box and plug these cables into the ESP32 Dev Board using the following pins:

gpio26 to H
gpio25 to M
gpio33 to L
gpio27 to ION
5V to 5V
GND to GND

Use the following code for ESPHome:

esphome:
  name: ma-40
  friendly_name: MA-40

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: 

ota:
  password: 

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Ma-40"
    password: 

captive_portal:

globals:
  - id: filter_time
    type: int
    restore_value: yes
    initial_value: '0'

interval:
- interval: 1min
  then:
    if:
      condition:
        fan.is_on: ma40
      then:
      - lambda: |-
          id(filter_time) += 1;

sensor:
  - platform: template
    name: Filter On Time
    id: filter_time_disp
    unit_of_measurement: "minutes"
    device_class: "duration"
    state_class: "measurement"
    accuracy_decimals: 0
    lambda: |-
      return id(filter_time);

binary_sensor:
  - platform: template
    name: "Filter End of Life"
    lambda: |-
      if (id(filter_time) > 180000){
        return true;
      } else {
        return false;
      }

button:
  - platform: template
    name: "Reset Filter Life"
    on_press:
      lambda: |-
        id(filter_time) = 0;

switch:
  - platform: gpio
    pin: 26
    name: "High"
    id: high
    interlock: [medium, low]
    internal: true
  - platform: gpio
    pin: 25
    name: "Medium"
    id: medium
    interlock: [high, low]
    internal: true
  - platform: gpio
    pin: 33
    name: "Low"
    id: low
    interlock: [high, medium]
    internal: true
  - platform: gpio
    pin: 27
    name: "Ion"
    id: ion

output:
  - platform: template
    id: custom_fan
    type: float 
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            # action for off
            - switch.turn_off: high
            - switch.turn_off: medium
            - switch.turn_off: low
            - switch.turn_off: ion
      - if:
          condition:
            lambda: return ((state > 0) && (state < 0.4));
          then:
            # action for speed 1
            - switch.turn_off: high
            - switch.turn_off: medium
            - switch.turn_on: low
            
      - if:
          condition:
            lambda: return ((state > 0.4) && (state < 0.7));
          then:
            # action for speed 2
            - switch.turn_off: high
            - switch.turn_off: low
            - switch.turn_on: medium
            
      - if:
          condition:
            lambda: return ((state > 0.7));
          then:
            # action for speed 3
            - switch.turn_off: low
            - switch.turn_off: medium
            - switch.turn_on: high

fan:
  - platform: speed
    id: ma40
    output: custom_fan
    name: "Fan"
    speed_count: 3
3 Likes

Your MA-40 should display in Home Assistant like so:

1 Like

Hey Steven. I have an ma-40uv. It looks like I have the same board as the picture in your post. Except mine has the U1 chip blown. Because it is blown I can’t read the numbers on it to cross reference it so I can order a replacement. You mentioned that you have more pics, any chance you have one showing this chip? Would really appreciate it. My email address is [email protected]
Thanks.

@stevo32792 i wanted to thank you for the excellent write up. I recently purchased a 112. Is there any possibility you might post how you did yours? I would love to follow your guide.

Thanks again.

Matt

Was looking at the board for the 112, based on multi meter it looks like VSP goes from .6v => .65v => .75v => .96v. based on the fan speed button. I haven’t actually tried controlling the fan with the pin though so more testing is needed.

I’m probably not going to look more into this further and just DIY a box air purifier since medify filters are expensive.

Yes! I can put something together tomorrow most likely! I will link the post when I have it up. Sorry for the slow response, I need to turn on email notifications. This gives me time to open up the box and actually measure the pin for the motor control on the MA-112. Just got an oscilloscope in last month for figuring out how to control a Blisslight so I can make sure I got everything right for the MA-112.

1 Like

@stevo32792 thank you so much sir, I really appreciate it! I’m excited to try it out.

Matt

1 Like

We are planning on potentially getting some of these filters. They are H13/14 which is really great at this price point (kind of IQAir quality, at least the filters) so it’s disappointing they have 0 smarts. But maybe I’m contradicting myself - it’s the price point :).

@stevo32792 hey Steven, I was curious if you ever got things to work with the 112? Not trying to rush you of course just curious, would love to try to automate it if possible. Thank you sir.

Matt

@stevo32792 did you ever happen to get this to work with the 112 unit? I would love to do so on mine, as well!

Cheers
Matt

Hi @stevo32792, I have both the MA-40 and the MA-112 unit as well. Your guide and code worked perfectly to quickly automate the MA-40, thank you. I have now opened up my MA-112 to see if I could automate it using your code as a base, but it is certainly proving more difficult.

As a novice with ESP Home and electronics I would appreciate any guidance. Right now I’m proceeding with a lot of trial and error and the support of forums and gpt4-o. I have been able to power on a ESP32 using the 5V + GND pins. The ION pin appears to work similarly to the above example code (I hear a hiss when I trigger it). I took Humbly’s voltage measures as is and have been trying to get the VSP to work using the dac output from the ESP32, but various configurations have not worked (see an example below).

A few additional things to note… the board Humbly posted is an intermediary board which the touch panel plugs into @ CN7 (VOC, GND, RXD, TXD).There are additional “Magnetic Control” plugs connected to this board that appear to be a safety mechanism. Finally, this board connects from CN1 (see Humbly’s picture above) to the control board box that is very similar to the MA-40. It seems like the mag control sensors detect if the top black vent as well as the panels covering the two filters are attached. They must be attached for the unit to start under normal operation. Lastly, for anyone else trying to open this unit you will need a tri-wing bit to for some screws.

@matthewcbyington, not sure if Steve is seeing these messages, but curious if you had any thoughts on how to proceed here? Any idea if the FG and PC pins are needed to start the fan?

output:
  - platform: esp32_dac
    pin: GPIO25  # Replace with your GPIO pin
    id: dac_output

# Template output to control fan speeds
  - platform: template
    id: vsp_template_output
    type: float
    write_action:
      - lambda: |-
          if (state == 0) {
            // Fan off
            id(dac_output).set_level(0.0);
          } else if (state == 1) {
            // Low speed (0.6V)
            id(dac_output).set_level(0.6 / 3.3);
          } else if (state == 2) {
            // Medium-Low speed (0.65V)
            id(dac_output).set_level(0.65 / 3.3);
          } else if (state == 3) {
            // Medium speed (0.75V)
            id(dac_output).set_level(0.75 / 3.3);
          } else if (state == 4) {
            // High speed (0.96V)
            id(dac_output).set_level(0.96 / 3.3);

fan:
  - platform: speed
    id: ma112_fan
    name: "Fan"
    output: vsp_template_output
    speed_count: 4  # Four speed levels including the .65V level

1 Like

Sorry, I’ve just been dealing with some mental health issues and have been focusing on other parts of my life. I’d been putting off the guide unit I could get the unit open so I could mark down how to open the unit, which pins on the MA-112 would go to which pins on the ESP32, and to verify the original MA-112 signals with an oscilloscope. For now I can attach my code for the MA-112, noting that the control for the fan is an Analogue Output from the ESP32, and there is also a feedback (rpm counter) from the MA-112 to the ESP32. I believe VSP is the control for the fan (analogue output) and PC is the pulse counter. There are no safeguards in place with the magnetic switches.

esphome:
  name: ma-112
  friendly_name: MA-112

esp32:
  board: esp32dev
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "KEY GOES HERE"

ota:
- platform: esphome
  password: "PASSWORD GOES HERE"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "MA-112"
    password: "FALLBACK PASSWORD"

captive_portal:

globals:
  - id: filter_time
    type: int
    restore_value: yes
    initial_value: '0'

interval:
- interval: 1min
  then:
    if:
      condition:
        fan.is_on: ma112
      then:
      - lambda: |-
          id(filter_time) += 1;

sensor:
  - platform: pulse_counter
    pin: 25
    name: "Motor RPM"
    update_interval: 1s
  - platform: template
    name: Filter On Time
    id: filter_time_disp
    unit_of_measurement: "minutes"
    device_class: "duration"
    state_class: "measurement"
    accuracy_decimals: 0
    lambda: |-
      return id(filter_time);

binary_sensor:
  - platform: template
    name: "Filter End of Life"
    lambda: |-
      if (id(filter_time) > 180000){
        return true;
      } else {
        return false;
      }

button:
  - platform: template
    name: "Reset Filter Life"
    on_press:
      lambda: |-
        id(filter_time) = 0;

switch:
  - platform: gpio
    pin: 27
    name: "Ion"
    id: ion
  - platform: gpio
    pin: 33
    name: "Power"
    id: power
    internal: true

output:
  - platform: esp32_dac
    pin: 26
    id: fanspeed
  - platform: template
    id: custom_fan
    type: float 
    write_action:
      - if:
          condition:
            lambda: return ((state == 0));
          then:
            # action for off
            - switch.turn_off: power
            - output.turn_off: fanspeed
            - switch.turn_off: ion

      - if:
          condition:
            lambda: return ((state > 0));
          then:
            # action for on
            - switch.turn_on: power
            - output.turn_on: fanspeed
            - output.set_level:
                id: fanspeed
                level: !lambda return (0.48 + (state * 0.13));

fan:
  - platform: speed
    id: ma112
    output: custom_fan
    name: "Fan"
    speed_count: 100
1 Like

No worries Steve, wishing you the best and hope you are doing better now.

Thank you for that code it worked like a charm. Only thing I had to play around with was the FG and PC pins. The FG pin worked as the tachometer for me and the PC as power control. Descriptions from gpt:

  • FG (Fan Ground/Fan Feedback):

  • If FG is a feedback signal (tachometer), it’s configured as a pulse_counter sensor to measure RPM.

  • PC (Power Control):

  • Configured as a GPIO switch. You can use it to enable or disable power to certain components.

Perfect! Thanks for that feedback. I might go ahead and post this information into a separate MA-112 thread just so google picks it up when someone goes to search for it. Like I had mentioned I was holding off as I didn’t have all the details properly documented. After that I’ll probably do a Blisslights thread, wahoo!

As for the health stuff, on the road to recovery. Thanks for asking!

Edit: It’s up - Guide - Medify MA-112 with ESPHome

1 Like

@stevo32792 I also hope you’re doing better. We all have to take care of ourselves first and foremost.

@cwe5590 apologies I didn’t respond in time here. I am really looking forward to following @stevo32792 's guide!!

Matt

This is awesome! Thanks for putting this out. Is there not a way to make the front panel operational while still connecting to home assistant?

It is possible, but would require either replacing the main board or main board microcontroller. Replacing the main board would not be permanent, but would require designing and fabricating a new main board. The other option would be permanent. If I were doing it myself I would opt for designing a new main board as it would be the most streamline solution, but its a lot more work.

Ahhh gotcha. Sounds way above my pay grade. If you decide to tackle it, I’d love to see the process!