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.
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.
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!
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 )
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 )
[/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.