Skip to content

Commit

Permalink
Merge pull request #535 from Dummy0815/service_extension
Browse files Browse the repository at this point in the history
Service extension
  • Loading branch information
StephanJoubert authored Jul 5, 2024
2 parents d5cf155 + 4e83620 commit d4296b1
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 80 deletions.
2 changes: 1 addition & 1 deletion custom_components/solarman/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
"issue_tracker": "https://github.com/StephanJoubert/home_assistant_solarman/issues",
"requirements": ["pyyaml","pysolarmanv5"],
"version": "1.0.0"
}
}
14 changes: 9 additions & 5 deletions custom_components/solarman/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _do_setup_platform(hass: HomeAssistant, config, async_add_entities : AddEnti
inverter_sn = config.get(CONF_INVERTER_SERIAL)
if inverter_sn == 0:
inverter_sn = _inverter_scanner.get_serialno()

inverter_mb_slaveid = config.get(CONF_INVERTER_MB_SLAVEID)
if not inverter_mb_slaveid:
inverter_mb_slaveid = DEFAULT_INVERTER_MB_SLAVEID
Expand Down Expand Up @@ -70,10 +70,10 @@ def _do_setup_platform(hass: HomeAssistant, config, async_add_entities : AddEnti
_LOGGER.debug(hass_sensors)

async_add_entities(hass_sensors)
# Register the services with home assistant.
register_services (hass, inverter)
# Register the services with home assistant.
register_services (hass)





Expand Down Expand Up @@ -156,6 +156,10 @@ def unique_id(self):
def state(self):
# Return the state of the sensor.
return self.p_state

def inverter(self):
# Return the inverter of the sensor. """
return self.inverter

def update(self):
self.p_state = getattr(self.inverter, self._field_name, None)
Expand Down
166 changes: 150 additions & 16 deletions custom_components/solarman/services.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,190 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.helpers import config_validation as cv, entity_registry, entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.exceptions import ServiceValidationError
import voluptuous as vol
from .const import *
from .solarman import Inverter
import logging

log = logging.getLogger(__name__)

SERVICE_WRITE_REGISTER = 'write_holding_register'
SERVICE_WRITE_MULTIPLE_REGISTERS = 'write_multiple_holding_registers'
SERVICE_READ_HOLDING_REGISTER = 'read_holding_register'
SERVICE_READ_MULTIPLE_HOLDING_REGISTERS = 'read_multiple_holding_registers'
SERVICE_WRITE_HOLDING_REGISTER = 'write_holding_register'
SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS = 'write_multiple_holding_registers'
PARAM_DEVICE = 'device'
PARAM_REGISTER = 'register'
PARAM_VALUE = 'value'
PARAM_VALUES = 'values'

PARAM_COUNT = 'count'
PARAM_VALUE = 'value'
PARAM_VALUES = 'values'


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

SERVICE_READ_REGISTER_SCHEMA = vol.Schema(
{
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))
}
)

SERVICE_READ_MULTIPLE_REGISTERS_SCHEMA = vol.Schema(
{
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
vol.Required(PARAM_COUNT): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))
}
)

SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema(
{
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
vol.Required(PARAM_VALUE): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
}
)

SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA = vol.Schema(
{
vol.Required(PARAM_DEVICE): vol.All(vol.Coerce(str)),
vol.Required(PARAM_REGISTER): vol.All(vol.Coerce(int), vol.Range(min=0, max=65535)),
vol.Required(PARAM_VALUES): vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(min=0, max=65535))]),
}
)

def register_services (hass: HomeAssistant, inverter: Inverter ):
def register_services (hass: HomeAssistant ):

def getInverter(device_id):
inverter: Inverter | None
entity_comp: EntityComponent[entity.Entity] | None
registry = entity_registry.async_get(hass)
entries = entity_registry.async_entries_for_device(registry, device_id)
for entity_reg in entries:
entity_id = entity_reg.entity_id
domain = entity_id.partition(".")[0]
entity_comp = hass.data.get("entity_components", {}).get(domain)
if entity_comp is None:
log.info(f'read_holding_register: Component for {entity_id} not loaded')
continue

if (entity_obj := entity_comp.get_entity(entity_id)) is None:
log.info(f'read_holding_register: Entity {entity_id} not found')
continue

if (inverter := entity_obj.inverter) is None:
log.info(f'read_holding_register: Entity {entity_id} has no inverter')
continue

break

return inverter


async def read_holding_register(call) -> int:
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
raise ServiceValidationError(
"No communication interface for device found",
translation_domain=DOMAIN,
translation_key="no_interface_found"
)

try:
response = inverter.service_read_holding_register( register=call.data.get(PARAM_REGISTER) )
except Exception as e:
raise ServiceValidationError(
e,
translation_domain=DOMAIN,
translation_key="call_failed"
)

result = {call.data.get(PARAM_REGISTER): response[0]}
return result

async def read_multiple_holding_registers(call) -> int:
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
raise ServiceValidationError(
"No communication interface for device found",
translation_domain=DOMAIN,
translation_key="no_interface_found"
)

