Skip to content

Commit

Permalink
Refactor energy mng items
Browse files Browse the repository at this point in the history
  • Loading branch information
jotonedev committed Nov 3, 2024
1 parent df566e7 commit 5ebbee7
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 46 deletions.
13 changes: 5 additions & 8 deletions docs/api/items/energy.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,15 @@ The Energy Management module provides the main interface to the OpenWebNet energ
options:
show_inheritance_diagram: true

::: pyown.items.energy.base.BaseEnergyManagement
options:
show_inheritance_diagram: true
::: pyown.items.energy.base.TypeEnergy

::: pyown.items.energy.stop_go.StopGo
::: pyown.items.energy.base.EnergyManagement
options:
show_inheritance_diagram: true

::: pyown.items.energy.power_meter.PowerMeter
::: pyown.items.energy.stop_go.StopGoStatus

::: pyown.items.energy.stop_go.StopGo
options:
show_inheritance_diagram: true

::: pyown.items.energy.actuator.Actuator
options:
show_inheritance_diagram: true
5 changes: 2 additions & 3 deletions pyown/items/energy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from .actuator import Actuator
from .power_meter import PowerMeter
from .stop_go import StopGo
from .base import EnergyManagement, TypeEnergy
from .stop_go import StopGo, StopGoStatus
10 changes: 0 additions & 10 deletions pyown/items/energy/actuator.py

This file was deleted.

80 changes: 69 additions & 11 deletions pyown/items/energy/base.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from abc import ABC, abstractmethod
from asyncio import Task
from enum import StrEnum, Enum, auto
from typing import Callable, Self, Coroutine, AsyncIterator
from typing import Self

from ..base import BaseItem, CoroutineCallback
from ...exceptions import InvalidMessage
from ...messages import DimensionResponse, BaseMessage, NormalMessage
from ...tags import Who, What, Value, Where, Dimension
from ...client import BaseClient
from ...exceptions import InvalidTag
from ...messages import BaseMessage
from ...tags import Who, What, Where, Dimension

__all__ = [
"BaseEnergyManagement",
"EnergyManagement",
"DimensionEnergy",
"WhatEnergy"
"WhatEnergy",
"TypeEnergy",
]


