Panasonic Aquarera M-series local integration

Home Assistant can communicate with a Panasonic heat pump (series M) directly, by using the modbus protocol. This protocol is by default already available in HA. The setup is local, without dependencies of cloud services or risks from exposure to the internet. That makes it quite attractive. The integration turned out to be very doable.

Modbus is a protocol developed way back in 1979 and is still widely used for communication between industrial devices. It can be run over TCP or via a serial connection using the RS485 protocol. General information on Modbus is easily available on the net.

As a side node, there exists another approach that directly accesses the internal proprietary bus on the main board that goes by the name ‘Heishamon’.

Extra materials needed
In order to go this route some extra items are needed:

  • Extension PCB for the control box: Panasonic CZ-NSMB
  • RS485 data cable from the controlbox to HA
  • RS485 to USB converter.

Unfortunately the Panasonic CZ-NSMB extension PCB is overpriced. Prices, as of 2025, vary greatly in a range from € 150 to € 400. A proper RS485 cable of 10 meter costs another € 20. The RS 485 converter will be around € 20. This total extra cost adds up to about € 200.

As a plus, the extension PCB is an official Panasonic part that will not spoil the guarantee. Also it seems to provide galvanic isolation between the control box side of things and the external Modbus side.

Installation of the extension PCB
The extension PCB is to be placed inside the control box. Inside the box are unshielded mains power connections, possibly even three phase 400 volts. Power down the entire system before opening the box. Be extremely careful.

Take proper care of the cable management and make sure that there can be no tension on any cable from outside the box.

With these measures in place it is quite simple to place the extension pcb.

With the PCB comes a cable to connect to the main board on a connector CN-CNT or CN-CNT2. (The second connector seems to be new for the M-series.) A small manual explains the installation.

The Modbus PCB can operate simultaneously with the wifi/ ethernet module needed for the Comfort Cloud. I assume (but do not possess and did not test) the other so called ‘optional PCB’ can also be active at the same time.

On the extension PCB are dip switches to set the slave address (1 - 63), connection speed (57600 or 115200), and termination resistor (enable). The maximum data rate of the Panasonic CZ-NSMB is 115200 bps.

RS485 cable
RS485 communication can be very reliable, provided the use of the right type of cable. The cable should be (one) twisted pair, shielded, with an earth wire, and an impedance of 120 ohms.

Cable length can be long (up to 1200 meters), a reduction in data rate is needed when the length increases. Take especially notice of the impedance. A wrong impedance leads to reflection of the data signals. Ethernet cable is sometimes used but does not fully conform to the specs (lack of shielding, impedance mismatch).

RS485 to USB converter
A two wire type converter is sufficient. I got good results with a model from ‘Waveshare’. Power by USB is sufficient.

Alternatively it is also possible to convert RS485 to TCP.

Master and slave
In Modbus lingo the controlbox is called the slave, Home Assistant is the master. The master sends a request to the slave, the slave answers with a response. Note that this is opposite from the nowadays usual client-server terminology. The connection over RS485 is called ‘RTU’ for ‘remote terminal unit’.

Connecting RS 485

  • connect A to A
  • connect B to B
  • connect the ground on both sides also

A termination resistor of 120 ohms should be placed on both sides. On the control box side this resistor is built in and can be activated by a dip switch. In the converter a termination resistor usually is present too.

I had some connection issues at 115200 bps every other day. After a lost connection a restart of the heat pump was needed. A reduction of the line speed to 57600 solved it.

Testing
Connect the USB cable to a computer/ notebook to test the communication with the heat pump first. In MacOS ModBsRTUMaster is a suitable application…

The missing manual
Not in the box with the extension board is the user manual that provides detailed information about the connection and the data definitions. It is a must to have. Google: " [Panasonic CZ-NSMB User Manual]".

Configuration in Home Assistant
Modbus has been documented very well: Modbus documentation

The connection details and all entity definitions are to be made in configuration.yaml. In settings, add-ons, add a file editor to your set up. Make back ups frequently. After every few edits perform a test. In development utilities perform a configuration check reload the YAMl configuration for the Modbus part.

Fields that you add become available as regular entities.

First you need to specify the connection in configuration.yaml:

modbus:
  - name: modbus_hub
    type: serial
    port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_BG02XRJW-if00-port0
    baudrate: 57600
    bytesize: 8
    method: rtu
    parity: N
    stopbits: 2
    delay: 2
    message_wait_milliseconds: 30
    timeout: 5

