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
10 changes: 5 additions & 5 deletions pyomnilogic_local/bow.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class Bow(OmniEquipment[MSPBoW, TelemetryBoW]):
lights: EquipmentDict[ColorLogicLight] = EquipmentDict()
pumps: EquipmentDict[Pump] = EquipmentDict()
chlorinator: Chlorinator | None = None
csads: EquipmentDict[CSAD] = EquipmentDict()
csad: CSAD | None = None

def __init__(self, omni: OmniLogic, mspconfig: MSPBoW, telemetry: Telemetry) -> None:
super().__init__(omni, mspconfig, telemetry)
Expand All @@ -164,8 +164,8 @@ def __repr__(self) -> str:
parts.append("heater=True")
if self.chlorinator is not None:
parts.append("chlorinator=True")
if len(self.csads) > 0:
parts.append(f"csads={len(self.csads)}")
if self.csad is not None:
parts.append("csad=True")

return f"Bow({', '.join(parts)})"

Expand Down Expand Up @@ -277,10 +277,10 @@ def _update_chlorinators(self, mspconfig: MSPBoW, telemetry: Telemetry) -> None:
def _update_csads(self, mspconfig: MSPBoW, telemetry: Telemetry) -> None:
"""Update the CSADs based on the MSP configuration."""
if mspconfig.csad is None:
self.csads = EquipmentDict()
self.csad = None
return

self.csads = EquipmentDict([CSAD(self._omni, csad, telemetry) for csad in mspconfig.csad])
self.csad = CSAD(self._omni, mspconfig.csad, telemetry)

def _update_filters(self, mspconfig: MSPBoW, telemetry: Telemetry) -> None:
"""Update the filters based on the MSP configuration."""
Expand Down
85 changes: 19 additions & 66 deletions pyomnilogic_local/cli/get/bows.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

import click

from pyomnilogic_local.cli.utils import echo_properties
from pyomnilogic_local.omnitypes import BodyOfWaterType

if TYPE_CHECKING:
from pyomnilogic_local import OmniLogic
from pyomnilogic_local.models.mspconfig import MSPBoW
from pyomnilogic_local.models.telemetry import TelemetryType


@click.command()
Expand All @@ -31,70 +28,26 @@ def bows(ctx: click.Context) -> None:
all_bows = omnilogic.all_bows
for bow in all_bows:
echo_properties(bow)
click.echo("\n Equipment Counts:")

_print_equipment_count("Filter", len(bow.filters))
_print_equipment_count("Pump", len(bow.pumps))
_print_equipment_count("Heater (virtual)", 1 if bow.heater else 0)
_print_equipment_count("Heaters (physical)", len(bow.heater.heater_equipment) if bow.heater else 0)
_print_equipment_count("Sensors", len(bow.sensors))
_print_equipment_count("Lights", len(bow.lights))
_print_equipment_count("Relays", len(bow.relays))
_print_equipment_count("Chlorinator (virtual)", 1 if bow.chlorinator else 0)
_print_equipment_count("Chlorinators (physical)", len(bow.chlorinator.chlorinator_equipment) if bow.chlorinator else 0)
_print_equipment_count("CSAD (virtual)", 1 if bow.csad else 0)
_print_equipment_count("CSADs (physical)", len(bow.csad.csad_equipment) if bow.csad else 0)

click.echo("=" * 60)

if len(all_bows) == 0:
click.echo("No Bodies of Water found in the system configuration.")


def _print_bow_info(bow: MSPBoW, telemetry: TelemetryType | None) -> None:
"""Format and print Body of Water information in a nice table format.

Args:
bow: BOW object from MSPConfig with attributes to display
telemetry: Telemetry object containing current state information
"""
click.echo("\n" + "=" * 60)
click.echo("BODY OF WATER")
click.echo("=" * 60)

# Combine config and telemetry data
bow_data: dict[Any, Any] = {**dict(bow), **dict(telemetry)} if telemetry else dict(bow)

# Fields to exclude from main display (we'll show equipment counts instead)
exclude_fields = {"filter", "relay", "heater", "sensor", "colorlogic_light", "pump", "chlorinator", "csad"}

for attr_name, value in bow_data.items():
if attr_name in exclude_fields:
continue

if attr_name == "type":
value = str(BodyOfWaterType(value))
elif isinstance(value, list):
# Format lists nicely
value = ", ".join(str(v) for v in value) if value else "None"

# Format the attribute name to be more readable
display_name = attr_name.replace("_", " ").title()
click.echo(f"{display_name:20} : {value}")

# Show equipment summary
click.echo("\nAttached Equipment:")
click.echo("-" * 60)

equipment_counts = []
if bow.filter:
equipment_counts.append(f"Filters: {len(bow.filter)}")
if bow.pump:
equipment_counts.append(f"Pumps: {len(bow.pump)}")
if bow.heater:
equipment_counts.append("Heater: 1 (virtual)")
if bow.heater.heater_equipment:
equipment_counts.append(f" - Physical Heaters: {len(bow.heater.heater_equipment)}")
if bow.sensor:
equipment_counts.append(f"Sensors: {len(bow.sensor)}")
if bow.colorlogic_light:
equipment_counts.append(f"ColorLogic Lights: {len(bow.colorlogic_light)}")
if bow.relay:
equipment_counts.append(f"Relays: {len(bow.relay)}")
if bow.chlorinator:
equipment_counts.append("Chlorinator: 1")
if bow.csad:
equipment_counts.append(f"CSADs: {len(bow.csad)}")

if equipment_counts:
for count in equipment_counts:
click.echo(f" {count}")
else:
click.echo(" None")

click.echo("=" * 60)
def _print_equipment_count(name: str, count: int) -> None:
"""Helper function to print equipment counts with styling."""
click.echo(f" - {click.style(name, fg='green')}: {count}")
3 changes: 1 addition & 2 deletions pyomnilogic_local/models/mspconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ class MSPBoW(OmniBase):
colorlogic_light: list[MSPColorLogicLight] | None = Field(alias="ColorLogic-Light", default=None)
pump: list[MSPPump] | None = Field(alias="Pump", default=None)
chlorinator: MSPChlorinator | None = Field(alias="Chlorinator", default=None)
csad: list[MSPCSAD] | None = Field(alias="CSAD", default=None)
csad: MSPCSAD | None = Field(alias="CSAD", default=None)

# 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 @@ -462,7 +462,6 @@ 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.CSAD,
OmniType.CL_LIGHT,
OmniType.FAVORITES,
OmniType.FILTER,
Expand Down
4 changes: 1 addition & 3 deletions pyomnilogic_local/omnilogic.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,7 @@ def all_csad_equipment(self) -> EquipmentDict[CSADEquipment]:
@property
def all_csads(self) -> EquipmentDict[CSAD]:
"""Returns all CSAD instances across all bows in the backyard."""
csads: list[CSAD] = []
for bow in self.backyard.bow.values():
csads.extend(bow.csads.values())
csads = [bow.csad for bow in self.backyard.bow.values() if bow.csad is not None]
return EquipmentDict(csads)

@property
Expand Down
Loading