Expand Down Expand Up @@ -46,8 +47,10 @@ class DimensionEnergy(Dimension, StrEnum):
STATUS_STOP_GO_POWER_FAILURE_UPSTREAM: Status Stop&Go (Power failure upstream/close)
DAILY_TOTALIZERS_HOURLY_16BIT: Daily totalizers on an hourly basis for 16-bit Daily graphics
MONTHLY_AVERAGE_HOURLY_16BIT: Monthly average on an hourly basis for 16-bit Media Daily graphics
MONTHLY_TOTALIZERS_CURRENT_YEAR_32BIT: Monthly totalizers current year on a daily basis for 32-bit Monthly graphics
MONTHLY_TOTALIZERS_LAST_YEAR_32BIT: Monthly totalizers on a daily basis last year compared to 32-bit graphics TouchX Previous Year
MONTHLY_TOTALIZERS_CURRENT_YEAR_32BIT: Monthly totalizers current year on a daily basis for
32-bit Monthly graphics
MONTHLY_TOTALIZERS_LAST_YEAR_32BIT: Monthly totalizers on a daily basis last year compared to
32-bit graphics TouchX Previous Year
"""
ACTIVE_POWER = "113"
END_AUTOMATIC_UPDATE_SIZE = "1200"
Expand Down Expand Up @@ -107,14 +110,69 @@ class WhatEnergy(What, StrEnum):
RESET_REPORT = "75"


class BaseEnergyManagement(BaseItem):
class TypeEnergy(Enum):
"""
Base class for energy management items.
Type of energy management items.
Attributes:
POWER_METER: Power meter.
ACTUATOR: Actuator.
STOP_GO: Stop&Go device.
"""
POWER_METER = auto()
ACTUATOR = auto()
STOP_GO = auto()


class EnergyManagement(BaseItem):
"""
Used to control energy management items, like actuators with current sensors, etc...
Allowed where tags:
- 1N (N=[1-127]): Stop&Go devices,
these are circuit breakers capable of detecting a fault and opening the circuit.
- 5N (N=[1-255]): Power meters, these are devices that measure the power consumption.
- 7N#0 (N=[1-255]): Actuators,
these implement the same functionalities as power meters but can also control the power flow.
"""
_who = Who.ENERGY_MANAGEMENT

_event_callbacks: dict[DimensionEnergy, list[CoroutineCallback]] = {}

def __init__(self, client: BaseClient, where: Where | str, *, who: Who | str | None = None):
"""
Initializes the item and check if the where tag is valid.
Args:
client: The client to use to communicate with the server.
where: The location of the item.
who: The type of item.
Raises:
InvalidTag: If the where tag is not valid.
"""
super().__init__(client, where, who=who)
self.get_type()

def get_type(self) -> TypeEnergy:
"""
The type of the item.
Returns:
The type of the item.
Raises:
InvalidTag: If the where tag is not valid.
"""
if self.where.string.startswith("1") and self.where.string[1:].isnumeric():
return TypeEnergy.STOP_GO
elif self.where.string.startswith("5") and self.where.string[1:].isnumeric():
return TypeEnergy.POWER_METER
elif self.where.string.startswith("7") and self.where.parameters[0] == "0" and self.where.tag[1:].isnumeric():
return TypeEnergy.ACTUATOR
else:
raise InvalidTag(self.where)

@classmethod
async def call_callbacks(cls, item: Self, message: BaseMessage) -> list[Task]:
raise NotImplementedError
10 changes: 0 additions & 10 deletions pyown/items/energy/power_meter.py

This file was deleted.

161 changes: 158 additions & 3 deletions pyown/items/energy/stop_go.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,165 @@
from .base import BaseEnergyManagement
from dataclasses import dataclass
from typing import AsyncIterator

from .base import EnergyManagement, TypeEnergy, DimensionEnergy, WhatEnergy
from ...client import BaseClient
from ...exceptions import InvalidTag, InvalidMessage
from ...messages import DimensionResponse
from ...tags import Where, Who

__all__ = [
"StopGo",
"StopGoStatus",
]


class StopGo(BaseEnergyManagement):
pass
@dataclass
class StopGoStatus:
"""
Status of a Stop&Go device.
Attributes:
open: set if the circuit is open
failure: set if a failure is detected
block: set if the circuit is blocked
open_cc: set if the circuit is open due to a current overload
open_ground_fault: set if the circuit is open due to a ground fault
open_vmax: set if the circuit is open due to a voltage overload
auto_reset_off: set if the automatic reset is disabled
check_off: set if the check is disabled
waiting_closing: set if the circuit is waiting to be closed
first_24h_open: set if the circuit was open for the first 24 hours
power_fail_down: set if the circuit is open due to a power failure downstream
power_fail_up: set if the circuit is open due to a power failure upstream
"""
open: bool | None = None
failure: bool | None = None
block: bool | None = None
open_cc: bool | None = None
open_ground_fault: bool | None = None
open_vmax: bool | None = None
self_test_off: bool | None = None
auto_reset_off: bool | None = None
check_off: bool | None = None
waiting_closing: bool | None = None
first_24h_open: bool | None = None
power_fail_down: bool | None = None
power_fail_up: bool | None = None


class StopGo(EnergyManagement):
"""
Used to manage the Stop&Go items.
"""

def __init__(self, client: BaseClient, where: Where | str, *, who: Who | str | None = None):
"""
Initializes the item and check if the where tag is valid.
Args:
client: The client to use to communicate with the server.
where: The location of the item.
who: The type of item.
Raises:
InvalidTag: If the where tag is not valid.
"""
super().__init__(client, where, who=who)
if self.get_type() != TypeEnergy.STOP_GO:
raise InvalidTag(f"Invalid tag for a Stop&Go item: {where}")

async def enable_automatic_reset(self) -> None:
"""
Enable the automatic reset of the Stop&Go device.
"""
await self.send_normal_message(WhatEnergy.AUTO_RESET_ON)

async def disable_automatic_reset(self) -> None:
"""
Disable the automatic reset of the Stop&Go device.
"""
await self.send_normal_message(WhatEnergy.AUTO_RESET_OFF)

async def request_status(
self,
*,
messages: AsyncIterator[DimensionResponse] | None = None,
dim_req: DimensionEnergy = DimensionEnergy.STATUS_STOP_GO_GENERAL
) -> StopGoStatus:
"""
Request the status of the Stop&Go device.
Args:
messages: The messages to parse the status from.
It's used internally to avoid code duplication.
dim_req: The dimension to request the status from.
It's used internally to avoid code duplication.
Returns:
The status of the Stop&Go device.
Raises:
InvalidMessage: If the response is invalid.
"""
status = StopGoStatus()

if messages is None:
messages = self.send_dimension_request(dim_req)

async for msg in messages:
if not isinstance(msg, DimensionResponse):
raise InvalidMessage(msg.message)

dim = msg.dimension
val = int(msg.values[0].string)

if val is None:
raise InvalidMessage(msg.message)

match dim:
case DimensionEnergy.STATUS_STOP_GO_GENERAL:
# in this case we have a value containing a 13-bit bitmask with each bit representing one attribute
status.open = bool(val & 0x01) # bit 1
status.failure = bool(val & 0x02) # bit 2
status.block = bool(val & 0x04) # bit 3
status.open_cc = bool(val & 0x08) # bit 4
status.open_ground_fault = bool(val & 0x10) # bit 5
status.open_vmax = bool(val & 0x20) # bit 6
status.self_test_off = bool(val & 0x40) # bit 7
status.auto_reset_off = bool(val & 0x80) # bit 8
status.check_off = bool(val & 0x100) # bit 9
status.waiting_closing = bool(val & 0x200) # bit 10
status.first_24h_open = bool(val & 0x400) # bit 11
status.power_fail_down = bool(val & 0x800) # bit 12
status.power_fail_up = bool(val & 0x1000) # bit 13
case DimensionEnergy.STATUS_STOP_GO_OPEN_CLOSE:
# in this case the value is a single bit representing the open/close status
status.open = bool(val)
case DimensionEnergy.STATUS_STOP_GO_FAILURE_NO_FAILURE:
status.failure = bool(val)
case DimensionEnergy.STATUS_STOP_GO_BLOCK_NOT_BLOCK:
status.block = bool(val)
case DimensionEnergy.STATUS_STOP_GO_OPEN_CC_BETWEEN_N:
status.open_cc = bool(val)
case DimensionEnergy.STATUS_STOP_GO_OPENED_GROUND_FAULT:
status.open_ground_fault = bool(val)
case DimensionEnergy.STATUS_STOP_GO_OPEN_VMAX:
status.open_vmax = bool(val)
case DimensionEnergy.STATUS_STOP_GO_SELF_TEST_DISABLED:
status.self_test_off = bool(val)
case DimensionEnergy.STATUS_STOP_GO_AUTOMATIC_RESET_OFF:
status.auto_reset_off = bool(val)
case DimensionEnergy.STATUS_STOP_GO_CHECK_OFF:
status.check_off = bool(val)
case DimensionEnergy.STATUS_STOP_GO_WAITING_FOR_CLOSING:
status.waiting_closing = bool(val)
case DimensionEnergy.STATUS_STOP_GO_FIRST_24H_OPENING:
status.first_24h_open = bool(val)
case DimensionEnergy.STATUS_STOP_GO_POWER_FAILURE_DOWNSTREAM:
status.power_fail_down = bool(val)
case DimensionEnergy.STATUS_STOP_GO_POWER_FAILURE_UPSTREAM:
status.power_fail_up = bool(val)
case _:
raise InvalidMessage(msg.message)

return status
4 changes: 3 additions & 1 deletion pyown/items/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .automation import Automation
from .base import BaseItem
from .energy.base import EnergyManagement
from .gateway import Gateway
from .lighting import Light
from ..tags import Who
Expand All @@ -13,6 +14,7 @@
ITEM_TYPES: Final[dict[Who, Type[BaseItem]]] = {
Who.LIGHTING: Light,
Who.AUTOMATION: Automation,
Who.GATEWAY: Gateway
Who.GATEWAY: Gateway,
Who.ENERGY_MANAGEMENT: EnergyManagement,
}
"""A dictionary that maps the Who tag to the corresponding item class."""

0 comments on commit 5ebbee7

Please sign in to comment.