AQUARK Mr.Pure Medium MPS14 -ph

Hi, I’m thinking about buying an AQUARK Mr.Pure Medium MPS14 -ph chlorinator. Mr Pure Full Inverter Salt Chlorinator - Aquark this device has an invergo application which is basically tuya. It also has canbus RS485. The question is whether anyone has already dealt with it, or it would be enough to modify the already made ESP32 Home for similar devices. I am attaching the Canbus technical specification. Well thank you.




1 Like

I’m also thinking about to buy the Aquark Mr. Pure. Where you able to implement it in HA ?

In the near future, I will publish a working version of the Mr.Pure chlorinator configuration for ESPHome. Please note that there are several firmware versions which differ in Modbus address mapping, especially for reading discrete inputs. I will publish the configuration that matches my version, and I will also attach all versions of the manuals I have for different firmware variants.

Since no one has helped me with anything over the past year, please do not ask any questions — I will not be answering them. Everything will be clear from the provided .yaml, and I will also include the exact hardware I used.

1 Like

This is the final working configuration for connecting the Mr.Pure
(Mr Pure Full Inverter Salt Chlorinator - Aquark) to ESPHome for Home Assistant.

The following hardware was used for the connection:
• M8 3-pin (male) connector
• M5Stack ATOM Lite ESP32 Development Board with Wi-Fi Programmable Kit
• M5Stack ATOM Tail485 TTL to RS485 communication module (Level conversion serial port)

There are multiple versions of the Mr.Pure unit depending on the serial number, so this configuration may not work for all devices — but it provides a useful guide for making adjustments.

If the configuration doesn’t work for you, especially the binary sensors (other sensors are usually consistent across versions), please contact the manufacturer directly and provide your device’s serial number. They will send you the correct MODBUS protocol for your specific version.

esphome:
  name: mrpure
  friendly_name: mr.pure

esp32:
  board: m5stack-core-esp32
  framework:
    type: arduino



# Enable logging
logger: 
  baud_rate: 0
  level: VERY_VERBOSE
  hardware_uart: UART0


  
# Enable Home Assistant API
api:
  encryption:
    key: ""

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: !secret wifi ssid
  password: !secret wifi password
  manual_ip:
    static_ip: xxx.xxx.x.x
    gateway: xxx.xxx.x.x
    subnet: xxx.xxx.xxx.x
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Mrpure Fallback Hotspot"
    password: ""

captive_portal:
web_server:
  port: 80
uart:
  id: mod_bus
  rx_pin: 32
  tx_pin: 26
  baud_rate: 9600
  data_bits: 8
  parity: NONE
  stop_bits: 1
  rx_buffer_size: 256

  
modbus:
#  flow_control_pin: GPIO4
  id: modbus2
  send_wait_time: 500ms

modbus_controller:
  - id: mrpure
    ## the Modbus device addr
    address: 0x0008
    modbus_id: modbus2
    update_interval: 5s  

  
    

  
  
    
    

select:
  - platform: modbus_controller
    name: "PWP Operating Mode"
    id: pwp_operating_mode
    modbus_controller_id: mrpure
    address: 0x0000
    value_type: U_WORD
    optionsmap: 
      "Shutdown": 0
      "Auto mode": 1
      "Shock mode": 2
      

number:
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Ideal pH setting"
    id: setting_ph
    register_type: holding
    address: 0x0003
    value_type: U_WORD
    min_value: 7.00
    max_value: 7.60
    step: 0.1
    mode: slider

    lambda: |-
      // Konvertuj z Modbus hodnoty (napr. 725) na zobrazenú hodnotu (napr. 7.25)
      return x / 100.0;

    write_lambda: |-
      // Konvertuj zo zobrazenej hodnoty (napr. 7.25) na Modbus hodnotu (napr. 725)
      ESP_LOGD("main", "Modbus Number incoming value = %f", x);
      return (uint16_t)(x * 100.0);
  - platform: modbus_controller
    name: "Pool Volume"
    id: pool_volume
    modbus_controller_id: mrpure
    register_type: holding
    address: 0x0004
    value_type: U_WORD
    unit_of_measurement: "m³"
    min_value: 5
    max_value: 100
    step: 1
    mode: slider
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Ideal ORP settings"
    id: setting_orp
    register_type: holding
    address: 0x0002
    value_type: U_WORD
    min_value: 650
    max_value: 800
    step: 5
    mode: slider
    unit_of_measurement: "mV"
    lambda: "return x ;"
    write_lambda: |- 
      ESP_LOGD("main", "Modbus Number incoming value = %f", x);
      return x;
    
