Skip to content

Commit

Permalink
Add firmware/hardware data to device infos
Browse files Browse the repository at this point in the history
  • Loading branch information
mletenay committed Jun 25, 2024
1 parent 5a948de commit d1a3555
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 27 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ Beware ! Perform it at your own risk, hacking the device may void the waranty an

Step by step instructions how to enable local network access to the CCA can be found here [Getting Data DIRECTLY from a Tigo TAP - is it possible ?](https://diysolarforum.com/threads/getting-data-directly-from-a-tigo-tap-is-it-possible.37414/page-2#post-1085251) or here [Details, Protokolle, Zugang auf Tigo CCA](https://www.photovoltaikforum.com/thread/149592-details-protokolle-zugang-auf-tigo-cca/?postID=3649832#post3649832).

In short:
Open web page `http://[cca ip address]/cgi-bin/shell` (Tigo/$olar), then login into it via ssh (root/gW$70#c). When in, remount the filesystem to be writable `mount -o remount,rw /` and expose the web console to local network by adding SNAT to its internal net via `echo "/usr/sbin/iptables -t nat -D INPUT -p tcp --dport 80 -j SNAT --to 10.11.1.1" >> /etc/rc.httpd`.
After reboot, the web console at `http://[cca ip address]/cgi-bin/mmdstatus` (Tigo/$olar) should be permanently accessible.
In short:
Open web page `http://[cca ip address]/cgi-bin/shell` (Tigo/$olar), then login into it via ssh (root/gW$70#c). When in, remount the filesystem to be writable `mount -o remount,rw /` and expose the web console to local network by adding SNAT to its internal net via `echo "/usr/sbin/iptables -t nat -D INPUT -p tcp --dport 80 -j SNAT --to 10.11.1.1" >> /etc/rc.httpd`.
After reboot, the web console at `http://[cca ip address]/cgi-bin/mmdstatus` (Tigo/$olar) should be permanently accessible.
This integration intentionally parses that page for getting panel monitoring data, it does not work with the raw csv/json data which can be exposed by further mofifications of the CCA filesystem.

Just a remark, the procedure above exposes not only the administration console, but also a nice, user oriented web app at http://[cca ip address]/summary/

## Installation

Install this component using HACS by adding custom repository https://github.com/mletenay/home-assistant-tigo and searching for Tigo in the Integrations.
Expand Down
2 changes: 2 additions & 0 deletions basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

async def _test_command():
tigo = TigoCCA("192.168.1.125", "Tigo", "$olar")
status = await asyncio.wait_for(tigo.read_config(), 1)
print(tigo.panels)
status = await asyncio.wait_for(tigo.get_status(), 1)
print(status)

Expand Down
7 changes: 4 additions & 3 deletions custom_components/tigo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

try:
_LOGGER.debug("Connecting to Tigo CCA at %s", host)
tigo = TigoCCA(host, username, password)
status = await tigo.get_status()
cca = TigoCCA(host, username, password)
await cca.read_config()
status = await cca.get_status()
except Exception as err:
raise ConfigEntryNotReady from err

# Create update coordinator
coordinator = TigoUpdateCoordinator(hass, entry, tigo, status)
coordinator = TigoUpdateCoordinator(hass, entry, cca, status)

# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
Expand Down
23 changes: 17 additions & 6 deletions custom_components/tigo/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, SCAN_INTERVAL
from .tigo_cca import PanelStatus, TigoCCA, TigoCcaStatus
from .tigo_cca import PanelStatus, PanelVersionInfo, TigoCCA, TigoCcaStatus

_LOGGER = logging.getLogger(__name__)

Expand All @@ -37,7 +37,7 @@ def __init__(
self.serial_nr = status.unit_id
self.device_info = _cca_device(cca, status)
self.panel_device_infos = {
p.label: _panel_device(status, p) for p in status.panels.values()
p.label: _panel_device(status, p) for p in cca.panels.values()
}

async def _async_update_data(self) -> TigoCcaStatus:
Expand All @@ -47,20 +47,31 @@ async def _async_update_data(self) -> TigoCcaStatus:
except Exception as ex:
raise UpdateFailed(ex) from ex

def panel_status(self, label: str) -> PanelStatus:
"""Return panel status."""
return self.data.panels.get(label)


def _cca_device(cca: TigoCCA, status: TigoCcaStatus) -> DeviceInfo:
return DeviceInfo(
configuration_url=cca.url_root,
configuration_url=cca.url_root + "/cgi-bin/mmdstatus",
identifiers={(DOMAIN, status.unit_id)},
name=f"Tigo CCA {status.unit_id}",
model=cca.hw_platform,
sw_version=cca.fw_version,
manufacturer="Tigo",
serial_number=status.unit_id,
hw_version=status.hw_platform,
sw_version=status.fw_version,
)


def _panel_device(cca: TigoCcaStatus, panel: PanelStatus) -> DeviceInfo:
def _panel_device(cca: TigoCcaStatus, panel: PanelVersionInfo) -> DeviceInfo:
return DeviceInfo(
identifiers={(DOMAIN, panel.mac)},
name=f"Panel {panel.label}",
manufacturer="Tigo",
model=panel.model,
via_device=(DOMAIN, cca.unit_id),
serial_number=panel.mac,
hw_version=panel.hw,
sw_version=panel.fw,
)
59 changes: 52 additions & 7 deletions custom_components/tigo/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ class TigoSensorEntityDescription(SensorEntityDescription):
getter: Callable[[PanelStatus], Any] = None


_CCA_TEMP: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="cca_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
)

_VOLTAGE_IN: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="voltage_in",
device_class=SensorDeviceClass.VOLTAGE,
Expand Down Expand Up @@ -73,6 +80,12 @@ class TigoSensorEntityDescription(SensorEntityDescription):
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
getter=lambda panel: panel.temperature,
)
_PWM: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="pwm",
state_class=SensorStateClass.MEASUREMENT,
getter=lambda panel: panel.pwm,
# entity_category=EntityCategory.DIAGNOSTIC,
)
_RSSI: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="rssi",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
Expand All @@ -97,6 +110,7 @@ class TigoSensorEntityDescription(SensorEntityDescription):
_CURRENT,
_POWER,
_TEMP,
_PWM,
_RSSI,
_BYPASS,
)
Expand All @@ -112,18 +126,42 @@ async def async_setup_entry(
KEY_COORDINATOR
]

