Skip to content

Commit

Permalink
0.10.3.0 - Changes per description below
Browse files Browse the repository at this point in the history
1. Removed lastevent attribute from Alarm Entity.  I did say I'd do this eventually!  Please use lasteventname and lasteventaction instead.
2. Sensors have 2 Zone Name attributes. The name translated by the HA language translation files and the zone name that is reported by the Panel itself.
3. Renamed mode attribute of the Alarm Entity.  This seemed to be in conflict with HA somehow so it is now called emulationmode.
4. Lots mode decoding of B0 message data
5. Instead of terminating the Integration on a disconnection, the Integration with stop and then attempt to connect again.  I struggle to test this as my system works so please let me know of issues.
  • Loading branch information
davesmeghead committed Jan 4, 2025
1 parent 2d7771d commit 9887163
Show file tree
Hide file tree
Showing 13 changed files with 458 additions and 313 deletions.
6 changes: 3 additions & 3 deletions custom_components/visonic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,12 @@ async def service_panel_eventlog(call):
else:
sendHANotification(f"Event log failed - Panel not found")

async def service_panel_reconnect(call):
async def async_service_panel_reconnect(call):
"""Handler for panel reconnect service"""
_LOGGER.info("Service Panel reconnect called")
client, panel = getClient(call)
if client is not None:
await client.service_panel_reconnect(call)
await client.async_service_panel_reconnect(call)
elif panel is not None:
sendHANotification(f"Service Panel reconnect failed - Panel {panel} not found")
else:
Expand Down Expand Up @@ -398,7 +398,7 @@ async def handle_reload(call) -> None:
hass.services.async_register(
DOMAIN,
ALARM_PANEL_RECONNECT,
service_panel_reconnect,
async_service_panel_reconnect,
schema=ALARM_SCHEMA_RECONNECT,
)
hass.services.async_register(
Expand Down
6 changes: 5 additions & 1 deletion custom_components/visonic/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,11 @@ def extra_state_attributes(self):
attr["zone_tamper"] = t

#attr["zone type"] = self.ztype
attr["zone_name"] = self._visonic_device.getZoneLocation()
zn = self._visonic_device.getZoneLocation()
if len(zn) == 2:
attr["zone_name"] = zn[0]
attr["zone_name_panel"] = "Unknown" if zn[1] is None else zn[1]

attr["zone_type"] = self._visonic_device.getZoneType()
attr["zone_chime"] = self._visonic_device.getChimeType()
attr["zone_trouble"] = self._visonic_device.getProblem()
Expand Down
18 changes: 11 additions & 7 deletions custom_components/visonic/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
PIN_REGEX,
)

CLIENT_VERSION = "0.10.2.0"
CLIENT_VERSION = "0.10.3.0"

MAX_CLIENT_LOG_ENTRIES = 300

