RS485 przez WiFi bez ESPHome (Epever Tracer)

Cześć, mam w samochodzie regulator ładowania Epever Tracer 2210an, który ma port RS485. Obecnie jest tam podłączony prosty ekran MT50, ale chciałbym go zamienić na komunikację bezprzewodową, WiFi lub BLE (ekran wywalić - tylko 1 rzecz podłączona).

Po różnych poszukiwaniach, ostatecznie skłaniam się aby spróbować z donglem od Epever, który, jeśli dobrze rozumiem, jest mostkiem RS485 ↔ WiFi (TCP/IP).

Modbus to dla mnie nowość i mam dość podstawowe pytanie: czy, co do zasady, to może zadziałać w taki sposób, że w HA definiuję sensor/switch w platformie modbus, podając jako typ rtuovertcp oraz adres/port tego dongla w sieci + odpowiednie rzeczy od modbusa (slave, rejestr, itd.)? Chodzi mi o konfigurację bez ESPHome: tylko ten dongle i HA (bo widziałem przykłady dla Epever ale właśnie przez ESPHome, choćby tutaj). W zasadzie potrzebny mi tylko sensor pokazujący napięcie akumulatora i ewentualnie switch przełączający “load” (zasilanie podłączonych urządzeń) - i myślałem żeby “zgapić” konfigurację tych rzeczy z ESPHome.

Ten dongiel nie obsługuje protokołu modbus, zamyka więc dalsze rozważania o jego zastosowaniu.
Chyba, że masz specyfikaję protokołu Epever.
Pojekt na ESPHome sygeruje, że po stronie RS485 jest to modbus, to strona tcp ma własny “chmurowy”. Można to w inżynierii wsteczej spróbować rozkminić ale potrzebny jest warsztat i wiedza.
Przy użyciu zwykłego konwertera np. Elfin EW11 to co zamyślasz ǰest zupełnie możliwe.

Dzięki. Dongiel ma tę zaletę, że zasila się z portu RS485 i jest malutki :slight_smile: Instrukcja nie wspomina o modbus tylko o “local monitoring”, ale to może być właśnie zgodne ze standardem rtuovertcp. Jak nie będzie, to zwrócę.

Znalazłem taki temat; na początku są posty dotyczące prób konfiguracji MQTT, ale w 17. poście jest odnośnik do custom dodatku, gdzie w kodzie chłop używa pythonowego ModbusTcpClient aby łączyć się z donglem. To mi dało pewną nadzieję.

Jak pisałem … inżynieria wsteczna. Na tym forum gościu znalazł, że jest to też konwerter modbus<>mqtt.
Te integracje działają przez mqtt. Możesz spróbować ale moim zdaniem łatwie będzie uzyć innego konwertera i komunikować przez modbus. To rozwiązanie z forum jak dla mnie posiada zbyt wiele warstw komunikacyjnych.
Szcegółowo się nie wczytywałem ale aby otrzymać odpowiedź trzeba najpierw coś wysłać (opublikować) na specjalny temacie.
Z ciekawości … ile taki dedykowany dongiel kosztuje? Gdyś już go posiadał to próba bylaby zasadna.

Znalazłem za 114 zł, podobna cena jak ten co podpowiedziałeś, Elfin EW11. Napiszę czy mi to zadziałało, czy nie, bo już zamówiłem :see_no_evil:

Na tym forum jeszcze trochę doczytałem i pradopodobnie ukrytą funkcją tego dongla jest jeszcze serwer portu szeregoweg na porcie 9999 (czyli to samo co EW11). Jest duże prawdopodobienstwo, że będzie to działać rtuovertcp. Wtedy ominie się mqtt i w HA utworzy bespośrednio encje modbus.
Trzeba bedzie tylko znać ID urządzenia, a resztę tzn. adresy i typy danych z projektu ESPHome.

1 polubienie

Mogłem się w końcu pobawić donglem Epever - działa tak jak myślałem. Ponieważ chyba nigdzie nie ma gotowej konfiguracji dla HA, podzielę się swoją. Opis w instrukcji dongla jest niewiarygodnie zamotany, ale na szczęście można po prostu podłączyć się do jego AP (fabryczne hasło: 12345678), potem adres 10.10.100.254, login/hasło: admin. W sekcji STA wpisujemy dane swojej sieci WiFi 2.4GHz, w sekcji Other Setting zmieniamy protokół na serwer i port 9999 (portu 502 niestety nie przyjmuje), dobrze też zmienić domyślnego usera i hasło.