entities: list[TigoSensorEntity] = []
entities: list[TigoPanelSensorEntity] = [
TigoCcaSensorEntity(coordinator, _CCA_TEMP)
]

for panel in coordinator.data.panels.values():
entities.extend(
TigoSensorEntity(coordinator, panel, sensor) for sensor in _PANEL_SENSORS
TigoPanelSensorEntity(coordinator, panel, sensor)
for sensor in _PANEL_SENSORS
)

async_add_entities(entities)


class TigoSensorEntity(CoordinatorEntity[TigoUpdateCoordinator], SensorEntity):
"""Representation of Tigo sensor."""
class TigoCcaSensorEntity(CoordinatorEntity[TigoUpdateCoordinator], SensorEntity):
"""Representation of Tigo CCA sensor."""

def __init__(
self,
coordinator: TigoUpdateCoordinator,
description: TigoSensorEntityDescription,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._attr_name = f"CCA {description.key}"
self._attr_unique_id = f"{description.key}-{coordinator.serial_nr}"
self._attr_device_info = coordinator.device_info
self.entity_description: TigoSensorEntityDescription = description

@property
def native_value(self):
"""Return the value reported by the sensor."""
return self.coordinator.data.temperature


class TigoPanelSensorEntity(CoordinatorEntity[TigoUpdateCoordinator], SensorEntity):
"""Representation of Tigo panel sensor."""

def __init__(
self,
Expand All @@ -139,9 +177,16 @@ def __init__(
self.entity_description: TigoSensorEntityDescription = description
self.panel_id = panel.label

def _panel_status(self) -> PanelStatus:
return self.coordinator.panel_status(self.panel_id)

@property
def native_value(self):
"""Return the value reported by the sensor."""
return self.entity_description.getter(
self.coordinator.data.panels.get(self.panel_id)
)
status: PanelStatus = self._panel_status()
return self.entity_description.getter(status) if status else None

@property
def available(self) -> bool:
"""Return if entity is available."""
return self.native_value is not None
Loading

0 comments on commit d1a3555

Please sign in to comment.