text_sensor:
  - platform: modbus_controller
    modbus_controller_id: mrpure
    id: water_quality_pool
    register_type: read
    address: 0x0006
    name: "Water Quality"
    lambda: !lambda |-
      uint16_t int_mode = (data[item->offset] << 8) + data[item->offset+1];
      ESP_LOGD("main","Parsed operation mode int : %d", int_mode);
      std::string mode_str;
      switch (int_mode) {
        case 0:  mode_str = "Poor"; break;
        case 1:  mode_str = "Good"; break;
        case 2:  mode_str = "Perfect"; break;
        
        default: mode_str = "Unknown"; break;
  
      }
      return mode_str;
  - platform: template
    name: "Water Flow Status"
    id: flow_status
    icon: "mdi:water"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_word).state) & 0x100) == 0) {
        return {"With flow"};
      } else {
        return {"No flow"};
      }
    update_interval: 5s

  - platform: template
    name: "Salt Addition Required"
    id: salt_status
    icon: "mdi:shaker-outline"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_word).state) & 0x200) == 0) {
        return {"Not required"};
      } else {
        return {"Required"};
      }
    update_interval: 5s

  - platform: template
    name: "Acid Refill Status"
    id: acid_status
    icon: "mdi:chemical-weapon"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_word).state) & 0x400) == 0) {
        return {"Not required"};
      } else {
        return {"Required"};
      }
    update_interval: 5s

  - platform: template
    name: "Probes Replacement Status"
    id: probes_status
    icon: "mdi:reload"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_word).state) & 0x800) == 0) {
        return {"Not required"};
      } else {
        return {"Required"};
      }
    update_interval: 5s

  - platform: template
    name: "Calibration Required"
    id: calibration_status
    icon: "mdi:target"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_word).state) & 0x1000) == 0) {
        return {"Not required"};
      } else {
        return {"Required"};
      }
    update_interval: 5s
  - platform: template
    name: "Power Supply Status"
    id: power_supply_status
    icon: "mdi:power-plug-off"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x01) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "pH Regulation Status"
    id: ph_regulation_status
    icon: "mdi:flask-outline"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x02) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "ORP Regulation Status"
    id: orp_regulation_status
    icon: "mdi:scale"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x04) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "Controller Overtemperature"
    id: overtemp_status
    icon: "mdi:thermometer-alert"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x08) == 0) {
        return {"Normal"};
      } else {
        return {"Protect"};
      }
    update_interval: 5s

  - platform: template
    name: "Cell Low Temp Protection"
    id: lowtemp_status
    icon: "mdi:snowflake"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x10) == 0) {
        return {"Normal"};
      } else {
        return {"Protect"};
      }
    update_interval: 5s

  - platform: template
    name: "WiFi Connection Status"
    id: wifi_connection_status
    icon: "mdi:wifi-off"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x20) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s
  - platform: template
    name: "pH Sensor Status"
    id: ph_sensor_status
    icon: "mdi:flask"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x40) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "ORP Sensor Status"
    id: orp_sensor_status
    icon: "mdi:scale-balance"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x80) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "Power Module Status"
    id: power_module_status
    icon: "mdi:chip"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x100) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

  - platform: template
    name: "RS485 Communication Status"
    id: rs485_status
    icon: "mdi:connection"
    entity_category: diagnostic
    lambda: |-
      if ((int(id(status_input_00).state) & 0x200) == 0) {
        return {"Trouble-free"};
      } else {
        return {"Faulty"};
      }
    update_interval: 5s

