diff --git a/kvlang/layout.kv b/kvlang/layout.kv index 97bb12a..88366b0 100644 --- a/kvlang/layout.kv +++ b/kvlang/layout.kv @@ -133,8 +133,7 @@ font_size: dp(16*app.scaleFactor) text: app.config['Station']['Name'] MenuField: - _color: '#9aba2fff' if app.CurrentConditions.Status['station_status'] == 'Online' else '#c8c8c8ff' if app.CurrentConditions.Status['station_status'] == '-' else '#d73027ff' - text: 'Station status: [color=' + self._color + ']' + app.CurrentConditions.Status['station_status'] + '[/color]' + text: 'Station status: ' + app.CurrentConditions.Status['station_status'] GridLayout: cols: 2 padding: [dp(0),dp(0),dp(0),dp(10)] diff --git a/kvlang/rainfall.kv b/kvlang/rainfall.kv index 843c571..2837f25 100644 --- a/kvlang/rainfall.kv +++ b/kvlang/rainfall.kv @@ -108,7 +108,7 @@ source: 'icons/misc/offline' + app.scaleSuffix keep_ratio: 0 allow_stretch: 1 - opacity: 1 if app.CurrentConditions.Status['station_status'] == 'Offline' or app.CurrentConditions.Status['station_status'] == 'Error' else 0 + opacity: 1 if 'Offline' in app.CurrentConditions.Status['station_status'] else 0 pos_hint: {'x': 230/262, 'y': 9/202} size_hint: (30/262, 30/202) diff --git a/kvlang/update.kv b/kvlang/update.kv index 651c44d..116e854 100644 --- a/kvlang/update.kv +++ b/kvlang/update.kv @@ -20,8 +20,8 @@ ## ============================================================================= : size_hint: (None, 76/480) - pos_hint: {'x': app.CurrentConditions.ids.PanelSix.x/app.window.width, 'y': 2/app.window.height} - width: app.CurrentConditions.ids.PanelSix.size[0] + pos_hint: {'x': app.CurrentConditions.ids.panel_six.x/app.window.width, 'y': 2/app.window.height} + width: app.CurrentConditions.ids.panel_six.size[0] UpdateNotifBackground: _panelTitle: 'Update Available' RelativeLayout: diff --git a/lib/config.py b/lib/config.py index e946845..ba793f6 100644 --- a/lib/config.py +++ b/lib/config.py @@ -27,7 +27,7 @@ import os # Define wfpiconsole version number -Version = 'v22.12.1' +Version = 'v22.12.2' # Define required variables TEMPEST = False diff --git a/lib/status.py b/lib/status.py index 688b091..cdfab8a 100644 --- a/lib/status.py +++ b/lib/status.py @@ -46,6 +46,7 @@ class station(Widget): def __init__(self, **kwargs): super().__init__(**kwargs) self.status_data = properties.Status() + self.offline_timeout = 600 self.app = App.get_running_app() self.set_status_panels() @@ -101,21 +102,21 @@ def get_observation_count(self): """ # Calculate timestamp 24 hours past - endTime = int(time.time()) - startTime = endTime - int(3600 * 24) + end_time = int(time.time()) + start_time = end_time - int(3600 * 24) # Get device observation counts - urlList = [] - Template = 'https://swd.weatherflow.com/swd/rest/observations/device/{}?time_start={}&time_end={}&token={}' + url_list = [] + template = 'https://swd.weatherflow.com/swd/rest/observations/device/{}?time_start={}&time_end={}&token={}' if self.app.config['Station']['TempestID']: - urlList.append(Template.format(self.app.config['Station']['TempestID'], startTime, endTime, self.app.config['Keys']['WeatherFlow'])) + url_list.append(template.format(self.app.config['Station']['TempestID'], start_time, end_time, self.app.config['Keys']['WeatherFlow'])) if self.app.config['Station']['SkyID']: - urlList.append(Template.format(self.app.config['Station']['SkyID'], startTime, endTime, self.app.config['Keys']['WeatherFlow'])) + url_list.append(template.format(self.app.config['Station']['SkyID'], start_time, end_time, self.app.config['Keys']['WeatherFlow'])) if self.app.config['Station']['OutAirID']: - urlList.append(Template.format(self.app.config['Station']['OutAirID'], startTime, endTime, self.app.config['Keys']['WeatherFlow'])) + url_list.append(template.format(self.app.config['Station']['OutAirID'], start_time, end_time, self.app.config['Keys']['WeatherFlow'])) if self.app.config['Station']['InAirID']: - urlList.append(Template.format(self.app.config['Station']['InAirID'], startTime, endTime, self.app.config['Keys']['WeatherFlow'])) - for URL in urlList: + url_list.append(template.format(self.app.config['Station']['InAirID'], start_time, end_time, self.app.config['Keys']['WeatherFlow'])) + for URL in url_list: UrlRequest(URL, on_success=self.parse_observation_count, on_failure=self.fail_observation_count, @@ -145,14 +146,14 @@ def fail_observation_count(self, request, response): request.url """ - deviceID = re.search(r'device\/(.*)\?', request.url).group(1) - if deviceID == self.app.config['Station']['TempestID']: + device_id = re.search(r'device\/(.*)\?', request.url).group(1) + if device_id == self.app.config['Station']['TempestID']: self.status_data['tempest_ob_count'] = '[color=d73027ff]Error[/color]' - elif deviceID == self.app.config['Station']['SkyID']: + elif device_id == self.app.config['Station']['SkyID']: self.status_data['sky_ob_count'] = '[color=d73027ff]Error[/color]' - elif deviceID == self.app.config['Station']['OutAirID']: + elif device_id == self.app.config['Station']['OutAirID']: self.status_data['out_air_ob_count'] = '[color=d73027ff]Error[/color]' - elif deviceID == self.app.config['Station']['InAirID']: + elif device_id == self.app.config['Station']['InAirID']: self.status_data['in_air_ob_count'] = '[color=d73027ff]Error[/color]' self.update_display() @@ -167,102 +168,110 @@ def get_device_status(self, dt): # Get TEMPEST device status if self.app.config['Station']['TempestID'] and 'obs_st' in self.app.CurrentConditions.Obs: - latestOb = self.app.CurrentConditions.Obs['obs_st']['obs'][0] - sampleTimeDiff = time.time() - latestOb[0] - deviceVoltage = float(latestOb[16]) - if sampleTimeDiff < 300 and deviceVoltage > 2.355: - device_status = '[color=9aba2fff]OK[/color]' - sampleDelay = '' + latest_ob = self.app.CurrentConditions.Obs['obs_st']['obs'][0] + sample_time_diff = time.time() - latest_ob[0] + device_voltage = float(latest_ob[16]) + wind_interval = float(latest_ob[5]) + if wind_interval == 3: + device_status = '[color=9aba2fff]Mode 1[/color]' + elif wind_interval == 6: + device_status = '[color=f9a825ff]Mode 2[/color]' + elif wind_interval == 60: + device_status = '[color=ef6c00ff]Mode 3[/color]' + elif wind_interval == 300: + device_status = '[color=b71c1cff]Mode 4[/color]' + if sample_time_diff < self.offline_timeout: + sample_delay = '' else: - if sampleTimeDiff < 3600: - sampleDelay = str(math.floor(sampleTimeDiff / 60)) + ' mins ago' - elif sampleTimeDiff < 7200: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hour ago' - elif sampleTimeDiff < 86400: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hours ago' + if sample_time_diff < 3600: + sample_delay = str(math.floor(sample_time_diff / 60)) + ' mins ago' + elif sample_time_diff < 7200: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hour ago' + elif sample_time_diff < 86400: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hours ago' else: - sampleDelay = str(math.floor(sampleTimeDiff / 86400)) + ' days ago' - device_status = '[color=d73027ff]Error[/color]' + sample_delay = str(math.floor(sample_time_diff / 86400)) + ' days ago' + device_status = '[color=d73027ff]Offline[/color]' # Store TEMPEST device status variables - self.status_data['tempest_sample_time'] = datetime.fromtimestamp(latestOb[0], Tz).strftime('%H:%M:%S') - self.status_data['tempest_last_sample'] = sampleDelay - self.status_data['tempest_voltage'] = '{:.2f}'.format(deviceVoltage) + self.status_data['tempest_sample_time'] = datetime.fromtimestamp(latest_ob[0], Tz).strftime('%H:%M:%S') + self.status_data['tempest_last_sample'] = sample_delay + self.status_data['tempest_voltage'] = '{:.2f}'.format(device_voltage) self.status_data['tempest_status'] = device_status # Get SKY device status if self.app.config['Station']['SkyID'] and 'obs_sky' in self.app.CurrentConditions.Obs: - latestOb = self.app.CurrentConditions.Obs['obs_sky']['obs'][0] - sampleTimeDiff = time.time() - latestOb[0] - deviceVoltage = float(latestOb[8]) - if sampleTimeDiff < 300 and deviceVoltage > 2.0: - device_status = '[color=9aba2fff]OK[/color]' - sampleDelay = '' + latest_ob = self.app.CurrentConditions.Obs['obs_sky']['obs'][0] + sample_time_diff = time.time() - latest_ob[0] + device_voltage = float(latest_ob[8]) + if sample_time_diff < self.offline_timeout and device_voltage > 2.0: + device_status = '[color=9aba2fff]Online[/color]' + sample_delay = '' else: - if sampleTimeDiff < 3600: - sampleDelay = str(math.floor(sampleTimeDiff / 60)) + ' mins ago' - elif sampleTimeDiff < 7200: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hour ago' - elif sampleTimeDiff < 86400: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hours ago' + if sample_time_diff < 3600: + sample_delay = str(math.floor(sample_time_diff / 60)) + ' mins ago' + elif sample_time_diff < 7200: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hour ago' + elif sample_time_diff < 86400: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hours ago' else: - sampleDelay = str(math.floor(sampleTimeDiff / 86400)) + ' days ago' - device_status = '[color=d73027ff]Error[/color]' + sample_delay = str(math.floor(sample_time_diff / 86400)) + ' days ago' + device_status = '[color=d73027ff]Offline[/color]' # Store SKY device status variables - self.status_data['sky_sample_time'] = datetime.fromtimestamp(latestOb[0], Tz).strftime('%H:%M:%S') - self.status_data['sky_last_sample'] = sampleDelay - self.status_data['sky_voltage'] = '{:.2f}'.format(deviceVoltage) + self.status_data['sky_sample_time'] = datetime.fromtimestamp(latest_ob[0], Tz).strftime('%H:%M:%S') + self.status_data['sky_last_sample'] = sample_delay + self.status_data['sky_voltage'] = '{:.2f}'.format(device_voltage) self.status_data['sky_status'] = device_status # Get outdoor AIR device status if self.app.config['Station']['OutAirID'] and 'obs_out_air' in self.app.CurrentConditions.Obs: - latestOb = self.app.CurrentConditions.Obs['obs_out_air']['obs'][0] - sampleTimeDiff = time.time() - latestOb[0] - deviceVoltage = float(latestOb[6]) - if sampleTimeDiff < 300 and deviceVoltage > 1.9: - device_status = '[color=9aba2fff]OK[/color]' - sampleDelay = '' + latest_ob = self.app.CurrentConditions.Obs['obs_out_air']['obs'][0] + sample_time_diff = time.time() - latest_ob[0] + device_voltage = float(latest_ob[6]) + if sample_time_diff < self.offline_timeout and device_voltage > 1.9: + device_status = '[color=9aba2fff]Online[/color]' + sample_delay = '' else: - if sampleTimeDiff < 3600: - sampleDelay = str(math.floor(sampleTimeDiff / 60)) + ' mins ago' - elif sampleTimeDiff < 7200: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hour ago' - elif sampleTimeDiff < 86400: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hours ago' + if sample_time_diff < 3600: + sample_delay = str(math.floor(sample_time_diff / 60)) + ' mins ago' + elif sample_time_diff < 7200: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hour ago' + elif sample_time_diff < 86400: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hours ago' else: - sampleDelay = str(math.floor(sampleTimeDiff / 86400)) + ' days ago' - device_status = '[color=d73027ff]Error[/color]' + sample_delay = str(math.floor(sample_time_diff / 86400)) + ' days ago' + device_status = '[color=d73027ff]Offline[/color]' # Store outdoor AIR device status variables - self.status_data['out_air_sample_time'] = datetime.fromtimestamp(latestOb[0], Tz).strftime('%H:%M:%S') - self.status_data['out_air_last_sample'] = sampleDelay - self.status_data['out_air_voltage'] = '{:.2f}'.format(deviceVoltage) + self.status_data['out_air_sample_time'] = datetime.fromtimestamp(latest_ob[0], Tz).strftime('%H:%M:%S') + self.status_data['out_air_last_sample'] = sample_delay + self.status_data['out_air_voltage'] = '{:.2f}'.format(device_voltage) self.status_data['out_air_status'] = device_status # Get indoor AIR device status if self.app.config['Station']['InAirID'] and 'obs_in_air' in self.app.CurrentConditions.Obs: - latestOb = self.app.CurrentConditions.Obs['obs_in_air']['obs'][0] - sampleTimeDiff = time.time() - latestOb[0] - deviceVoltage = float(latestOb[6]) - if sampleTimeDiff < 300 and deviceVoltage > 1.9: - device_status = '[color=9aba2fff]OK[/color]' - sampleDelay = '' + latest_ob = self.app.CurrentConditions.Obs['obs_in_air']['obs'][0] + sample_time_diff = time.time() - latest_ob[0] + device_voltage = float(latest_ob[6]) + if sample_time_diff < self.offline_timeout and device_voltage > 1.9: + device_status = '[color=9aba2fff]Online[/color]' + sample_delay = '' else: - if sampleTimeDiff < 3600: - sampleDelay = str(math.floor(sampleTimeDiff / 60)) + ' mins ago' - elif sampleTimeDiff < 7200: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hour ago' - elif sampleTimeDiff < 86400: - sampleDelay = str(math.floor(sampleTimeDiff / 3600)) + ' hours ago' + if sample_time_diff < 3600: + sample_delay = str(math.floor(sample_time_diff / 60)) + ' mins ago' + elif sample_time_diff < 7200: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hour ago' + elif sample_time_diff < 86400: + sample_delay = str(math.floor(sample_time_diff / 3600)) + ' hours ago' else: - sampleDelay = str(math.floor(sampleTimeDiff / 86400)) + ' days ago' - device_status = '[color=d73027ff]Error[/color]' + sample_delay = str(math.floor(sample_time_diff / 86400)) + ' days ago' + device_status = '[color=d73027ff]Offline[/color]' # Store AIR device status variables - self.status_data['in_air_sample_time'] = datetime.fromtimestamp(latestOb[0], Tz).strftime('%H:%M:%S') - self.status_data['in_air_last_sample'] = sampleDelay - self.status_data['in_air_voltage'] = '{:.2f}'.format(deviceVoltage) + self.status_data['in_air_sample_time'] = datetime.fromtimestamp(latest_ob[0], Tz).strftime('%H:%M:%S') + self.status_data['in_air_last_sample'] = sample_delay + self.status_data['in_air_voltage'] = '{:.2f}'.format(device_voltage) self.status_data['in_air_status'] = device_status # Set hub status (i.e. station_status) based on device status @@ -275,14 +284,14 @@ def get_device_status(self, dt): device_status_list.append(self.status_data['out_air_status']) if self.app.config['Station']['InAirID'] and 'obs_in_air' in self.app.CurrentConditions.Obs: device_status_list.append(self.status_data['in_air_status']) - if not device_status_list or all('-' in Status for Status in device_status_list): - self.status_data['station_status'] = '-' - elif all('Error' in Status for Status in device_status_list): - self.status_data['station_status'] = 'Offline' - elif all('OK' in Status for Status in device_status_list): - self.status_data['station_status'] = 'Online' + if not device_status_list or all('-' in status for status in device_status_list): + self.status_data['station_status'] = '[color=c8c8c8ff]-[/color]' + elif all('Offline' in status for status in device_status_list): + self.status_data['station_status'] = '[color=b71c1cff]Offline[/color]' + elif all('Online' in status or 'Mode' in status for status in device_status_list): + self.status_data['station_status'] = '[color=9aba2fff]Online[/color]' else: - self.status_data['station_status'] = 'Error' + self.status_data['station_status'] = '[color=ef6c00ff]Partly Offline[/color]' # Update display with new status self.update_display() @@ -304,16 +313,16 @@ def update_display(self): reference_error = True -# ============================================================================= +# ============================================================================== # station_status STATUS PANEL CLASS -# ============================================================================= +# ============================================================================== class station_status(BoxLayout): pass -# ============================================================================= +# ============================================================================== # [device]_status STATUS PANEL CLASSES -# ============================================================================= +# ============================================================================== class tempest_status(BoxLayout): pass diff --git a/panels/update.py b/panels/update.py index c6e1ecc..ad322e7 100644 --- a/panels/update.py +++ b/panels/update.py @@ -18,6 +18,7 @@ # Load required Kivy modules from kivy.uix.modalview import ModalView from kivy.properties import StringProperty +from kivy.app import App # ============================================================================== @@ -29,5 +30,6 @@ class updateNotification(ModalView): def __init__(self, latest_ver, **kwargs): super().__init__(**kwargs) + self.app = App.get_running_app() setattr(self.app, self.__class__.__name__, self) self.latest_ver = latest_ver diff --git a/service/websocket.py b/service/websocket.py index 5495ccc..af4c212 100644 --- a/service/websocket.py +++ b/service/websocket.py @@ -29,6 +29,7 @@ import asyncio import socket import json +import time import ssl @@ -51,17 +52,19 @@ async def create(cls): self.system = system() # Initialise websocketClient class variables - self._keep_running = True - self._switch_device = False - self.reply_timeout = 60 - self.ping_timeout = 60 - self.sleep_time = 10 - self.thread_list = {} - self.task_list = {} - self.connected = False - self.connection = None - self.station = int(self.config['Station']['StationID']) - self.url = 'wss://ws.weatherflow.com/swd/data?token=' + self.config['Keys']['WeatherFlow'] + self._keep_running = True + self._switch_device = False + self.watchdog_timeout = 300 + self.reply_timeout = 60 + self.ping_timeout = 60 + self.sleep_time = 10 + self.thread_list = {} + self.task_list = {} + self.watchdog_list = {} + self.connected = False + self.connection = None + self.station = int(self.config['Station']['StationID']) + self.url = 'wss://swd.weatherflow.com/swd/data?token=' + self.config['Keys']['WeatherFlow'] # Initialise Observation Parser self.app.obsParser = obsParser() @@ -122,13 +125,17 @@ async def __async__get_devices(self): self.device_list = {'tempest': None, 'sky': None, 'out_air': None, 'in_air': None} if self.config['Station']['TempestID']: self.device_list['tempest'] = self.config['Station']['TempestID'] + self.watchdog_list['obs_st'], self.watchdog_list['rapid_wind'] = time.time(), time.time() else: if self.config['Station']['SkyID']: self.device_list['sky'] = self.config['Station']['SkyID'] + self.watchdog_list['obs_sky'], self.watchdog_list['rapid_wind'] = time.time(), time.time() if self.config['Station']['OutAirID']: self.device_list['out_air'] = self.config['Station']['OutAirID'] + self.watchdog_list['obs_out_air'] = time.time() if self.config['Station']['InAirID']: self.device_list['in_air'] = self.config['Station']['InAirID'] + self.watchdog_list['obs_in_air'] = time.time() async def __async__listen_devices(self, action): devices = [] @@ -165,11 +172,23 @@ async def __async__getMessage(self): await self.task_list['verify'] return {} + async def __async__watchdog(self): + now = time.time() + watchdog_triggered = False + for ob in self.watchdog_list: + if self.watchdog_list[ob] < (now - self.watchdog_timeout): + watchdog_triggered = True + break + if watchdog_triggered: + Logger.warning(f'Websocket: {self.system.log_time()} - Watchdog triggered {ob}') + await self.__async__disconnect() + await self.__async__connect() + async def __async__decodeMessage(self): try: if self.message: if 'type' in self.message: - if self.message['type'] in ['ack', 'evt_precip']: + if self.message['type'] in ['connection_opened', 'ack', 'evt_precip']: pass else: if 'device_id' in self.message: @@ -177,6 +196,7 @@ async def __async__decodeMessage(self): if 'obs_st' in self.thread_list: while self.thread_list['obs_st'].is_alive(): await asyncio.sleep(0.1) + self.watchdog_list['obs_st'] = time.time() self.thread_list['obs_st'] = threading.Thread(target=self.app.obsParser.parse_obs_st, args=(self.message, self.config, ), name="obs_st") @@ -185,6 +205,7 @@ async def __async__decodeMessage(self): if 'obs_sky' in self.thread_list: while self.thread_list['obs_sky'].is_alive(): await asyncio.sleep(0.1) + self.watchdog_list['obs_sky'] = time.time() self.thread_list['obs_sky'] = threading.Thread(target=self.app.obsParser.parse_obs_sky, args=(self.message, self.config, ), name='obs_sky') @@ -194,6 +215,7 @@ async def __async__decodeMessage(self): if 'obs_out_air' in self.thread_list: while self.thread_list['obs_out_air'].is_alive(): await asyncio.sleep(0.1) + self.watchdog_list['obs_out_air'] = time.time() self.thread_list['obs_out_air'] = threading.Thread(target=self.app.obsParser.parse_obs_out_air, args=(self.message, self.config, ), name='obs_out_air') @@ -202,11 +224,13 @@ async def __async__decodeMessage(self): if 'obs_in_air' in self.thread_list: while self.thread_list['obs_in_air'].is_alive(): await asyncio.sleep(0.1) + self.watchdog_list['obs_in_air'] = time.time() self.thread_list['obs_in_air'] = threading.Thread(target=self.app.obsParser.parse_obs_in_air, args=(self.message, self.config, ), name='obs_in_air') self.thread_list['obs_in_air'].start() elif self.message['type'] == 'rapid_wind': + self.watchdog_list['rapid_wind'] = time.time() self.app.obsParser.parse_rapid_wind(self.message, self.config) elif self.message['type'] == 'evt_strike': self.app.obsParser.parse_evt_strike(self.message, self.config) @@ -223,6 +247,7 @@ async def __async__listen(self): try: while self._keep_running: self.message = await self.__async__getMessage() + await self.__async__watchdog() await self.__async__decodeMessage() except asyncio.CancelledError: raise