Expand Down Expand Up @@ -590,7 +590,7 @@ def getPanelStatusDict(self, partition : int | None = None, include_extended_sta
pd = self.visonicProtocol.getPanelStatusDict(partition, include_extended_status)
if partition is None:
#self.logstate_debug(f"Client Dict {pd}")
pd["lastevent"] = self.PanelLastEventName + "/" + self.PanelLastEventAction
#pd["lastevent"] = self.PanelLastEventName + "/" + self.PanelLastEventAction
pd["lasteventname"] = self.PanelLastEventName
pd["lasteventaction"] = self.PanelLastEventAction
pd["lasteventtime"] = self.PanelLastEventTime
Expand Down Expand Up @@ -1110,6 +1110,9 @@ def onPanelChangeHandler(self, event_id: AlCondition | PanelCondition, data : di
if event_id == AlCondition.PANEL_UPDATE:
if data is not None and len(data) == 4 and PE_NAME in data and data[PE_NAME] >= 0 and PE_PARTITION in data:
# The panel has partitions

self.logstate_debug(f"[onPanelChangeHandler] {type(self.myPanelEventCoordinator)} set to {self.myPanelEventCoordinator}")

partition = data[PE_PARTITION]
if self.myPanelEventCoordinator is None:
# initialise as a dict, the partition is the key
Expand Down Expand Up @@ -1249,7 +1252,7 @@ def onDisconnect(self, termination : AlTerminationType):

#self.panel_exception_counter = self.panel_exception_counter + 1
self.panel_disconnection_counter = self.panel_disconnection_counter + 1
asyncio.ensure_future(self.async_service_panel_stop(), loop=self.hass.loop)
asyncio.ensure_future(self.async_service_panel_reconnect(), loop=self.hass.loop)

# pmGetPin: Convert a PIN given as 4 digit string in the PIN PDU format as used in messages to powermax
def pmGetPin(self, code: str, forcedKeypad: bool, partition : int):
Expand Down Expand Up @@ -1713,7 +1716,7 @@ async def service_panel_x10(self, call):
def _createSocketConnection(self, address, port):
try:
#self.logstate_debug(f"Setting TCP socket Options {address} {port}")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setblocking(1) # Set blocking to on, this is the default but just make sure
Expand Down Expand Up @@ -1952,7 +1955,7 @@ async def service_comms_stop(self):
await asyncio.sleep(0.5)
# not a mistake, wait a bit longer to make sure it's closed as we get no feedback (we only get the fact that the queue is empty)

async def service_panel_reconnect(self, call):
async def async_service_panel_reconnect(self, call):
"""Service call to re-connect the connection."""
if not self.isPanelConnected():
raise HomeAssistantError(
Expand All @@ -1968,10 +1971,11 @@ async def service_panel_reconnect(self, call):
# Check security permissions (that this user has access to the alarm panel entity)
await self._checkUserPermission(call, POLICY_CONTROL, Platform.ALARM_CONTROL_PANEL + "." + slugify(self.getAlarmPanelUniqueIdent()))

self.logstate_debug("User has requested visonic panel reconnection")
self.logstate_debug(f"Stopping Client and Reconnecting to Visonic Panel {self.getPanelID()}")
#self.logstate_debug("User has requested visonic panel reconnection")
await self.async_service_panel_stop()
await asyncio.sleep(3.0)
await self.async_service_panel_start(False)
await self.async_service_panel_start(True)

async def async_disconnect_callback(self):
"""Service call to disconnect."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/visonic/examples/complete_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ async def service_panel_start(self):
else:
print("Failure - not connected to panel")

async def service_panel_reconnect(self, call):
async def async_service_panel_reconnect(self, call):
""" Service call to re-connect the connection """
print("User has requested visonic panel reconnection")
await self.service_comms_stop()
Expand Down
2 changes: 1 addition & 1 deletion custom_components/visonic/pyconst.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def getSensorType(self) -> AlSensorType:
return AlSensorType.UNKNOWN

@abstractmethod
def getZoneLocation(self) -> str:
def getZoneLocation(self) -> (str, str):
return ""

@abstractmethod
Expand Down
68 changes: 7 additions & 61 deletions custom_components/visonic/pyhelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def __init__(self, **kwargs):
self.sid = kwargs.get("sid", 0) # int sensor id
self.ztype = kwargs.get("ztype", 0) # int zone type
self.zname = kwargs.get("zname", "Unknown") # str zone name
self.zpanelname = kwargs.get("zpanelname", "") # str zone name
self.zchime = kwargs.get("zchime", "Unknown") # str zone chime
self.zchimeref = kwargs.get("zchimeref", {}) # set partition set (could be in more than one partition)
self.partition = kwargs.get("partition", {}) # set partition set (could be in more than one partition)
Expand Down Expand Up @@ -298,6 +299,7 @@ def __eq__(self, other):
and self.model == other.model
and self.ztype == other.ztype
and self.zname == other.zname
and self.zpanelname == other.zpanelname
and self.zchime == other.zchime
and self.partition == other.partition
and self.bypass == other.bypass
Expand Down Expand Up @@ -365,8 +367,8 @@ def getSensorType(self) -> AlSensorType:
def getLastTriggerTime(self) -> datetime:
return self.triggertime

def getZoneLocation(self) -> str:
return self.zname
def getZoneLocation(self) -> (str, str):
return (self.zname, self.zpanelname)

def getZoneType(self) -> str:
return self.ztypeName
Expand Down Expand Up @@ -500,62 +502,6 @@ def do_tamper(self, val : bool) -> bool:
return True # The value has changed
return False # The value has not changed

"""
# JSON conversions
def fromJSON(self, decode):
#log.debug(f" In sensor fromJSON start {self}")
if "triggered" in decode:
self.triggered = toBool(decode["triggered"])
if "open" in decode:
self.status = toBool(decode["open"])
if "bypass" in decode:
self.bypass = toBool(decode["bypass"])
if "low_battery" in decode:
self.lowbatt = toBool(decode["low_battery"])
if "enrolled" in decode:
self.enrolled = toBool(decode["enrolled"])
if "device_type" in decode:
st = decode["device_type"]
self.stype = AlSensorType.value_of(st.upper())
if "trigger_time" in decode:
self.triggertime = datetime.fromisoformat(decode["trigger_time"]) if str(decode["trigger_time"]) != "" else None
if "location" in decode:
self.zname = titlecase(decode["location"])
if "zone_type" in decode:
self.ztypeName = titlecase(decode["zone_type"])
if "device_tamper" in decode:
self.tamper = toBool(decode["device_tamper"])
if "zone_tamper" in decode:
self.ztamper = toBool(decode["zone_tamper"])
if "chime" in decode:
self.zchime = titlecase(decode["chime"])
if "sensor_model" in decode:
self.model = titlecase(decode["sensor_model"])
if "motion_delay_time" in decode:
self.motiondelaytime = titlecase(decode["motion_delay_time"])
#log.debug(f" In sensor fromJSON end {self}")
self.hasJPG = False
def toJSON(self) -> dict:
dd=json.dumps({
"zone": self.getDeviceID(),
"triggered": self.isTriggered(),
"open": self.isOpen(),
"bypass": self.isBypass(),
"low_battery": self.isLowBattery(),
"enrolled": self.isEnrolled(),
"device_type": str(self.getSensorType()),
"trigger_time": datetime.isoformat(self.getLastTriggerTime()) if self.getLastTriggerTime() is not None else "",
"location": str(self.getZoneLocation()),
"zone_type": str(self.getZoneType()),
"device_tamper": self.isTamper(),
"zone_tamper": self.isZoneTamper(),
"sensor_model": str(self.getSensorModel()),
"motion_delay_time": "" if self.getMotionDelayTime() is None else self.getMotionDelayTime(),
"chime": str(self.getChimeType()) }) # , ensure_ascii=True
return dd
"""

class AlSwitchDeviceHelper(AlSwitchDevice):

def __init__(self, **kwargs):
Expand Down Expand Up @@ -1347,9 +1293,9 @@ def fromJSON(self, decode) -> bool:
oldPanelBypass = self.PanelBypass
oldPanelAlarm = self.PanelAlarmStatus
if "mode" in decode:
d = decode["mode"].replace(" ","_").upper()
log.debug("Mode="+str(d))
if "emulationmode" in decode:
d = decode["emulationmode"].replace(" ","_").upper()
log.debug("emulationmode="+str(d))
self.PanelMode = AlPanelMode.value_of(d)
if "status" in decode:
d = decode["status"].replace(" ","_").upper()
Expand Down
Loading

0 comments on commit 9887163

Please sign in to comment.