Sinilink XY6014 modbus control with ESP8266

ESPhome configuration

substitutions:
  # Change this model to fit your particular one.
  # You can find it in Home Assistant as the device diagnostic "Model Name".
  model: "XY6014"
  device_name: "xy6014-controller"
  device_friendly_name: "sinilink-XY6014"
  device_description: "Power Supply"
 
  # Model specific settings (Don't change these!)
  XY6014_voltage_maximum: "60"
  XY6014_voltage_accuracy: "2"
  XY6014_voltage_multiplier: "0.01"
  XY6014_current_maximum: "14"
  XY6014_current_accuracy: "3"
  XY6014_current_multiplier: "0.01"
 
globals:
   - id:   vin_undervoltage_theshold
     type: float
     restore_value: no
     initial_value: '17'
 
esphome:
  name: $device_name
  friendly_name: $device_friendly_name
  comment: $device_description
  name_add_mac_suffix: false
  project:
    name: "sinilink.xy5008-controller"
    version: "1.0.0"
 
esp8266:
  board: d1_mini
 
# Enable logging
logger:
  level: INFO
  # Disable logging via UART, since we're using this for modbus communication
  baud_rate: 0
 
# Enable status LED
status_led:
  pin:
    number: GPIO2
    inverted: true
 
web_server:
  port: 80
  local: true
  log: false
 
# Enable Home Assistant API
api:
#  encryption:
#    key: !secret home_assistant_key
  reboot_timeout: 0s
 
ota:
  - platform: esphome
    #password: !secret ota_password
 
# Configure WiFi
wifi:
  networks:
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
 
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "XY6014 Fallback Hotspot"
    password: ""
 
text_sensor:
  - platform: wifi_info
    ip_address:
      name: ESP IP Address
      id: ip_address
      on_value:      
        then:
          - lambda: |-
 
  - platform: template
    name: "Turn On Time"
    lambda: |-
      char buffer[16];
      std::string ton_time;
      std::snprintf(buffer, 16, "%02d:%02d:%02d" , (int)id(turnon_hours).state, (int)id(turnon_minutes).state, (int)id(turnon_seconds).state);
      ton_time = buffer;
 
      return {ton_time};
    update_interval: 1s
 
interval:
  - interval: 5s
    then:
      - lambda: |-
          char ip[18];
          char *ptr =ip;
 
          strncpy(ip,id(ip_address).state.c_str(),17);
          ip[17]=0;
          int ip1 = atoi(ptr);
          ptr = strchr( ptr, '.');
          if (ptr!=NULL) {
            int ip2 = atoi(++ptr);
            ptr = strchr( ptr, '.');
            if (ptr!=NULL) {
              int ip3 = atoi(++ptr);
              ptr = strchr( ptr, '.');
              if (ptr!=NULL) {
                int ip4 = atoi(++ptr);
                esphome::modbus_controller::ModbusController *controller = id(powersupply);
                // create the payload
                std::vector<uint16_t> wifi_data = { 0x3b3a, 2, 4, uint16_t((ip1 << 8) | ip2), uint16_t((ip3 << 8) | ip4) };
                // Create a ModBUS command item with the wifi information as the payload
                esphome::modbus_controller::ModbusCommandItem set_wifi_command =
                      esphome::modbus_controller::ModbusCommandItem::create_write_multiple_command(controller, 0x30, 5, wifi_data);
                // Submit the command to the send queue
                powersupply->queue_command(set_wifi_command);
              }
            }
          }
 
  - interval: 1s
    then:
      - lambda: |-
          esphome::modbus_controller::ModbusController *controller = id(powersupply);
          esphome::modbus_controller::ModbusCommandItem set_wifi_command =
              esphome::modbus_controller::ModbusCommandItem::create_read_command(controller, esphome::modbus_controller::ModbusRegisterType::HOLDING, 0x30, 5);
          // Submit the command to the send queue
          powersupply->queue_command(set_wifi_command);
 
  - interval: 0.5s
    then:
      - lambda: |-
          static int count = 0;
          static int delay_count = 0;
          static int uv_count = 0;
 
          if ((id(output_switch).state) && (id(cutoff_current).state!=0) && (id(output_current).state < id(cutoff_current).state)){
            if (count>=5) {
              count = 0;
              id(output_switch).turn_off();
              id(out_relay).turn_off();
            }
            else count++;              
          }
          else count=0;
 
          if (id(input_voltage).state < id(vin_undervoltage_theshold)) { if (uv_count<4) uv_count++; }
          else {
             if (id(output_switch).state) id(out_relay).turn_on();
             uv_count=0;
          }
 
          if (uv_count >=4) {
              id(out_relay).turn_off();
          }    
 
          if (id(out_relay).state) {
              if (id(output_switch).state) delay_count=0;
              else delay_count++;
          }
          else delay_count=0;
 
          if (delay_count>=120*3) { 
              id(out_relay).turn_off();
              delay_count=0;
          }    
 
captive_portal:
 
uart:
  id: mod_bus
  tx_pin: GPIO1
  rx_pin: GPIO3
  baud_rate: 115200
  data_bits: 8
  stop_bits: 1
  parity: none
 
modbus:
 id: modbus1
# send_wait_time: 1ms
 
modbus_controller:
  - id: powersupply
    ## This address should be set to the "Address" value in the config menu
    address: 0x01
    modbus_id: modbus1
    setup_priority: 600
    update_interval: 1s
    command_throttle: 1ms
 
time:
  - platform: homeassistant
    id: ha_time
 
 
