Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/cd-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,15 @@ jobs:
# Adjust tag with desired version if applicable.
uses: python-semantic-release/python-semantic-release@v10.4.1
with:
github_token: ${{ secrets.PYTHON_SEMANTIC_RELEASE_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions"
git_committer_email: "actions@users.noreply.github.com"

- name: Publish | Upload to GitHub Release Assets
uses: python-semantic-release/publish-action@v10.4.1
if: steps.release.outputs.released == 'true'
with:
github_token: ${{ secrets.PYTHON_SEMANTIC_RELEASE_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.release.outputs.tag }}

- name: Upload | Distribution Artifacts
Expand Down
2 changes: 1 addition & 1 deletion pyomnilogic_local/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ async def async_set_chlorinator_params(
sc_timeout: int,
bow_type: int,
orp_timeout: int,
cfg_state: int = 3,
cfg_state: int = 3, # 3 == on, 2 == off
) -> None:
body_element = ET.Element("Request", {"xmlns": XML_NAMESPACE})

Expand Down
6 changes: 3 additions & 3 deletions pyomnilogic_local/chlorinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
if TYPE_CHECKING:
from pyomnilogic_local.models.telemetry import Telemetry
from pyomnilogic_local.omnilogic import OmniLogic
from pyomnilogic_local.omnitypes import ChlorinatorCellType, ChlorinatorOperatingMode
from pyomnilogic_local.omnitypes import ChlorinatorCellType, ChlorinatorDispenserType, ChlorinatorOperatingMode


class Chlorinator(OmniEquipment[MSPChlorinator, TelemetryChlorinator]):
Expand Down Expand Up @@ -83,7 +83,7 @@ def orp_timeout(self) -> int:
return self.mspconfig.orp_timeout

@property
def dispenser_type(self) -> str:
def dispenser_type(self) -> ChlorinatorDispenserType:
"""Type of chlorine dispenser (SALT, LIQUID, or TABLET)."""
return self.mspconfig.dispenser_type

Expand All @@ -99,7 +99,7 @@ def operating_state(self) -> int:
return self.telemetry.operating_state

@property
def operating_mode(self) -> ChlorinatorOperatingMode | int:
def operating_mode(self) -> ChlorinatorOperatingMode:
"""Current operating mode (DISABLED, TIMED, ORP_AUTO, or ORP_TIMED_RW).

Returns:
Expand Down
122 changes: 121 additions & 1 deletion pyomnilogic_local/cli/debug/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import asyncio
from pathlib import Path
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, cast

import click

Expand All @@ -15,6 +15,7 @@

if TYPE_CHECKING:
from pyomnilogic_local.api.api import OmniLogicAPI
from pyomnilogic_local.models.telemetry import TelemetryChlorinator


@click.group()
Expand Down Expand Up @@ -209,3 +210,122 @@ def set_equipment(ctx: click.Context, bow_id: int, equip_id: int, is_on: str) ->
except Exception as e:
click.echo(f"Error setting equipment: {e}", err=True)
raise click.Abort from e


@debug.command()
@click.argument("bow_id", type=int)
@click.argument("equip_id", type=int)
@click.argument("timed_percent", type=int)
@click.argument("op_mode", type=int)
@click.pass_context
def set_chlor_params(ctx: click.Context, bow_id: int, equip_id: int, timed_percent: int, op_mode: int) -> None:
"""Set chlorinator parameters with explicit control over configuration.

This command sets chlorinator parameters using the current chlorinator's
configuration for cell_type, sc_timeout, bow_type, and orp_timeout, while
allowing you to specify timed_percent and op_mode. The cfg_state is derived
from the chlorinator's current on/off state.

BOW_ID: The Body of Water (pool/spa) system ID
EQUIP_ID: The chlorinator equipment system ID
TIMED_PERCENT: Chlorine generation percentage (0-100)
OP_MODE: Operating mode (0=DISABLED, 1=TIMED, 2=ORP_AUTO, 3=ORP_TIMED_RW)

Examples:
# Set chlorinator to 75% in TIMED mode
omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 75 1

# Disable chlorinator
omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 0 0

# Set to ORP AUTO mode with 50% generation
omnilogic --host 192.168.1.100 debug set-chlor-params 7 12 50 2

"""
ensure_connection(ctx)
omni: OmniLogicAPI = ctx.obj["OMNI"]

# Validate timed_percent
if not 0 <= timed_percent <= 100:
click.echo(f"Error: timed_percent must be between 0-100, got {timed_percent}", err=True)
raise click.Abort

# Validate op_mode
if not 0 <= op_mode <= 2:
click.echo(f"Error: op_mode must be between 0-3, got {op_mode}", err=True)
raise click.Abort

# Get MSPConfig and Telemetry to find the chlorinator
try:
mspconfig_raw = asyncio.run(omni.async_get_mspconfig(raw=False))
telemetry_raw = asyncio.run(omni.async_get_telemetry(raw=False))
except Exception as e:
click.echo(f"Error retrieving configuration: {e}", err=True)
raise click.Abort from e

# Find the BOW
bow = None
if mspconfig_raw.backyard.bow:
for candidate_bow in mspconfig_raw.backyard.bow:
if candidate_bow.system_id == bow_id:
bow = candidate_bow
break

if bow is None:
click.echo(f"Error: Body of Water with ID {bow_id} not found", err=True)
raise click.Abort

# Find the chlorinator
if bow.chlorinator is None or bow.chlorinator.system_id != equip_id:
click.echo(f"Error: Chlorinator with ID {equip_id} not found in BOW {bow_id}", err=True)
raise click.Abort

chlorinator = bow.chlorinator

# Get telemetry for the chlorinator to determine is_on state
chlorinator_telemetry = telemetry_raw.get_telem_by_systemid(equip_id)
if chlorinator_telemetry is None:
click.echo(f"Warning: No telemetry found for chlorinator {equip_id}, defaulting cfg_state to 3 (on)", err=True)
cfg_state = 3
else:
# Cast to TelemetryChlorinator to access enable attribute
chlorinator_telem = cast("TelemetryChlorinator", chlorinator_telemetry)
# Determine cfg_state from enable flag in telemetry
cfg_state = 3 if chlorinator_telem.enable else 2

# Determine bow_type from equipment type (0=pool, 1=spa)
bow_type = 0 if bow.equip_type == "BOW_POOL" else 1

# Get parameters from chlorinator config
cell_type = chlorinator.cell_type.value
sc_timeout = chlorinator.superchlor_timeout
orp_timeout = chlorinator.orp_timeout

# Execute the command
try:
asyncio.run(
omni.async_set_chlorinator_params(
pool_id=bow_id,
equipment_id=equip_id,
timed_percent=timed_percent,
cell_type=cell_type,
op_mode=op_mode,
sc_timeout=sc_timeout,
bow_type=bow_type,
orp_timeout=orp_timeout,
cfg_state=cfg_state,
)
)
click.echo(
f"Sent command to chlorinator {equip_id} in BOW {bow_id}:\n"
f" Timed Percent: {timed_percent}%\n"
f" Operating Mode: {op_mode}\n"
f" Config State: {cfg_state} ({'on' if cfg_state == 3 else 'off'})\n"
f" Cell Type: {cell_type}\n"
f" SC Timeout: {sc_timeout}\n"
f" BOW Type: {bow_type}\n"
f" ORP Timeout: {orp_timeout}"
)
except Exception as e:
click.echo(f"Error setting chlorinator parameters: {e}", err=True)
raise click.Abort from e
2 changes: 1 addition & 1 deletion pyomnilogic_local/omnitypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class ChlorinatorOperatingMode(IntEnum, PrettyEnum):
DISABLED = 0
TIMED = 1
ORP_AUTO = 2
ORP_TIMED_RW = 3 # Chlorinator in ORP mode experienced condition that prevents ORP operation
ORP_TIMED_RW = 3 # Chlorinator in ORP mode experienced CSAD condition that prevents ORP operation


class ChlorinatorType(StrEnum, PrettyEnum):
Expand Down