Skip to content

Commit

Permalink
Replace bypass witgh generic status sensor
Browse files Browse the repository at this point in the history
  • Loading branch information
mletenay committed Jun 26, 2024
1 parent d1a3555 commit e7d7403
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 80 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Step by step instructions how to enable local network access to the CCA can be f
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/

Expand Down
2 changes: 1 addition & 1 deletion custom_components/tigo/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _panel_device(cca: TigoCcaStatus, panel: PanelVersionInfo) -> DeviceInfo:
identifiers={(DOMAIN, panel.mac)},
name=f"Panel {panel.label}",
manufacturer="Tigo",
model=panel.model,
model=panel.model(),
via_device=(DOMAIN, cca.unit_id),
serial_number=panel.mac,
hw_version=panel.hw,
Expand Down
20 changes: 10 additions & 10 deletions custom_components/tigo/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,16 @@ class TigoSensorEntityDescription(SensorEntityDescription):
_PWM: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="pwm",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:numeric",
getter=lambda panel: panel.pwm,
# entity_category=EntityCategory.DIAGNOSTIC,
# entity_category=EntityCategory.DIAGNOSTIC,
)
_STATUS: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="status",
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:information-outline",
getter=lambda panel: panel.status,
# entity_category=EntityCategory.DIAGNOSTIC,
)
_RSSI: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="rssi",
Expand All @@ -95,14 +103,6 @@ class TigoSensorEntityDescription(SensorEntityDescription):
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
)
_BYPASS: TigoSensorEntityDescription = TigoSensorEntityDescription(
key="bypass",
device_class=SensorDeviceClass.ENUM,
state_class=SensorStateClass.MEASUREMENT,
options=["off", "on"],
icon="mdi:weather-cloudy-arrow-right",
getter=lambda panel: "on" if panel.bypass else "off",
)

_PANEL_SENSORS: tuple[TigoSensorEntityDescription] = (
_VOLTAGE_IN,
Expand All @@ -111,8 +111,8 @@ class TigoSensorEntityDescription(SensorEntityDescription):
_POWER,
_TEMP,
_PWM,
_STATUS,
_RSSI,
_BYPASS,
)


Expand Down
142 changes: 74 additions & 68 deletions custom_components/tigo/tigo_cca.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@ class PanelVersionInfo:
mac: str = None
fw: str = None
hw: str = None
model: str = "TS4-A-O"

def model(self) -> str:
"""Return panel unit model."""
if "455" in self.hw:
return "TS4-A-M"
if "461" in self.hw or "462" in self.hw:
return "TS4-A-O"
if "466" in self.hw:
return "TS4-A-S"
if "481" in self.hw or "486" in self.hw or "488" in self.hw:
return "TS4-A-F"
if "484" in self.hw or "485" in self.hw or "487" in self.hw:
return "TS4-A-2F"
return "?"

def __str__(self):
"""Return string representation of panel info."""
Expand All @@ -36,11 +49,25 @@ class PanelStatus:
voltage_out: float = None
current: float = None
power: float = None
power_perc: float = None
pwm: int = None
temperature: float = None
status: int = None
rssi: int = None
bypass: bool = None

def status_name(self) -> str:
"""Return string representation of status.
The status int value is bitmap:
bit 7: ?
bit 6: panel on/off
bit 5: ?
bit 4: ?
bit 3: ?
bit 2: ?
bit 1: ?
bit 0: ?
"""
return "?"

def __str__(self):
"""Return string representation of panel status."""
Expand Down Expand Up @@ -113,70 +140,6 @@ def handle_endtag(self, tag):
self._in_td = False


class _PanelsStatusPageParser(HTMLParser):
def __init__(self, cca: TigoCcaStatus) -> None:
"""Initialize the panel status page parser."""
super().__init__()
self._cca = cca
self._in_table = False
self._in_row = False
self._td_nr = 0
self._td_done = False
self._status = None

def handle_starttag(self, tag, attrs):
if tag == "table" and dict(attrs).get("class") == "list_tb":
self._in_table = True
self._cca.panels.clear()
elif (
tag == "tr"
and dict(attrs).get("class") != "tb_red_title"
and self._in_table
):
self._in_row = True
self._status = PanelStatus()
elif tag == "td" and self._in_row:
self._td_nr += 1
self._td_done = False

def handle_data(self, data):
if self._td_nr > 0 and self._status and data != "n/a" and not self._td_done:
self._td_done = True
match self._td_nr:
case 1:
self._status.label = data
case 3:
self._status.mac = data
case 4:
self._status.voltage_in = float(data)
case 6:
self._status.voltage_out = float(data)
case 8:
self._status.current = float(data)
case 9:
self._status.power = float(data)
case 10:
self._status.power_perc = float(data.replace("%", ""))
case 11:
self._status.temperature = float(data)
case 12:
self._status.rssi = int(data)
case 15:
self._status.pwm = int(data)
case 19:
self._status.bypass = data != "off"
case _:
pass

def handle_endtag(self, tag):
if tag == "table" and self._in_table:
self._in_table = False
elif tag == "tr" and self._in_row:
self._in_row = False
self._td_nr = 0
self._cca.panels[self._status.label] = self._status


class _TableParser(HTMLParser):
def __init__(self) -> None:
"""Initialize the table parser."""
Expand Down Expand Up @@ -215,6 +178,49 @@ def _on_tr_end(self) -> None:
pass


class _PanelsStatusPageParser(_TableParser):
def __init__(self, cca: TigoCcaStatus) -> None:
"""Initialize the panel status page parser."""
super().__init__()
self._cca: TigoCcaStatus = cca
self._status: PanelStatus = None
self._age: str = None

def _on_td(self, nr: int, data: str) -> None:
match nr:
case 1:
self._status = PanelStatus()
case 2:
self._status.mac = data.strip()
case 3:
self._status.label = data.strip()
case 4:
self._age = data
case 13:
self._status.voltage_in = float(data)
case 14:
self._status.voltage_out = float(data)
case 15:
self._status.current = float(data)
case 16:
self._status.power = float(data)
case 17:
self._status.pwm = int(data)
case 18:
self._status.temperature = float(data)
case 22:
self._status.status = int(data, 16)
case 23:
self._status.rssi = int(data)
case _:
pass

def _on_tr_end(self) -> None:
if self._status and "hrs" not in self._age and "min" not in self._age:
self._cca.panels[self._status.label] = self._status
self._status = None


class _PanelsVersionPageParser(_TableParser):
def __init__(self, infos: dict[str, PanelVersionInfo]) -> None:
"""Initialize the panels versions page parser."""
Expand Down Expand Up @@ -292,6 +298,6 @@ async def get_status(self) -> TigoCcaStatus:
parser.feed(await self._get("/cgi-bin/lmudui"))
parser.close()
parser = _PanelsStatusPageParser(status)
parser.feed(await self._get("/cgi-bin/mmdstatus"))
parser.feed(await self._get("/cgi-bin/meshdatapower"))
parser.close()
return status

0 comments on commit e7d7403

Please sign in to comment.