The port is specified not like ‘ttyUSB0’ but as the full identifying name. If you have more than one serial device the orde in which the USB ports are bebeing assigned is not always teh same. So your heat pump might suddenly be connected on ttyUSB1 instead. The full name avoids this issue.

Of course the baud rate needs to match the setting on the control box.

Next come the definitions of sensors and binary-sensors. I have also added a few very basic templates. Template definitions are changing in Home Assistant. The included ones are of the new type.

In the configuration file below not all fields the heat pump can deliver are included. Some fields provide just static information (‘T-Cap’, ‘M-series’), those have been left out.
Since I only use Zone 1 and do not own a swimming pool, those fields are also not included.
Dutch people will enjoy that all entity names are in Dutch. Other users take notice that the addresses are added to the name, for easy reference with the user manual. Do not forget to change the port setting.

Feedback and additions are most welcome!

configuration.yaml

modbus:
  - name: modbus_hub
    type: serial
    port: /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_BG02XRJW-if00-port0
    baudrate: 57600
    bytesize: 8
    method: rtu
    parity: N
    stopbits: 2
    delay: 2
    message_wait_milliseconds: 30
    timeout: 5
    sensors:
      - name: "Buitentemperatuur - Pana 1"
        input_type: holding
        scan_interval: 60
        unit_of_measurement: "ºC"
        slave: 1
        address: 1
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_1_outdoor_actual_temp
      - name: "Uitstroomtemperatuur - Pana 2"
        input_type: holding
        scan_interval: 60
        unit_of_measurement: "ºC"
        slave: 1
        address: 2
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_2_outlet_water_temp
      - name: "Instroomtemperatuur - Pana 3"
        input_type: holding
        scan_interval: 60
        unit_of_measurement: "ºC"
        slave: 1
        address: 3
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_3_inlet_water_temp
      - name: "Mode - Pana 4"
        input_type: holding
        scan_interval: 60
        unit_of_measurement: M
        slave: 1
        address: 4
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0
        unique_id: mb_4_mode
      - name: "CV Temp verschuiving - Pana 12"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: "ºC"
        slave: 1
        address: 12
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_12_set_shift_temp
      - name: "Minimum temp compensatie - Pana 18"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: "ºC"
        slave: 1
        address: 18
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_18_min_temp_comp
      - name: "Maximum temp compensatie - Pana 19"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: "ºC"
        slave: 1
        address: 19
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_19_max_temp_comp
      - name: "Boiler actuele temp - Pana 32"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: "ºC"
        slave: 1
        address: 32
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_32_tank_actual_temp
      - name: "Boiler ingestelde water temp - Pana 33"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: "ºC"
        slave: 1
        address: 33
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_33_tank_water_set_temp
      - name: "Boiler max instelbare water temp - Pana 36"
        input_type: holding
        scan_interval: 720
        unit_of_measurement: "ºC"
        slave: 1
        address: 36
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_36_tank_water_max_set_temp
      - name: "Boiler energieverbruik - Pana 45"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: W
        slave: 1
        address: 45
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0 
        unique_id: mb_45_tank_energy_consumption
      - name: "CV energieverbruik - Pana 46"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: W
        slave: 1
        address: 46
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0 
        unique_id: mb_46_buffer_energy_consumption
      - name: "Flow direction - Pana 85"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: n
        slave: 1
        address: 85
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0  
        unique_id: mb_85_flow_direction
      - name: "SG ready control pattern - Pana 111"
        input_type: holding
        scan_interval: 3600
        unit_of_measurement: n
        slave: 1
        address: 111
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0
        unique_id: mb_111_sg_ready_control_pattern
      - name: "Waterdruk- Pana 112"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: bar
        slave: 1
        address: 112
        data_type: int16
        state_class: measurement
        scale: 0.001
        offset: 0
        precision: 1
        unique_id: mb_112_water_pressure
      - name: "SG ready - Pana 154"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: SG
        slave: 1
        address: 154
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_154_sg_ready
      - name: "delta T CV buffer - Pana 155"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: "ºC"
        slave: 1
        address: 155
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_155_delta_t_buffer_tank
      - name: "SG: DHW capacity up - Pana 156"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: "%"
        slave: 1
        address: 156
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_156_sg_dhw_capacity_up
      - name: "SG: heat capacity up - Pana 157"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: "%"
        slave: 1
        address: 157
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_157_sg_heat_capacity_up
      - name: "Stooklijn, punt max, verwarmen - Pana 158"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: "ºC"
        slave: 1
        address: 158
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_158_comp_curve_max_temp
      - name: "Stooklijn, punt min, verwarmen - Pana 159"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: "ºC"
        slave: 1
        address: 159
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_159_comp_curve_min_temp
      - name: "Stooklijn, punt min, buiten temp  - Pana 160"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: "ºC"
        slave: 1
        address: 160
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_160_comp_curve_min_temp_outside
      - name: "Stooklijn, punt max, buiten temp - Pana 161"
        input_type: holding
        scan_interval: 10
        unit_of_measurement: "ºC"
        slave: 1
        address: 161
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_161_comp_curve_max_temp_outside
      - name: "CV buffer actuele temp - Pana 180"
        input_type: holding
        scan_interval: 20
        unit_of_measurement: "ºC"
        slave: 1
        address: 180
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
      - name: "CV Temp verschuiving226 - Pana 226"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: "ºC"
        slave: 1
        address: 226
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_226_set_shift_temp
      - name: "Boiler sterilisatie temp - Pana 259"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: "ºC"
        slave: 1
        address: 259
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 1
        unique_id: mb_259_tank_sterilisation_temp
      - name: "Boiler sterilisatie tijdsduur - Pana 260"
        input_type: holding
        scan_interval: 120
        unit_of_measurement: min
        slave: 1
        address: 260
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0
        unique_id: mb_260_tank_sterilisation_time
      - name: "Min vermogen warmtepomp - Pana 428"
        input_type: holding
        scan_interval: 72000
        unit_of_measurement: n
        slave: 1
        address: 428
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0
        unique_id: mb_428_power_consumption_min_range
      - name: "Max vermogen warmtepomp - Pana 429"
        input_type: holding
        scan_interval: 72000
        unit_of_measurement: n
        slave: 1
        address: 429
        data_type: int16
        state_class: measurement
        scale: 1
        offset: 0
        precision: 0
        unique_id: mb_429_power_consumption_max_range
    binary_sensors:
      - name: "Boiler (aan/uit) - Pana 30"
        address: 30
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_30_tank
      - name: "Warmtepomp (aan/uit) - Pana 0"
        address: 0
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_0_operation
      - name: "Deice status - Pana 64"
        address: 64
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_64_deice-status
      - name: "Defrost signal - Pana 222"
        address: 222
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_222_defrost_signal
      - name: "Powerful - Pana 269"
        address: 269
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_269_powerful
      - name: "Quiet mode (actual) - Pana 270"
        address: 270
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_270_actual_quiet_mode
      - name: "Sterilization status (actual) - Pana 289"
        address: 289
        slave: 1
        scan_interval: 20
        input_type: holding
        unique_id: mb_289_actual_sterilization_status