sensor:
  - platform: modbus_controller
    modbus_controller_id: mrpure
    id: status_word
    name: "Status Input 0x0030"
    address: 0x30
    register_type: discrete_input
    value_type: U_WORD
    register_count: 16
    internal: true
  - platform: modbus_controller
    modbus_controller_id: mrpure
    id: status_input_00
    name: "Status Input 0x0000"
    address: 0x0000
    register_count: 16
    register_type: discrete_input
    value_type: U_WORD
    internal: true  
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "pH"
    id: ph_pool
    icon: "mdi:ph"
    register_type: read
    device_class: "ph"
    accuracy_decimals: 1
    filters:
      - lambda: return x /100;
    address: 0x0001
    value_type: U_WORD
  - platform: modbus_controller
    name: "Pool Volume "
    id: pool_volume_status
    modbus_controller_id: mrpure
    register_type: holding
    address: 0x0004
    value_type: U_WORD
    unit_of_measurement: "m³"
    
    icon: "mdi:pool"  
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "ORP"
    id: orp_pool
    register_type: read
    skip_updates: 2
    address: 0x0000
    value_type: U_WORD  
    unit_of_measurement: "mV"

  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Electrical conductivity"
    id: conductivity_pool
    register_type: read
    address: 0x0002
    skip_updates: 2
    value_type: U_WORD    
    unit_of_measurement: "ppm"
  
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Pool temperature"
    id: temperature_pool
    register_type: read
    device_class: "temperature"
    state_class: "measurement"
    accuracy_decimals: 2
    skip_updates: 2
    filters:
      - lambda: return x /10;
    address: 0x0003
    value_type: U_WORD
    unit_of_measurement: "°C"  

  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Controller Temperature"
    id: temperature_chlorine_pool
    register_type: read
    device_class: "temperature"
    state_class: "measurement"
    skip_updates: 2
    accuracy_decimals: 2
    filters:
      - lambda: return x /10;
    address: 0x0004
    value_type: U_WORD
    unit_of_measurement: "°C"  

  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Chlorine production"
    id: production_chlorine_pool
    register_type: read
    skip_updates: 2
    address: 0x0005
    value_type: U_WORD
    unit_of_measurement: "%"    
  
  
  
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Electrolytic current"
    id: electrolytic_current_pool
    register_type: read
    
    address: 0x000B
    value_type: U_WORD
    unit_of_measurement: "A"  
    filters:
      - lambda: return x /100;
  
  - platform: modbus_controller
    modbus_controller_id: mrpure
    name: "Elektrolytic voltage"
    id: electrolytic_voltage_pool
    register_type: read
    address: 0x000C
    
    value_type: U_WORD
    unit_of_measurement: "V"    
    filters:
      - lambda: return x /100;    


Good luck!

3 Likes

I just want to thank you for your work. I was looking for an electrolysis unit wich would integrate in home assistant and found this!!
Pool is getting finished, and I’m buying this unit! Thank you very much once again!

Can you please show me what type of M8 3-pin (male) connector you use?

I will use a esp32 module and RS485 converter. the connection to the Chlorinator is just A B and ground right?

Thanks in advance

Yes, pay attention to the correct connection of A and B according to the manual. I think they had it turned in the manual, but I don’t remember exactly 80 percent. You can also get the connector on aliexpress.
https://www.secomp.cz/prumyslovy-konektor-m8-3pin-m-sroubovaci_d15635.html
https://www.amazon.co.uk/male-connector-assembled-industrial-unshielded/dp/B0C6YH3KKG

@maros22 Hi, did you buy medium MPS14 with ph only or premium with orp? I saw orp values in your screenshots, but Im curious, if medium version with additional orp sond can read values or not.

Regards
Lubos (yep, Im Czech too :slight_smile: )

Thank you @maros22 for your work and for sharing your stuff.

I’ve just ordered the hardware to connect Mr Pure to Home Assistant, I’ll share my findings once I receive it and do it. I have the premium with orp device.

[quote=“LubosCzech, post:8, topic:743563”]
Hi, did you buy medium MPS14 with ph only or premium with orp? I saw orp values in your screenshots, but Im curious, if medium version with additional orp sond can read values or not.

Regards
Lubos (yep, Im Czech too :slight_smile: )
[/quote]Hi, I first bought it with just the ph and then I had it expanded by the importer, they gave me an orp probe and I had to send it to the Czech Republic so they could upload the firmware there

