Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix #153 and fix #154 #156

Merged
merged 9 commits into from
Mar 30, 2024
84 changes: 57 additions & 27 deletions custom_components/jablotron80/jablotron.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from custom_components.jablotron80.const import DEVICE_CONTROL_PANEL
LOGGER = logging.getLogger(__package__)
expected_warning_level = logging.warn
expected_warning_level = logging.WARN
verbose_connection_logging = False
_loop = None # global variable to store event loop

Expand Down Expand Up @@ -657,10 +657,13 @@ def factory(cable_model: str, serial_port: str):
def __init__(self, device: str) -> None:
self._device = device
self._cmd_q = queue.Queue()
self._cmd_list = []
self._output_q = queue.Queue()
self._stop = threading.Event()
self._connection = None
self._messages = asyncio.Event() # are there messages to process
self._send_cmd = None
self.update_devices = False

def get_record(self) -> List[bytearray]:

Expand Down Expand Up @@ -701,8 +704,10 @@ def is_connected(self) -> bool:


def add_command(self, command: JablotronCommand) -> None:
LOGGER.debug(f'Adding command {command}')
self._cmd_q.put(command)
if command not in self._cmd_list and command != self._send_cmd:
LOGGER.debug(f'Adding command {command}')
self._cmd_q.put(command)
self._cmd_list.append(command)

def _get_command(self) -> Union[JablotronCommand,None]:
# assume we have a command queue and return command if requested
Expand All @@ -711,7 +716,8 @@ def _get_command(self) -> Union[JablotronCommand,None]:

# get one command
cmd = self._cmd_q.get_nowait()

list_cmd = self._cmd_list.pop(0)

return cmd

def _forward_records(self,records: List[bytearray]) -> None:
Expand Down Expand Up @@ -748,54 +754,57 @@ def read_send_packet_loop(self) -> None:
return []
records = self._read_data()
self._forward_records(records)
send_cmd = self._get_command()
self._send_cmd = self._get_command()

if send_cmd is not None:
if self._send_cmd is not None:
# new command in queue

accepted = False
confirmed = False
retries = 2 # 2 retries signifies 3 attempts
# retries must be greater than the maximum number of simultaneously active devices
retries = 10 # 2 retries signifies 3 attempts

while retries >= 0 and not (accepted and confirmed):
level = logging.INFO
for i in range(0,len(send_cmd.code)):
if i == len(send_cmd.code)-1:
accepted_prefix = send_cmd.accepted_prefix
for i in range(0,len(self._send_cmd.code)):
if i == len(self._send_cmd.code)-1:
accepted_prefix = self._send_cmd.accepted_prefix
else:
accepted_prefix =b'\xa0\xff'

if not send_cmd.code is None:
cmd = self._get_cmd(send_cmd.code[i].to_bytes(1,byteorder='big'))
LOGGER.debug(f'Sending keypress, sequence:{i}')
if not self._send_cmd.code is None:
cmd = self._get_cmd(self._send_cmd.code[i].to_bytes(1,byteorder='big'))
LOGGER.debug(f'Sending keypress, sequence:{i}, retries:{retries}, command:{self._send_cmd.name}')
self._connection.write(cmd)
LOGGER.debug(f'keypress sent, sequence:{i}')
LOGGER.debug(f'keypress sent, sequence:{i}, retries:{retries}, command:{self._send_cmd.name}')

if self.read_until_found(accepted_prefix):
LOGGER.debug(f'keypress accepted, sequence:{i}')
LOGGER.debug(f'keypress accepted, sequence:{i}, retries:{retries}, command:{self._send_cmd.name}')
accepted = True
else:
if retries == 0:
level = logging.WARN

LOGGER.log(level, f'no accepted message for sequence:{i} received')
LOGGER.log(level, f'no accepted message for sequence:{i}, retries:{retries}, command:{self._send_cmd.name} received')
accepted = False
break # break from for loop into retry loop, has effect of starting full command sequence from scratch

if accepted:
if send_cmd.complete_prefix is not None:
if self._send_cmd.complete_prefix is not None:
# confirmation required, read until confirmation or to limit
if self.read_until_found(send_cmd.complete_prefix, send_cmd.max_records):
LOGGER.info(f"command {send_cmd} completed")
if self.read_until_found(self._send_cmd.complete_prefix, self._send_cmd.max_records):
LOGGER.info(f"command {self._send_cmd.name} completed")
else:
if retries == 0:
level = logging.WARN
LOGGER.log(level, f"no completion message found for command {send_cmd}")
send_cmd.confirm(False)
LOGGER.log(level, f"no completion message found for command {self._send_cmd.name}")
self._send_cmd.confirm(False)
continue