# template experimentation area
template:
  - sensor:
    - default_entity_id: sensor.mode_pana_4_string
      name: "Instelling verwarmen CV en boiler"
      state: >
        {% if states('sensor.mode_pana_4') | int == 1 %}
          CV
        {% elif states('sensor.mode_pana_4') | int == 2 %}
          CV, boiler
        {% elif states('sensor.mode_pana_4') | int == 3 %}
          boiler
        {% elif states('sensor.mode_pana_4') | int == 4 %}
          koelen CV, boiler
        {% elif states('sensor.mode_pana_4') | int == 5 %}
          koelen CV
        {% elif states('sensor.mode_pana_4') | int == 6 %}
          auto (standaard)
        {% elif states('sensor.mode_pana_4') | int == 7 %}
          auto boiler
        {% else %}
          onbekend
        {% endif %}
    - default_entity_id: sensor.sg_ready_control_pattern_pana_111_string
      name: "SG ready control pattern"
      state: >
        {% if states('sensor.sg_ready_control_pattern_pana_111') | int == 1 %}
          normal
        {% elif states('sensor.sg_ready_control_pattern_pana_111') | int == 2 %}
          HP stop
        {% elif states('sensor.sg_ready_control_pattern_pana_111') | int == 3 %}
          capacity 1
        {% elif states('sensor.sg_ready_control_pattern_pana_111') | int == 4 %}
          capacity 2
        {% else %}
          onbekend
        {% endif %}

For people interested, heishamon is indeed a replacement board for Panasonic CZ-NSMB.

I maintain a home-assistant integration for heishamon (heishamon can send/receive data over MQTT).
Supported heatpumps are listed here