Konfigurację HA zrobiłem w 2 częściach: integracja modbus oraz sensory template trigger-based. Sensory modbus nie obsługują szablonów, a dane tu wymagają obróbki przed prezentacją; po drugie starałem się czytać rejestry blokami, bo tak jest efektywniej - i rozbijam to na sensory właśnie w sensorach template. Z modbusa do bezpośredniego używania jest tylko switch przełączający load, a reszta sensorów pełni rolę pomocniczą i równie dobrze można je ukryć.

Integracja modbus
modbus:
  - name: 'Epever Tracer 2210AN'
    type: rtuovertcp
    host: 192.168.18.47
    port: 9999
    delay: 0
    message_wait_milliseconds: 200
    timeout: 5
    switches:
      # do włączania/wyłączania zasilania urządzeń podłączonych do kontrolera, gdy ustawienie/sensor
      # 'Load controlling modes' = 0 (to oznacza właśnie manualną kontrolę). Ja używam tylko tego trybu,
      # ale gdybym kiedyś chciał, to przy tych innych można wyłączać/wyłączać zasilanie za pomocą switcha (coil)
      # na adresie 3. I wtedy działa to chyba tak, że przy włączonym load faktyczne włączenie zasilania zależy
      # od ustawień trybu (np. harmonogram), a przy wyłączonym jest cały czas off.
      - name: 'Manual load control'
        unique_id: epever_manual_load_control
        write_type: coil
        address: 2
        verify:
    sensors:
      - name: 'modbus data: epever, load controlling modes'
        # 0x0000 Manual Control
        # 0x0001 Light ON/OFF
        # 0x0002 Light ON+ Timer/
        # 0x0003 Time Control
        unique_id: b9d49308-6533-4f33-8f05-dba8531e8793
        input_type: holding
        data_type: uint16
        address: 0x903D
      # data i czas na kontrolerze
      - name: 'modbus data: epever, datetime'
        unique_id: e2ece5da-5fe8-4044-8f9f-834b238361cf
        input_type: holding
        # 3 rejestry
        count: 3
        data_type: custom
        structure: '>3H'
        address: 0x9013
        scan_interval: 60
      # rzadko zmieniane ustawienia baterii (rodzaj, prądy ładowania, itd.)
      - name: 'modbus data: epever, battery settings'
        unique_id: a8a4d36d-b734-49eb-9fcd-b9b426184731
        input_type: holding
        # 15 rejestrów
        count: 15
        data_type: custom
        structure: '>15H'
        address: 0x9000
        scan_interval: 0  # wyłączona aktualizacja, tylko przy starcie HA lub reload integracji
      # status baterii i podłączonego sprzętu - tu są głównie informacje o błędach
      - name: 'modbus data: epever, status'
        unique_id: 4a3f9eb9-3120-4076-af19-b63c04bfe535
        input_type: input
        # 3 rejestry
        count: 3
        data_type: custom
        structure: '>3H'
        address: 0x3200
        scan_interval: 60
      # statystyki (napięcia min/max, ilość wytworzonego prądu, itd.), pierwsze 16 rejestrów
      # wszystkich rejestrów jest 31, dla bezpieczeństwa dzielę je na pół, aby nie przekroczyć czasem 255 znaków
      - name: 'modbus data: epever, statistics 1'
        unique_id: a9544a0a-600a-4fbf-8e53-4a6205d46011
        input_type: input
        # 16 rejestrów
        count: 16
        data_type: custom
        structure: '>16H'
        address: 0x3300
        scan_interval: 900  # 15 minut
      # statystyki, kolejne 15 rejestrów
      - name: 'modbus data: epever, statistics 2'
        unique_id: a9544a0a-600a-4fbf-8e53-4a6205d46012
        input_type: input
        # 15 rejestrów
        count: 15
        data_type: custom
        structure: '>15H'
        address: 0x3310
        scan_interval: 900 # 15 minut
      # bieżące info o obciążeniu przez podłączone urządzenia
      - name: 'modbus data: epever, load'
        unique_id: 3e116ce2-a574-4d13-ae11-3bd8e2f862c6
        input_type: input
        count: 4
        data_type: custom
        structure: '>4H'
        address: 0x310C
        scan_interval: 60
      # bieżące info o ładowaniu baterii; 21 rejestrów, małe liczby, ale nie są ciągłe adresy, więc na 2x
      - name: 'modbus data: epever, charging 1'
        unique_id: 967d0eaf-2019-4b9a-90dd-fd3c64784e9e
        input_type: input
        count: 19
        data_type: custom
        structure: '>19H'
        address: 0x3100
        scan_interval: 60
      - name: 'modbus data: epever, charging 2'
        unique_id: 278ed174-d21e-409d-b307-3a1b3610ae82
        input_type: input
        count: 2
        data_type: custom
        structure: '>2H'
        address: 0x311A
        scan_interval: 60