send_cmd.confirm(True)
self._send_cmd.confirm(True)
confirmed = True
if self._send_cmd.name == 'Details':
self.update_devices = True
self._cmd_q.task_done()

retries -=1
Expand Down Expand Up @@ -1399,6 +1408,7 @@ def __init__(self, hass: HomeAssistant, config: Dict[str, Any], options: Dict[s
self._query.type = "button"

self._active_devices = {}
self._active_devices_tmp = {}
self._active_codes = {}
self._codes = {}
self._device_query_pending = False
Expand All @@ -1408,6 +1418,8 @@ def __init__(self, hass: HomeAssistant, config: Dict[str, Any], options: Dict[s
self._stop = threading.Event()
self._havestate = asyncio.Event() # has the first state message been received

self._force_query = False

if CONFIGURATION_CENTRAL_SETTINGS in config:
self.mode = config[CONFIGURATION_CENTRAL_SETTINGS][DEVICE_CONFIGURATION_SYSTEM_MODE]
self._settings.add_setting(JablotronSettings.SETTING_ARM_WITHOUT_CODE, not config[CONFIGURATION_CENTRAL_SETTINGS][DEVICE_CONFIGURATION_REQUIRE_CODE_TO_ARM])
Expand Down Expand Up @@ -1699,13 +1711,11 @@ def _clear_triggers(self) -> None:
# device.deactivate()
#else:
device.active = False
self._active_devices.clear()
for code in self._active_codes.values():
#if self.system_mode == JA80CentralUnit.SYSTEM_MODE_UNSPLIT:
# device.deactivate()
#else:
code.active = False
self._active_codes.clear()

def _clear_source(self, source_id:bytes) -> None:
source = self._get_source(source_id)
Expand All @@ -1725,6 +1735,7 @@ def _activate_source(self,source_id:bytes ,type=None) -> None:
source = self._get_source(source_id)
if isinstance(source,JablotronDevice):
self._activate_device(source)
self._activate_device_tmp(source)
elif isinstance(source,JablotronCode):
self._activate_code(source)
else:
Expand All @@ -1747,6 +1758,13 @@ def _activate_code_object(self, source):
zone = self._get_zone_via_object(source)
if not zone is None:
zone.code_activated(source)
def _update_device(self):
for device in self._active_devices.values():
if device.device_id in self._active_devices_tmp:
device.active = True
else:
device.active = False
self._active_devices_tmp.clear()

def _activate_device(self, source):
if isinstance(source,JablotronDevice):
Expand All @@ -1755,7 +1773,12 @@ def _activate_device(self, source):
zone = self._get_zone_via_object(source)
if not zone is None:
zone.device_activated(source)


def _activate_device_tmp(self, source):
if isinstance(source,JablotronDevice):
source.active = True
self._active_devices_tmp[source.device_id] = source

def _fault_source(self,source_id:bytes) -> None:
source = self._get_source(source_id)
if isinstance(source,JablotronDevice):
Expand Down Expand Up @@ -1992,7 +2015,7 @@ def _process_state(self, data: bytearray, packet_data: str) -> None:
self.status = JA80CentralUnit.STATUS_NORMAL
self._call_zones(function_name="disarm")

if activity == 0x00:# and not self.led_alarm:
if activity == 0x00 and not self.led_alarm:
# clear active statuses
self._clear_triggers()

Expand Down Expand Up @@ -2123,8 +2146,9 @@ def _process_state(self, data: bytearray, packet_data: str) -> None:
# something is active
if detail == 0x00:
# don't send query if we already have "triggered detector" displayed
if activity_name not in self.statustext.message or activity_name == self.statustext.message:
if activity_name not in self.statustext.message or activity_name == self.statustext.message or self._force_query:
self._send_device_query()
self._force_query = False
else:
log = False
else:
Expand Down Expand Up @@ -2155,6 +2179,8 @@ def _process_state(self, data: bytearray, packet_data: str) -> None:
self._activate_source(detail)
self._confirm_device_query()

self._force_query = True

else:
warn = True
activity_name = f'Unknown Activity:{hex(activity)}'
Expand Down Expand Up @@ -2198,6 +2224,10 @@ def _process_state(self, data: bytearray, packet_data: str) -> None:
else:
LOGGER.debug('message: ' + message)

if self._connection.update_devices:
self._update_device()
self._connection.update_devices = False

#LOGGER.info(f'Status: {hex(status)}, {format(status, "008b")}')
#LOGGER.info(f'{self}')

Expand Down