From bf4d3e9942780ded8712d5fc248b12d2462cc220 Mon Sep 17 00:00:00 2001
From: David Rapan <david@rapan.cz>
Date: Tue, 17 Dec 2024 04:21:50 +0100
Subject: [PATCH] feat: Gen/SL/MI sensors named by selected IO mode

---
 custom_components/solarman/common.py          |  5 ++-
 custom_components/solarman/entity.py          |  7 ++-
 .../inverter_definitions/deye_hybrid.yaml     | 21 ++++-----
 .../inverter_definitions/deye_p3.yaml         | 44 ++++++++++++-------
 4 files changed, 45 insertions(+), 32 deletions(-)

diff --git a/custom_components/solarman/common.py b/custom_components/solarman/common.py
index b05f378..5c6c590 100644
--- a/custom_components/solarman/common.py
+++ b/custom_components/solarman/common.py
@@ -198,6 +198,9 @@ def get_addr_value(data, code, addr):
 def ilen(object):
     return len(object) if not isinstance(object, int) else 1
 
+def replace_first(object: str, newvalue, separator: str = ' '):
+    return separator.join(filter(None, (newvalue, str(parts[1] if (parts := object.split(separator, 1)) and len(parts) > 1 else ''))))
+
 def get_or_def(o, k, d):
     return o.get(k, d) or d
 
@@ -236,7 +239,7 @@ def get_tuple(tuple, index = 0):
     return tuple[index] if tuple else None
 
 def get_battery_power_capacity(capacity, voltage):
-    return capacity * voltage / 1000
+    return capacity * voltage / 1000 / 1.16
 
 def get_battery_cycles(charge, capacity, voltage):
     return charge / get_battery_power_capacity(capacity, voltage)
diff --git a/custom_components/solarman/entity.py b/custom_components/solarman/entity.py
index 1d0c1c7..3515a14 100644
--- a/custom_components/solarman/entity.py
+++ b/custom_components/solarman/entity.py
@@ -39,6 +39,11 @@ def create_entity(creator, description):
     try:
         entity = creator(description)
 
+        if description is not None and (nlookup := description.get("name_lookup")) is not None and (prefix := entity.coordinator.data.get(nlookup)) is not None:
+            description["name"] = replace_first(description["name"], get_tuple(prefix))
+            description["key"] = slugify('_'.join(filter(None, (description["name"], description["platform"]))))
+            entity = creator(description)
+
         entity.update()
 
         return entity
@@ -75,7 +80,7 @@ def set_state(self, state, value = None) -> bool:
         return True
 
     def update(self):
-        if (data := self.coordinator.data.get(self._attr_key)) and self.set_state(*data) and self.attributes:
+        if (data := self.coordinator.data.get(self._attr_key)) is not None and self.set_state(*data) and self.attributes:
             if "inverse_sensor" in self.attributes and self._attr_native_value:
                 self._attr_extra_state_attributes["−x"] = -self._attr_native_value
             for attr in filter(lambda a: a in self.coordinator.data, self.attributes):
diff --git a/custom_components/solarman/inverter_definitions/deye_hybrid.yaml b/custom_components/solarman/inverter_definitions/deye_hybrid.yaml
index a0f91d9..0b02036 100644
--- a/custom_components/solarman/inverter_definitions/deye_hybrid.yaml
+++ b/custom_components/solarman/inverter_definitions/deye_hybrid.yaml
@@ -879,14 +879,14 @@ parameters:
         rule: 1
         registers: [0x00C0]
 
-      - name: "SmartLoad"
+      - name: "Generator"
+        name_lookup: "io_mode_select"
         update_interval: 30
         platform: "binary_sensor"
         device_class: power
         rule: 1
         registers: [0x00C3]
         bit: 3
-        icon: "mdi:lightning-bolt-circle"
 
       - name: "Output Frequency"
         l: 1
@@ -1010,16 +1010,8 @@ parameters:
         registers: [0x00C2]
         icon: "mdi:transmission-tower"
 
-      - name: "Generator"
-        update_interval: 30
-        platform: "binary_sensor"
-        device_class: power
-        rule: 1
-        mask: 0x00F0
-        registers: [0x00C3]
-
       - name: "Generator Power"
-        alt: "Microinverter Power"
+        name_lookup: "io_mode_select"
         class: "power"
         state_class: "measurement"
         uom: "W"
@@ -1027,6 +1019,7 @@ parameters:
         registers: [0x00A6]
 
       - name: "Generator Frequency"
+        name_lookup: "io_mode_select"
         state_class: "measurement"
         uom: "Hz"
         scale: 0.01
@@ -1496,7 +1489,9 @@ parameters:
         registers: [0x00EA]
         icon: "mdi:generator-stationary"
 
-      - name: "SmartLoad Mode"
+      - name: "IO Mode"
+        alt: Generator input mode
+        description: The Generator/SmartLoad/Microinverter input-output mode
         platform: "select"
         rule: 1
         registers: [0x00EB]