sensor:
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 2
    name: "Output voltage"
    id: output_voltage
    device_class: voltage
    state_class: measurement
    unit_of_measurement: "V"
    register_type: holding
    value_type: U_WORD
    accuracy_decimals: ${${model}_voltage_accuracy}
    filters:
      - multiply: ${${model}_voltage_multiplier}
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 3
    name: "Output current"
    id: output_current
    device_class: current
    state_class: measurement
    unit_of_measurement: "A"
    register_type: holding
    value_type: U_WORD
    accuracy_decimals: ${${model}_current_accuracy}
    filters:
      - multiply: ${${model}_current_multiplier}
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 4
    name: "Output Power"
    device_class: power
    state_class: measurement
    unit_of_measurement: "W"
    register_type: holding
    value_type: U_WORD
    accuracy_decimals: 2
    filters:
      - multiply: 0.1
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 5
    name: "Input voltage"
    id: input_voltage
    device_class: voltage
    unit_of_measurement: "V"
    register_type: holding
    value_type: U_WORD
    accuracy_decimals: ${${model}_voltage_accuracy}
    #accuracy_decimals: 3
    filters:
    #  - multiply: ${${model}_voltage_multiplier}
      - multiply: 0.01
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 6
    name: "Battery charge"
    device_class: "energy_storage"
    state_class: measurement
    unit_of_measurement: "Ah"
    icon: "mdi:battery-60"
    register_type: holding
    value_type: U_DWORD_R
    accuracy_decimals: 3
    filters:
      - multiply: 0.001
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 8
    name: "Battery energy"
    device_class: "energy_storage"
    state_class: measurement
    unit_of_measurement: "Wh"
    icon: "mdi:battery-60"
    register_type: holding
    value_type: U_DWORD_R
    accuracy_decimals: 3
    filters:
      - multiply: 0.001
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 10
    name: "turnon_hours"
    id: turnon_hours
    unit_of_measurement: "hours"
    register_type: holding
    value_type: U_WORD
    internal: true
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 11
    name: "turnon_minutes"
    id: turnon_minutes
    unit_of_measurement: "minutes"
    register_type: holding
    value_type: U_WORD
    internal: true
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    address: 12
    name: "turnon_seconds"
    id: turnon_seconds
    unit_of_measurement: "seconds"
    register_type: holding
    value_type: U_WORD
    internal: true
 
  - platform: modbus_controller
    name: "Temperature"
    device_class: temperature
    state_class: measurement
    modbus_controller_id: powersupply
    register_type: holding
    address: 13
    value_type: S_WORD
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
 
  - platform: modbus_controller
    name: "Temperature external"
    state_class: measurement
    modbus_controller_id: powersupply
    register_type: holding
    address: 14
    value_type: S_WORD
    device_class: temperature
    unit_of_measurement: "°C"
    accuracy_decimals: 1
    filters:
      - multiply: 0.1
 
  - platform: modbus_controller
    name: "Host type"
    state_class: measurement
    modbus_controller_id: powersupply
    register_type: holding
    address: 48
    value_type: U_WORD
    internal: true
 
 
 
  - platform: template
    name: "Battery Charge Power"
    id: battery_power
    unit_of_measurement: "W"
    device_class: "power"
    state_class: "measurement"
    accuracy_decimals: 1
    lambda: |-
      if (id(output_current).state > 0.05) {
        return id(output_voltage).state * id(output_current).state;
      } else {
        return 0.0;
      }
 
  - platform: total_daily_energy
    name: "Battery Charge Today"
    power_id: battery_power
    unit_of_measurement: "Wh"
    device_class: "energy"
    state_class: "total_increasing"
    accuracy_decimals: 0
    restore: false
 
 
binary_sensor:
  - platform: modbus_controller
    modbus_controller_id: powersupply
    name: "Constant Voltage"
    address: 17
    register_type: holding
    bitmask: 0x1
    filters:
      - invert:
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    name: "Constant Current"
    address: 17
    register_type: holding
    bitmask: 0x1 
 
number:
  - platform: modbus_controller
    modbus_controller_id: powersupply
    name: "Set output voltage"
    device_class: voltage
    unit_of_measurement: "V"
    entity_category: config
    mode: box
    address: 0
    value_type: U_WORD
    min_value: 0
    max_value: ${${model}_voltage_maximum}
    step: ${${model}_voltage_multiplier}
    lambda: !lambda return x * ${${model}_voltage_multiplier};
    write_lambda: !lambda return x * (1/${${model}_voltage_multiplier});
 
  - platform: modbus_controller
    modbus_controller_id: powersupply
    name: "Set output current"
    device_class: current
    unit_of_measurement: "A"
    entity_category: config
    mode: box
    address: 1
    value_type: U_WORD
    min_value: 0
    max_value: ${${model}_current_maximum}
    step: ${${model}_current_multiplier}
    lambda: !lambda return x * ${${model}_current_multiplier};
    write_lambda: !lambda return x * (1/${${model}_current_multiplier});
 
  - platform: template
    name: "Under current cutoff (battery charge)"
    id: cutoff_current
    device_class: current
    unit_of_measurement: "A"
    entity_category: config
    mode: box
    min_value: 0
    max_value: ${${model}_current_maximum}
    step: ${${model}_current_multiplier}
    optimistic: true
 
switch:
  - platform: modbus_controller
    modbus_controller_id: powersupply
    name: "Output"
    id: output_switch
    address: 18
    register_type: holding
    bitmask: 0x1
    entity_category: config
 
  - platform: gpio
    id: out_relay
    name: "Output relay"
    pin:
      number: GPIO4
      inverted: false

Flash
esphome run sinilink-modbus.yaml --device /dev/ttyUSB0