Skip to content

Commit be8f3a4

Browse files
Add sensors for batteries
1 parent 710385a commit be8f3a4

File tree

2 files changed

+339
-3
lines changed

2 files changed

+339
-3
lines changed

custom_components/solaredge_modbus_multi/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
SUNSPEC_NOT_IMPL_UINT32 = 0xFFFFFFFF
2727
SUNSPEC_NOT_ACCUM_ACC32 = 0x00000000
2828
SUNSPEC_ACCUM_LIMIT = 4294967295
29+
SUNSPEC_NOT_IMPL_FLOAT32 = 0x7FC00000
2930

3031
SUNSPEC_SF_RANGE = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3132

custom_components/solaredge_modbus_multi/sensor.py

Lines changed: 338 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
from .const import (
2929
DOMAIN,
3030
SUNSPEC_NOT_IMPL_UINT16, SUNSPEC_NOT_IMPL_INT16, SUNSPEC_NOT_IMPL_UINT32,
31-
SUNSPEC_NOT_ACCUM_ACC32, SUNSPEC_ACCUM_LIMIT, SUNSPEC_SF_RANGE,
32-
DEVICE_STATUS, DEVICE_STATUS_DESC,
31+
SUNSPEC_NOT_ACCUM_ACC32, SUNSPEC_ACCUM_LIMIT, SUNSPEC_SF_RANGE, SUNSPEC_NOT_IMPL_FLOAT32,
32+
DEVICE_STATUS, DEVICE_STATUS_DESC, BATTERY_STATUS,
3333
VENDOR_STATUS, SUNSPEC_DID, METER_EVENTS,
3434
ENERGY_VOLT_AMPERE_HOUR, ENERGY_VOLT_AMPERE_REACTIVE_HOUR,
3535
)
@@ -171,10 +171,22 @@ async def async_setup_entry(
171171
entities.append(SolarEdgeDevice(battery, config_entry))
172172
entities.append(Manufacturer(battery, config_entry))
173173
entities.append(Model(battery, config_entry))
174-
entities.append(Version(battery, config_entry))
175174
entities.append(SerialNumber(battery, config_entry))
176175
entities.append(DeviceAddress(battery, config_entry))
177176
entities.append(DeviceAddressParent(battery, config_entry))
177+
entities.append(Version(battery, config_entry))
178+
entities.append(SolarEdgeBatteryAvgTemp(battery, config_entry))
179+
entities.append(SolarEdgeBatteryMaxTemp(battery, config_entry))
180+
entities.append(SolarEdgeBatteryVoltage(battery, config_entry))
181+
entities.append(SolarEdgeBatteryCurrent(battery, config_entry))
182+
entities.append(SolarEdgeBatteryPower(battery, config_entry))
183+
entities.append(SolarEdgeBatteryEnergyExport(battery, config_entry))
184+
entities.append(SolarEdgeBatteryEnergyImport(battery, config_entry))
185+
entities.append(SolarEdgeBatteryMaxEnergy(battery, config_entry))
186+
entities.append(SolarEdgeBatteryAvailableEnergy(battery, config_entry))
187+
entities.append(SolarEdgeBatterySOH(battery, config_entry))
188+
entities.append(SolarEdgeBatterySOE(battery, config_entry))
189+
entities.append(SolarEdgeBatteryStatus(battery, config_entry))
178190

179191
if entities:
180192
async_add_entities(entities)
@@ -1254,3 +1266,326 @@ def native_value(self):
12541266

12551267
except TypeError:
12561268
return None
1269+
1270+
class SolarEdgeBatteryAvgTemp(HeatSinkTemperature):
1271+
@property
1272+
def unique_id(self) -> str:
1273+
return f"{self._platform.model}_{self._platform.serial}_avg_temp"
1274+
1275+
@property
1276+
def name(self) -> str:
1277+
return f"{self._platform._device_info['name']} Average Temperature"
1278+
1279+
@property
1280+
def native_value(self):
1281+
try:
1282+
if (
1283+
self._platform.decoded_model['B_Temp_Average'] == SUNSPEC_NOT_IMPL_FLOAT32
1284+
or self._platform.decoded_model['B_Temp_Average'] == 0xFF7FFFFF
1285+
or self._platform.decoded_model['B_Temp_Average'] == 0x7F7FFFFF
1286+
):
1287+
return None
1288+
1289+
else:
1290+
return round(self._platform.decoded_model['B_Temp_Average'], 1)
1291+
1292+
except TypeError:
1293+
return None
1294+
1295+
class SolarEdgeBatteryMaxTemp(HeatSinkTemperature):
1296+
@property
1297+
def unique_id(self) -> str:
1298+
return f"{self._platform.model}_{self._platform.serial}_max_temp"
1299+
1300+
@property
1301+
def name(self) -> str:
1302+
return f"{self._platform._device_info['name']} Max Temperature"
1303+
1304+
@property
1305+
def native_value(self):
1306+
try:
1307+
if (
1308+
self._platform.decoded_model['B_Temp_Max'] == SUNSPEC_NOT_IMPL_FLOAT32
1309+
or self._platform.decoded_model['B_Temp_Max'] == 0xFF7FFFFF
1310+
or self._platform.decoded_model['B_Temp_Max'] == 0x7F7FFFFF
1311+
):
1312+
return None
1313+
1314+
else:
1315+
return round(self._platform.decoded_model['B_DC_Voltage'], 1)
1316+
1317+
except TypeError:
1318+
return None
1319+
1320+
class SolarEdgeBatteryVoltage(DCVoltage):
1321+
@property
1322+
def native_value(self):
1323+
try:
1324+
if (
1325+
self._platform.decoded_model['B_DC_Voltage'] == SUNSPEC_NOT_IMPL_FLOAT32
1326+
or self._platform.decoded_model['B_DC_Voltage'] == 0xFF7FFFFF
1327+
or self._platform.decoded_model['B_DC_Voltage'] == 0x7F7FFFFF
1328+
):
1329+
return None
1330+
1331+
elif self._platform.decoded_model['B_Status'] in [0]:
1332+
return None
1333+
1334+
else:
1335+
return round(self._platform.decoded_model['B_DC_Voltage'], 2)
1336+
1337+
except TypeError:
1338+
return None
1339+
1340+
class SolarEdgeBatteryCurrent(DCCurrent):
1341+
@property
1342+
def native_value(self):
1343+
try:
1344+
if (
1345+
self._platform.decoded_model['B_DC_Current'] == SUNSPEC_NOT_IMPL_FLOAT32
1346+
or self._platform.decoded_model['B_DC_Current'] == 0xFF7FFFFF
1347+
or self._platform.decoded_model['B_DC_Current'] == 0x7F7FFFFF
1348+
):
1349+
return None
1350+
1351+
elif self._platform.decoded_model['B_Status'] in [0]:
1352+
return None
1353+
1354+
else:
1355+
return round(self._platform.decoded_model['B_DC_Current'], 2)
1356+
1357+
except TypeError:
1358+
return None
1359+
1360+
class SolarEdgeBatteryPower(DCPower):
1361+
icon = 'mdi:lightning-bolt'
1362+
1363+
@property
1364+
def native_value(self):
1365+
try:
1366+
if (
1367+
self._platform.decoded_model['B_DC_Power'] == SUNSPEC_NOT_IMPL_FLOAT32
1368+
or self._platform.decoded_model['B_DC_Power'] == 0xFF7FFFFF
1369+
or self._platform.decoded_model['B_DC_Power'] == 0x7F7FFFFF
1370+
):
1371+
return None
1372+
1373+
elif self._platform.decoded_model['B_Status'] in [0]:
1374+
return None
1375+
1376+
else:
1377+
return round(self._platform.decoded_model['B_DC_Power'], 2)
1378+
1379+
except TypeError:
1380+
return None
1381+
1382+
class SolarEdgeBatteryEnergyExport(SolarEdgeSensorBase):
1383+
device_class = SensorDeviceClass.ENERGY
1384+
state_class = SensorStateClass.TOTAL_INCREASING
1385+
native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
1386+
icon = 'mdi:transmission-tower-export'
1387+
1388+
def __init__(self, platform, config_entry):
1389+
super().__init__(platform, config_entry)
1390+
"""Initialize the sensor."""
1391+
1392+
@property
1393+
def unique_id(self) -> str:
1394+
return f"{self._platform.model}_{self._platform.serial}_energy_export"
1395+
1396+
@property
1397+
def name(self) -> str:
1398+
return f"{self._platform._device_info['name']} Energy Export"
1399+
1400+
@property
1401+
def native_value(self):
1402+
try:
1403+
if self._platform.decoded_model['B_Export_Energy_WH'] == 0xFFFFFFFFFFFFFFFF:
1404+
return None
1405+
1406+
else:
1407+
try:
1408+
return watts_to_kilowatts(update_accum(self, self._platform.decoded_model['B_Export_Energy_WH']))
1409+
except:
1410+
return None
1411+
1412+
except TypeError:
1413+
return None
1414+
1415+
class SolarEdgeBatteryEnergyImport(SolarEdgeSensorBase):
1416+
device_class = SensorDeviceClass.ENERGY
1417+
state_class = SensorStateClass.TOTAL_INCREASING
1418+
native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
1419+
icon = 'mdi:transmission-tower-import'
1420+
1421+
def __init__(self, platform, config_entry):
1422+
super().__init__(platform, config_entry)
1423+
"""Initialize the sensor."""
1424+
1425+
@property
1426+
def unique_id(self) -> str:
1427+
return f"{self._platform.model}_{self._platform.serial}_energy_import"
1428+
1429+
@property
1430+
def name(self) -> str:
1431+
return f"{self._platform._device_info['name']} Energy Import"
1432+
1433+
@property
1434+
def native_value(self):
1435+
try:
1436+
if self._platform.decoded_model['B_Import_Energy_WH'] == 0xFFFFFFFFFFFFFFFF:
1437+
return None
1438+
1439+
else:
1440+
try:
1441+
return watts_to_kilowatts(update_accum(self, self._platform.decoded_model['B_Import_Energy_WH']))
1442+
except:
1443+
return None
1444+
1445+
except TypeError:
1446+
return None
1447+
1448+
class SolarEdgeBatteryMaxEnergy(SolarEdgeSensorBase):
1449+
device_class = SensorDeviceClass.ENERGY
1450+
state_class = SensorStateClass.MEASUREMENT
1451+
native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
1452+
1453+
def __init__(self, platform, config_entry):
1454+
super().__init__(platform, config_entry)
1455+
"""Initialize the sensor."""
1456+
1457+
@property
1458+
def unique_id(self) -> str:
1459+
return f"{self._platform.model}_{self._platform.serial}_max_energy"
1460+
1461+
@property
1462+
def name(self) -> str:
1463+
return f"{self._platform._device_info['name']} Maximum Energy"
1464+
1465+
@property
1466+
def native_value(self):
1467+
if (
1468+
self._platform.decoded_model['B_Energy_Max']== SUNSPEC_NOT_IMPL_FLOAT32
1469+
or self._platform.decoded_model['B_Energy_Max'] == 0xFF7FFFFF
1470+
or self._platform.decoded_model['B_Energy_Max'] == 0x7F7FFFFF
1471+
):
1472+
return None
1473+
1474+
else:
1475+
return watts_to_kilowatts(self._platform.decoded_model['B_Energy_Max'])
1476+
1477+
class SolarEdgeBatteryAvailableEnergy(SolarEdgeSensorBase):
1478+
device_class = SensorDeviceClass.ENERGY
1479+
state_class = SensorStateClass.MEASUREMENT
1480+
native_unit_of_measurement = ENERGY_KILO_WATT_HOUR
1481+
1482+
def __init__(self, platform, config_entry):
1483+
super().__init__(platform, config_entry)
1484+
"""Initialize the sensor."""
1485+
1486+
@property
1487+
def unique_id(self) -> str:
1488+
return f"{self._platform.model}_{self._platform.serial}_avail_energy"
1489+
1490+
@property
1491+
def name(self) -> str:
1492+
return f"{self._platform._device_info['name']} Available Energy"
1493+
1494+
@property
1495+
def native_value(self):
1496+
if (
1497+
self._platform.decoded_model['B_Energy_Available']== SUNSPEC_NOT_IMPL_FLOAT32
1498+
or self._platform.decoded_model['B_Energy_Available'] == 0xFF7FFFFF
1499+
or self._platform.decoded_model['B_Energy_Available'] == 0x7F7FFFFF
1500+
):
1501+
return None
1502+
1503+
else:
1504+
return watts_to_kilowatts(self._platform.decoded_model['B_Energy_Available'])
1505+
1506+
class SolarEdgeBatterySOH(SolarEdgeSensorBase):
1507+
state_class = SensorStateClass.MEASUREMENT
1508+
native_unit_of_measurement = PERCENTAGE
1509+
entity_category = EntityCategory.DIAGNOSTIC
1510+
1511+
def __init__(self, platform, config_entry):
1512+
super().__init__(platform, config_entry)
1513+
"""Initialize the sensor."""
1514+
1515+
@property
1516+
def unique_id(self) -> str:
1517+
return f"{self._platform.model}_{self._platform.serial}_battery_soh"
1518+
1519+
@property
1520+
def name(self) -> str:
1521+
return f"{self._platform._device_info['name']} State of Health"
1522+
1523+
@property
1524+
def native_value(self):
1525+
if (
1526+
self._platform.decoded_model['B_SOH']== SUNSPEC_NOT_IMPL_FLOAT32
1527+
or self._platform.decoded_model['B_SOH'] == 0xFF7FFFFF
1528+
or self._platform.decoded_model['B_SOH'] == 0x7F7FFFFF
1529+
):
1530+
return None
1531+
else:
1532+
return round(self._platform.decoded_model['B_SOH'], 0)
1533+
1534+
class SolarEdgeBatterySOE(SolarEdgeSensorBase):
1535+
state_class = SensorStateClass.MEASUREMENT
1536+
native_unit_of_measurement = PERCENTAGE
1537+
entity_category = EntityCategory.DIAGNOSTIC
1538+
1539+
def __init__(self, platform, config_entry):
1540+
super().__init__(platform, config_entry)
1541+
"""Initialize the sensor."""
1542+
1543+
@property
1544+
def unique_id(self) -> str:
1545+
return f"{self._platform.model}_{self._platform.serial}_battery_soe"
1546+
1547+
@property
1548+
def name(self) -> str:
1549+
return f"{self._platform._device_info['name']} State of Energy"
1550+
1551+
@property
1552+
def native_value(self):
1553+
if (
1554+
self._platform.decoded_model['B_SOE']== SUNSPEC_NOT_IMPL_FLOAT32
1555+
or self._platform.decoded_model['B_SOE'] == 0xFF7FFFFF
1556+
or self._platform.decoded_model['B_SOE'] == 0x7F7FFFFF
1557+
):
1558+
return None
1559+
else:
1560+
return round(self._platform.decoded_model['B_SOE'], 0)
1561+
1562+
class SolarEdgeBatteryStatus(Status):
1563+
1564+
def __init__(self, platform, config_entry):
1565+
super().__init__(platform, config_entry)
1566+
"""Initialize the sensor."""
1567+
1568+
@property
1569+
def native_value(self):
1570+
try:
1571+
if (self._platform.decoded_model['B_Status'] == SUNSPEC_NOT_IMPL_UINT32):
1572+
return None
1573+
1574+
else:
1575+
return str(self._platform.decoded_model['B_Status'])
1576+
1577+
except TypeError:
1578+
return None
1579+
1580+
@property
1581+
def extra_state_attributes(self):
1582+
attrs = {}
1583+
1584+
try:
1585+
if self._platform.decoded_model['B_Status'] in BATTERY_STATUS:
1586+
attrs["status_text"] = BATTERY_STATUS[self._platform.decoded_model['B_Status']]
1587+
1588+
except KeyError:
1589+
pass
1590+
1591+
return attrs

0 commit comments

Comments
 (0)