Skip to content

Commit

Permalink
feat: support chlorinator on/off switch (#34)
Browse files Browse the repository at this point in the history
* feat: initial work towards adding chlorinator support

* feat: more work for chlorinator support
  • Loading branch information
cryptk committed Jun 7, 2023
1 parent b6e39e5 commit f8398d6
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 2 deletions.
75 changes: 75 additions & 0 deletions pyomnilogic_local/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,3 +414,78 @@ async def async_set_light_show(

req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")
return await self.async_send_message(MessageType.SET_STANDALONE_LIGHT_SHOW, req_body, False)

async def async_set_chlorinator_enable(self, pool_id: int, enabled: int | bool) -> None:
body_element = ET.Element("Request", {"xmlns": "http://nextgen.hayward.com/api"})

name_element = ET.SubElement(body_element, "Name")
name_element.text = "SetCHLOREnable"

parameters_element = ET.SubElement(body_element, "Parameters")
parameter = ET.SubElement(parameters_element, "Parameter", name="poolId", dataType="int")
parameter.text = str(pool_id)
parameter = ET.SubElement(parameters_element, "Parameter", name="Enabled", dataType="bool", alias="Data")
parameter.text = str(int(enabled))

req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")

return await self.async_send_message(MessageType.SET_CHLOR_ENABLED, req_body, False)

async def async_set_chlorinator_params(
self,
pool_id: int,
equipment_id: int,
cfg_state: int,
op_mode: int,
bow_type: int,
cell_type: int,
timed_percent: int,
sc_timeout: int,
orp_timeout: int,
) -> None:
body_element = ET.Element("Request", {"xmlns": "http://nextgen.hayward.com/api"})

name_element = ET.SubElement(body_element, "Name")
name_element.text = "SetCHLORParams"

parameters_element = ET.SubElement(body_element, "Parameters")
parameter = ET.SubElement(parameters_element, "Parameter", name="poolId", dataType="int")
parameter.text = str(pool_id)
parameter = ET.SubElement(parameters_element, "Parameter", name="ChlorID", dataType="int", alias="EquipmentID")
parameter.text = str(equipment_id)
parameter = ET.SubElement(parameters_element, "Parameter", name="CfgState", dataType="byte", alias="Data1")
parameter.text = str(cfg_state)
parameter = ET.SubElement(parameters_element, "Parameter", name="OpMode", dataType="byte", alias="Data2")
parameter.text = str(op_mode)
parameter = ET.SubElement(parameters_element, "Parameter", name="BOWType", dataType="byte", alias="Data3")
parameter.text = str(bow_type)
parameter = ET.SubElement(parameters_element, "Parameter", name="CellType", dataType="byte", alias="Data4")
parameter.text = str(cell_type)
parameter = ET.SubElement(parameters_element, "Parameter", name="TimedPercent", dataType="byte", alias="Data5")
parameter.text = str(timed_percent)
parameter = ET.SubElement(parameters_element, "Parameter", name="SCTimeout", dataType="byte", unit="hour", alias="Data6")
parameter.text = str(sc_timeout)
parameter = ET.SubElement(parameters_element, "Parameter", name="ORPTimout", dataType="byte", unit="hour", alias="Data7")
parameter.text = str(orp_timeout)

req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")

return await self.async_send_message(MessageType.SET_CHLOR_PARAMS, req_body, False)

async def async_set_chlorinator_superchlorinate(self, pool_id: int, equipment_id: int, enabled: int | bool) -> None:
body_element = ET.Element("Request", {"xmlns": "http://nextgen.hayward.com/api"})

name_element = ET.SubElement(body_element, "Name")
name_element.text = "SetUISuperCHLORCmd"

parameters_element = ET.SubElement(body_element, "Parameters")
parameter = ET.SubElement(parameters_element, "Parameter", name="poolId", dataType="int")
parameter.text = str(pool_id)
parameter = ET.SubElement(parameters_element, "Parameter", name="ChlorID", dataType="int", alias="EquipmentID")
parameter.text = str(equipment_id)
parameter = ET.SubElement(parameters_element, "Parameter", name="IsOn", dataType="byte", alias="Data1")
parameter.text = str(int(enabled))

req_body = ET.tostring(body_element, xml_declaration=True, encoding="unicode")

return await self.async_send_message(MessageType.SET_SUPERCHLORINATE, req_body, False)
30 changes: 28 additions & 2 deletions pyomnilogic_local/models/mspconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..exceptions import OmniParsingException
from ..types import (
BodyOfWaterType,
ChlorinatorDispenserType,
ColorLogicLightType,
ColorLogicShow,
FilterType,
Expand Down Expand Up @@ -137,6 +138,30 @@ def __init__(self, **data: Any) -> None:
self.heater_equipment = [MSPHeaterEquip.parse_obj(equip) for equip in heater_equip_data[OmniType.HEATER_EQUIP]]


class MSPChlorinatorEquip(OmniBase):
omni_type: OmniType = OmniType.CHLORINATOR_EQUIP
enabled: Literal["yes", "no"] = Field(alias="Enabled")


class MSPChlorinator(OmniBase):
_sub_devices = {"chlorinator_equipment"}

omni_type: OmniType = OmniType.CHLORINATOR
enabled: Literal["yes", "no"] = Field(alias="Enabled")
timed_percent: int = Field(alias="Timed-Percent")
superchlor_timeout: int = Field(alias="SuperChlor-Timeout")
dispenser_type: ChlorinatorDispenserType | str = Field(alias="Dispenser-Type")
chlorinator_equipment: list[MSPChlorinatorEquip] | None

def __init__(self, **data: Any) -> None:
super().__init__(**data)

# The heater equipment are nested down inside a list of "Operations", which also includes non Heater-Equipment items. We need to
# first filter down to just the heater equipment items, then populate our self.heater_equipment with parsed versions of those items.
chlorinator_equip_data = [op for op in data.get("Operation", {}) if OmniType.CHLORINATOR_EQUIP in op][0]
self.chlorinator_equipment = [MSPChlorinatorEquip.parse_obj(equip) for equip in chlorinator_equip_data[OmniType.CHLORINATOR_EQUIP]]


class MSPColorLogicLight(OmniBase):
omni_type: OmniType = OmniType.CL_LIGHT
type: ColorLogicLightType | str = Field(alias="Type")
Expand All @@ -149,7 +174,7 @@ def __init__(self, **data: Any) -> None:


class MSPBoW(OmniBase):
_sub_devices = {"filter", "relay", "heater", "sensor", "colorlogic_light", "pump"}
_sub_devices = {"filter", "relay", "heater", "sensor", "colorlogic_light", "pump", "chlorinator"}

omni_type: OmniType = OmniType.BOW
type: BodyOfWaterType | str = Field(alias="Type")
Expand All @@ -159,6 +184,7 @@ class MSPBoW(OmniBase):
sensor: list[MSPSensor] | None = Field(alias="Sensor")
colorlogic_light: list[MSPColorLogicLight] | None = Field(alias="ColorLogic-Light")
pump: list[MSPPump] | None = Field(alias="Pump")
chlorinator: MSPChlorinator | None = Field(alias="Chlorinator")

# We override the __init__ here so that we can trigger the propagation of the bow_id down to all of it's sub devices after the bow
# itself is initialized
Expand Down Expand Up @@ -204,7 +230,7 @@ def load_xml(xml: str) -> MSPConfig:
# everything that *could* be a list into a list to make the parsing more consistent.
force_list=(
OmniType.BOW_MSP,
OmniType.CHLORINATOR,
OmniType.CHLORINATOR_EQUIP,
OmniType.CL_LIGHT,
OmniType.FAVORITES,
OmniType.FILTER,
Expand Down
7 changes: 7 additions & 0 deletions pyomnilogic_local/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ class MessageType(Enum):
REQUEST_CONFIGURATION = 1
SET_FILTER_SPEED = 9
SET_HEATER_COMMAND = 11
SET_SUPERCHLORINATE = 15
REQUEST_LOG_CONFIG = 31
SET_SOLAR_SET_POINT_COMMAND = 40
SET_HEATER_MODE_COMMAND = 42
SET_CHLOR_ENABLED = 121
SET_HEATER_ENABLED = 147
SET_CHLOR_PARAMS = 155
SET_EQUIPMENT = 164
CREATE_SCHEDULE = 230
DELETE_SCHEDULE = 231
Expand Down Expand Up @@ -84,6 +87,10 @@ class ChlorinatorOperatingMode(PrettyEnum):
ORP = 2


class ChlorinatorDispenserType(str, PrettyEnum):
SALT = "SALT_DISPENSING"


class ColorLogicSpeed(PrettyEnum):
ONE_SIXTEENTH = 0
ONE_EIGHTH = 1
Expand Down

0 comments on commit f8398d6

Please sign in to comment.