Skip to content

Commit

Permalink
0.9.6.21 - Added X10 Home Assistant Service (Action)
Browse files Browse the repository at this point in the history
0.9.6.21 - Added X10 Home Assistant Service (Action)
Although the X10 devices are Switches in Home Assistant, and provide a simple On/Off control in the Frontend, it is now possible to create a service (action) call to change the X10 device state as Off, On, Dimmer, Brighten.  These last two have no feedback on how dim/bright the device is and so it cannot be made a Light Entity in Home Assistant.
  • Loading branch information
davesmeghead committed Aug 10, 2024
1 parent bacdea0 commit 9d1cf68
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 11 deletions.
32 changes: 30 additions & 2 deletions custom_components/visonic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
SERVICE_RELOAD,
)

from .pyconst import AlPanelCommand
from .pyconst import AlPanelCommand, AlX10Command
from .client import VisonicClient
from .const import (
DOMAIN,
ALARM_PANEL_EVENTLOG,
ALARM_PANEL_RECONNECT,
ALARM_PANEL_COMMAND,
ALARM_PANEL_X10,
ALARM_SENSOR_BYPASS,
ALARM_SENSOR_IMAGE,
ATTR_BYPASS,
Expand All @@ -45,6 +46,7 @@
CONF_EMULATION_MODE,
CONF_SENSOR_EVENTS,
CONF_COMMAND,
CONF_X10_COMMAND,
available_emulation_modes,
AvailableNotifications
)
Expand All @@ -53,7 +55,7 @@

CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

# the 5 schemas for the HA service calls
# the 6 schemas for the HA service calls
ALARM_SCHEMA_EVENTLOG = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
Expand All @@ -69,6 +71,13 @@
}
)

ALARM_SCHEMA_X10 = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(CONF_X10_COMMAND) : vol.In([x.lower().replace("_"," ").title() for x in list(AlX10Command.get_variables().keys())]),
}
)

ALARM_SCHEMA_RECONNECT = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
Expand Down Expand Up @@ -187,6 +196,17 @@ async def service_panel_command(call):
sendHANotification(f"Service Panel command failed - Panel {panel} not found")
else:
sendHANotification(f"Service Panel command failed - Panel not found")

async def service_panel_x10(call):
"""Handler for panel command service"""
_LOGGER.info(f"Service Panel x10 called")
client, panel = getClient(call)
if client is not None:
await client.service_panel_x10(call)
elif panel is not None:
sendHANotification(f"Service Panel x10 failed - Panel {panel} not found")
else:
sendHANotification(f"Service Panel x10 failed - Panel not found")

async def service_sensor_bypass(call):
"""Handler for sensor bypass service"""
Expand Down Expand Up @@ -242,12 +262,20 @@ async def handle_reload(call) -> None:
service_panel_command,
schema=ALARM_SCHEMA_COMMAND,
)
hass.services.async_register(
DOMAIN,
ALARM_PANEL_X10,
service_panel_x10,
schema=ALARM_SCHEMA_X10,
)
hass.services.async_register(
DOMAIN,
ALARM_SENSOR_BYPASS,
service_sensor_bypass,
schema=ALARM_SCHEMA_BYPASS,
)


# hass.services.async_register(
# DOMAIN,
# ALARM_SENSOR_IMAGE,
Expand Down
41 changes: 38 additions & 3 deletions custom_components/visonic/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
CONF_RETRY_CONNECTION_COUNT,
CONF_RETRY_CONNECTION_DELAY,
CONF_COMMAND,
CONF_X10_COMMAND,
DOMAIN,
NOTIFICATION_ID,
NOTIFICATION_TITLE,
Expand All @@ -122,7 +123,7 @@
# "trigger",
#]

CLIENT_VERSION = "0.9.6.20"
CLIENT_VERSION = "0.9.6.21"

MAX_CLIENT_LOG_ENTRIES = 300

Expand Down Expand Up @@ -913,7 +914,7 @@ def getConfigData(self) -> PanelConfig:

async def _checkUserPermission(self, call, perm, entity):
user = await self.hass.auth.async_get_user(call.context.user_id)
self.logstate_debug(f"User check {call.context.user_id=} user={user=}")
#self.logstate_debug(f"User check {call.context.user_id=} user={user=}")

