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
22 changes: 22 additions & 0 deletions pyomnilogic_local/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,29 @@
from __future__ import annotations

from .api import OmniLogicAPI
from .exceptions import (
OmniCommandError,
OmniConnectionError,
OmniFragmentationError,
OmniLogicError,
OmniMessageFormatError,
OmniProtocolError,
OmniTimeoutError,
OmniValidationError,
)
from .message import OmniLogicMessage
from .protocol import OmniLogicProtocol

__all__ = [
"OmniCommandError",
"OmniConnectionError",
"OmniFragmentationError",
"OmniLogicAPI",
"OmniLogicError",
"OmniLogicMessage",
"OmniLogicProtocol",
"OmniMessageFormatError",
"OmniProtocolError",
"OmniTimeoutError",
"OmniValidationError",
]
73 changes: 41 additions & 32 deletions pyomnilogic_local/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,30 +111,39 @@ def __init__(
self.controller_port = controller_port
self.response_timeout = response_timeout

@overload
async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[True]) -> str: ...
@overload
async def async_send_message(self, message_type: MessageType, message: str | None, need_response: Literal[False]) -> None: ...
async def async_send_message(self, message_type: MessageType, message: str | None, need_response: bool = False) -> str | None:
async def async_send(self, message_type: MessageType, message: str) -> None:
"""Send a message via the Hayward Omni UDP protocol along with properly handling timeouts and responses.

Args:
message_type (MessageType): A selection from MessageType indicating what type of communication you are sending
message (str | None): The XML body of the message to deliver
need_response (bool, optional): Should a response be received and returned to the caller. Defaults to False.
message (str): The XML body of the message to deliver

Returns:
None
"""
loop = asyncio.get_running_loop()
transport, protocol = await loop.create_datagram_endpoint(OmniLogicProtocol, remote_addr=(self.controller_ip, self.controller_port))

try:
await protocol.async_send(message_type, message)
finally:
transport.close()

async def async_send_and_receive(self, message_type: MessageType, message: str) -> str:
"""Convenience method to send a message and receive a response without needing to specify need_response every time.

Args:
message_type (MessageType): A selection from MessageType indicating what type of communication you are sending
message (str): The message payload to send.

Returns:
str | None: The response body sent from the Omni if need_response indicates that a response will be sent
str: The response body sent from the Omni
"""
loop = asyncio.get_running_loop()
transport, protocol = await loop.create_datagram_endpoint(OmniLogicProtocol, remote_addr=(self.controller_ip, self.controller_port))

resp: str | None = None
try:
if need_response:
resp = await protocol.send_and_receive(message_type, message, response_timeout=self.response_timeout)
else:
await protocol.send_message(message_type, message)
resp = await protocol.async_send_and_receive(message_type, message)
finally:
transport.close()

Expand Down Expand Up @@ -164,7 +173,7 @@ async def async_get_mspconfig(self, raw: bool = False) -> MSPConfig | str:

_LOGGER.debug("Sending RequestConfiguration with body: %s", req_body)

resp = await self.async_send_message(MessageType.REQUEST_CONFIGURATION, req_body, True)
resp = await self.async_send_and_receive(MessageType.REQUEST_CONFIGURATION, req_body)

_LOGGER.debug("Received response for RequestConfiguration: %s", resp)