Właściwe sensory, integracja template
template:
- trigger:
    - trigger: state
      entity_id: sensor.modbus_data_epever_datetime
      not_to: ['unknown','unavailable']
      id: datetime
    - trigger: state
      entity_id: sensor.modbus_data_epever_battery_settings
      not_to: ['unknown','unavailable']
      id: batt_settings
    - trigger: state
      entity_id: sensor.modbus_data_epever_status
      not_to: ['unknown','unavailable']
      id: status
    - trigger: state
      entity_id: sensor.modbus_data_epever_statistics_1
      not_to: ['unknown','unavailable']
      id: statistics1
    - trigger: state
      entity_id: sensor.modbus_data_epever_statistics_2
      not_to: ['unknown','unavailable']
      id: statistics2
    - trigger: state
      entity_id: sensor.modbus_data_epever_load
      not_to: ['unknown','unavailable']
      id: load
    - trigger: state
      entity_id: sensor.modbus_data_epever_charging_1
      not_to: ['unknown','unavailable']
      id: charging1
    - trigger: state
      entity_id: sensor.modbus_data_epever_charging_2
      not_to: ['unknown','unavailable']
      id: charging2
    - trigger: state
      entity_id: sensor.modbus_data_epever_load_controlling_modes
      not_to: ['unknown','unavailable']
      id: load_control
      
  action:
    - variables:
        # stałe
        batt_type: '{{ { 0: "User defined", 1: "Sealed", 2: "GEL", 3: "Flooded" } if trigger.id == "batt_settings" }}'
        battery_status_voltage: >-
          {{ {
                0: "Normal",
                1: "Overvolt",
                2: "Under volt",
                3: "Low Volt Disconnect",
                4: "Fault"
            } if trigger.id == 'status' }}
        battery_status_temperature: >-
          {{ {
                0: "Normal",
                1: "Over Temperature",
                2: "Low Temperature"
            } if trigger.id == 'status' }}
        battery_status_res: '{{ {0: "Normal", 1: "Abnormal" } if trigger.id == "status" }}'
        battery_status_vol: '{{ {0: "Correct", 1: "Wrong" } if trigger.id == "status" }}'
        charging_equipment_status_input_voltage: >-
          {{ {
                0: "Normal",
                1: "No power connected",
                2: "Higher Volt Input",
                3: "Input Volt Error"
            } if trigger.id == 'status' }}
        charging_equipment_status_battery: >-
          {{ {
                0: "Not charging",
                1: "Float",
                2: "Boost",
                3: "Equalization"
            } if trigger.id == 'status' }}
        fault_status: '{{ {0: "Normal", 1: "Fault" } if trigger.id == "status" }}'
        running_status: '{{ {0: "Standby", 1: "Running" } if trigger.id == "status" }}'
        discharging_equipment_status_voltage: >-
          {{ {
                0: "Normal",
                1: "Low",
                2: "High",
                3: "No access input volt error"
            } if trigger.id == 'status' }}
        discharging_equipment_status_output: >-
          {{ {
                0: "Light Load",
                1: "Moderate",
                2: "Rated",
                3: "Overload"
            } if trigger.id == 'status' }}
        load_controlling_mode: >-
          {{ {
                0: "Manual control",
                1: "Light ON/OFF",
                2: "Light ON + timer",
                3: "Time control"
          } if trigger.id == "load_control" }}
        
        # zmienne (dane z modbus)
        datetime: "{{ trigger.to_state.state.split(',')|map('int')|list if trigger.id == 'datetime' }}"
        batt_settings: "{{ trigger.to_state.state.split(',')|map('int')|list if trigger.id == 'batt_settings' }}"
        status: "{{ trigger.to_state.state.split(',')|map('int')|list if trigger.id == 'status' }}"
        batt_status: >-
          {{ {
            'voltage': battery_status_voltage.get(status[0]|bitwise_and(0x0007)),
            'temperature': battery_status_temperature.get((status[0]/2**4)|int|bitwise_and(0x000f)),
            'internalResistance': battery_status_res.get((status[0]/2**8)|int|bitwise_and(0x0001)),
            'ratedVoltage': battery_status_vol.get((status[0]/2**15)|int|bitwise_and(0x0001)),
          } if trigger.id == 'status' }}
        eq_status: >-
          {{ {
            'inputVoltage': charging_equipment_status_input_voltage.get((status[1]/2**14)|int|bitwise_and(0x0003)),
            'mosfetShort': (status[1]/2**13)|int|bitwise_and(0x0001)|bool,
            'chargingAntiReverseMosfetShort': (status[1]/2**12)|int|bitwise_and(0x0001)|bool,
            'antiReverseMosfetShort': (status[1]/2**11)|int|bitwise_and(0x0001)|bool,
            'inputOverCurrent': (status[1]/2**10)|int|bitwise_and(0x0001)|bool,
            'loadOverCurrent': (status[1]/2**9)|int|bitwise_and(0x0001)|bool,
            'loadShort': (status[1]/2**8)|int|bitwise_and(0x0001)|bool,
            'loadMosfetShort': (status[1]/2**7)|int|bitwise_and(0x0001)|bool,
            'pvInputShort': (status[1]/2**7)|int|bitwise_and(0x0001)|bool,
            'battery': charging_equipment_status_battery.get((status[1]/2**2)|int|bitwise_and(0x0003)),
            'fault': (status[1]/2)|int|bitwise_and(0x0001)|bool,
            'running': status[1]|int|bitwise_and(0x0001)|bool,
          } if trigger.id == 'status' }}
        dis_status: >-
          {{ {
            'inputVoltage': discharging_equipment_status_voltage.get((status[2]/2**14)|int|bitwise_and(0x0003)),
            'outputPower': discharging_equipment_status_voltage.get((status[2]/2**12)|int|bitwise_and(0x0003)),
            'shortCircuit': (status[2]/2**11)|int|bitwise_and(0x0001)|bool,
            'unableDischarge': (status[2]/2**10)|int|bitwise_and(0x0001)|bool,
            'unableStopDischarging': (status[2]/2**9)|int|bitwise_and(0x0001)|bool,
            'outputVoltageAbnormal': (status[2]/2**8)|int|bitwise_and(0x0001)|bool,
            'inputOverpressure': (status[2]/2**7)|int|bitwise_and(0x0001)|bool,
            'highVoltageSideShortCircuit': (status[2]/2**6)|int|bitwise_and(0x0001)|bool,
            'boostOverpressure': (status[2]/2**5)|int|bitwise_and(0x0001)|bool,
            'outputOverpressure': (status[2]/2**4)|int|bitwise_and(0x0001)|bool,
            'fault': (status[2]/2)|int|bitwise_and(0x0001)|bool,
            'running': status[2]|int|bitwise_and(0x0001)|bool,
          } if trigger.id == 'status' }}
        statistics1: >-
          {% if trigger.id == 'statistics1' %}
            {% set s = trigger.to_state.state.split(',')|map('float')|list %}
            {{ {
              'maxVoltToday': (s[0]/100)|round(1),
              'minVoltToday': (s[1]/100)|round(1),
              'maxBatteryVoltToday': (s[2]/100)|round(1),
              'minBatteryVoltToday': (s[3]/100)|round(1),
              'consumedEnergyToday': ((s[4] + s[5]*16)/100)|round(0),
              'consumedEnergyMonth': ((s[6] + s[7]*16)/100)|round(1),
              'consumedEnergyYear': ((s[8] + s[9]*16)/100)|round(1),
              'totalConsumedEnergy': ((s[10] + s[11]*16)/100)|round(1),
              'generatedEnergyToday': ((s[12] + s[13]*16)/100)|round(0),
              'generatedEnergyMonth': ((s[14] + s[15]*16)/100)|round(1),
              } }}
          {% endif %}
        statistics2: >-
          {% if trigger.id == 'statistics2' %}
            {% set s = trigger.to_state.state.split(',')|map('float')|list %}
            {% set s11 = 0 if s[11] == 0xFFFF else s[11] %}
            {% set s12 = 0 if s[12] == 0xFFFF else s[12] %}
            {{ {
              'generatedEnergyYear': ((s[0] + s[1]*16)/100)|round(1),
              'totalGeneratedEnergy': ((s[2] + s[3]*16)/100)|round(1),
              'carbonDioxideReduction': ((s[4] + s[5]*16)/100)|round(1),
              'batteryVoltage': (s[10]/100)|round(1),
              'batteryCurrent': ((s11 + s12*16)/100)|round(1),
              'batteryTemperature': (s[13]/100)|round(1),
              'ambientTemperature': (s[14]/100)|round(1),
              } }}
          {% endif %}
        load: >-
          {% if trigger.id == 'load' %}
            {% set s = trigger.to_state.state.split(',')|map('int')|list %}
            {{ {
                'loadVoltage': (s[0]/100)|round(1),
                'loadCurrent': (s[1]/100)|round(1),
                'loadPower': ((s[2] + s[3]*16)/100)|round(1),
              } }}
          {% endif %}
        charging1: >-
          {% if trigger.id == 'charging1' %}
            {% set s = trigger.to_state.state.split(',')|map('int')|list %}
            {{ {
                'chargingInputVoltage': (s[0]/100)|round(1),
                'chargingInputCurrent': (s[1]/100)|round(1),
                'chargingInputPower': ((s[2] + s[3]*16)/100)|round(1),
                'chargingOutputVoltage': (s[4]/100)|round(1),
                'chargingOutputCurrent': (s[5]/100)|round(1),
                'chargingOutputPower': ((s[6] + s[7]*16)/100)|round(1),
                'dischargingOutputVoltage': (s[12]/100)|round(1),
                'dischargingOutputCurrent': (s[13]/100)|round(1),
                'dischargingOutputPower': ((s[14] + s[15]*16)/100)|round(1),
                'batteryTemperature': (s[16]/100)|round(1),
                'temperatureInside': (s[17]/100)|round(1),
                'powerComponentsTemperature': (s[18]/100)|round(1),
              } }}
          {% endif %}
        charging2: >-
          {% if trigger.id == 'charging2' %}
            {% set s = trigger.to_state.state.split(',')|map('int')|list %}
            {{ {
                'batterySoC': s[0]|round(0),
                'remoteBatteryTemperature': (s[1]/100)|round(1),
              } }}
          {% endif %}
        load_control: "{{ load_controlling_mode.get(trigger.to_state.state|int(0)) if trigger.id == 'load_control' }}"
  sensor:
    - name: "Epever, date and time"
      unique_id: 40a34179-2d3f-4f6d-9575-9d9e3848d939
      state: >-
        {% if trigger.id == 'datetime' %}
          {% set year = (2000 + datetime[2]/256)|int %}
          {% set month = datetime[2]|bitwise_and(0xff) %}
          {% set day = (datetime[1]/256)|int %}
          {% set hour = datetime[1]|bitwise_and(0xff) %}
          {% set min = (datetime[0]/256)|int %}
          {% set sec = datetime[0]|bitwise_and(0xff) %}
          {{ '{}-{:02}-{:02} {:02}:{:02}:{:02}'.format(year,month,day,hour,min,sec) }}
        {% else %}
          {{ this.state|default('unknown') }}
        {% endif %}
    - name: "Epever, battery settings"
      unique_id: 72766f7d-b80b-4b3b-a614-898c11dac4ea
      state: >-
          {{ batt_type.get(batt_settings[0]) if trigger.id == 'batt_settings' else this.state|default('unknown') }}
      attributes:
        batteryType: "{{ batt_type.get(batt_settings[0]) if trigger.id == 'batt_settings' else this.attributes.get('batteryType') }}"
        batteryCapacity: "{{ batt_settings[1] if trigger.id == 'batt_settings' else this.attributes.get('batteryCapacity') }}"
        temperatureCompensationCoefficient: "{{ batt_settings[2]/100 if trigger.id == 'batt_settings' else this.attributes.get('temperatureCompensationCoefficient') }}"
        highVoltDisconnect: "{{ batt_settings[3]/100 if trigger.id == 'batt_settings' else this.attributes.get('highVoltDisconnect') }}"
        chargingLimitVoltage: "{{ batt_settings[4]/100 if trigger.id == 'batt_settings' else this.attributes.get('chargingLimitVoltage') }}"
        overVoltageReconnect: "{{ batt_settings[5]/100 if trigger.id == 'batt_settings' else this.attributes.get('overVoltageReconnect') }}"
        equalizationVoltage: "{{ batt_settings[6]/100 if trigger.id == 'batt_settings' else this.attributes.get('equalizationVoltage') }}"
        boostVoltage: "{{ batt_settings[7]/100 if trigger.id == 'batt_settings' else this.attributes.get('boostVoltage') }}"
        floatVoltage: "{{ batt_settings[8]/100 if trigger.id == 'batt_settings' else this.attributes.get('floatVoltage') }}"
        boostReconnectVoltage: "{{ batt_settings[9]/100 if trigger.id == 'batt_settings' else this.attributes.get('boostReconnectVoltage') }}"
        lowVoltageReconnect: "{{ batt_settings[10]/100 if trigger.id == 'batt_settings' else this.attributes.get('lowVoltageReconnect') }}"
        underVoltageRecover: "{{ batt_settings[11]/100 if trigger.id == 'batt_settings' else this.attributes.get('underVoltageRecover') }}"
        underVoltageWarning: "{{ batt_settings[12]/100 if trigger.id == 'batt_settings' else this.attributes.get('underVoltageWarning') }}"
        lowVoltageDisconnect: "{{ batt_settings[13]/100 if trigger.id == 'batt_settings' else this.attributes.get('lowVoltageDisconnect') }}"
        dischargingLimitVoltage: "{{ batt_settings[14]/100 if trigger.id == 'batt_settings' else this.attributes.get('dischargingLimitVoltage') }}"
    - name: "Epever, battery status"
      unique_id: de0d3703-12af-40b2-80d3-54d7ac51b4e6
      state: "{{ batt_status.items()|map(attribute='1')|join(', ') if trigger.id == 'status' else this.state|default('unknown') }}"
      attributes:
        voltage: "{{ batt_status['voltage'] if trigger.id == 'status' else this.attributes.get('voltage') }}"
        temperature: "{{ batt_status['temperature'] if trigger.id == 'status' else this.attributes.get('temperature') }}"
        internalResistance: "{{ batt_status['internalResistance'] if trigger.id == 'status' else this.attributes.get('internalResistance') }}"
        ratedVoltage: "{{ batt_status['ratedVoltage'] if trigger.id == 'status' else this.attributes.get('ratedVoltage') }}"
    - name: "Epever, equipment status"
      unique_id: 9e9d7117-4af1-44cc-bcf1-d15d63e4b982
      state: >-
        {% if trigger.id == 'status' %}
          {% set inputVoltage = eq_status['inputVoltage'] %}
          {% set battery = eq_status['battery'] %}
          {% set running = 'Running' if eq_status['running'] else 'Not running' %}
          {% set problem = eq_status.items()|selectattr('1','eq',true)|rejectattr('0','eq','running')|first is defined %}
          {{ inputVoltage }}, {{ battery }}, {{ running }}, {{ 'Problem!' if problem else 'OK' }}
        {% else %}
          {{ this.state|default('unknown') }}
        {% endif %}
      attributes:
        inputVoltage: "{{ eq_status['inputVoltage'] if trigger.id == 'status' else this.attributes.get('inputVoltage') }}"
        mosfetShort: "{{ eq_status['mosfetShort'] if trigger.id == 'status' else this.attributes.get('mosfetShort') }}"
        chargingAntiReverseMosfetShort: "{{ eq_status['chargingAntiReverseMosfetShort'] if trigger.id == 'status' else this.attributes.get('chargingAntiReverseMosfetShort') }}"
        antiReverseMosfetShort: "{{ eq_status['antiReverseMosfetShort'] if trigger.id == 'status' else this.attributes.get('antiReverseMosfetShort') }}"
        inputOverCurrent: "{{ eq_status['inputOverCurrent'] if trigger.id == 'status' else this.attributes.get('inputOverCurrent') }}"
        loadOverCurrent: "{{ eq_status['loadOverCurrent'] if trigger.id == 'status' else this.attributes.get('loadOverCurrent') }}"
        loadShort: "{{ eq_status['loadShort'] if trigger.id == 'status' else this.attributes.get('loadShort') }}"
        loadMosfetShort: "{{ eq_status['loadMosfetShort'] if trigger.id == 'status' else this.attributes.get('loadMosfetShort') }}"
        pvInputShort: "{{ eq_status['pvInputShort'] if trigger.id == 'status' else this.attributes.get('pvInputShort') }}"
        battery: "{{ eq_status['battery'] if trigger.id == 'status' else this.attributes.get('battery') }}"
        fault: "{{ eq_status['fault'] if trigger.id == 'status' else this.attributes.get('fault') }}"
        running: "{{ eq_status['running'] if trigger.id == 'status' else this.attributes.get('running') }}"
    - name: "Epever, discharge status"
      unique_id: 89392181-bf34-43bd-ba8e-eccbe00460b5
      state: >-
        {% if trigger.id == 'status' %}
          {% set inputVoltage = dis_status['inputVoltage'] %}
          {% set outputPower = dis_status['outputPower'] %}
          {% set running = 'Running' if dis_status['running'] else 'Not running' %}
          {% set problem = dis_status.items()|selectattr('1','eq',true)|rejectattr('0','eq','running')|first is defined %}
          {{ inputVoltage }}, {{ outputPower }}, {{ running }}, {{ 'Problem!' if problem else 'OK' }}
        {% else %}
          {{ this.state|default('unknown') }}
        {% endif %}
      attributes:
        inputVoltage: "{{ dis_status['inputVoltage'] if trigger.id == 'status' else this.attributes.get('inputVoltage') }}"
        outputPower: "{{ dis_status['outputPower'] if trigger.id == 'status' else this.attributes.get('outputPower') }}"
        shortCircuit: "{{ dis_status['shortCircuit'] if trigger.id == 'status' else this.attributes.get('shortCircuit') }}"
        unableDischarge: "{{ dis_status['unableDischarge'] if trigger.id == 'status' else this.attributes.get('unableDischarge') }}"
        unableStopDischarging: "{{ dis_status['unableStopDischarging'] if trigger.id == 'status' else this.attributes.get('unableStopDischarging') }}"
        outputVoltageAbnormal: "{{ dis_status['outputVoltageAbnormal'] if trigger.id == 'status' else this.attributes.get('outputVoltageAbnormal') }}"
        inputOverpressure: "{{ dis_status['inputOverpressure'] if trigger.id == 'status' else this.attributes.get('inputOverpressure') }}"
        highVoltageSideShortCircuit: "{{ dis_status['highVoltageSideShortCircuit'] if trigger.id == 'status' else this.attributes.get('highVoltageSideShortCircuit') }}"
        boostOverpressure: "{{ dis_status['boostOverpressure'] if trigger.id == 'status' else this.attributes.get('boostOverpressure') }}"
        outputOverpressure: "{{ dis_status['outputOverpressure'] if trigger.id == 'status' else this.attributes.get('outputOverpressure') }}"
        fault: "{{ dis_status['fault'] if trigger.id == 'status' else this.attributes.get('fault') }}"
        running: "{{ dis_status['running'] if trigger.id == 'status' else this.attributes.get('running') }}"
    - name: "Epever, statistics"
      unique_id: 638e4bee-80e9-4f9d-a44b-2244ab8f2858
      state: "{{ statistics1['generatedEnergyToday'] if trigger.id == 'statistics1' else this.state|default(0) }}"
      device_class: "energy"
      unit_of_measurement: "Wh"
      attributes:
        maxVoltToday: "{{ statistics1['maxVoltToday'] if trigger.id == 'statistics1' else this.attributes.get('maxVoltToday') }}"
        minVoltToday: "{{ statistics1['minVoltToday'] if trigger.id == 'statistics1' else this.attributes.get('minVoltToday') }}"
        maxBatteryVoltToday: "{{ statistics1['maxBatteryVoltToday'] if trigger.id == 'statistics1' else this.attributes.get('maxBatteryVoltToday') }}"
        minBatteryVoltToday: "{{ statistics1['minBatteryVoltToday'] if trigger.id == 'statistics1' else this.attributes.get('minBatteryVoltToday') }}"
        consumedEnergyToday_Wh: "{{ statistics1['consumedEnergyToday'] if trigger.id == 'statistics1' else this.attributes.get('consumedEnergyToday_Wh') }}"
        consumedEnergyMonth: "{{ statistics1['consumedEnergyMonth'] if trigger.id == 'statistics1' else this.attributes.get('consumedEnergyMonth') }}"
        consumedEnergyYear: "{{ statistics1['consumedEnergyYear'] if trigger.id == 'statistics1' else this.attributes.get('consumedEnergyYear') }}"
        totalConsumedEnergy: "{{ statistics1['totalConsumedEnergy'] if trigger.id == 'statistics1' else this.attributes.get('totalConsumedEnergy') }}"
        generatedEnergyToday_Wh: "{{ statistics1['generatedEnergyToday'] if trigger.id == 'statistics1' else this.attributes.get('generatedEnergyToday_Wh') }}"
        generatedEnergyMonth: "{{ statistics1['generatedEnergyMonth'] if trigger.id == 'statistics1' else this.attributes.get('generatedEnergyMonth') }}"
        generatedEnergyYear: "{{ statistics2['generatedEnergyYear'] if trigger.id == 'statistics2' else this.attributes.get('generatedEnergyYear') }}"
        totalGeneratedEnergy: "{{ statistics2['totalGeneratedEnergy'] if trigger.id == 'statistics2' else this.attributes.get('totalGeneratedEnergy') }}"
        carbonDioxideReduction: "{{ statistics2['carbonDioxideReduction'] if trigger.id == 'statistics2' else this.attributes.get('carbonDioxideReduction') }}"
        batteryVoltage: "{{ statistics2['batteryVoltage'] if trigger.id == 'statistics2' else this.attributes.get('batteryVoltage') }}"
        batteryCurrent: "{{ statistics2['batteryCurrent'] if trigger.id == 'statistics2' else this.attributes.get('batteryCurrent') }}"
        batteryTemperature: "{{ statistics2['batteryTemperature'] if trigger.id == 'statistics2' else this.attributes.get('batteryTemperature') }}"
        ambientTemperature: "{{ statistics2['ambientTemperature'] if trigger.id == 'statistics2' else this.attributes.get('ambientTemperature') }}"
    - name: "Epever, load"
      unique_id: 8b951010-2174-4b3d-bf8e-a4cdf1ccbdb3
      state: "{{ load['loadVoltage'] if trigger.id == 'load' else this.state|default(0) }}"
      unit_of_measurement: "V"
      attributes:
        loadVoltage: "{{ load['loadVoltage'] if trigger.id == 'load' else this.attributes.get('loadVoltage') }}"
        loadCurrent: "{{ load['loadCurrent'] if trigger.id == 'load' else this.attributes.get('loadCurrent') }}"
        loadPower: "{{ load['loadPower'] if trigger.id == 'load' else this.attributes.get('loadPower') }}"
    - name: "Epever, charging"
      unique_id: 8eb904bd-b30d-4624-b719-c03a9e8f0023
      state: "{{ charging1['chargingOutputVoltage'] if trigger.id == 'charging1' else this.state|default(0) }}"
      unit_of_measurement: "V"
      attributes:
        chargingInputVoltage: "{{ charging1['chargingInputVoltage'] if trigger.id == 'charging1' else this.attributes.get('chargingInputVoltage') }}"
        chargingInputCurrent: "{{ charging1['chargingInputCurrent'] if trigger.id == 'charging1' else this.attributes.get('chargingInputCurrent') }}"
        chargingInputPower: "{{ charging1['chargingInputPower'] if trigger.id == 'charging1' else this.attributes.get('chargingInputPower') }}"
        chargingOutputVoltage: "{{ charging1['chargingOutputVoltage'] if trigger.id == 'charging1' else this.attributes.get('chargingOutputVoltage') }}"
        chargingOutputCurrent: "{{ charging1['chargingOutputCurrent'] if trigger.id == 'charging1' else this.attributes.get('chargingOutputCurrent') }}"
        chargingOutputPower: "{{ charging1['chargingOutputPower'] if trigger.id == 'charging1' else this.attributes.get('chargingOutputPower') }}"
        dischargingOutputVoltage: "{{ charging1['dischargingOutputVoltage'] if trigger.id == 'charging1' else this.attributes.get('dischargingOutputVoltage') }}"
        dischargingOutputCurrent: "{{ charging1['dischargingOutputCurrent'] if trigger.id == 'charging1' else this.attributes.get('dischargingOutputCurrent') }}"
        dischargingOutputPower: "{{ charging1['dischargingOutputPower'] if trigger.id == 'charging1' else this.attributes.get('dischargingOutputPower') }}"
        batteryTemperature: "{{ charging1['batteryTemperature'] if trigger.id == 'charging1' else this.attributes.get('batteryTemperature') }}"
        temperatureInside: "{{ charging1['temperatureInside'] if trigger.id == 'charging1' else this.attributes.get('temperatureInside') }}"
        powerComponentsTemperature: "{{ charging1['powerComponentsTemperature'] if trigger.id == 'charging1' else this.attributes.get('powerComponentsTemperature') }}"
        batterySoC: "{{ charging2['batterySoC'] if trigger.id == 'charging2' else this.attributes.get('batterySoC') }}"
        remoteBatteryTemperature: "{{ charging2['remoteBatteryTemperature'] if trigger.id == 'charging2' else this.attributes.get('remoteBatteryTemperature') }}"
    - name: "Epever, load control"
      unique_id: 3739731e-a961-4e17-8115-f7fa5fc9e93a
      state: "{{ load_control if trigger.id == 'load_control' else this.state|default('unknown') }}"