try:
response = inverter.service_read_multiple_holding_registers(
register=call.data.get(PARAM_REGISTER),
count=call.data.get(PARAM_COUNT) )
except Exception as e:
raise ServiceValidationError(
e,
translation_domain=DOMAIN,
translation_key="call_failed"
)

result = {}
register=call.data.get(PARAM_REGISTER)
for i in range(0,call.data.get(PARAM_COUNT)):
result[register+i] = response[i]
return result

async def write_holding_register(call) -> None:
inverter.service_write_holding_register(
register=call.data.get(PARAM_REGISTER),
value=call.data.get(PARAM_VALUE))
log.debug(f'write_holding_register: call={call}')
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
raise ServiceValidationError(
"No communication interface for device found",
translation_domain=DOMAIN,
translation_key="no_interface_found",
)

try:
inverter.service_write_holding_register(
register=call.data.get(PARAM_REGISTER),
value=call.data.get(PARAM_VALUE))
except Exception as e:
raise ServiceValidationError(
e,
translation_domain=DOMAIN,
translation_key="call_failed"
)

return

async def write_multiple_holding_registers(call) -> None:
inverter.service_write_multiple_holding_registers(
register=call.data.get(PARAM_REGISTER),
values=call.data.get(PARAM_VALUES))
log.debug(f'write_holding_register: call={call}')
if (inverter := getInverter(call.data.get(PARAM_DEVICE))) is None:
raise ServiceValidationError(
"No communication interface for device found",
translation_domain=DOMAIN,
translation_key="no_interface_found",
)

try:
inverter.service_write_multiple_holding_registers(
register=call.data.get(PARAM_REGISTER),
values=call.data.get(PARAM_VALUES))
except Exception as e:
raise ServiceValidationError(
e,
translation_domain=DOMAIN,
translation_key="call_failed"
)

return

hass.services.async_register(
DOMAIN, SERVICE_WRITE_REGISTER, write_holding_register, schema=SERVICE_WRITE_REGISTER_SCHEMA
DOMAIN, SERVICE_READ_HOLDING_REGISTER, read_holding_register, schema=SERVICE_READ_REGISTER_SCHEMA, supports_response=SupportsResponse.OPTIONAL
)

hass.services.async_register(
DOMAIN, SERVICE_READ_MULTIPLE_HOLDING_REGISTERS, read_multiple_holding_registers, schema=SERVICE_READ_MULTIPLE_REGISTERS_SCHEMA, supports_response=SupportsResponse.OPTIONAL
)

hass.services.async_register(
DOMAIN, SERVICE_WRITE_HOLDING_REGISTER, write_holding_register, schema=SERVICE_WRITE_REGISTER_SCHEMA
)

hass.services.async_register(
DOMAIN, SERVICE_WRITE_MULTIPLE_REGISTERS, write_multiple_holding_registers, schema=SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA
DOMAIN, SERVICE_WRITE_MULTIPLE_HOLDING_REGISTERS, write_multiple_holding_registers, schema=SERVICE_WRITE_MULTIPLE_REGISTERS_SCHEMA
)
return
84 changes: 80 additions & 4 deletions custom_components/solarman/services.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,77 @@
read_holding_register:
name: Read Holding Register (Modbus Function Code 3)
description: Read a single register value

fields:
device:
name: Device
description: The Device
example: "inverter_roof"
required: true
selector:
device:
filter:
- integration: solarman
register:
name: Register
description: Modbus register address
example: "16384"
required: true
selector:
number:
min: 0
max: 65535
mode: box

read_multiple_holding_registers:
name: Read Multiple Holding Registers (Modbus Function Code 3)
description: Read values from multiple consecutive registers at once.

fields:
device:
name: Device
description: The Device
example: "inverter_roof"
required: true
selector:
device:
filter:
- integration: solarman
register:
name: Register
description: Modbus register address
example: "16384"
required: true
selector:
number:
min: 0
max: 65535
mode: box
count:
name: Count
description: Count of registers to read
example: "4"
required: true
selector:
number:
min: 1
max: 65535
mode: box

write_holding_register:
name: Write Holding Register (Modbus Function Code 6)
description: NOTE USE WITH CARE!
description: NOTE USE WITH CARE! (Some devices might not accept Code 6 in this case try to use 'Write Multiple Holding Registers')

fields:
device:
name: Device
description: The Device
example: "inverter_roof"
required: true
selector:
device:
filter:
- integration: solarman
register:
name: Register
description: Modbus register address
Expand All @@ -13,7 +82,6 @@ write_holding_register:
min: 0
max: 65535
mode: box

value:
name: Values
description: Value to write
Expand All @@ -25,9 +93,18 @@ write_holding_register:

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

fields:
device:
name: Device
description: The Device
example: "inverter_roof"
required: true
selector:
device:
filter:
- integration: solarman
register:
name: Register
description: Modbus register address
Expand All @@ -38,7 +115,6 @@ write_multiple_holding_registers:
min: 0
max: 65535
mode: box

values:
name: Values
description: Values to write
Expand Down
Loading

0 comments on commit d4296b1

Please sign in to comment.