From 10d244b432fcbf731ce5c1ccfca4c0eee7d823f4 Mon Sep 17 00:00:00 2001 From: wortelus Date: Tue, 2 Aug 2022 00:06:38 +0200 Subject: [PATCH 1/3] added sanity and type checks --- main.py | 10 +++- preprocessing.py | 62 +++++++++++++------- sanity.py | 143 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+), 23 deletions(-) create mode 100644 sanity.py diff --git a/main.py b/main.py index 9df9798..ab216d8 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ import math import pickle +import sys import time from os.path import exists @@ -8,6 +9,7 @@ from StreamDeck.DeviceManager import DeviceManager from yaml import load import pyxpudpserver as XPUDP +import logging try: from yaml import CLoader as Loader, CDumper as Dumper @@ -17,7 +19,9 @@ import preprocessing except ImportError: print("You don't seem to have preprocessing.py near its main.py. correct your installation") - exit(1) + sys.exit(1) + +logging.basicConfig(encoding='utf-8') # 0 is to move down, 1 to move up @@ -178,7 +182,7 @@ def main(): if not current_deck: print("Deck for current session NOT FOUND, verify the serial in config.yaml and index specified") - exit(1) + sys.exit(1) # loading info and check for connected SD count == config.yaml SD count print("configuration for {} decks, {} connected".format(len(global_cfg["stream-decks"]), deck_count)) @@ -195,7 +199,7 @@ def main(): # for now the software works only for one panel panel = global_cfg["stream-decks"][0] - keys_dir = panel["directory"] + keys_dir = str(panel["directory"]) key_count = panel["keys"] try: diff --git a/preprocessing.py b/preprocessing.py index 5239f7c..f02423c 100644 --- a/preprocessing.py +++ b/preprocessing.py @@ -1,4 +1,6 @@ +import logging import os.path +import sys import numpy as np import yaml @@ -10,11 +12,17 @@ from yaml import CLoader as Loader, CDumper as Dumper except ImportError: from yaml import Loader, Dumper + try: import dynamic except ImportError: print("You don't seem to have dynamic.py near its main.py and preprocessing.py. correct your installation") - exit(1) + sys.exit(1) +try: + import sanity +except ImportError: + print("You don't seem to have sanity.py near its main.py and preprocessing.py. correct your installation") + sys.exit(1) ACTION_CFG = "actions.yaml" ACTION_CFG_NAME = "actions" @@ -53,27 +61,17 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare cmd_on=None, cmd_off=None, cmd_on_mul=None, cmd_off_mul=None, gauge=None, display=None, special_label=None): # Constants - self.index = index - if self.index is None: - print("ERROR: button with name {} has no set index, quitting...".format(name)) - quit(1) - self.name = name - if self.name is None or self.name == "": - print("ERROR: button with index {} has no set name, quitting...".format(index)) - quit(1) + sanity.vital_check(index, name) + self.index = index + self.name = name self.icon = icon - self.cmd_type = cmd_type - if cmd_type is None or cmd_type == "": - print("WARN: {} has no set type, setting none as default (no press action)".format(name)) - self.cmd_type = "none" + self.cmd_type = sanity.cmd_check(index, name, cmd_type) # verify string type - if label: - label = str(label) - self.label = label + self.label = sanity.label_check(index, name, label) self.dataref = dataref if dataref_multiplier is None: @@ -101,6 +99,10 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare else: self.auto_switch = auto_switch + # generate warnings for weird commands + sanity.cmd2_check(index, name, cmd_type, cmd, cmd_mul, cmd_release, cmd_release_mul, + cmd_on, cmd_off, cmd_on_mul, cmd_off_mul) + self.cmd = cmd self.cmd_mul = cmd_mul self.cmd_release = cmd_release @@ -118,10 +120,16 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare self.display = None self.special_label = None if file_names is not None: + # sanity check file_names corresponding to dataref_states + sanity.file_names_check(index, name, file_names, self.dataref_states) + self.file_names = np.empty(len(file_names), dtype=object) for i, fn in enumerate(file_names): self.file_names[i] = get_filename_button_static_png(fn) elif gauge: + # sanity check for gauge (if it contains everything) + sanity.gauge_check(index, name, gauge) + # overwrite default dataref_states self.dataref_states = dynamic.get_dataref_states(gauge) # special case - gauge with needles :) @@ -134,6 +142,9 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare # so we pregenerate them artificial names for the use in main global images dict self.file_names = dynamic.create_dynamic_filenames(self.gauge["name"], self.dataref_states) elif display: + # sanity check for display (if it contains everything) + sanity.display_check(index, name, display) + # overwrite default dataref_states self.dataref_states = dynamic.get_dataref_states(display) # special case - display of number values @@ -150,14 +161,19 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare # todo pass elif self.dataref_states is not None: + if icon is None: + logging.error("#{} {} is trying to set dataref_states without the 'icon' parameter, quitting...") + sys.exit(1) + self.file_names = np.empty(len(self.dataref_states), dtype=object) for i, state in enumerate(self.dataref_states): self.file_names[i] = get_filename_button_dataref_png(icon, state) else: - self.file_names = np.empty(1, dtype=object) if icon is None: - print("static icon is not present on {} button".format(name)) - exit(1) + logging.error("#{} {} is trying to set static icon without the 'icon' parameter, quitting...") + sys.exit(1) + + self.file_names = np.empty(1, dtype=object) self.file_names[0] = get_filename_button_static_png(icon) @@ -167,7 +183,7 @@ def load_preset(target_dir, yaml_keyset, deck_key_count, preload_labels=False): preset_cfg = safe_load(stream) except yaml.YAMLError as err: print("cannot load {}, ensure you have proper syntax config {}".format(yaml_keyset, err)) - exit(1) + sys.exit(1) keys = preset_cfg["actions"] # key_count = len(keys) @@ -178,6 +194,12 @@ def load_preset(target_dir, yaml_keyset, deck_key_count, preload_labels=False): for _, key in enumerate(keys): index = key.get("index") name = key.get("name") + # try to convert to int because it is used as array index + try: + index = int(index) + except ValueError: + logging.error("button with name {} has index non-convertable to integer, quitting...".format(name)) + sys.exit(1) cmd_type = key.get("type") preset[index] = Button( index, diff --git a/sanity.py b/sanity.py new file mode 100644 index 0000000..881f5ef --- /dev/null +++ b/sanity.py @@ -0,0 +1,143 @@ +import logging +import sys + +VITAL_FIELDS = ["index", "name"] +CMD_TYPES = ["none", "dir", "single", "dual"] + +GAUGE_FIELDS = ["name", "background", "needle", "min", "max", "step", "needle-center", "range-degrees"] +DISPLAY_FIELDS = ["name", "text-center", "font-path", "font-size", "zero-pad", + "min", "max", "step", "keep-decimal", "background"] + + +# returns nothing +def vital_check(index, name): + if index is None: + logging.error("index on button with name {} not set, quitting...".format(name)) + sys.exit(1) + try: + index = int(index) + except ValueError: + logging.error("button with name {} has non-numeric index -> check for mistype, quitting...".format(name)) + sys.exit(1) + if name is None: + logging.error("name on button with index of {} not set, quitting...".format(index)) + sys.exit(1) + return + + +# returns accepted cmd_type +def cmd_check(index, name, cmd_type): + if cmd_type is None or cmd_type == "": + logging.warning("#{} {} has no set type, setting none as default (no press action)".format(index, name)) + return "none" + + for ct in CMD_TYPES: + if cmd_type == ct: + return cmd_type + + logging.warning("#{} {} has no known type of {}, setting none as default (no press action)" + .format(index, name, cmd_type)) + return "none" + + +# checks if label can be interpreted as str, returns label as str +def label_check(index, name, label): + if not label: + return label + try: + label = str(label) + return label + except ValueError: + logging.error("#{} {} has non-string label, quitting...".format(index, name)) + sys.exit(1) + + +# checks if the commands are set corresponding to its cmd_type +# this just generates warnings, doesn't return anything +def cmd2_check(index, name, cmd_type, cmd=None, cmd_mul=None, cmd_release=None, cmd_release_mul=None, + cmd_on=None, cmd_off=None, cmd_on_mul=None, cmd_off_mul=None): + if cmd_type == "none": + if cmd or cmd_mul or cmd_release or cmd_release_mul or cmd_on or cmd_off or cmd_on_mul or cmd_off_mul: + logging.warning("#{} {} has set type of 'none', but commands are set too. The commands won't work" + .format(index, name)) + elif cmd_type == "single": + if cmd_on or cmd_off or cmd_on_mul or cmd_off_mul: + logging.warning("#{} {} has set type of 'single', but (some) on/off commands are set too. " + "The on/off commands won't work" + .format(index, name)) + if not cmd and not cmd_mul and not cmd_release and not cmd_release_mul: + logging.warning("#{} {} has set type of 'single', but no single commands are set." + .format(index, name)) + elif cmd_type == "dual": + if cmd or cmd_mul: + logging.warning("#{} {} has set type of 'dual', but ''single'' commands are set too. " + "The single commands won't work" + .format(index, name)) + if cmd_release or cmd_release_mul: + logging.warning("#{} {} has set type of 'dual' and release commands are set too. " + "The commands will be executed, but it's weird. " + "If it's intentional, you can ignore this message." + .format(index, name)) + return + + +# check if file_names and dataref_states are in sync +def file_names_check(index, name, file_names, dataref_states): + if not file_names: + return + + if dataref_states is None: + logging.error("#{} {} has set file_names parameter, but not dataref_states are specified (is it static?)" + ",quitting...".format(index, name)) + sys.exit(1) + + if not isinstance(file_names, list) or not isinstance(dataref_states, list): + logging.error("#{} {} has set file_names ({}) or dataref_states ({}) as not a list, quitting..." + .format(index, name, type(file_names), type(dataref_states))) + sys.exit(1) + + if len(file_names) != len(dataref_states): + logging.error("#{} {} has different length of file_names and dataref_states parameter, quitting..." + .format(index, name)) + sys.exit(1) + return + + +# checks if gauge dict contains everything vital +def gauge_check(index, name, gauge): + if not gauge: + return + + for field in GAUGE_FIELDS: + if field not in gauge: + logging.error("#{} {} has set gauge, but parameter {} is missing, quitting..." + .format(index, name, field)) + sys.exit(1) + + # additional + if "x" not in gauge["needle-center"] or "y" not in gauge["needle-center"]: + logging.error("#{} {} has set gauge, but parameter x or y in 'needle-center' is missing, quitting..." + .format(index, name)) + sys.exit(1) + + return + + +# checks if display dict contains everything vital +def display_check(index, name, display): + if not display: + return + + for field in DISPLAY_FIELDS: + if field not in display: + logging.error("#{} {} has set display, but parameter {} is missing, quitting..." + .format(index, name, field)) + sys.exit(1) + + # additional + if "x" not in display["text-center"] or "y" not in display["text-center"]: + logging.error("#{} {} has set display, but parameter 'x' or 'y' in 'text-center' is missing, quitting..." + .format(index, name)) + sys.exit(1) + + return From 54e5a169bbe4709df97816b155f642ff1515c556 Mon Sep 17 00:00:00 2001 From: wortelus Date: Tue, 2 Aug 2022 00:07:58 +0200 Subject: [PATCH 2/3] sanitized Zibo configs --- B737-800X/actions.yaml | 15 ++++++++++----- B737-800X/ctr.yaml | 8 ++++---- B737-800X/efis.yaml | 8 ++++---- B737-800X/lowerovhd.yaml | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/B737-800X/actions.yaml b/B737-800X/actions.yaml index 7e429e7..9c37008 100644 --- a/B737-800X/actions.yaml +++ b/B737-800X/actions.yaml @@ -38,19 +38,19 @@ actions: command-release: FlyWithLua/streamdeck_handler/fmc_prst_end - index: 5 name: speedbrake armed - type: single + type: none icon: speedbrakearmed dataref: laminar/B738/annunciator/speedbrake_armed dataref-multiplier: 1.5 - index: 6 name: speedbrake do not arm - type: single + type: none icon: speedbrakedonotarm dataref: laminar/B738/annunciator/speedbrake_extend dataref-multiplier: 1.5 - index: 7 name: stab out of trim - type: single + type: none icon: staboutoftrim dataref: laminar/B738/annunciator/stab_out_of_trim dataref-multiplier: 1.5 @@ -72,13 +72,13 @@ actions: step: 1 - index: 28 name: le flaps transit - type: single + type: none icon: leflapstransit dataref: laminar/B738/annunciator/slats_transit dataref-multiplier: 1.5 - index: 29 name: le flaps ext - type: single + type: none icon: leflapsext dataref: laminar/B738/annunciator/slats_extend dataref-multiplier: 1.5 @@ -133,6 +133,11 @@ actions: type: dir icon: dir label: MCP + - index: 17 + name: mcpctl + type: dir + icon: dir + label: MCP CTL - index: 8 name: efis type: dir diff --git a/B737-800X/ctr.yaml b/B737-800X/ctr.yaml index a1d658c..4229649 100644 --- a/B737-800X/ctr.yaml +++ b/B737-800X/ctr.yaml @@ -48,13 +48,13 @@ actions: dataref: laminar/B738/autobrake/autobrake_pos - index: 7 name: autobrake disarm - type: single + type: none icon: autobrakedisarm dataref: laminar/B738/annunciator/auto_brake_disarm dataref-multiplier: 1.5 - index: 23 name: anti skid inop - type: single + type: none icon: antiskidinop dataref: laminar/B738/annunciator/anti_skid_inop dataref-multiplier: 1.5 @@ -101,13 +101,13 @@ actions: command: laminar/B738/alert/below_gs_pilot - index: 1 name: takeoff config - type: single + type: none icon: takeoffconfig dataref: laminar/B738/annunciator/takeoff_config dataref-multiplier: 1.5 - index: 2 name: cabin altitude - type: single + type: none icon: cabinaltitude dataref: laminar/B738/annunciator/cabin_alt dataref-multiplier: 1.5 diff --git a/B737-800X/efis.yaml b/B737-800X/efis.yaml index 634077d..1bc389d 100644 --- a/B737-800X/efis.yaml +++ b/B737-800X/efis.yaml @@ -65,7 +65,7 @@ actions: # align: center - index: 1 name: mins - type: single + type: none icon: mins dataref: laminar/B738/EFIS_control/cpt/minimums - index: 2 @@ -108,7 +108,7 @@ actions: command: laminar/B738/EFIS_control/capt/map_mode_dn - index: 17 name: map mode - type: single + type: none icon: mapmode dataref-states: - 0.0 @@ -137,7 +137,7 @@ actions: command: laminar/B738/EFIS_control/capt/baro_in_hpa_dn - index: 6 name: baro - type: single + type: none icon: baro dataref: laminar/B738/EFIS_control/capt/baro_in_hpa - index: 7 @@ -178,7 +178,7 @@ actions: command: laminar/B738/EFIS_control/capt/map_range_dn - index: 22 name: map range - type: single + type: none icon: maprange dataref-states: - 0.0 diff --git a/B737-800X/lowerovhd.yaml b/B737-800X/lowerovhd.yaml index 0281625..0ffcf7e 100644 --- a/B737-800X/lowerovhd.yaml +++ b/B737-800X/lowerovhd.yaml @@ -175,7 +175,7 @@ actions: - 2.0 - index: 8 name: apu egt - type: single + type: none dataref: laminar/B738/electrical/apu_temp gauge: name: apuegt @@ -256,7 +256,7 @@ actions: command-off: laminar/B738/toggle_switch/eng_start_source_left - index: 5 name: arm emer exit lights ann - type: single + type: none icon: notarmed dataref: laminar/B738/annunciator/emer_exit dataref-multiplier: 1.5 From 40478d9228adf44334cd37366f524856894c9cda Mon Sep 17 00:00:00 2001 From: wortelus Date: Tue, 2 Aug 2022 00:17:31 +0200 Subject: [PATCH 3/3] format fix --- preprocessing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/preprocessing.py b/preprocessing.py index f02423c..3800020 100644 --- a/preprocessing.py +++ b/preprocessing.py @@ -162,7 +162,8 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare pass elif self.dataref_states is not None: if icon is None: - logging.error("#{} {} is trying to set dataref_states without the 'icon' parameter, quitting...") + logging.error("#{} {} is trying to set dataref_states without the 'icon' parameter, quitting..." + .format(index, name)) sys.exit(1) self.file_names = np.empty(len(self.dataref_states), dtype=object) @@ -170,7 +171,8 @@ def __init__(self, index, name, icon, cmd_type, label=None, dataref=None, datare self.file_names[i] = get_filename_button_dataref_png(icon, state) else: if icon is None: - logging.error("#{} {} is trying to set static icon without the 'icon' parameter, quitting...") + logging.error("#{} {} is trying to set static icon without the 'icon' parameter, quitting..." + .format(index, name)) sys.exit(1) self.file_names = np.empty(1, dtype=object)