"""Constants for the PyISYoX Module."""
from __future__ import annotations
import datetime
from enum import IntEnum, IntFlag, StrEnum
# Time Constants / Strings
EMPTY_TIME = datetime.datetime(year=1, month=1, day=1)
MILITARY_TIME = "%Y/%m/%d %H:%M:%S"
STANDARD_TIME = "%Y/%m/%d %I:%M:%S %p"
XML_STRPTIME = "%Y%m%d %H:%M:%S"
XML_STRPTIME_YY = "%y%m%d %H:%M:%S"
[docs]
class EventStreamStatus(StrEnum):
"""Event Stream Status Codes."""
LOST_CONNECTION = "lost_connection"
CONNECTED = "connected"
#: Socket is open but the controller is still replaying every
#: node's *current* status (a burst of ST/DON/DOF frames that are
#: NOT live changes). Distinct from :attr:`INITIALIZING` (no socket
#: yet) and :attr:`CONNECTED` (replay drained, live events flowing).
#: Consumers that fire on events (e.g. HA ``event`` entities) should
#: treat frames received before ``CONNECTED`` as state sync, not
#: real events, to avoid spurious triggers on every (re)connect.
SYNCING = "stream_syncing"
DISCONNECTED = "disconnected"
START_UPDATES = "start_updates"
STOP_UPDATES = "stop_updates"
INITIALIZING = "stream_initializing"
LOADED = "stream_loaded"
RECONNECT_FAILED = "reconnect_failed"
RECONNECTING = "reconnecting"
DISCONNECTING = "stream_disconnecting"
NOT_STARTED = "not_started"
ISY_VALUE_UNKNOWN = -1 * float("inf")
X10_COMMANDS: dict[str, int] = {
"all_off": 1,
"all_on": 4,
"on": 3,
"off": 11,
"bright": 7,
"dim": 15,
}
#: ``<enabled>`` element in profile / program XML.
TAG_ENABLED = "enabled"
[docs]
class Protocol(StrEnum):
"""Entity protocol string enum."""
FOLDER = "program_folder"
GROUP = "group"
INSTEON = "insteon"
INT_VAR = "integer_variable"
ISY = "isy"
NETWORK = "network"
NODE_SERVER = "node_server"
NODE_FOLDER = "node_folder"
PROGRAM = "program"
STATE_VAR = "state_variable"
UPB = "upb"
MATTER = "matter"
ZIGBEE = "zigbee"
ZWAVE = "zwave"
#: Family id present but not one we map to a known device protocol
#: (RCS, Brultech, NCD, UDI, group families, folders, …).
UNKNOWN = "unknown"
[docs]
class NodeFamily(StrEnum):
"""Node family string enum.
IDs 0-9 come from ``ISY-WSDK-5.0.4/WSDL/family.xsd`` (still the
latest published WSDL). 10 (Node Server / PG3) and 12-15 are IoX 6
additions confirmed against eisy hardware: 12 is the Z-Matter
radio acting as a Z-Wave controller, 15 the same radio acting as
a Matter/Thread controller, 13 the folder family, 14 Zigbee.
"""
CORE = "0"
INSTEON = "1"
UPB = "2"
RCS = "3"
ZWAVE = "4"
AUTO = "5"
GENERIC = "6"
UDI = "7"
BRULTECH = "8"
NCD = "9"
NODESERVER = "10"
ZMATTER_ZWAVE = "12"
FOLDER = "13"
ZIGBEE = "14"
MATTER = "15"
PROP_BATTERY_LEVEL = "BATLVL"
PROP_BUSY = "BUSY"
PROP_COMMS_ERROR = "ERR"
PROP_ENERGY_MODE = "CLIEMD"
PROP_HEAT_COOL_STATE = "CLIHCS"
PROP_HUMIDITY = "CLIHUM"
PROP_ON_LEVEL = "OL"
PROP_RAMP_RATE = "RR"
PROP_SCHEDULE_MODE = "CLISMD"
PROP_SETPOINT_COOL = "CLISPC"
PROP_SETPOINT_COOL_DELTA = "CLISPCD" # auto-changeover cool setpoint delta
PROP_SETPOINT_HEAT = "CLISPH"
PROP_SETPOINT_HEAT_DELTA = "CLISPHD" # auto-changeover heat setpoint delta
PROP_STATUS = "ST"
PROP_TEMPERATURE = "CLITEMP"
PROP_UOM = "UOM"
PROP_ZWAVE_PREFIX = "ZW_"
VAR_INTEGER = "1"
VAR_STATE = "2"
CLIMATE_SETPOINT_MIN_GAP = 2
CMD_BACKLIGHT = "BL"
CMD_BEEP = "BEEP"
CMD_BRIGHTEN = "BRT"
CMD_CLIMATE_FAN_SETTING = "CLIFS"
CMD_CLIMATE_MODE = "CLIMD"
CMD_DIM = "DIM"
CMD_DISABLE = "disable"
CMD_DISABLE_RUN_AT_STARTUP = "disableRunAtStartup"
CMD_ENABLE = "enable"
CMD_ENABLE_RUN_AT_STARTUP = "enableRunAtStartup"
CMD_FADE_DOWN = "FDDOWN"
CMD_FADE_STOP = "FDSTOP"
CMD_FADE_UP = "FDUP"
CMD_MANUAL_DIM_BEGIN = "BMAN" # Depreciated, use Fade
CMD_MANUAL_DIM_STOP = "SMAN" # Depreciated, use Fade
CMD_MODE = "MODE"
CMD_OFF = "DOF"
CMD_OFF_FAST = "DFOF"
CMD_ON = "DON"
CMD_ON_FAST = "DFON"
CMD_QUERY = "QUERY"
CMD_RESET = "RESET"
CMD_SECURE = "SECMD"
CMD_X10 = "X10"
# Alarm-panel control verbs (Z-Wave / plugin alarm nodedefs).
CMD_ALARM_ARM = "ARM"
CMD_ALARM_DISARM = "DISARM"
COMMAND_FRIENDLY_NAME: dict[str, str] = {
"ADRPST": "auto_dr_processing_state",
"AIRFLOW": "air_flow",
"ALARM": "alarm",
"ANGLE": "angle_position",
"ANGLPOS": "angle_position",
"ATMPRES": "atmospheric_pressure",
"AWAKE": "awake",
"BARPRES": "barometric_pressure",
"CC": "current",
"CLIFRS": "fan_running_state",
"CLIFSO": "fan_setting_override",
"CO2LVL": "co2_level",
"CPW": "power",
"CTL": "controller_action",
"CV": "voltage",
"DELAY": "delay",
"DEWPT": "dew_point",
"DISTANC": "distance",
"DOF3": "off_3x_key_presses",
"DOF4": "off_4x_key_presses",
"DOF5": "off_5x_key_presses",
"DON3": "on_3x_key_presses",
"DON4": "on_4x_key_presses",
"DON5": "on_5x_key_presses",
"ELECCON": "electrical_conductivity",
"ELECRES": "electrical_resistivity",
PROP_COMMS_ERROR: "device_communication_errors",
"ETO": "evapotranspiration",
"FATM": "fat_mass",
"FREQ": "frequency",
"GPV": "general_purpose",
"GUST": "gust",
"GV0": "custom_control_0",
"GV1": "custom_control_1",
"GV2": "custom_control_2",
"GV3": "custom_control_3",
"GV4": "custom_control_4",
"GV5": "custom_control_5",
"GV6": "custom_control_6",
"GV7": "custom_control_7",
"GV8": "custom_control_8",
"GV9": "custom_control_9",
"GV10": "custom_control_10",
"GV11": "custom_control_11",
"GV12": "custom_control_12",
"GV13": "custom_control_13",
"GV14": "custom_control_14",
"GV15": "custom_control_15",
"GV16": "custom_control_16",
"GV17": "custom_control_17",
"GV18": "custom_control_18",
"GV19": "custom_control_19",
"GV20": "custom_control_20",
"GV21": "custom_control_21",
"GV22": "custom_control_22",
"GV23": "custom_control_23",
"GV24": "custom_control_24",
"GV25": "custom_control_25",
"GV26": "custom_control_26",
"GV27": "custom_control_27",
"GV28": "custom_control_28",
"GV29": "custom_control_29",
"GV30": "custom_control_30",
"GVOL": "gas_volume",
"HAIL": "hail",
"HEATIX": "heat_index",
"HR": "heart_rate",
"LUMIN": "luminance",
"METHANE": "methane_density",
"MOIST": "moisture",
"MOON": "moon_phase",
"MUSCLEM": "muscle_mass",
"OZONE": "ozone",
"PCNT": "pulse_count",
"PF": "power_factor",
"PM10": "particulate_matter_10",
"PM25": "particulate_matter_2.5",
"POP": "percent_chance_of_precipitation",
"PPW": "polarized_power",
"PRECIP": "precipitation",
"PULSCNT": "pulse_count",
"RADON": "radon_concentration",
"RAINRT": "rain_rate",
"RELMOD": "relative_modulation_level",
"RESPR": "respiratory_rate",
"RFSS": "rf_signal_strength",
"ROTATE": "rotation",
"RR": "ramp_rate",
"SEISINT": "seismic_intensity",
"SEISMAG": "seismic_magnitude",
"SMOKED": "smoke_density",
"SOILH": "soil_humidity",
"SOILR": "soil_reactivity",
"SOILS": "soil_salinity",
"SOILT": "soil_temperature",
"SOLRAD": "solar_radiation",
"SPEED": "speed",
"SVOL": "sound_volume",
"TANKCAP": "tank_capacity",
"TEMPEXH": "exhaust_temperature",
"TEMPOUT": "outside_temperature",
"TIDELVL": "tide_level",
"TIME": "time",
"TIMEREM": "time_remaining",
"TPW": "total_energy_used",
"UAC": "user_number",
"USRNUM": "user_number",
"UV": "uv_light",
"VOCLVL": "voc_level",
"WATERF": "water_flow",
"WATERP": "water_pressure",
"WATERT": "water_temperature",
"WATERTB": "boiler_water_temperature",
"WATERTD": "domestic_hot_water_temperature",
"WEIGHT": "weight",
"WINDCH": "wind_chill",
"WINDDIR": "wind_direction",
"WVOL": "water_volume",
CMD_BACKLIGHT: "backlight",
CMD_BEEP: "beep",
CMD_BRIGHTEN: "bright",
CMD_CLIMATE_FAN_SETTING: "fan_state",
CMD_CLIMATE_MODE: "climate_mode",
CMD_DIM: "dim",
CMD_FADE_DOWN: "fade_down",
CMD_FADE_STOP: "fade_stop",
CMD_FADE_UP: "fade_up",
CMD_MANUAL_DIM_BEGIN: "brighten_manual",
CMD_MANUAL_DIM_STOP: "stop_manual",
CMD_MODE: "mode",
CMD_OFF: "off",
CMD_OFF_FAST: "fastoff",
CMD_ON: "on",
CMD_ON_FAST: "faston",
CMD_RESET: "reset",
CMD_SECURE: "secure",
CMD_X10: "x10_command",
PROP_BATTERY_LEVEL: "battery_level",
PROP_BUSY: "busy",
PROP_ENERGY_MODE: "energy_saving_mode",
PROP_HEAT_COOL_STATE: "heat_cool_state",
PROP_HUMIDITY: "humidity",
PROP_ON_LEVEL: "on_level",
PROP_SCHEDULE_MODE: "schedule_mode",
PROP_SETPOINT_COOL: "cool_setpoint",
PROP_SETPOINT_HEAT: "heat_setpoint",
PROP_STATUS: "status",
PROP_TEMPERATURE: "temperature",
PROP_UOM: "unit_of_measure",
}
#: Control codes that represent a silent property/state update rather than
#: a surfaced device command. Reference helper for consumers deciding which
#: event frames to surface. (Kept pending issue #1.)
EVENT_PROPS_IGNORED: list[str] = [
CMD_BEEP,
CMD_BRIGHTEN,
CMD_DIM,
CMD_MANUAL_DIM_BEGIN,
CMD_MANUAL_DIM_STOP,
CMD_FADE_UP,
CMD_FADE_DOWN,
CMD_FADE_STOP,
CMD_OFF,
CMD_OFF_FAST,
CMD_ON,
CMD_ON_FAST,
CMD_RESET,
CMD_X10,
PROP_BUSY,
]
# Special Units of Measure
UOM_ISYV4_DEGREES = "degrees"
UOM_ISYV4_NONE = "n/a"
UOM_BOOLEAN = "2" # 0 = False / 1 = True
UOM_BYTE = "100" # 0-255 8-bit range, no unit
UOM_CLIMATE_MODES = "98"
UOM_CLIMATE_MODES_ZWAVE = "67"
UOM_DOUBLE_TEMP = "101"
UOM_FAN_MODES = "99"
UOM_INDEX = "25"
UOM_ON_OFF = "78" # 0 = Off / 100 = On
UOM_OPEN_CLOSED = "79" # 0 = Open / 100 = Closed
UOM_PERCENTAGE = "51"
UOM_RAW = "56"
UOM_SECONDS = "57"
UOM_TO_STATES: dict[str, dict[str, str]] = {
"11": { # Deadbolt Status
"0": "unlocked",
"100": "locked",
"101": "unknown",
"102": "problem",
},
"15": { # Door Lock Alarm
"1": "master code changed",
"2": "tamper code entry limit",
"3": "escutcheon removed",
"4": "key-manually locked",
"5": "locked by touch",
"6": "key-manually unlocked",
"7": "remote locking jammed bolt",
"8": "remotely locked",
"9": "remotely unlocked",
"10": "deadbolt jammed",
"11": "battery too low to operate",
"12": "critical low battery",
"13": "low battery",
"14": "automatically locked",
"15": "automatic locking jammed bolt",
"16": "remotely power cycled",
"17": "lock handling complete",
"19": "user deleted",
"20": "user added",
"21": "duplicate pin",
"22": "jammed bolt by locking with keypad",
"23": "locked by keypad",
"24": "unlocked by keypad",
"25": "keypad attempt outside schedule",
"26": "hardware failure",
"27": "factory reset",
"28": "manually not fully locked",
"29": "all user codes deleted",
"30": "new user code not added-duplicate code",
"31": "keypad temporarily disabled",
"32": "keypad busy",
"33": "new program code entered",
"34": "rf unlock with invalid user code",
"35": "rf lock with invalid user codes",
"36": "window-door is open",
"37": "window-door is closed",
"38": "window-door handle is open",
"39": "window-door handle is closed",
"40": "user code entered on keypad",
"41": "power cycled",
},
"66": { # Thermostat Heat/Cool State
"0": "idle",
"1": "heating",
"2": "cooling",
"3": "fan_only",
"4": "pending heat",
"5": "pending cool",
"6": "vent",
"7": "aux heat",
"8": "2nd stage heating",
"9": "2nd stage cooling",
"10": "2nd stage aux heat",
"11": "3rd stage aux heat",
},
"67": { # Thermostat Mode
"0": "off",
"1": "heat",
"2": "cool",
"3": "auto",
"4": "aux/emergency heat",
"5": "resume",
"6": "fan_only",
"7": "furnace",
"8": "dry air",
"9": "moist air",
"10": "auto changeover",
"11": "energy save heat",
"12": "energy save cool",
"13": "away",
"14": "program auto",
"15": "program heat",
"16": "program cool",
},
"68": { # Thermostat Fan Mode
"0": "auto",
"1": "on",
"2": "auto high",
"3": "high",
"4": "auto medium",
"5": "medium",
"6": "circulation",
"7": "humidity circulation",
"8": "left-right circulation",
"9": "up-down circulation",
"10": "quiet",
},
"78": {"0": "off", "100": "on"}, # 0-Off 100-On
"79": {"0": "open", "100": "closed"}, # 0-Open 100-Close
"80": { # Thermostat Fan Run State
"0": "off",
"1": "on",
"2": "on high",
"3": "on medium",
"4": "circulation",
"5": "humidity circulation",
"6": "right/left circulation",
"7": "up/down circulation",
"8": "quiet circulation",
},
"84": {"0": "unlock", "1": "lock"}, # Secure Mode
"93": { # Power Management Alarm
"1": "power applied",
"2": "ac mains disconnected",
"3": "ac mains reconnected",
"4": "surge detection",
"5": "volt drop or drift",
"6": "over current detected",
"7": "over voltage detected",
"8": "over load detected",
"9": "load error",
"10": "replace battery soon",
"11": "replace battery now",
"12": "battery is charging",
"13": "battery is fully charged",
"14": "charge battery soon",
"15": "charge battery now",
},
"94": { # Appliance Alarm
"1": "program started",
"2": "program in progress",
"3": "program completed",
"4": "replace main filter",
"5": "failure to set target temperature",
"6": "supplying water",
"7": "water supply failure",
"8": "boiling",
"9": "boiling failure",
"10": "washing",
"11": "washing failure",
"12": "rinsing",
"13": "rinsing failure",
"14": "draining",
"15": "draining failure",
"16": "spinning",
"17": "spinning failure",
"18": "drying",
"19": "drying failure",
"20": "fan failure",
"21": "compressor failure",
},
"95": { # Home Health Alarm
"1": "leaving bed",
"2": "sitting on bed",
"3": "lying on bed",
"4": "posture changed",
"5": "sitting on edge of bed",
},
"96": { # VOC Level
"1": "clean",
"2": "slightly polluted",
"3": "moderately polluted",
"4": "highly polluted",
},
"97": { # Barrier Status
"0": "closed",
"100": "open",
"101": "unknown",
"102": "stopped",
"103": "closing",
"104": "opening",
**{str(b): f"{b} %" for a, b in enumerate(list(range(1, 100)))}, # 1-99 are percentage open
},
"98": { # Insteon Thermostat Mode
"0": "off",
"1": "heat",
"2": "cool",
"3": "auto",
"4": "fan_only",
"5": "program_auto",
"6": "program_heat",
"7": "program_cool",
},
"99": {"7": "on", "8": "auto"}, # Insteon Thermostat Fan Mode
"115": { # Most recent On style action taken for lamp control
"0": "on",
"1": "off",
"2": "fade up",
"3": "fade down",
"4": "fade stop",
"5": "fast on",
"6": "fast off",
"7": "triple press on",
"8": "triple press off",
"9": "4x press on",
"10": "4x press off",
"11": "5x press on",
"12": "5x press off",
},
}
# Translate the "RR" Property to Seconds
INSTEON_RAMP_RATES: dict[str, float] = {
"0": 540,
"1": 480,
"2": 420,
"3": 360,
"4": 300,
"5": 270,
"6": 240,
"7": 210,
"8": 180,
"9": 150,
"10": 120,
"11": 90,
"12": 60,
"13": 47,
"14": 43,
"15": 38.5,
"16": 34,
"17": 32,
"18": 30,
"19": 28,
"20": 26,
"21": 23.5,
"22": 21.5,
"23": 19,
"24": 8.5,
"25": 6.5,
"26": 4.5,
"27": 2,
"28": 0.5,
"29": 0.3,
"30": 0.2,
"31": 0.1,
}
# Insteon battery / stateless devices — motion sensors, RemoteLincs,
# binary-alarm nodedefs, etc. Their ``ST`` is not a persistent state, so
# ``runtime.Group`` skips these members when aggregating a scene's on/off
# state (see ``pyisyox.runtime.group``).
INSTEON_STATELESS_NODEDEFID: list[str] = [
"BinaryAlarm",
"BinaryAlarm_ADV",
"BinaryControl",
"BinaryControl_ADV",
"RemoteLinc2",
"RemoteLinc2_ADV",
"DimmerSwitchOnly",
]
# Referenced from ISY-WSDK 4_fam.xml
# Included for user translations in external modules.
# This is the Node.zwave_props.category property.
DEVTYPE_CATEGORIES: dict[str, str] = {
"0": "uninitialized",
"101": "unknown",
"102": "alarm",
"103": "av control point",
"104": "binary sensor",
"105": "class a motor control",
"106": "class b motor control",
"107": "class c motor control",
"108": "controller",
"109": "dimmer switch",
"110": "display",
"111": "door lock",
"112": "doorbell",
"113": "entry control",
"114": "gateway",
"115": "installer tool",
"116": "motor multiposition",
"117": "climate sensor",
"118": "multilevel sensor",
"119": "multilevel switch",
"120": "on/off power strip",
"121": "on/off power switch",
"122": "on/off scene switch",
"123": "open/close valve",
"124": "pc controller",
"125": "remote",
"126": "remote control",
"127": "av remote control",
"128": "simple remote control",
"129": "repeater",
"130": "residential hrv",
"131": "satellite receiver",
"132": "satellite receiver",
"133": "scene controller",
"134": "scene switch",
"135": "security panel",
"136": "set-top box",
"137": "siren",
"138": "smoke alarm",
"139": "subsystem controller",
"140": "thermostat",
"141": "toggle",
"142": "television",
"143": "energy meter",
"144": "pulse meter",
"145": "water meter",
"146": "gas meter",
"147": "binary switch",
"148": "binary alarm",
"149": "aux alarm",
"150": "co2 alarm",
"151": "co alarm",
"152": "freeze alarm",
"153": "glass break alarm",
"154": "heat alarm",
"155": "motion sensor",
"156": "smoke alarm",
"157": "tamper alarm",
"158": "tilt alarm",
"159": "water alarm",
"160": "door/window alarm",
"161": "test alarm",
"162": "low battery alarm",
"163": "co end of life alarm",
"164": "malfunction alarm",
"165": "heartbeat",
"166": "overheat alarm",
"167": "rapid temp rise alarm",
"168": "underheat alarm",
"169": "leak detected alarm",
"170": "level drop alarm",
"171": "replace filter alarm",
"172": "intrusion alarm",
"173": "tamper code alarm",
"174": "hardware failure alarm",
"175": "software failure alarm",
"176": "contact police alarm",
"177": "contact fire alarm",
"178": "contact medical alarm",
"179": "wakeup alarm",
"180": "timer",
"181": "power management",
"182": "appliance",
"183": "home health",
"184": "barrier",
"185": "notification sensor",
"186": "color switch",
"187": "multilevel switch off on",
"188": "multilevel switch down up",
"189": "multilevel switch close open",
"190": "multilevel switch ccw cw",
"191": "multilevel switch left right",
"192": "multilevel switch reverse forward",
"193": "multilevel switch pull push",
"194": "basic set",
"195": "wall controller",
"196": "barrier handle",
"197": "sound switch",
}
# Referenced from ISY-WSDK cat.xml
# Included for user translations in external modules.
# This is the first part of the Node.type property (before the first ".")
NODE_CATEGORIES: dict[str, str] = {
"0": "generic controller",
"1": "dimming control",
"2": "switch/relay control",
"3": "network bridge",
"4": "irrigation control",
"5": "climate control",
"6": "pool control",
"7": "sensors/actuators",
"8": "home entertainment",
"9": "energy management",
"10": "appliance control",
"11": "plumbing",
"12": "communications",
"13": "computer",
"14": "windows/shades",
"15": "access control",
"16": "security/health/safety",
"17": "surveillance",
"18": "automotive",
"19": "pet care",
"20": "toys",
"21": "timers/clocks",
"22": "holiday",
"113": "a10/x10",
"127": "virtual",
"254": "unknown",
}
[docs]
class SystemStatus(StrEnum):
"""System Status Enum — the ``<action>`` value on ``_5`` event frames."""
NOT_BUSY = "0"
BUSY = "1"
IDLE = "2"
SAFE_MODE = "3"
[docs]
@classmethod
def label(cls, value: str) -> str:
"""Friendly lower-case name for a system-status value, or the
raw value verbatim if it isn't one we know.
Mirrors :meth:`pyisyox.runtime.SystemEventControl.label` so the
two compose cleanly in log lines
(``system_status = not_busy``).
"""
try:
return cls(value).name.lower()
except ValueError:
return value
[docs]
class NodeFlag(IntFlag):
"""Node operations flag enum."""
INIT = 0x01 # needs to be initialized
TO_SCAN = 0x02 # needs to be scanned
GROUP = 0x04 # it’s a group!
ROOT = 0x08 # it’s the root group
IN_ERR = 0x10 # it’s in error!
NEW = 0x20 # brand new node
TO_DELETE = 0x40 # has to be deleted later
DEVICE_ROOT = 0x80 # root device such as KPL load
DEV_CMD_MEMORY_WRITE = "0x2E"
DEV_BL_ADDR = "0x0264"
DEV_OL_ADDR = "0x0032"
DEV_RR_ADDR = "0x0021"
[docs]
class UDHierarchyNodeType(IntEnum):
"""Enum representation of node types."""
NOTSET = 0
NODE = 1
GROUP = 2
FOLDER = 3