Skip to content

Commit d4296b1

Browse files
Merge pull request #535 from Dummy0815/service_extension
Service extension
2 parents d5cf155 + 4e83620 commit d4296b1

File tree

5 files changed

+350
-80
lines changed

5 files changed

+350
-80
lines changed

custom_components/solarman/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@
99
"issue_tracker": "https://github.com/StephanJoubert/home_assistant_solarman/issues",
1010
"requirements": ["pyyaml","pysolarmanv5"],
1111
"version": "1.0.0"
12-
}
12+
}

custom_components/solarman/sensor.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _do_setup_platform(hass: HomeAssistant, config, async_add_entities : AddEnti
3838
inverter_sn = config.get(CONF_INVERTER_SERIAL)
3939
if inverter_sn == 0:
4040
inverter_sn = _inverter_scanner.get_serialno()
41-
41+
4242
inverter_mb_slaveid = config.get(CONF_INVERTER_MB_SLAVEID)
4343
if not inverter_mb_slaveid:
4444
inverter_mb_slaveid = DEFAULT_INVERTER_MB_SLAVEID
@@ -70,10 +70,10 @@ def _do_setup_platform(hass: HomeAssistant, config, async_add_entities : AddEnti
7070
_LOGGER.debug(hass_sensors)
7171

7272
async_add_entities(hass_sensors)
73-
# Register the services with home assistant.
74-
register_services (hass, inverter)
75-
76-
73+
# Register the services with home assistant.
74+
register_services (hass)
75+
76+
7777

7878

7979

@@ -156,6 +156,10 @@ def unique_id(self):
156156
def state(self):
157157
# Return the state of the sensor.
158158
return self.p_state
159+
160+
def inverter(self):
161+
# Return the inverter of the sensor. """
162+
return self.inverter
159163

160164
def update(self):
161165
self.p_state = getattr(self.inverter, self._field_name, None)
Lines changed: 150 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,190 @@
1-
from homeassistant.core import HomeAssistant
2-
from homeassistant.helpers import config_validation as cv
1+
from homeassistant.core import HomeAssistant, SupportsResponse
2+
from homeassistant.helpers import config_validation as cv, entity_registry, entity
3+
from homeassistant.helpers.entity_component import EntityComponent
4+
from homeassistant.exceptions import ServiceValidationError
35
import voluptuous as vol
46
from .const import *
57
from .solarman import Inverter
8+
import logging
69

10+
log = logging.getLogger(__name__)
711

8-
SERVICE_WRITE_REGISTER = 'write_holding_register'
9-
SERVICE_WRITE_MULTIPLE_REGISTERS = 'write_multiple_holding_registers'
12+
SERVICE_READ_HOLDING_REGISTER = 'read_holding_register'
13+
SERVICE_READ_MULTIPLE_HOLDING_REGISTERS = 'read_multiple_holding_registers'
14+
SERVICE_WRITE_HOLDING_REGISTER = 'write_holding_register'
15+
SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS = 'write_multiple_holding_registers'
16+
PARAM_DEVICE = 'device'
1017
PARAM_REGISTER = 'register'
11-
PARAM_VALUE = 'value'
12-
PARAM_VALUES = 'values'
13-
18+
PARAM_COUNT = 'count'
19+
PARAM_VALUE = 'value'
20+
PARAM_VALUES = 'values'
1421

1522

1623
# Register the services one can invoke on the inverter.
1724
# Apart from this, it also need to be defined in the file
1825
# services.yaml for the Home Assistant UI in "Developer Tools"
1926

27+
SERVICE_READ_REGISTER_SCHEMA = vol.Schema(
28+
{
29+
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
30+
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))
31+
}
32+
)
33+
34+
SERVICE_READ_MULTIPLE_REGISTERS_SCHEMA = vol.Schema(
35+
{
36+
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
37+
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
38+
vol.Required(PARAM_COUNT): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))
39+
}
40+
)
2041

2142
SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema(
2243
{
44+
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
2345
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
2446
vol.Required(PARAM_VALUE): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
2547
}
2648
)
2749

2850
SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA = vol.Schema(
2951
{
52+
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
3053
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
3154
vol.Required(PARAM_VALUES): vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))]),
3255
}
3356
)
3457

