Skip to content

Commit

Permalink
Merge pull request #5 from Pho3niX90/feature/storage_mode
Browse files Browse the repository at this point in the history
Feature/storage mode
  • Loading branch information
Pho3niX90 authored Dec 4, 2023
2 parents d6584da + 94f9bd7 commit b2149fa
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 30 deletions.
4 changes: 3 additions & 1 deletion custom_components/solis_modbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@

import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant, ServiceCall

from .const import DOMAIN, CONTROLLER
from .modbus_controller import ModbusController

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["sensor", "number"]
PLATFORMS = [Platform.SENSOR, Platform.NUMBER, Platform.SWITCH]

SCHEME_HOLDING_REGISTER = vol.Schema(
{
Expand All @@ -23,6 +24,7 @@

async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Modbus integration."""

# Check if there are any configurations in the YAML file
# if DOMAIN in config:
# hass.async_create_task(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/solis_modbus/const.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DOMAIN = "solis_modbus"
CONTROLLER = "modbus_controller"
VERSION = "1.0.3"
VERSION = "1.0.5"
POLL_INTERVAL_SECONDS = 5
4 changes: 2 additions & 2 deletions custom_components/solis_modbus/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"domain": "solis_modbus",
"name": "Solis Modbus",
"version": "1.0.3",
"documentation": "https://link-to-your-docs",
"version": "1.0.5",
"documentation": "https://github.com/Pho3niX90",
"dependencies": [],
"codeowners": ["@pho3nix90"],
"iot_class": "local_polling",
Expand Down
12 changes: 4 additions & 8 deletions custom_components/solis_modbus/modbus_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,21 @@ def read_input_register(self, register, count=1):
if result.isError():
raise ValueError(f"Failed to read Modbus register ({register}): {result}")
_LOGGER.debug(f'register value, register = {register}, result = {result.registers}')
if count > 1:
return result.registers
return result.registers[0]
return result.registers
except ModbusIOException as e:
raise ValueError(f"Failed to read Modbus register: {str(e)}")

def read_holding_register(self, register, count=1):
def read_holding_register(self, register: int, count=1):
try:
result = self.client.read_holding_registers(register, count, slave=1)
if result.isError():
raise ValueError(f"Failed to read Modbus register ({register}): {result}")
_LOGGER.debug(f'holding register value, register = {register}, result = {result.registers}')
if count > 1:
return result.registers
return result.registers[0]
return result.registers
except ModbusIOException as e:
raise ValueError(f"Failed to read Modbus holding register: {str(e)}")

def write_holding_register(self, register, value):
def write_holding_register(self, register: int, value):
try:
result = self.client.write_register(register, value, slave=1)
if result.isError():
Expand Down
23 changes: 11 additions & 12 deletions custom_components/solis_modbus/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from homeassistant.components.sensor import SensorDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
UnitOfElectricCurrent,
UnitOfElectricCurrent, PERCENTAGE,
)
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
Expand All @@ -26,22 +26,13 @@

_LOGGER = logging.getLogger(__name__)

hyphen = ""
nameID_midfix = ""
entityID_midfix = ""


async def async_setup_entry(hass, config_entry: ConfigEntry, async_add_devices):
"""Set up the number platform."""
modbus_controller = hass.data[DOMAIN][CONTROLLER]
# We only want this platform to be set up via discovery.
_LOGGER.info("Loading the Lux number platform")
_LOGGER.info("Options %s", len(config_entry.options))

global hyphen
global nameID_midfix
global entityID_midfix

platform_config = config_entry.data or {}
if len(config_entry.options) > 0:
platform_config = config_entry.options
Expand All @@ -61,6 +52,14 @@ async def async_setup_entry(hass, config_entry: ConfigEntry, async_add_devices):
"default": 50.0, "multiplier": 10,
"min_val": 0, "max_val": 100, "step": 0.1, "device_class": SensorDeviceClass.CURRENT,
"unit_of_measurement": UnitOfElectricCurrent.AMPERE, "enabled": True},
{"type": "SNE", "name": "Solis Inverter Backup SOC", "register": 43024,
"default": 80.0, "multiplier": 1,
"min_val": 0, "max_val": 100, "step": 1,
"unit_of_measurement": PERCENTAGE, "enabled": True},
# {"type": "SNE", "name": "Solis Inverter Storage Control Switch Value", "register": 43110,
# "default": 80.0, "multiplier": 1,
# "min_val": 0, "max_val": 100, "step": 1,
# "unit_of_measurement": PERCENTAGE, "enabled": True},
]

for entity_definition in numbers:
Expand Down Expand Up @@ -125,7 +124,7 @@ def update(self):

if value == 0:
_LOGGER.debug(f'got 0 for register {self._register}, forcing update')
value = controller.read_holding_register(self._register)
value = controller.read_holding_register(self._register)[0]

_LOGGER.debug(f'Update number entity with value = {value / self._multiplier}')

Expand All @@ -147,7 +146,7 @@ def set_native_value(self, value):
if self._attr_native_value == value:
return

_LOGGER.debug(
_LOGGER.warning(
f'Writing value to holding register = {self._register}, value = {value}, value modified = {value * self._multiplier}')
self._modbus_controller.write_holding_register(self._register, round(value * self._multiplier))
self._attr_native_value = value
Expand Down
44 changes: 38 additions & 6 deletions custom_components/solis_modbus/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from homeassistant.components.sensor import SensorEntity, RestoreSensor
from homeassistant.components.sensor.const import SensorDeviceClass, SensorStateClass
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfFrequency, UnitOfTemperature, \
UnitOfElectricPotential, UnitOfElectricCurrent, UnitOfPower, \
Expand Down Expand Up @@ -203,7 +204,6 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
"register": ['33132'],
"decimal_places": 0,
"state_class": SensorStateClass.MEASUREMENT},

{"type": "SS", "name": "Solis Inverter Battery Voltage",
"unique": "solis_modbus_inverter_battery_voltage",
"register": ['33133'], "device_class": SensorDeviceClass.VOLTAGE,
Expand Down Expand Up @@ -471,6 +471,27 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
"state_class": SensorStateClass.MEASUREMENT}
]
},
{
"register_start": 43024,
"entities": [
{"type": "SS", "name": "Solis Inverter Backup SOC",
"unique": "solis_modbus_inverter_backup_soc",
"register": ['43024'],
"decimal_places": 0,
"unit_of_measurement": PERCENTAGE,
"state_class": SensorStateClass.MEASUREMENT}
]
},
# {
# "register_start": 43110,
# "entities": [
# {"type": "SS", "name": "Solis Inverter Storage Control Switch Value",
# "unique": "solis_modbus_inverter_storage_control_switch_value",
# "register": ['43110'],
# "decimal_places": 0,
# "state_class": SensorStateClass.MEASUREMENT}
# ]
# },
{
"register_start": 43141,
"entities": [
Expand All @@ -489,9 +510,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
]
}
]
sensorsDerived = [{"type": "SDS", "name": "Solis Inverter Current Status String",
"unique": "solis_modbus_inverter_current_status_string", "decimal_places": 0,
"register": ['33095']}]
sensorsDerived = [
{"type": "SDS", "name": "Solis Inverter Current Status String",
"unique": "solis_modbus_inverter_current_status_string", "decimal_places": 0,
"register": ['33095']},
# {
# "type": "SDS", "name": "Solis Inverter Current Status String",
# "unique": "solis_modbus_inverter_current_status_string", "decimal_places": 0,
# "register": ['43110']
# }
]

for sensor_group in sensors:
for entity_definition in sensor_group['entities']:
Expand All @@ -515,6 +543,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, asyn
def async_update(now):
"""Update Modbus data periodically."""
controller = hass.data[DOMAIN][CONTROLLER]

if not controller.connected():
controller.connect()

for sensor_group in sensors:
start_register = sensor_group['register_start']
count = sum(len(entity.get('register', [])) for entity in sensor_group.get('entities', []))
Expand Down Expand Up @@ -566,6 +598,8 @@ def __init__(self, hass, entity_definition):
self._attr_name = entity_definition["name"]
self._attr_unique_id = "{}_{}".format(DOMAIN, entity_definition["unique"])

self._device_class = SwitchDeviceClass.SWITCH

self._register: List[int] = entity_definition["register"]
self._state = None
self._unit_of_measurement = entity_definition.get("unit_of_measurement", None)
Expand Down Expand Up @@ -666,8 +700,6 @@ def update(self):
try:
if not self.is_added_to_hass:
return
if not self._modbus_controller.connected():
self._modbus_controller.connect()

n_value = get_value(self)

Expand Down
153 changes: 153 additions & 0 deletions custom_components/solis_modbus/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import logging
from datetime import timedelta
from typing import List, Any

from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.event import async_track_time_interval

from custom_components.solis_modbus.const import POLL_INTERVAL_SECONDS, DOMAIN, CONTROLLER, VERSION

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass, config_entry: ConfigEntry, async_add_devices):
modbus_controller = hass.data[DOMAIN][CONTROLLER]

switch_sensors = [
{
"read_register": 33132, 'write_register': 43110,
"entities": [
{"type": "SBS", "bit_position": 0, "name": "Solis Inverter Self-Use Mode"},
{"type": "SBS", "bit_position": 1, "name": "Solis Inverter Time Of Use Mode"},
{"type": "SBS", "bit_position": 2, "name": "Solis Inverter OFF-Grid Mode"},
{"type": "SBS", "bit_position": 3, "name": "Solis Inverter Battery Wakeup Switch"},
{"type": "SBS", "bit_position": 4, "name": "Solis Inverter Reserve Battery Mode"},
{"type": "SBS", "bit_position": 5, "name": "Solis Inverter Allow Grid To Charge The Battery"},
{"type": "SBS", "bit_position": 6, "name": "Solis Inverter Feed In Priority Mode"},
]
}
]

switchEntities: List[SolisBinaryEntity] = []

for main_entity in switch_sensors:
for child_entity in main_entity['entities']:
type = child_entity["type"]
if type == "SBS":
child_entity['read_register'] = main_entity['read_register']
child_entity['write_register'] = main_entity['write_register']
switchEntities.append(SolisBinaryEntity(hass, modbus_controller, child_entity))

hass.data[DOMAIN]['switch_entities'] = switchEntities
async_add_devices(switchEntities, True)

@callback
def async_update(now):
"""Update Modbus data periodically."""
# for entity in hass.data[DOMAIN]["switch_entities"]:
# entity.update()
# Schedule the update function to run every X seconds

async_track_time_interval(hass, async_update, timedelta(seconds=POLL_INTERVAL_SECONDS * 5))

return True


class SolisBinaryEntity(SwitchEntity):

def __init__(self, hass, modbus_controller, entity_definition):
self._hass = hass
self._modbus_controller = modbus_controller
self._read_register: int = entity_definition["read_register"]
self._write_register: int = entity_definition["write_register"]
self._bit_position = entity_definition["bit_position"]
self._attr_unique_id = "{}_{}_{}_{}".format(DOMAIN, self._modbus_controller.host, self._read_register,
self._bit_position)
self._attr_name = entity_definition["name"]
self._attr_available = False
self._attr_is_on = None

async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
_LOGGER.debug(f"async_added_to_hass {self._attr_name}")

def update(self):
"""Update Modbus data periodically."""
value: int = self._hass.data[DOMAIN]['values'][str(self._read_register)]

initial_state = self._attr_is_on
if not self._attr_available:
self._attr_available = True
self._attr_is_on = get_bool(value, self._bit_position)

if initial_state != self._attr_is_on:
_LOGGER.debug(
f'state change for {self._read_register}-{self._bit_position} from {initial_state} to {self._attr_is_on}')
return self._attr_is_on

@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._attr_is_on

def turn_on(self, **kwargs: Any) -> None:
_LOGGER.debug(f"{self._read_register}-{self._bit_position} turn on called ")
self.set_register_bit(True)

def turn_off(self, **kwargs: Any) -> None:
_LOGGER.debug(f"{self._read_register}-{self._bit_position} turn off called ")
self.set_register_bit(False)

def set_register_bit(self, value):
"""Set or clear a specific bit in the Modbus register."""
controller = self._hass.data[DOMAIN][CONTROLLER]
current_register_value: int = self._hass.data[DOMAIN]['values'][str(self._read_register)]
new_register_value: int = set_bit(current_register_value, self._bit_position, value)

_LOGGER.debug(
f"Attempting bit {self._bit_position} to {value} in register {self._read_register}. New value for register {new_register_value}")
# we only want to write when values has changed. After, we read the register again to make sure it applied.
if current_register_value != new_register_value:
controller.write_holding_register(self._write_register, new_register_value)
self._hass.data[DOMAIN]['values'][str(self._read_register)] = new_register_value

self._attr_is_on = value

self._attr_available = True

@property
def device_info(self):
"""Return device info."""
return DeviceInfo(
identifiers={(DOMAIN, self._hass.data[DOMAIN][CONTROLLER].host)},
manufacturer="Solis",
model="Solis S6",
name="Solis S6",
sw_version=VERSION,
)


def set_bit(value, bit_position, new_bit_value):
"""Set or clear a specific bit in an integer value."""
mask = 1 << bit_position
value &= ~mask # Clear the bit
if new_bit_value:
value |= mask # Set the bit
return round(value)


def get_bool(modbus_value, bit_position):
"""
Decode Modbus value to boolean state for the specified bit position.
Parameters:
- modbus_value: The Modbus value to decode.
- bit_position: The position of the bit to extract (0-based).
Returns:
- True if the bit is ON, False if the bit is OFF.
"""
# Check if the bit is ON by shifting 1 to the specified position and performing bitwise AND
return (modbus_value >> bit_position) & 1 == 1

0 comments on commit b2149fa

Please sign in to comment.