Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion custom_components/solarman/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@ async def write(self, value, state = None) -> None:
self.set_state(state, value)
self.async_write_ha_state()
#await self.entity_description.update_fn(self.coordinator., int(value))
#await self.coordinator.async_request_refresh()
if getattr(self, "mask", None) is not None:
await self.coordinator.async_request_refresh()
74 changes: 74 additions & 0 deletions custom_components/solarman/inverter_definitions/deye_p3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3150,6 +3150,80 @@ parameters:
rule: 1
registers: [0x02DA]
icon: "mdi:solar-power-variant"

- group: EV Charger
update_interval: 300
items:
- name: "EV Charger Power Mode"
description: "Solar Only: charging uses surplus solar power and starts when battery SOC reaches 99%; charging stops below 95% SOC. Free Work: charging can use both inverter and grid power."
platform: select
rule: 1
registers: [0x0103]
mask: 0x0003
icon: "mdi:ev-station"
lookup:
- key: 0x0001
value: "Solar Only"
- key: 0x0002
value: "Free Work"

- name: "EV Charger Inverter Connection Port"
description: "Physical inverter port used by the EV charger. Affects load management, power balancing, and off-grid operation."
platform: select
rule: 1
registers: [0x0103]
mask: 0x0030
icon: "mdi:power-plug"
lookup:
- key: 0x0010
value: "Grid Port"
- key: 0x0020
value: "Load Port"

- name: "EV Charger Off-Grid SOC"
description: "Minimum battery SOC required to allow EV charging in off-grid mode. Charging stops when SOC falls below this threshold."
platform: number
state_class: measurement
uom: "%"
rule: 1
mask: 0xFF00
divide: 256
registers: [0x0103]
icon: "mdi:battery-charging"
configurable:
mode: box
min: 0
max: 100

- name: "EV Charger Maximum Power"
description: "Maximum EV charging power allowed by the inverter. The inverter will not request a charging limit above this value. Minimum charging power is approximately ~1.4 kW single-phase or ~4.1 kW three-phase."
platform: number
class: power
state_class: measurement
uom: "W"
scale: 1
rule: 1
registers: [0x0104]
icon: "mdi:ev-station"
configurable:
min: 0
max: 22000
step: 1
mode: box
range:
min: 0
max: 22000

- name: "EV Charger Requested Power Limit"
description: "Charging power limit currently requested by the inverter and sent to the EV charger. Dynamically adjusted based on available solar power in Solar Only mode, or set to the configured maximum in Free Work mode."
update_interval: 5
class: power
state_class: measurement
uom: "W"
scale: 1
rule: 1
registers: [0x02C5]
icon: "mdi:ev-station"

- group: Battery 1
via_device: "inverter"
Expand Down
11 changes: 10 additions & 1 deletion custom_components/solarman/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def __init__(self, coordinator, sensor):
if "offset" in sensor:
self.offset = get_number(sensor["offset"])

self.mask = sensor.get("mask")
self.divide = sensor.get("divide")

if "configurable" in sensor and (configurable := sensor["configurable"]):
if "mode" in configurable:
self._attr_mode = configurable["mode"]
Expand All @@ -65,4 +68,10 @@ async def async_set_native_value(self, value: float) -> None:
value_int = int(value if self.scale is None else value / self.scale)
if self.offset is not None:
value_int += self.offset
await self.write(value_int if value_int < 0xFFFF else 0xFFFF, get_number(value))
if self.divide is not None:
value_int *= self.divide
if self.mask is not None:
current = await self.coordinator.device.execute(self.code_read, self.register, count = 1)
current_raw = current[0] if current else 0
value_int = (current_raw & ~self.mask) | (value_int & self.mask)
await self.write(value_int if value_int <= 0xFFFF else 0xFFFF, get_number(value))
12 changes: 9 additions & 3 deletions custom_components/solarman/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ class SolarmanSelectEntity(SolarmanWritableEntity, SelectEntity):
def __init__(self, coordinator, sensor):
SolarmanWritableEntity.__init__(self, coordinator, sensor)

self.mask = display.get("mask") if (display := sensor.get("display")) else None
display = sensor.get("display")
self.mask = (display.get("mask") if display else None) or sensor.get("mask")

if "lookup" in sensor:
self.dictionary = sensor["lookup"]
Expand All @@ -126,7 +127,7 @@ def get_key(self, value: str):
if self.dictionary:
for o in self.dictionary:
if o["value"] == value and (key := from_bit_index(o["bit"]) if "bit" in o else o["key"]) is not None:
return (key if not "mode" in o else (self._attr_value | key)) if not self.mask else (self._attr_value & (0xFFFFFFFF - self.mask) | key)
return key if self.mask else (key if not "mode" in o else (self._attr_value | key))

return self.options.index(value)

Expand All @@ -141,4 +142,9 @@ def current_option(self):

async def async_select_option(self, option: str):
"""Change the selected option."""
await self.write(self.get_key(option), option)
key = self.get_key(option)
if self.mask is not None:
current = await self.coordinator.device.execute(self.code_read, self.register, count = 1)
current_raw = current[0] if current else 0
key = (current_raw & ~self.mask) | (key & self.mask)
await self.write(key, option)