Skrypt do synchronizacji daty i czasu na kontrolerze
sequence:
  - action: modbus.write_register
    data:
      hub: Epever Tracer 2210AN
      address: 36883
      value: >-
        {% set n = now() %} 
        {% set d2 = (n.year - 2000)*256 + n.month %}
        {% set d1 = n.day*256 + n.hour %}
        {% set d0 = n.minute*256 + n.second %}
        {{ [d0,d1,d2] }}
alias: Epever, sync datetime

Rozbujałem się i wyszło tego całkiem dużo :slight_smile:, kilka przykładów:

Obrazki




Edycja & ostrzeżenie: po kilku dniach, wbudowany serwer portu szeregowego przestał odpowiadać. Restart, odłączenie zasilania oraz reset do ustawień fabrycznych + ponowna konfiguracja nie pomogły. Adapter nie pokazuje logów, więc diabli wiedzą co mu dolega.

Każdy sprzęt może się popsuć, natomiast z tym donglem jest jeszcze 1 poważny problem: po podłączeniu do WiFi wciąż aktywny jest AP i nie da się go wyłączyć, ani zmienić mu hasła. Zaś strona www pokazuje hasła otwartym tekstem. Więc jedyna linia obrony przed ogłaszaniem wszem i wobec hasła do naszego WiFi, to użytkownik/hasło web portalu, z maksymalną długością 15 znaków.

Reasumując, jednak nie polecam!

2 polubienia