Skip to content

Commit d3fbceb

Browse files
committed
fix: cleanup command output formatting in CLI
1 parent 674b818 commit d3fbceb

10 files changed

Lines changed: 621 additions & 84 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,5 @@ cython_debug/
170170
# Testing files for PCAP parseer
171171
*.pcap
172172
*.pcapng
173+
174+
dev_files/
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
2+
# mypy: disable-error-code="misc"
3+
4+
from typing import Any
5+
6+
import click
7+
8+
from pyomnilogic_local.models.mspconfig import (
9+
MSPBackyard,
10+
MSPConfig,
11+
)
12+
from pyomnilogic_local.models.telemetry import (
13+
Telemetry,
14+
TelemetryType,
15+
)
16+
from pyomnilogic_local.omnitypes import (
17+
BackyardState,
18+
)
19+
20+
21+
@click.command()
22+
@click.pass_context
23+
def backyard(ctx: click.Context) -> None:
24+
"""Display backyard-level information and equipment summary.
25+
26+
Shows overall backyard status including air temperature, system state,
27+
configuration checksum, MSP firmware version, and a summary of all
28+
installed equipment.
29+
30+
Example:
31+
omnilogic get backyard
32+
"""
33+
mspconfig: MSPConfig = ctx.obj["MSPCONFIG"]
34+
telemetry: Telemetry = ctx.obj["TELEMETRY"]
35+
36+
_print_backyard_info(mspconfig.backyard, telemetry.get_telem_by_systemid(mspconfig.backyard.system_id))
37+
38+
39+
def _print_backyard_info(backyardconfig: MSPBackyard, telemetry: TelemetryType | None) -> None:
40+
"""Format and print backyard information in a nice table format.
41+
42+
Args:
43+
backyard: Backyard object from MSPConfig with attributes to display
44+
telemetry: Telemetry object containing current state information
45+
"""
46+
click.echo("\n" + "=" * 60)
47+
click.echo("BACKYARD")
48+
click.echo("=" * 60)
49+
50+
# Combine config and telemetry data
51+
backyard_data: dict[Any, Any] = {**dict(backyardconfig), **dict(telemetry)} if telemetry else dict(backyardconfig)
52+
53+
# Fields to exclude from main display (we'll show equipment counts instead)
54+
exclude_fields = {"sensor", "bow", "colorlogic_light", "relay"}
55+
56+
for attr_name, value in backyard_data.items():
57+
if attr_name in exclude_fields:
58+
continue
59+
60+
if attr_name == "state":
61+
value = BackyardState(value).pretty()
62+
elif isinstance(value, list):
63+
# Format lists nicely
64+
value = ", ".join(str(v) for v in value) if value else "None"
65+
66+
# Format the attribute name to be more readable
67+
display_name = attr_name.replace("_", " ").title()
68+
click.echo(f"{display_name:20} : {value}")
69+
70+
# Show equipment summary
71+
click.echo("\nAttached Equipment:")
72+
click.echo("-" * 60)
73+
74+
equipment_counts = []
75+
76+
if backyardconfig.bow:
77+
equipment_counts.append(f"Bodies of Water: {len(backyardconfig.bow)}")
78+
for bow in backyardconfig.bow:
79+
equipment_counts.append(f" - {bow.name} ({bow.type})")
80+
81+
if backyardconfig.sensor:
82+
equipment_counts.append(f"Backyard Sensors: {len(backyardconfig.sensor)}")
83+
84+
if backyardconfig.colorlogic_light:
85+
equipment_counts.append(f"Backyard ColorLogic Lights: {len(backyardconfig.colorlogic_light)}")
86+
87+
if backyardconfig.relay:
88+
equipment_counts.append(f"Backyard Relays: {len(backyardconfig.relay)}")
89+
90+
if equipment_counts:
91+
for count in equipment_counts:
92+
click.echo(f" {count}")
93+
else:
94+
click.echo(" None")
95+
96+
click.echo("=" * 60)