if user is None:
raise UnknownUser(
Expand Down Expand Up @@ -1051,7 +1052,7 @@ def _generateBusEventReason(self, event_id: PanelCondition, reason: AlCommandSta
def setX10(self, ident: int, state: AlX10Command):
"""Send an X10 command to the panel."""
if not self.DisableAllCommands:
# ident in range 0 to 15, state can be one of "off", "on", "dim", "brighten"
# ident in range 0 to 15, state can be one of "off", "on", "dimmer", "brighten"
if self.visonicProtocol is not None:
retval = self.visonicProtocol.setX10(ident, state)
self._generateBusEventReason(PanelCondition.CHECK_X10_COMMAND, retval, "X10", "Send X10 Command")
Expand Down Expand Up @@ -1259,6 +1260,19 @@ def sendBypass(self, devid: int, bypass: bool, code: str) -> AlCommandStatus:
self.createNotification(AvailableNotifications.COMMAND_NOT_SENT, f"Visonic Alarm Panel: Panel Commands Disabled")
return AlCommandStatus.FAIL_USER_CONFIG_PREVENTED

def sendX10(self, devid: int, command : AlX10Command) -> AlCommandStatus:
"""Send the x10 command to the panel."""
if not self.DisableAllCommands:
if self.visonicProtocol is not None:
retval = self.visonicProtocol.setX10(devid, command)
else:
retval = AlCommandStatus.FAIL_PANEL_NO_CONNECTION
self._generateBusEventReason(PanelCondition.CHECK_X10_COMMAND, retval, "X10", "X10 Request")
return retval
else:
self.createNotification(AvailableNotifications.COMMAND_NOT_SENT, f"Visonic Alarm Panel: Panel Commands Disabled")
return AlCommandStatus.FAIL_USER_CONFIG_PREVENTED

async def service_sensor_bypass(self, call):
"""Service call to bypass a sensor in the panel."""
if await self.check_the_basics(call, "sensor bypass"):
Expand Down Expand Up @@ -1333,6 +1347,27 @@ async def service_panel_command(self, call):
except Exception as ex:
self.logstate_warning(f"Not making command request. Exception {ex}")

def sendX10Command(self, devid: int, command : AlX10Command):
"""Send a request to set the X10 device """
if not self.DisableAllCommands:
self.sendX10(devid, command)
else:
self.createNotification(AvailableNotifications.COMMAND_NOT_SENT, f"Visonic Alarm Panel: Panel Commands Disabled")

async def service_panel_x10(self, call):
"""Service call to set an x10 device in the panel."""
if await self.check_the_basics(call, "x10 command"):
devid, eid = await self.decode_entity(call, Platform.SWITCH, "x10 switch command", AvailableNotifications.X10_PROBLEM) # ************************************************************************************************
if devid is not None and devid >= 1 and devid <= 16:
if CONF_X10_COMMAND in call.data:
command = call.data[CONF_X10_COMMAND]
command_x = AlX10Command.value_of(command.upper());
self.logstate_debug(f" X10 Command {command} {command_x}")
self.sendX10Command(devid, command_x)
else:
self.createNotification(AvailableNotifications.COMMAND_NOT_SENT, f"Attempt to set X10 device for panel {self.getPanelID()}, command not set for entity {eid}")
else:
self.createNotification(AvailableNotifications.X10_PROBLEM, f"Attempt to set X10 device for panel {self.getPanelID()}, incorrect device {devid} for entity {eid}")

# =======================================================================================================
# =======================================================================================================
Expand Down
3 changes: 3 additions & 0 deletions custom_components/visonic/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SensorEntityFeature(IntFlag):

# The HA Services. These strings match the content of the services.yaml file
ALARM_PANEL_COMMAND = "alarm_panel_command"
ALARM_PANEL_X10 = "alarm_panel_x10"
ALARM_PANEL_EVENTLOG = "alarm_panel_eventlog"
ALARM_PANEL_RECONNECT = "alarm_panel_reconnect"
ALARM_SENSOR_BYPASS = "alarm_sensor_bypass"
Expand Down Expand Up @@ -82,6 +83,7 @@ class SensorEntityFeature(IntFlag):
CONF_LANGUAGE = "language"
CONF_EMULATION_MODE = "emulation_mode"
CONF_COMMAND = "command"
CONF_X10_COMMAND = "x10command"

# settings than can be modified
CONF_ENABLE_REMOTE_ARM = "allow_remote_arm"
Expand Down Expand Up @@ -116,6 +118,7 @@ class AvailableNotifications(str, Enum):
IMAGE_PROBLEM = 'image_problem'
EVENTLOG_PROBLEM = 'eventlog_problem'
COMMAND_NOT_SENT = 'command_not_sent'
X10_PROBLEM = 'x10_problem'

available_emulation_modes = [
"Powerlink Emulation",
Expand Down
2 changes: 1 addition & 1 deletion custom_components/visonic/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"loggers": ["visonic"],
"requirements": ["Pillow", "pyserial_asyncio"],
"single_config_entry": false,
"version": "0.9.6.20"
"version": "0.9.6.21"
}
2 changes: 1 addition & 1 deletion custom_components/visonic/pyconst.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class AlPanelCommand(AlEnum):
class AlX10Command(AlEnum):
OFF = AlIntEnum(0)
ON = AlIntEnum(1)
DIM = AlIntEnum(2)
DIMMER = AlIntEnum(2)
BRIGHTEN = AlIntEnum(3)
a = AlX10Command()

Expand Down
2 changes: 1 addition & 1 deletion custom_components/visonic/pyhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def fromJSON(self, decode):
self.location = titlecase(decode["location"])
if "state" in decode:
s = AlX10Command.value_of(decode["state"].upper())
self.state = (s == AlX10Command.ON or s == AlX10Command.BRIGHTEN or s == AlX10Command.DIM)
self.state = (s == AlX10Command.ON or s == AlX10Command.BRIGHTEN or s == AlX10Command.DIMMER)

def toJSON(self) -> dict:
dd=json.dumps({
Expand Down
4 changes: 2 additions & 2 deletions custom_components/visonic/pyvisonic.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def convertByteArray(s) -> bytearray:
from pyhelper import (toString, MyChecksumCalc, AlImageManager, ImageRecord, titlecase, pmPanelTroubleType_t, pmPanelAlarmType_t, AlPanelInterfaceHelper,
AlSensorDeviceHelper, AlSwitchDeviceHelper)

PLUGIN_VERSION = "1.3.6.5"
PLUGIN_VERSION = "1.3.6.6"

# Some constants to help readability of the code

Expand Down Expand Up @@ -346,7 +346,7 @@ def convertByteArray(s) -> bytearray:

# Data to embed in the MSG_X10PGM message
pmX10State_t = {
AlX10Command.OFF : 0x00, AlX10Command.ON : 0x01, AlX10Command.DIM : 0x0A, AlX10Command.BRIGHTEN : 0x0B
AlX10Command.OFF : 0x00, AlX10Command.ON : 0x01, AlX10Command.DIMMER : 0x0A, AlX10Command.BRIGHTEN : 0x0B
}

##############################################################################################################################################################################################################################################
Expand Down
22 changes: 22 additions & 0 deletions custom_components/visonic/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ alarm_panel_reconnect:
integration: visonic
domain: alarm_control_panel


alarm_panel_x10:
fields:
entity_id:
required: true
example: 'switch.visonic_x02'
selector:
entity:
integration: visonic
domain: switch
x10command:
required: true
example: "Off"
default: "Off"
selector:
select:
options:
- "Off"
- "On"
- "Dimmer"
- "Brighten"

alarm_sensor_bypass:
fields:
entity_id:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/visonic/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def device_info(self):
"manufacturer": "Visonic",
}

# "off" "on" "dim" "brighten"
# "off" "on" "dimmer" "brighten"
def turnmeonandoff(self, state : AlX10Command):
"""Send disarm command."""
self._client.setX10(self._x10id, state)
Expand Down
22 changes: 22 additions & 0 deletions custom_components/visonic/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@
}
}
},
"alarm_panel_x10": {
"name": "Command X10",
"description": "Send an X10 Command to an X10 Device.",
"fields": {
"entity_id": {
"name": "Visonic X10 Switch",
"description": "Name of the visonic x10 switch. This is case sensitive and a mandatory setting. The 'switch.' text is optional."
},
"x10command": {
"name": "X10 Command",
"description": "X10 Command: Off, On, Dimmer, Brighten."
}
}
},
"alarm_panel_reconnect": {
"name": "Panel Reconnection",
"description": "Reconnect to the Alarm Panel following a previous problem.",
Expand Down Expand Up @@ -239,6 +253,14 @@
"arm_away_instant": "Arm Away Instant"
}
},
"x10command": {
"options": {
"Off": "Off",
"On": "On",
"Dimmer": "Decrease Brightness",
"Brighten": "Increase Brightness"
}
},
"siren_sounding": {
"options": {
"intruder": "Intruder",
Expand Down

0 comments on commit 9d1cf68

Please sign in to comment.