35-
def register_services (hass: HomeAssistant, inverter: Inverter ):
58+
def register_services (hass: HomeAssistant ):
59+
60+
def getInverter(device_id):
61+
inverter: Inverter | None
62+
entity_comp: EntityComponent[entity.Entity] | None
63+
registry = entity_registry.async_get(hass)
64+
entries = entity_registry.async_entries_for_device(registry, device_id)
65+
for entity_reg in entries:
66+
entity_id = entity_reg.entity_id
67+
domain = entity_id.partition(".")[0]
68+
entity_comp = hass.data.get("entity_components", {}).get(domain)
69+
if entity_comp is None:
70+
log.info(f'read_holding_register: Component for {entity_id} not loaded')
71+
continue
72+
73+
if (entity_obj := entity_comp.get_entity(entity_id)) is None:
74+
log.info(f'read_holding_register: Entity {entity_id} not found')
75+
continue
76+
77+
if (inverter := entity_obj.inverter) is None:
78+
log.info(f'read_holding_register: Entity {entity_id} has no inverter')
79+
continue
80+
81+
break
82+
83+
return inverter
84+
85+
86+
async def read_holding_register(call) -> int:
87+
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
88+
raise ServiceValidationError(
89+
"No communication interface for device found",
90+
translation_domain=DOMAIN,
91+
translation_key="no_interface_found"
92+
)
93+
94+
try:
95+
response = inverter.service_read_holding_register( register=call.data.get(PARAM_REGISTER) )
96+
except Exception as e:
97+
raise ServiceValidationError(
98+
e,
99+
translation_domain=DOMAIN,
100+
translation_key="call_failed"
101+
)
102+
103+
result = {call.data.get(PARAM_REGISTER): response[0]}
104+
return result
105+
106+
async def read_multiple_holding_registers(call) -> int:
107+
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
108+
raise ServiceValidationError(
109+
"No communication interface for device found",
110+
translation_domain=DOMAIN,
111+
translation_key="no_interface_found"
112+
)
113+
114+
try:
115+
response = inverter.service_read_multiple_holding_registers(
116+
register=call.data.get(PARAM_REGISTER),
117+
count=call.data.get(PARAM_COUNT) )
118+
except Exception as e:
119+
raise ServiceValidationError(
120+
e,
121+
translation_domain=DOMAIN,
122+
translation_key="call_failed"
123+
)
124+
125+
result = {}
126+
register=call.data.get(PARAM_REGISTER)
127+
for i in range(0,call.data.get(PARAM_COUNT)):
128+
result[register+i] = response[i]
129+
return result
36130

37131
async def write_holding_register(call) -> None:
38-
inverter.service_write_holding_register(
39-
register=call.data.get(PARAM_REGISTER),
40-
value=call.data.get(PARAM_VALUE))
132+
log.debug(f'write_holding_register: call={call}')
133+
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
134+
raise ServiceValidationError(
135+
"No communication interface for device found",
136+
translation_domain=DOMAIN,
137+
translation_key="no_interface_found",
138+
)
139+
140+
try:
141+
inverter.service_write_holding_register(
142+
register=call.data.get(PARAM_REGISTER),
143+
value=call.data.get(PARAM_VALUE))
144+
except Exception as e:
145+
raise ServiceValidationError(
146+
e,
147+
translation_domain=DOMAIN,
148+
translation_key="call_failed"
149+
)
150+
41151
return
42152

43153
async def write_multiple_holding_registers(call) -> None:
44-
inverter.service_write_multiple_holding_registers(
45-
register=call.data.get(PARAM_REGISTER),
46-
values=call.data.get(PARAM_VALUES))
154+
log.debug(f'write_holding_register: call={call}')
155+
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
156+
raise ServiceValidationError(
157+
"No communication interface for device found",
158+
translation_domain=DOMAIN,
159+
translation_key="no_interface_found",
160+
)
161+
162+
try:
163+
inverter.service_write_multiple_holding_registers(
164+
register=call.data.get(PARAM_REGISTER),
165+
values=call.data.get(PARAM_VALUES))
166+
except Exception as e:
167+
raise ServiceValidationError(
168+
e,
169+
translation_domain=DOMAIN,
170+
translation_key="call_failed"
171+
)
172+
47173
return
48174

