Skip to content

Commit

Permalink
Merge remote-tracking branch 'gjbadros/master' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
eseglem committed Nov 11, 2023
2 parents 00d2b80 + 2a2fed8 commit edb6839
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 124 deletions.
63 changes: 35 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ Easiest way to install this component is through [HACS][hacs].
Configuration through `configuration.yaml`, not available in UI yet.

Example Config:
```

```yaml
wattbox:
- host: 192.168.1.100
name: wattbox1
username: username1
password: password1
name_regexp: "^Fixed Prefix (.*)$"
skip_regexp: "SKIP"
scan_interval: 00:00:10
- host: 192.168.1.101
name: wattbox2
Expand All @@ -40,40 +43,44 @@ wattbox:

Configuration Options:

* *host*: Host IP of the WattBox (Required)
* *port*: Port of the HTTP interface (Default 80)
* *username*: Username for authentication (Default wattbox)
* *password*: Password for authentication (Default wattbox)
* *name*: Name for the WattBox (Default wattbox)
* *resources*: A list of resources to enable (Default all of them)
* *scan_interval*: A time interval run updates at (Default 30s, format HH:MM:SS)
- _host_: Host IP of the WattBox (Required)
- _port_: Port of the HTTP interface (Default 80)
- _username_: Username for authentication (Default wattbox)
- _password_: Password for authentication (Default wattbox)
- _name_: Name for the WattBox (Default wattbox)
- _resources_: A list of resources to enable (Default all of them)
- _scan_interval_: A time interval run updates at (Default 30s, format HH:MM:SS)
- _name_regexp_: A regexp to extract the name to use for the outlet instead of just the index. If there is a match group, it is used, else the whole match is used.
- _skip_regexp_: A regexp to use that, if the outlet name matches, the outlet is not added as a switch entity.

Resources:
* audible_alarm
* auto_reboot
* battery_health
* battery_test
* cloud_status
* has_ups
* mute
* power_lost
* safe_voltage_status
* battery_charge
* battery_load
* current_value
* est_run_time
* power_value
* voltage_value

Master switch will turn on / off all the switches that the physical switch on the box does. You can config that through the UI on the wattbox directly. If ALL of the switches controlled by Master are on, then Master will be on. Otherwise it will be off.

Be careful, if the WattBox controls the power to its own networking equipment you can turn it off and not have remote access until you fix it. You may even have to plug it in elsewhere to get back online and turn that outlet back on in HA.

- audible_alarm
- auto_reboot
- battery_health
- battery_test
- cloud_status
- has_ups
- mute
- power_lost
- safe_voltage_status
- battery_charge
- battery_load
- current_value
- est_run_time
- power_value
- voltage_value

Be careful, if the WattBox controls the power to its own networking equipment you can turn it off and not have remote access until you fix it. You may even have to plug it in elsewhere to get back online and turn that outlet back on in HA. You can use the _skip_regexp_ option for those outlets.

Master switch will turn on / off all the switches that the physical switch on the box does. You can config that through the UI on the wattbox directly. If ALL of the switches controlled by Master are on, then Master will be on. Otherwise it will be off. If any outlets on a wattbox are skipped via _skip_regexp_ then
the master switch for that wattbox will also not be added as an entity.

Based on [integration_blueprint template][blueprint]

<!---->

***
---

[wattbox]: https://www.snapav.com/shop/en/snapav/wattbox
[hacs]: https://hacs.xyz/
Expand Down
20 changes: 11 additions & 9 deletions custom_components/wattbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

from .const import (
BINARY_SENSOR_TYPES,
CONF_NAME_REGEXP,
CONF_SKIP_REGEXP,
DEFAULT_NAME,
DEFAULT_PASSWORD,
DEFAULT_PORT,
Expand All @@ -54,6 +56,8 @@
vol.Optional(CONF_USERNAME, default=DEFAULT_USER): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_NAME_REGEXP): cv.string,
vol.Optional(CONF_SKIP_REGEXP): cv.string,
vol.Optional(CONF_RESOURCES, default=ALL_SENSOR_TYPES): vol.All(
cv.ensure_list, [vol.In(ALL_SENSOR_TYPES)]
),
Expand Down Expand Up @@ -89,17 +93,18 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
from pywattbox.ip_wattbox import async_create_ip_wattbox