@@ -1505,7 +1500,7 @@ parameters:
           - key: 0x0000
             value: "Generator"
           - key: 0x0001
-            value: "Output"
+            value: "SmartLoad"
           - key: 0x0002
             value: "Microinverter"
 
diff --git a/custom_components/solarman/inverter_definitions/deye_p3.yaml b/custom_components/solarman/inverter_definitions/deye_p3.yaml
index 064f69b..2d5b37c 100644
--- a/custom_components/solarman/inverter_definitions/deye_p3.yaml
+++ b/custom_components/solarman/inverter_definitions/deye_p3.yaml
@@ -676,7 +676,9 @@ parameters:
         registers: [0x0084]
         icon: "mdi:generator-stationary"
 
-      - name: "SmartLoad Mode"
+      - name: "IO Mode"
+        alt: Generator input mode
+        description: The Generator/SmartLoad/Microinverter input-output mode
         platform: "select"
         rule: 1
         registers: [0x0085]
@@ -685,7 +687,7 @@ parameters:
           - key: 0x0000
             value: "Generator"
           - key: 0x0001
-            value: "Output"
+            value: "SmartLoad"
           - key: 0x0002
             value: "Microinverter"
 
@@ -1827,7 +1829,8 @@ parameters:
         bit: 2
         icon: "mdi:transmission-tower"
 
-      - name: "SmartLoad"
+      - name: "Generator"
+        name_lookup: "io_mode_select"
         platform: "binary_sensor"
         device_class: power
         rule: 1
@@ -2496,11 +2499,12 @@ parameters:
         rule: 1
         registers: [0x028F]
 
-  - group: SmartLoad/Generator
+  - group: Generator/SmartLoad/Microinverter
     update_interval: 5
     items:
-      # SmartLoad/Generator - The voltage of phase A
-      - name: "SmartLoad L1 Voltage"
+      # Generator/SmartLoad/Microinverter - The voltage of phase A
+      - name: "Generator L1 Voltage"
+        name_lookup: "io_mode_select"
         l: 1
         class: "voltage"
         state_class: "measurement"
@@ -2509,8 +2513,9 @@ parameters:
         rule: 1
         registers: [0x0295]
 
-      # SmartLoad/Generator - The voltage of phase B
-      - name: "SmartLoad L2 Voltage"
+      # Generator/SmartLoad/Microinverter - The voltage of phase B
+      - name: "Generator L2 Voltage"
+        name_lookup: "io_mode_select"
         l: 2
         class: "voltage"
         state_class: "measurement"
@@ -2519,8 +2524,9 @@ parameters:
         rule: 1
         registers: [0x0296]
 
-      # SmartLoad/Generator - The voltage of phase C
-      - name: "SmartLoad L3 Voltage"
+      # Generator/SmartLoad/Microinverter - The voltage of phase C
+      - name: "Generator L3 Voltage"
+        name_lookup: "io_mode_select"
         l: 3
         class: "voltage"
         state_class: "measurement"
@@ -2530,7 +2536,8 @@ parameters:
         registers: [0x0297]
 
       # Generator - The power of phase A is S16bit (low 16 bits) + S16bit (high 16 bits)
-      - name: "SmartLoad L1 Power"
+      - name: "Generator L1 Power"
+        name_lookup: "io_mode_select"
         l: 1
         class: "power"
         state_class: "measurement"
@@ -2538,8 +2545,9 @@ parameters:
         rule: 2
         registers: [0x0298, 0x029C]
 
-      # SmartLoad/Generator - The power of phase A is S16bit (low 16 bits) + S16bit (high 16 bits)
-      - name: "SmartLoad L2 Power"
+      # Generator/SmartLoad/Microinverter - The power of phase A is S16bit (low 16 bits) + S16bit (high 16 bits)
+      - name: "Generator L2 Power"
+        name_lookup: "io_mode_select"
         l: 2
         class: "power"
         state_class: "measurement"
@@ -2547,8 +2555,9 @@ parameters:
         rule: 2
         registers: [0x0299, 0x029D]
 
-      # SmartLoad/Generator - The power of phase A is S16bit (low 16 bits) + S16bit (high 16 bits)
-      - name: "SmartLoad L3 Power"
+      # Generator/SmartLoad/Microinverter - The power of phase A is S16bit (low 16 bits) + S16bit (high 16 bits)
+      - name: "Generator L3 Power"
+        name_lookup: "io_mode_select"
         l: 3
         class: "power"
         state_class: "measurement"
@@ -2556,8 +2565,9 @@ parameters:
         rule: 2
         registers: [0x029A, 0x029E]
 
-      # SmartLoad/Generator - The Power is S16bit (low 16 bits) + S16bit (high 16 bits)
-      - name: "SmartLoad Power"
+      # Generator/SmartLoad/Microinverter - The Power is S16bit (low 16 bits) + S16bit (high 16 bits)
+      - name: "Generator Power"
+        name_lookup: "io_mode_select"
         l: 1
         description: Combined power of all three phases
         class: "power"