So once again, you can’t just connect an ORP probe to the medium version, there must be firmware even if it is physically possible to connect the probe. I strongly recommend upgrading to the medium and premium, I think I paid about 240 EUR including the probe. Another thing is correct calibration, accurate determination of the medium value and rinsing the probe with distilled water and a napkin after each measurement. Another thing is that the salinity should be around 3g per liter, which is about 3000ppm on the ORP probe. When adding salt, the bypass to the chlorine generator must be turned on, as a pinch of salt can damage the platinum layer.

small change for update on esphome Current version: 2025.7.2
esphome:
name: mrpure
friendly_name: mr.pure

esp32:
board: m5stack-atom
framework:
type: arduino

Enable logging

logger:
level: DEBUG
baud_rate: 0
hardware_uart: UART0

@maros22 How do you power the M5Stack Tail485? Via the RS485 connector or from an external source?

From what I read, the Tail485 is designed to be powered by the powered via the RS485 bus, shouldn’t we need a 4 pin connector in this case?

Hi, you can power the tail with a classic 12v adapter.

Data are A and B, I did not use grounding, I have a short route of 1.5m, I connected grounding only on the connector, I did not connect it to the tail

I got all the hardware working but can’t see to get any suitable data, perhaps someone can point me to what I might be doing wrong?

If I set the wiring according with the instructions (A pin 4, B pin 3) I get no data (No CRC error message and no modbus offline messages). If I invert the pins, I get the following:

[10:38:34][W][modbus_controller:027]: Modbus device=8 set offline

[10:27:36][W][modbus:135]: Modbus CRC Check failed! 73!=00

[10:44:20][D][modbus_controller:039]: Modbus command to device=8 register=0x0B no response received - removed from send queue

Does this means it’s receiving data but unable to properly “decode” it?

Hi,

Definitely try swapping the A and B wires (RS485) — with Mr. Pure and many similar devices, the wiring in the manual often doesn’t match reality. In my experience, not only were A and B reversed, but even the GND pin was labeled differently! So:
• Try switching A/B (RS485) even if the manual says you have them correct.
• Make sure GND (ground) is connected between the ESP (or converter) and the Mr. Pure device. No common ground = no comms.
• Trust what actually works (when you get data without CRC errors), not just wire colors or manual diagrams.
• If it still doesn’t work, post a photo of your wiring — sometimes the manufacturer’s labeling or documentation is just wrong.

Don’t worry, this is pretty common. Just keep trying the different combinations until you get clean communication — in many cases, the correct setup is not what the manual says!

Good luck!

Don’t worry, this is pretty common. Just keep trying the different combinations until you get clean communication — in many cases, the correct setup is not what the manual says!

If that still doesn’t help, let me know and I’ll send you a photo of my working wiring as a reference.

Good luck!

Should you connect GND between your ESP (or RS485 converter) and the Mr. Pure device?

  • Short answer:
    • For most short cables (up to 2–3 meters), connecting GND is usually not necessary*.*
    • For longer runs, or in electrically noisy environments, connecting GND may help communication stability — but be careful!

When to connect GND:

  • If you experience unreliable communication, random CRC errors, or device not responding (even after checking wiring A/B), try connecting GND between the ESP/RS485 converter and Mr. Pure.
  • For long RS485 cables (longer than 2–3 meters), connecting GND can help prevent signal reference issues.
  • If both devices are powered from the same power supply (common ground), it is usually safe and recommended.

When NOT to connect GND:

  • If each device (ESP/RS485 and Mr. Pure) is powered from a different power source (e.g., separate DIN rail supplies), connecting GND may create a ground loop — this can cause more problems than it solves (unwanted current, damage, noise).
  • If you already have stable communication with GND disconnected, leave it disconnected.

Summary:

  • Short cable, same power supply? Connect GND — it’s safe and may help.
  • Different supplies or long ground paths? Try first without GND; only connect if you have issues.

If you’re still not sure, you can send a photo of your setup and wiring and we’ll help check!

I was finally able to get it work by experimenting with the COM connections instead of relying on the connections from the manual. What worked for me was inverting pins from the manual.

Thanks @maros22 for mentioning the swap of wires, that “fixed” it.