49175
hass.services.async_register(
50-
DOMAIN, SERVICE_WRITE_REGISTER, write_holding_register, schema=SERVICE_WRITE_REGISTER_SCHEMA
176+
DOMAIN, SERVICE_READ_HOLDING_REGISTER, read_holding_register, schema=SERVICE_READ_REGISTER_SCHEMA, supports_response=SupportsResponse.OPTIONAL
177+
)
178+
179+
hass.services.async_register(
180+
DOMAIN, SERVICE_READ_MULTIPLE_HOLDING_REGISTERS, read_multiple_holding_registers, schema=SERVICE_READ_MULTIPLE_REGISTERS_SCHEMA, supports_response=SupportsResponse.OPTIONAL
181+
)
182+
183+
hass.services.async_register(
184+
DOMAIN, SERVICE_WRITE_HOLDING_REGISTER, write_holding_register, schema=SERVICE_WRITE_REGISTER_SCHEMA
51185
)
52186

53187
hass.services.async_register(
54-
DOMAIN, SERVICE_WRITE_MULTIPLE_REGISTERS, write_multiple_holding_registers, schema=SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA
188+
DOMAIN, SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS, write_multiple_holding_registers, schema=SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA
55189
)
56190
return

custom_components/solarman/services.yaml

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,77 @@
1+
read_holding_register:
2+
name: Read Holding Register (Modbus Function Code 3)
3+
description: Read a single register value
4+
5+
fields:
6+
device:
7+
name: Device
8+
description: The Device
9+
example: "inverter_roof"
10+
required: true
11+
selector:
12+
device:
13+
filter:
14+
- integration: solarman
15+
register:
16+
name: Register
17+
description: Modbus register address
18+
example: "16384"
19+
required: true
20+
selector:
21+
number:
22+
min: 0
23+
max: 65535
24+
mode: box
25+
26+
read_multiple_holding_registers:
27+
name: Read Multiple Holding Registers (Modbus Function Code 3)
28+
description: Read values from multiple consecutive registers at once.
29+
30+
fields:
31+
device:
32+
name: Device
33+
description: The Device
34+
example: "inverter_roof"
35+
required: true
36+
selector:
37+
device:
38+
filter:
39+
- integration: solarman
40+
register:
41+
name: Register
42+
description: Modbus register address
43+
example: "16384"
44+
required: true
45+
selector:
46+
number:
47+
min: 0
48+
max: 65535
49+
mode: box
50+
count:
51+
name: Count
52+
description: Count of registers to read
53+
example: "4"
54+
required: true
55+
selector:
56+
number:
57+
min: 1
58+
max: 65535
59+
mode: box
60+
161
write_holding_register:
262
name: Write Holding Register (Modbus Function Code 6)
3-
description: NOTE USE WITH CARE!
63+
description: NOTE USE WITH CARE! (Some devices might not accept Code 6 in this case try to use 'Write Multiple Holding Registers')
464

565
fields:
66+
device:
67+
name: Device
68+
description: The Device
69+
example: "inverter_roof"
70+
required: true
71+
selector:
72+
device:
73+
filter:
74+
- integration: solarman
675
register:
776
name: Register
877
description: Modbus register address
@@ -13,7 +82,6 @@ write_holding_register:
1382
min: 0
1483
max: 65535
1584
mode: box
16-
1785
value:
1886
name: Values
1987
description: Value to write
@@ -25,9 +93,18 @@ write_holding_register:
2593

2694
write_multiple_holding_registers:
2795
name: Write Multiple Holding Registers (Modbus Function Code 16)
28-
description: NOTE USE WITH CARE!
96+
description: NOTE USE WITH CARE! (Some devices might not accept Code 16 in this case try to use 'Write Holding Register')
2997

3098
fields:
99+
device:
100+
name: Device
101+
description: The Device
102+
example: "inverter_roof"
103+
required: true
104+
selector:
105+
device:
106+
filter:
107+
- integration: solarman
31108
register:
32109
name: Register
33110
description: Modbus register address
@@ -38,7 +115,6 @@ write_multiple_holding_registers:
38115
min: 0
39116
max: 65535
40117
mode: box
41-
42118
values:
43119
name: Values
44120
description: Values to write

0 commit comments

Comments
 (0)