_LOGGER.debug("Creating IP WattBox")
hass.data[DOMAIN_DATA][name] = await async_create_ip_wattbox(
wattbox = await async_create_ip_wattbox(
host=host, user=username, password=password, port=port
)
else:
_LOGGER.debug("Importing HTTP Wattbox")
from pywattbox.http_wattbox import async_create_http_wattbox

_LOGGER.debug("Creating HTTP WattBox")
hass.data[DOMAIN_DATA][name] = await async_create_http_wattbox(
wattbox = await async_create_http_wattbox(
host=host, user=username, password=password, port=port
)
hass.data[DOMAIN_DATA][name] = wattbox

# Load platforms
for platform in PLATFORMS:
Expand All @@ -120,7 +125,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_LOGGER.debug(", ".join([str(v) for _, v in hass.data[DOMAIN_DATA].items()]))
_LOGGER.debug(repr(hass.data[DOMAIN_DATA]))
for _, wattbox in hass.data[DOMAIN_DATA].items():
_LOGGER.debug("%s has %s outlets", wattbox, len(wattbox.outlets))
_LOGGER.debug("%s has %s outlets%s", wattbox, len(wattbox.outlets))
for outlet in wattbox.outlets:
_LOGGER.debug("Outlet: %s - %s", outlet, repr(outlet))

Expand All @@ -132,12 +137,9 @@ async def update_data(_dt: datetime, hass: HomeAssistant, name: str) -> None:

# This is where the main logic to update platform data goes.
try:
await hass.data[DOMAIN_DATA][name].async_update()
_LOGGER.debug(
"Updated: %s - %s",
hass.data[DOMAIN_DATA][name],
repr(hass.data[DOMAIN_DATA][name]),
)
wattbox = hass.data[DOMAIN_DATA][name]
await wattbox.async_update()
_LOGGER.debug("Updated: %s - %s", wattbox, repr(wattbox))
# Send update to topic for entities to see
async_dispatcher_send(hass, TOPIC_UPDATE.format(DOMAIN, name))
except Exception as error:
Expand Down
18 changes: 9 additions & 9 deletions custom_components/wattbox/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import BINARY_SENSOR_TYPES, DOMAIN_DATA
from .const import BINARY_SENSOR_TYPES
from .entity import WattBoxEntity

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -42,18 +42,18 @@ class WattBoxBinarySensor(WattBoxEntity, BinarySensorEntity):

def __init__(self, hass: HomeAssistant, name: str, sensor_type: str) -> None:
super().__init__(hass, name, sensor_type)
self.type: str = sensor_type
self._flipped = BINARY_SENSOR_TYPES[self.type]["flipped"]
self._attr_name = name + " " + BINARY_SENSOR_TYPES[self.type]["name"]
self._attr_device_class = BINARY_SENSOR_TYPES[self.type]["device_class"]
self.sensor_type: str = sensor_type
self._flipped = BINARY_SENSOR_TYPES[self.sensor_type]["flipped"]
self._attr_name = name + " " + BINARY_SENSOR_TYPES[self.sensor_type]["name"]
self._attr_device_class = BINARY_SENSOR_TYPES[self.sensor_type]["device_class"]
self._attr_unique_id = (
f"{self._wattbox.serial_number}-bsensor-{self.sensor_type}"
)

async def async_update(self) -> None:
"""Update the sensor."""
# Get domain data
wattbox = self.hass.data[DOMAIN_DATA][self.wattbox_name]

# Check the data and update the value.
value: bool | None = getattr(wattbox, self.type, None)
value: bool | None = getattr(self._wattbox, self.sensor_type, None)
if value is not None and self._flipped:
value = not value
self._attr_is_on = value
6 changes: 5 additions & 1 deletion custom_components/wattbox/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# Base component constants
DOMAIN: Final[str] = "wattbox"
DOMAIN_DATA: Final[str] = f"{DOMAIN}_data"
VERSION: Final[str] = "0.8.1"
VERSION: Final[str] = "0.9.0"
PLATFORMS: Final[List[str]] = ["binary_sensor", "sensor", "switch"]
ISSUE_URL: Final[str] = "https://github.com/eseglem/hass-wattbox/issues"

Expand All @@ -41,6 +41,10 @@

TOPIC_UPDATE: Final[str] = "{}_data_update_{}"

# config options
CONF_NAME_REGEXP: Final[str] = 'name_regexp'
CONF_SKIP_REGEXP: Final[str] = 'skip_regexp'


class _BinarySensorDict(TypedDict):
"""TypedDict for use in BINARY_SENSOR_TYPES"""
Expand Down
8 changes: 5 additions & 3 deletions custom_components/wattbox/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from pywattbox.base import BaseWattBox

from .const import DOMAIN, TOPIC_UPDATE
from .const import DOMAIN, DOMAIN_DATA, TOPIC_UPDATE


class WattBoxEntity(Entity):
"""WattBox Entity class."""

_wattbox: BaseWattBox
_async_unsub_dispatcher_connect: Callable
_attr_should_poll: Literal[False] = False

def __init__(self, hass: HomeAssistant, name: str, *_args: Any) -> None:
self.hass = hass
self._wattbox = self.hass.data[DOMAIN_DATA][name]
self.topic: str = TOPIC_UPDATE.format(DOMAIN, name)
self._attr_extra_state_attributes: Dict[str, Any] = {}
self.wattbox_name: str = name
self.topic: str = TOPIC_UPDATE.format(DOMAIN, self.wattbox_name)

async def async_added_to_hass(self) -> None:
"""Register callbacks."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/wattbox/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"domain": "wattbox",
"name": "WattBox",
"version": "0.8.0",
"version": "0.9.0",
"integration_type": "device",
"documentation": "https://github.com/eseglem/hass-wattbox",
"issue_tracker": "https://github.com/eseglem/hass-wattbox/issues",
Expand Down
18 changes: 8 additions & 10 deletions custom_components/wattbox/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType

from .const import DOMAIN_DATA, SENSOR_TYPES
from .const import SENSOR_TYPES
from .entity import WattBoxEntity

_LOGGER = logging.getLogger(__name__)
Expand All @@ -26,12 +26,10 @@ async def async_setup_platform(
name: str = discovery_info[CONF_NAME]
entities: List[Union[WattBoxSensor, IntegrationSensor]] = []

resource: str
for resource in discovery_info[CONF_RESOURCES]:
sensor_type = resource.lower()

if sensor_type not in SENSOR_TYPES:
if (sensor_type := resource.lower()) not in SENSOR_TYPES:
continue

entities.append(WattBoxSensor(hass, name, sensor_type))

# TODO: Add a setting for this, default to true?
Expand All @@ -57,17 +55,17 @@ class WattBoxSensor(WattBoxEntity, SensorEntity):
def __init__(self, hass: HomeAssistant, name: str, sensor_type: str) -> None:
super().__init__(hass, name, sensor_type)
self.sensor_type: str = sensor_type
self._attr_name = name + " " + SENSOR_TYPES[self.sensor_type]["name"]
self._attr_name = f"{name} {SENSOR_TYPES[self.sensor_type]['name']}"
self._attr_native_unit_of_measurement = SENSOR_TYPES[self.sensor_type]["unit"]
self._attr_suggested_unit_of_measurement = SENSOR_TYPES[self.sensor_type][
"unit"
]
self._attr_icon = SENSOR_TYPES[self.sensor_type]["icon"]
self._attr_unique_id = f"{self._wattbox.serial_number}-sensor-{sensor_type}"

async def async_update(self) -> None:
"""Update the sensor."""
# Get new data (if any)
wattbox = self.hass.data[DOMAIN_DATA][self.wattbox_name]

# Check the data and update the value.
self._attr_native_value = getattr(wattbox, self.sensor_type, STATE_UNKNOWN)
self._attr_native_value = getattr(
self._wattbox, self.sensor_type, STATE_UNKNOWN
)
Loading

0 comments on commit edb6839

Please sign in to comment.