Expand Down Expand Up @@ -206,7 +215,7 @@ async def async_get_filter_diagnostics(self, pool_id: int, equipment_id: int, ra

_LOGGER.debug("Sending GetUIFilterDiagnosticInfo with body: %s", req_body)

resp = await self.async_send_message(MessageType.GET_FILTER_DIAGNOSTIC_INFO, req_body, True)
resp = await self.async_send_and_receive(MessageType.GET_FILTER_DIAGNOSTIC_INFO, req_body)

_LOGGER.debug("Received response for GetUIFilterDiagnosticInfo: %s", resp)

Expand Down Expand Up @@ -235,7 +244,7 @@ async def async_get_telemetry(self, raw: bool = False) -> Telemetry | str:

_LOGGER.debug("Sending RequestTelemetryData with body: %s", req_body)

resp = await self.async_send_message(MessageType.GET_TELEMETRY, req_body, True)
resp = await self.async_send_and_receive(MessageType.GET_TELEMETRY, req_body)

_LOGGER.debug("Received response for RequestTelemetryData: %s", resp)

Expand Down Expand Up @@ -276,7 +285,7 @@ async def async_set_heater(

_LOGGER.debug("Sending SetUIHeaterCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_HEATER_COMMAND, req_body, False)
return await self.async_send(MessageType.SET_HEATER_COMMAND, req_body)

async def async_set_solar_heater(
self,
Expand Down Expand Up @@ -311,7 +320,7 @@ async def async_set_solar_heater(

_LOGGER.debug("Sending SetUISolarSetPointCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_SOLAR_SET_POINT_COMMAND, req_body, False)
return await self.async_send(MessageType.SET_SOLAR_SET_POINT_COMMAND, req_body)

async def async_set_heater_mode(
self,
Expand Down Expand Up @@ -346,7 +355,7 @@ async def async_set_heater_mode(

_LOGGER.debug("Sending SetUIHeaterModeCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_HEATER_MODE_COMMAND, req_body, False)
return await self.async_send(MessageType.SET_HEATER_MODE_COMMAND, req_body)

async def async_set_heater_enable(
self,
Expand Down Expand Up @@ -381,7 +390,7 @@ async def async_set_heater_enable(

_LOGGER.debug("Sending SetHeaterEnable with body: %s", req_body)

return await self.async_send_message(MessageType.SET_HEATER_ENABLED, req_body, False)
return await self.async_send(MessageType.SET_HEATER_ENABLED, req_body)

async def async_set_equipment(
self,
Expand Down Expand Up @@ -443,7 +452,7 @@ async def async_set_equipment(

_LOGGER.debug("Sending SetUIEquipmentCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_EQUIPMENT, req_body, False)
return await self.async_send(MessageType.SET_EQUIPMENT, req_body)

async def async_set_filter_speed(self, pool_id: int, equipment_id: int, speed: int) -> None:
"""Set the speed for a variable speed filter/pump.
Expand Down Expand Up @@ -471,7 +480,7 @@ async def async_set_filter_speed(self, pool_id: int, equipment_id: int, speed: i

_LOGGER.debug("Sending SetUIFilterSpeedCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_FILTER_SPEED, req_body, False)
return await self.async_send(MessageType.SET_FILTER_SPEED, req_body)

async def async_set_light_show(
self,
Expand Down Expand Up @@ -543,7 +552,7 @@ async def async_set_light_show(

_LOGGER.debug("Sending SetStandAloneLightShow with body: %s", req_body)

return await self.async_send_message(MessageType.SET_STANDALONE_LIGHT_SHOW, req_body, False)
return await self.async_send(MessageType.SET_STANDALONE_LIGHT_SHOW, req_body)

async def async_set_chlorinator_enable(self, pool_id: int, enabled: int | bool) -> None:
body_element = ET.Element("Request", {"xmlns": XML_NAMESPACE})
Expand All @@ -561,7 +570,7 @@ async def async_set_chlorinator_enable(self, pool_id: int, enabled: int | bool)

_LOGGER.debug("Sending SetCHLOREnable with body: %s", req_body)

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

# This is used to set the ORP target value on a CSAD
async def async_set_csad_orp_target_level(
Expand All @@ -587,7 +596,7 @@ async def async_set_csad_orp_target_level(

_LOGGER.debug("Sending SetUICSADORPTargetLevel with body: %s", req_body)

return await self.async_send_message(MessageType.SET_CSAD_ORP_TARGET, req_body, False)
return await self.async_send(MessageType.SET_CSAD_ORP_TARGET, req_body)

# This is used to set the pH target value on a CSAD
async def async_set_csad_target_value(
Expand All @@ -613,7 +622,7 @@ async def async_set_csad_target_value(

_LOGGER.debug("Sending UISetCSADTargetValue with body: %s", req_body)

return await self.async_send_message(MessageType.SET_CSAD_TARGET_VALUE, req_body, False)
return await self.async_send(MessageType.SET_CSAD_TARGET_VALUE, req_body)

async def async_set_chlorinator_params(
self,
Expand Down Expand Up @@ -656,7 +665,7 @@ async def async_set_chlorinator_params(

_LOGGER.debug("Sending SetCHLORParams with body: %s", req_body)

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

async def async_set_chlorinator_superchlorinate(
self,
Expand All @@ -681,7 +690,7 @@ async def async_set_chlorinator_superchlorinate(

_LOGGER.debug("Sending SetUISuperCHLORCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_SUPERCHLORINATE, req_body, False)
return await self.async_send(MessageType.SET_SUPERCHLORINATE, req_body)

async def async_restore_idle_state(self) -> None:
body_element = ET.Element("Request", {"xmlns": XML_NAMESPACE})
Expand All @@ -695,7 +704,7 @@ async def async_restore_idle_state(self) -> None:

_LOGGER.debug("Sending RestoreIdleState with body: %s", req_body)

return await self.async_send_message(MessageType.RESTORE_IDLE_STATE, req_body, False)
return await self.async_send(MessageType.RESTORE_IDLE_STATE, req_body)

async def async_set_spillover(
self,
Expand Down Expand Up @@ -738,7 +747,7 @@ async def async_set_spillover(

_LOGGER.debug("Sending SetUISpilloverCmd with body: %s", req_body)

return await self.async_send_message(MessageType.SET_SPILLOVER, req_body, False)
return await self.async_send(MessageType.SET_SPILLOVER, req_body)

async def async_set_group_enable(
self,
Expand Down Expand Up @@ -781,7 +790,7 @@ async def async_set_group_enable(

_LOGGER.debug("Sending RunGroupCmd with body: %s", req_body)

return await self.async_send_message(MessageType.RUN_GROUP_CMD, req_body, False)
return await self.async_send(MessageType.RUN_GROUP_CMD, req_body)

async def async_edit_schedule(
self,
Expand Down Expand Up @@ -854,4 +863,4 @@ async def async_edit_schedule(

_LOGGER.debug("Sending EditUIScheduleCmd with body: %s", req_body)

return await self.async_send_message(MessageType.EDIT_SCHEDULE, req_body, False)
return await self.async_send(MessageType.EDIT_SCHEDULE, req_body)
4 changes: 2 additions & 2 deletions pyomnilogic_local/api/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
BLOCK_MESSAGE_HEADER_OFFSET = 8 # Offset to skip block message header and get to payload

# Timing Constants (in seconds)
OMNI_RETRANSMIT_TIME = 2.1 # Time Omni waits before retransmitting a packet
OMNI_RETRANSMIT_TIME = 2 # Time Omni waits before retransmitting a packet
OMNI_RETRANSMIT_COUNT = 5 # Number of retransmit attempts (6 total including initial)
ACK_WAIT_TIMEOUT = 1 # Timeout waiting for ACK response, 0.5 showed to be just a tad too short in some cases.
ACK_WAIT_TIMEOUT = OMNI_RETRANSMIT_TIME * 2 # Timeout waiting for ACK response, 0.5 showed to be just a tad too short in some cases.
DEFAULT_RESPONSE_TIMEOUT = OMNI_RETRANSMIT_TIME * OMNI_RETRANSMIT_COUNT # Default timeout for receiving responses

# Network Constants
Expand Down
Loading
Loading