pyomnilogic_local/cli/get/bows.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
2+
# mypy: disable-error-code="misc"
3+
4+
from typing import Any
5+
6+
import click
7+
8+
from pyomnilogic_local.models.mspconfig import (
9+
MSPBoW,
10+
MSPConfig,
11+
)
12+
from pyomnilogic_local.models.telemetry import (
13+
Telemetry,
14+
TelemetryType,
15+
)
16+
from pyomnilogic_local.omnitypes import (
17+
BodyOfWaterType,
18+
)
19+
20+
21+
@click.command()
22+
@click.pass_context
23+
def bows(ctx: click.Context) -> None:
24+
"""List all Bodies of Water (BOWs) and their current status.
25+
26+
Displays information about all bodies of water including their system IDs,
27+
names, types (pool/spa), water temperature, flow status, and attached equipment.
28+
29+
Example:
30+
omnilogic get bows
31+
"""
32+
mspconfig: MSPConfig = ctx.obj["MSPCONFIG"]
33+
telemetry: Telemetry = ctx.obj["TELEMETRY"]
34+
35+
bows_found = False
36+
37+
# Check for BOWs in the backyard
38+
if mspconfig.backyard.bow:
39+
for bow in mspconfig.backyard.bow:
40+
bows_found = True
41+
_print_bow_info(bow, telemetry.get_telem_by_systemid(bow.system_id))
42+
43+
if not bows_found:
44+
click.echo("No Bodies of Water found in the system configuration.")
45+
46+
47+
def _print_bow_info(bow: MSPBoW, telemetry: TelemetryType | None) -> None:
48+
"""Format and print Body of Water information in a nice table format.
49+
50+
Args:
51+
bow: BOW object from MSPConfig with attributes to display
52+
telemetry: Telemetry object containing current state information
53+
"""
54+
click.echo("\n" + "=" * 60)
55+
click.echo("BODY OF WATER")
56+
click.echo("=" * 60)
57+
58+
# Combine config and telemetry data
59+
bow_data: dict[Any, Any] = {**dict(bow), **dict(telemetry)} if telemetry else dict(bow)
60+
61+
# Fields to exclude from main display (we'll show equipment counts instead)
62+
exclude_fields = {"filter", "relay", "heater", "sensor", "colorlogic_light", "pump", "chlorinator", "csad"}
63+
64+
for attr_name, value in bow_data.items():
65+
if attr_name in exclude_fields:
66+
continue
67+
68+
if attr_name == "type":
69+
value = BodyOfWaterType(value).pretty()
70+
elif isinstance(value, list):
71+
# Format lists nicely
72+
value = ", ".join(str(v) for v in value) if value else "None"
73+
74+
# Format the attribute name to be more readable
75+
display_name = attr_name.replace("_", " ").title()
76+
click.echo(f"{display_name:20} : {value}")
77+
78+
# Show equipment summary
79+
click.echo("\nAttached Equipment:")
80+
click.echo("-" * 60)
81+
82+
equipment_counts = []
83+
if bow.filter:
84+
equipment_counts.append(f"Filters: {len(bow.filter)}")
85+
if bow.pump:
86+
equipment_counts.append(f"Pumps: {len(bow.pump)}")
87+
if bow.heater:
88+
equipment_counts.append("Heater: 1 (virtual)")
89+
if bow.heater.heater_equipment:
90+
equipment_counts.append(f" - Physical Heaters: {len(bow.heater.heater_equipment)}")
91+
if bow.sensor:
92+
equipment_counts.append(f"Sensors: {len(bow.sensor)}")
93+
if bow.colorlogic_light:
94+
equipment_counts.append(f"ColorLogic Lights: {len(bow.colorlogic_light)}")
95+
if bow.relay:
96+
equipment_counts.append(f"Relays: {len(bow.relay)}")
97+
if bow.chlorinator:
98+
equipment_counts.append("Chlorinator: 1")
99+
if bow.csad:
100+
equipment_counts.append(f"CSADs: {len(bow.csad)}")
101+
102+
if equipment_counts:
103+
for count in equipment_counts:
104+
click.echo(f" {count}")
105+
else:
106+
click.echo(" None")
107+
108+
click.echo("=" * 60)
Lines changed: 13 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
# Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
22
# mypy: disable-error-code="misc"
33

4-
from typing import Any
5-
64
import click
75

86
from pyomnilogic_local.cli import ensure_connection
9-
from pyomnilogic_local.models.mspconfig import (
10-
MSPColorLogicLight,
11-
MSPConfig,
12-
)
13-
from pyomnilogic_local.models.telemetry import (
14-
Telemetry,
15-
TelemetryType,
16-
)
17-
from pyomnilogic_local.omnitypes import (
18-
ColorLogicBrightness,
19-
ColorLogicPowerState,
20-
ColorLogicShow,
21-
ColorLogicSpeed,
22-
)
7+
from pyomnilogic_local.cli.get.backyard import backyard
8+
from pyomnilogic_local.cli.get.bows import bows
9+
from pyomnilogic_local.cli.get.filters import filters
10+
from pyomnilogic_local.cli.get.heaters import heaters
11+
from pyomnilogic_local.cli.get.lights import lights
12+
from pyomnilogic_local.cli.get.valves import valves
2313

2414

2515
@click.group()
@@ -35,67 +25,10 @@ def get(ctx: click.Context) -> None:
3525
ensure_connection(ctx)
3626

3727

38-
@get.command()
39-
@click.pass_context
40-
def lights(ctx: click.Context) -> None:
41-
"""List all ColorLogic lights and their current settings.
42-
43-
Displays information about all lights including their system IDs, names,
44-
current state, and available light shows.
45-
46-
Example:
47-
omnilogic get lights
48-
"""
49-
mspconfig: MSPConfig = ctx.obj["MSPCONFIG"]
50-
telemetry: Telemetry = ctx.obj["TELEMETRY"]
51-
52-
lights_found = False
53-
54-
# Check for lights in the backyard
55-
if mspconfig.backyard.colorlogic_light:
56-
for light in mspconfig.backyard.colorlogic_light:
57-
lights_found = True
58-
_print_light_info(light, telemetry.get_telem_by_systemid(light.system_id))
59-
60-
# Check for lights in Bodies of Water
61-
if mspconfig.backyard.bow:
62-
for bow in mspconfig.backyard.bow:
63-
if bow.colorlogic_light:
64-
for cl_light in bow.colorlogic_light:
65-
lights_found = True
66-
_print_light_info(cl_light, telemetry.get_telem_by_systemid(cl_light.system_id))
67-
68-
if not lights_found:
69-
click.echo("No ColorLogic lights found in the system configuration.")
70-
71-
72-
def _print_light_info(light: MSPColorLogicLight, telemetry: TelemetryType | None) -> None:
73-
"""Format and print light information in a nice table format.
74-
75-
Args:
76-
light: Light object from MSPConfig with attributes to display
77-
telemetry: Telemetry object containing current state information
78-
"""
79-
click.echo("\n" + "=" * 60)
80-
81-
light_data: dict[Any, Any] = {**dict(light), **dict(telemetry)} if telemetry else dict(light)
82-
for attr_name, value in light_data.items():
83-
if attr_name == "brightness":
84-
value = ColorLogicBrightness(value).pretty()
85-
elif attr_name == "effects" and isinstance(value, list):
86-
show_names = [show.pretty() if hasattr(show, "pretty") else str(show) for show in value]
87-
value = ", ".join(show_names) if show_names else "None"
88-
elif attr_name == "show" and value is not None:
89-
value = ColorLogicShow(value).pretty()
90-
elif attr_name == "speed":
91-
value = ColorLogicSpeed(value).pretty()
92-
elif attr_name == "state":
93-
value = ColorLogicPowerState(value).pretty()
94-
elif isinstance(value, list):
95-
# Format other lists nicely
96-
value = ", ".join(str(v) for v in value) if value else "None"
97-
98-
# # Format the attribute name to be more readable
99-
display_name = attr_name.replace("_", " ").title()
100-
click.echo(f"{display_name:20} : {value}")
101-
click.echo("=" * 60)
28+
# Register subcommands
29+
get.add_command(backyard)
30+
get.add_command(bows)
31+
get.add_command(filters)
32+
get.add_command(heaters)
33+
get.add_command(lights)
34+
get.add_command(valves)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Need to figure out how to resolve the 'Untyped decorator makes function "..." untyped' errors in mypy when using click decorators
2+
# mypy: disable-error-code="misc"
3+
4+
from typing import Any
5+
6+
import click
7+
8+
from pyomnilogic_local.models.mspconfig import (
9+
MSPConfig,
10+
MSPFilter,
11+
)
12+
from pyomnilogic_local.models.telemetry import (
13+
Telemetry,
14+
TelemetryType,
15+
)
16+
from pyomnilogic_local.omnitypes import (
17+
FilterState,
18+
FilterType,
19+
FilterValvePosition,
20+
FilterWhyOn,
21+
)
22+
23+
24+
@click.command()
25+
@click.pass_context
26+
def filters(ctx: click.Context) -> None:
27+
"""List all filters and their current settings.
28+
29+
Displays information about all filters including their system IDs, names,
30+
current state, speed, valve position, and power usage.
31+
32+
Example:
33+
omnilogic get filters
34+
"""
35+
mspconfig: MSPConfig = ctx.obj["MSPCONFIG"]
36+
telemetry: Telemetry = ctx.obj["TELEMETRY"]
37+
38+
filters_found = False
39+
40+
# Check for filters in Bodies of Water
41+
if mspconfig.backyard.bow:
42+
for bow in mspconfig.backyard.bow:
43+
if bow.filter:
44+
for filt in bow.filter:
45+
filters_found = True
46+
_print_filter_info(filt, telemetry.get_telem_by_systemid(filt.system_id))
47+
48+
if not filters_found:
49+
click.echo("No filters found in the system configuration.")
50+
51+
52+
def _print_filter_info(filt: MSPFilter, telemetry: TelemetryType | None) -> None:
53+
"""Format and print filter information in a nice table format.
54+
55+
Args:
56+
filt: Filter object from MSPConfig with attributes to display
57+
telemetry: Telemetry object containing current state information
58+
"""
59+
click.echo("\n" + "=" * 60)
60+
click.echo("FILTER")
61+
click.echo("=" * 60)
62+
63+
filter_data: dict[Any, Any] = {**dict(filt), **dict(telemetry)} if telemetry else dict(filt)
64+
for attr_name, value in filter_data.items():
65+
if attr_name == "state":
66+
value = FilterState(value).pretty()
67+
elif attr_name == "type":
68+
value = FilterType(value).pretty()
69+
elif attr_name == "valve_position":
70+
value = FilterValvePosition(value).pretty()
71+
elif attr_name == "why_on":
72+
value = FilterWhyOn(value).pretty()
73+
elif isinstance(value, list):
74+
# Format lists nicely
75+
value = ", ".join(str(v) for v in value) if value else "None"
76+
77+
# Format the attribute name to be more readable
78+
display_name = attr_name.replace("_", " ").title()
79+
click.echo(f"{display_name:20} : {value}")
80+
click.echo("=" * 60)

0 commit comments

Comments
 (0)