Example #1
0
 def __init__(self, client: mclient.Client, opts: BasicConfig, logger: logging.Logger, device_id: str):
     self.__client = client
     self.__logger = logger.getChild("PID")
     self._config = PluginConfig(opts, "PID")
     self._device_id = device_id
     self._hvac_call = PID_Controller(self._config, client, logger)
     self._device = None
Example #2
0
 def __init__(self, conf: conf.BasicConfig):
     self.c = PluginConfig(conf, PluginLoader.getConfigKey())
     meters = self.c.get("meter", default=None)
     if not isinstance(meters, list):
         meters = []
         self.c["meters"] = []
     self.currIndex = len(meters) - 1
Example #3
0
    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "systray")
        self._log = log.getChild("STRAY")

        self.icon_thread = Thread(name="systray",
                                  daemon=False,
                                  target=self.sicon_thread)
        self.icon_thread.start()
Example #4
0
 def setConfig(self, config: PluginConfig):
     super().setConfig(config)
     self._config = config
     for display in config.get("displays/list", default=[]):
         d = Display.from_dict(display)
         d.connected = False
         d.connected_via = ""
         self._display_list[hash(d)] = d
Example #5
0
    def __init__(self, client: mclient.Client, opts: BasicConfig,
                 logger: logging.Logger, device_id: str):
        threading.Thread.__init__(self)
        self.__client = client
        self.__logger = logger.getChild("PiMotion")
        self._config = PluginConfig(opts, "PiMotion")
        self._device_id = device_id

        self.__logger.debug("PiMotion.__init__()")

        self.name = "PiCamera"
        self.__logger.debug("PiMotion.register()")
        self._doExit = False
        self._camera = None

        path = self._config.get("record/path", "~/Videos")
        if not path.endswith("/"):
            path += "/"
        path = "{}/aufnahmen/".format(path)
        pathlib.Path(path).mkdir(parents=True, exist_ok=True)

        path = self._config.get("record/path", "~/Videos")
        if not path.endswith("/"):
            path += "/"
        path = "{}/magnitude/".format(path)
        pathlib.Path(path).mkdir(parents=True, exist_ok=True)
        self._image_font = ImageFont.truetype(font=self._config.get(
            "font", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
                                              size=9,
                                              encoding="unic")

        self._pilQueue = queue.Queue(5)
        self._splitStream = None
        self._err_topics = None
        self.was_errored = True
        self.set_do_record(self._config.get("record/enabled", True))

        self.start()
        self._pilThread = threading.Thread(target=self.pil_magnitude_save,
                                           name="MagSave")
        self._pilThread.start()
Example #6
0
    def __init__(self, config: PluginConfig, log:Logger):
        threading.Thread.__init__(self)
        self.__serial_port = config.get("serial", "/dev/ttyAMA0")
        self._log = log.getChild("Connection")

        self._device = DeviceInfo()
        self._device.model = None
        self._device_ready = False
        self._shutdown = False
        self._ved = None
        self._calls = {}

        self.name = "VE.Direct Serial"
Example #7
0
    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "PnP")
        self._allowed_services = self._config.get("allowed", ["monitor"])
        self._log = log.getChild("WMI_PnP")

        self._log.debug("Erstelle Watcher für PnP änderung...")
        self._pnp_added_thread = Thread(name="wmiPnpAdded", target=self.deviceAddedThread)
        self._pnp_added_thread.start()
        self._pnp_removed_thread = Thread(name="wmiPnpRemoved", target=self.deviceRemovedThread)
        self._pnp_removed_thread.start()

        self._log.info("Holen der Geräte informationen...")
        # Get All PnP Devices
        obj = wmi.WMI().Win32_PnPEntity()
        #Filter for Monitors
        for x in obj:
            self._log.debug("Verarbeite {}...".format(x.id))
            id, entry = self._getPopulatedPnP(x)
            if id is not None:
                self.addDevice(id, entry, True)
                self._log.debug("Erlaubtes Gerät hinzugefügt.")
        self._pnp_cache_rdy = True
        self._log.info("Geräte ausgelesen")
Example #8
0
class KaifaPluginConfig:
    def __init__(self, conf: conf.BasicConfig):
        self.c = PluginConfig(conf, PluginLoader.getConfigKey())
        meters = self.c.get("meter", default=None)
        if not isinstance(meters, list):
            meters = []
            self.c["meters"] = []
        self.currIndex = len(meters) - 1

    def addSmartMeter(self):
        meter = {}
        meter["port"] = ConsoleInputTools.get_input("Serielle Schnittstelle", require_val=True)
        meter["baudrate"] = ConsoleInputTools.get_number_input("Baudrate", map_no_input_to=2400)
        meter["parity"] = ConsoleInputTools.get_input("Parity", require_val=False, std_val="serial.PARITY_NONE")
        meter["stopbits"] = ConsoleInputTools.get_input("Stopbits", std_val="serial.STOPBITS_ONE")
        meter["bytesize"] = ConsoleInputTools.get_input("Bytesize", std_val="serial.EIGHTBITS")
        meter["key_hex_string"] = ConsoleInputTools.get_input("Entschlüsselungskey", require_val=True)
        meter["interval"] = ConsoleInputTools.get_number_input("Interval (Nicht mehr als 5)", map_no_input_to=1)
        meter["supplier"] = ConsoleInputTools.get_input("Netz Betreiber", std_val="EVN")
        self.c["meters"].append(meter)
        self.currIndex += 1
        
    def remSmartMeter(self):
        print("Auflistung aller Seriellen Ports:")
        print("0 um zu beenden.")
        for index in len(self.c["meter"]):
            port = self.c[f"meter/{index}"]
            print(f"{index+1}. {port}")
        
        toDelete = ConsoleInputTools.get_number_input("Nummer wählen", map_no_input_to=0)
        if toDelete == 0:
            return
        toDelete -= 0
        del self.c[f"meter/{toDelete}"]


    def run(self):
        while True:
            print("Mögliche Aktionen: \n 1. Smart Meter hinzufügen | 2. Smart Meter löschen\n 0. Beenden")
            action = ConsoleInputTools.get_number_input("Aktion", "0")
            match action:
                case 0: return
                case 1: self.addSmartMeter()
                case 2: self.remSmartMeter()
                case _: print("Ungültige Auswahl")
Example #9
0
class PID_Plugin:

    def __init__(self, client: mclient.Client, opts: BasicConfig, logger: logging.Logger, device_id: str):
        self.__client = client
        self.__logger = logger.getChild("PID")
        self._config = PluginConfig(opts, "PID")
        self._device_id = device_id
        self._hvac_call = PID_Controller(self._config, client, logger)
        self._device = None

    def set_pluginManager(self, pm):
        self._pluginManager = pm
        self._device = HvacDevice(self.__logger.getChild("dev"), pm, self._hvac_call, self._config.get("name"))

    def register(self, wasConnected: bool):
        if not wasConnected:
            self._hvac_call.initialize(self._device)
        self._hvac_call.register()

    def sendStates(self):
        self.sendUpdate()

    def sendUpdate(self, fromHandler=False):
        pass

    def on_message(self, client, userdata, message: mclient.MQTTMessage):
        pass

    def stop(self):
        if self._hvac_call is not None:
            self._hvac_call.stop()
Example #10
0
class WMI_PnP:
    _all_services = []
    _pnp_cache: dict[str,dict] = {}
    _pnp_cache_rdy = False
    _shutdown = False
    _registered_devices: dict[str,BinarySensor.BinarySensor] = {}
    _pman: PluginManager = None

    def deviceRemovedThread(self):
        raw_wql = "SELECT * FROM __InstanceDeletionEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_PnPEntity\'" # for removed devices
        c = wmi.WMI ()
        watcher = c.watch_for(raw_wql=raw_wql)
        while not self._pnp_cache_rdy:
            sleep(1)
        self._log.debug("PnP cache ready. Beginne aktives warten auf events")
        while not self._shutdown:
            try:
                pnp = watcher(2500)
                id, entry = self._getPopulatedPnP(pnp)
                if id is None:
                    continue
                if id in self._pnp_cache.keys():
                    self._log.debug("Gerät {} getrennt. sende update...".format(id))
                    self.addDevice(id, entry, False)
                self.sendUpdate(True)
            except wmi.x_wmi_timed_out:
                pass

    def deviceAddedThread(self):
        raw_wql = "SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA \'Win32_PnPEntity\'" # for added devices
        c = wmi.WMI ()
        watcher = c.watch_for(raw_wql=raw_wql)
        while not self._pnp_cache_rdy:
            sleep(1)
        self._log.debug("PnP cache ready. Beginne aktives warten auf events")
        while not self._shutdown:
            try:
                pnp = watcher(2500)
                self._log.debug("New Device plugged in: {}".format(pnp))
                id, entry = self._getPopulatedPnP(pnp)
                if id is None:
                    continue
                self._log.debug("Device is allowed")
                self.addDevice(id, entry, True)
                self.sendUpdate(True)
            except wmi.x_wmi_timed_out:
                pass
    
    def _getPopulatedPnP(self, pnp) -> Tuple[str, dict]:
        pnp_entry = {}
        service = pnp.wmi_property("Service").value
        if service not in self._all_services:
            self._all_services.append(service)
        
        if service not in self._allowed_services:
            return None, None

        for props in pnp.properties.keys():
            property = pnp.wmi_property(props)
            pnp_entry[property.name] = property.value
        return pnp.id, pnp_entry



    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "PnP")
        self._allowed_services = self._config.get("allowed", ["monitor"])
        self._log = log.getChild("WMI_PnP")

        self._log.debug("Erstelle Watcher für PnP änderung...")
        self._pnp_added_thread = Thread(name="wmiPnpAdded", target=self.deviceAddedThread)
        self._pnp_added_thread.start()
        self._pnp_removed_thread = Thread(name="wmiPnpRemoved", target=self.deviceRemovedThread)
        self._pnp_removed_thread.start()

        self._log.info("Holen der Geräte informationen...")
        # Get All PnP Devices
        obj = wmi.WMI().Win32_PnPEntity()
        #Filter for Monitors
        for x in obj:
            self._log.debug("Verarbeite {}...".format(x.id))
            id, entry = self._getPopulatedPnP(x)
            if id is not None:
                self.addDevice(id, entry, True)
                self._log.debug("Erlaubtes Gerät hinzugefügt.")
        self._pnp_cache_rdy = True
        self._log.info("Geräte ausgelesen")

    def register(self, wasConnected: bool, pman: PluginManager):
        self._pman = pman
        self.sendUpdate(not wasConnected)

    def addDevice(self, id: str, dev: dict, isConnected=False):
        if not isConnected and id in self._pnp_cache.keys():
            self._pnp_cache.pop(id)
        elif isConnected and id not in self._pnp_cache.keys():
            self._pnp_cache[id] = dev

        if self._pman is not None:
            if id not in self._registered_devices.keys():
                if id not in self._config.get("devices", {}).keys():
                    self._config["devices"][id] = {
                        "Name": dev["Name"],
                        "Service": dev["Service"],
                        "PnPID": id,
                        "DeviceID": dev["DeviceID"]
                    }
                uid = "bsens.win32.wmi.PnP.{}.{}".format(id, dev["Service"])
                sensor = BinarySensor.BinarySensor(
                    self._log, 
                    pman=self._pman,
                    name=dev["Name"],
                    binary_sensor_type=BinarySensorDeviceClasses.PLUG,
                    value_template="{{value_json.pnp_present}}",
                    json_attributes=True,
                    unique_id=uid,
                    subnode_id=dev["DeviceID"]
                )
                self._registered_devices[id] = sensor
                sensor.register()
                self.sendDeviceState(id)
            else:
                sensor = self._registered_devices[id]
                sensor.register()
                self.sendDeviceState(id)

    def sendDeviceState(self, PnPDeviceID: str, forceOff=False):
        dev = self._registered_devices[PnPDeviceID]
        dev_pluggedin = 1 if PnPDeviceID in self._pnp_cache.keys() and not forceOff else 0
        self._log.debug("Gerät {} is da? {}. [{}] Update wird gesendet!".format(PnPDeviceID, dev_pluggedin, self._pnp_cache.keys()))
        js = { }
        js.update(self._config.get("devices", {}).get(PnPDeviceID,{}))
        try:
            js.update(self._pnp_cache.get(PnPDeviceID, None).copy())
        except:
            pass
        js["pnp_present"] = dev_pluggedin
        dev.turn(js)


    def _rebuild_devices(self):
        devices = self._config.get("devices", {})
        for PNPDeviceID in devices.keys():
            self.addDevice(PNPDeviceID, devices[PNPDeviceID], isConnected=PNPDeviceID in self._pnp_cache.keys())
        for PNPDeviceID in self._pnp_cache.keys():
            self.addDevice(PNPDeviceID, self._pnp_cache[PNPDeviceID], isConnected=True)

    def sendUpdate(self, cache_changed=False):
        if cache_changed:
            self._rebuild_devices()
        for PnPDeviceID in self._registered_devices:
            self.sendDeviceState(PnPDeviceID)
    
    def shutdown_watchers(self):
        self._shutdown = True
        self._log.debug("Warte auf added Thread...")
        self._pnp_added_thread.join()
        self._log.debug("Warte auf removed Thread...")
        self._pnp_removed_thread.join()
        self._log.debug("WMI PnP Überwachung beendet")
        for PnPDeviceID in self._registered_devices:
            self.sendDeviceState(PnPDeviceID, True)
Example #11
0
    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "pwr")
        self._log = log.getChild("WindowMessages")

        if self._config.get("pwr", True):
            from Mods.win32submods.pwr.powerevents import Powerevents
            pe = Powerevents(self)
            self._window_message_receivers.append(pe)
        if self._config.get("dev", True):
            from Mods.win32submods.pwr.pwr_devices import DeviceManagementMessagesProcessor
            pe = DeviceManagementMessagesProcessor(self)
            self._window_message_receivers.append(pe)

        def wndproc(hwnd, msg, wparam, lparam):
            for rec in self._window_message_receivers:
                try:
                    val = rec.on_window_event(hwnd, msg, wparam, lparam)
                    if val is not None:
                        return val
                except:
                    self._log.exception("wndproc")

        def window_pump():
            self._log.debug("Win32 API Window erstellen...")
            hinst = win32api.GetModuleHandle(None)
            wndclass = win32gui.WNDCLASS()
            wndclass.hInstance = hinst
            wndclass.lpszClassName = "mqttScriptPowereventWindowClass"
            CMPFUNC = CFUNCTYPE(c_bool, c_int, c_uint, c_uint, c_void_p)
            wndproc_pointer = CMPFUNC(wndproc)
            wndclass.lpfnWndProc = {win32con.WM_POWERBROADCAST : wndproc_pointer}
            try:
                myWindowClass = win32gui.RegisterClass(wndclass)
                self._hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
                                            myWindowClass, 
                                            "mqttScriptPowereventWindow", 
                                            0, 
                                            0, 
                                            0, 
                                            win32con.CW_USEDEFAULT, 
                                            win32con.CW_USEDEFAULT, 
                                            win32con.HWND_MESSAGE, 
                                            0, 
                                            hinst, 
                                            None)
            except Exception as e:
                self._log.exception("Window konnte nicht erstellt werden!")

            if self._hwnd is None:
                self._log.error("hwnd is none!")
                return
            else:
                self._log.debug("Windows Handle ({}) erstellt".format(self._hwnd))
            self._log.debug("Begin pumping...")
            try:
                while not self._shutdown:
                    win32gui.PumpWaitingMessages()
                    time.sleep(1)
            except:
                self._log.exception("Pumping of messages failed")

        self._window_pump_thread = threading.Thread(name="window_pump", target=window_pump)
        self._window_pump_thread.start()
Example #12
0
class WindowEventProcessor:
    _shutdown = False
    _pman: Union[PluginManager,None] = None
    _hwnd: Union[int,None] = 0
    _window_message_receivers: list[WindowEventReciever] = []

    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "pwr")
        self._log = log.getChild("WindowMessages")

        if self._config.get("pwr", True):
            from Mods.win32submods.pwr.powerevents import Powerevents
            pe = Powerevents(self)
            self._window_message_receivers.append(pe)
        if self._config.get("dev", True):
            from Mods.win32submods.pwr.pwr_devices import DeviceManagementMessagesProcessor
            pe = DeviceManagementMessagesProcessor(self)
            self._window_message_receivers.append(pe)

        def wndproc(hwnd, msg, wparam, lparam):
            for rec in self._window_message_receivers:
                try:
                    val = rec.on_window_event(hwnd, msg, wparam, lparam)
                    if val is not None:
                        return val
                except:
                    self._log.exception("wndproc")

        def window_pump():
            self._log.debug("Win32 API Window erstellen...")
            hinst = win32api.GetModuleHandle(None)
            wndclass = win32gui.WNDCLASS()
            wndclass.hInstance = hinst
            wndclass.lpszClassName = "mqttScriptPowereventWindowClass"
            CMPFUNC = CFUNCTYPE(c_bool, c_int, c_uint, c_uint, c_void_p)
            wndproc_pointer = CMPFUNC(wndproc)
            wndclass.lpfnWndProc = {win32con.WM_POWERBROADCAST : wndproc_pointer}
            try:
                myWindowClass = win32gui.RegisterClass(wndclass)
                self._hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
                                            myWindowClass, 
                                            "mqttScriptPowereventWindow", 
                                            0, 
                                            0, 
                                            0, 
                                            win32con.CW_USEDEFAULT, 
                                            win32con.CW_USEDEFAULT, 
                                            win32con.HWND_MESSAGE, 
                                            0, 
                                            hinst, 
                                            None)
            except Exception as e:
                self._log.exception("Window konnte nicht erstellt werden!")

            if self._hwnd is None:
                self._log.error("hwnd is none!")
                return
            else:
                self._log.debug("Windows Handle ({}) erstellt".format(self._hwnd))
            self._log.debug("Begin pumping...")
            try:
                while not self._shutdown:
                    win32gui.PumpWaitingMessages()
                    time.sleep(1)
            except:
                self._log.exception("Pumping of messages failed")

        self._window_pump_thread = threading.Thread(name="window_pump", target=window_pump)
        self._window_pump_thread.start()

    def killPluginManager(self):
        if self._pman is None:
            self._log.error("Shutdown on windows shutdown failed!")
            return
        import threading
        t = threading.Thread(target=self._pman.shutdown, name="WindowsAsyncDestroy", daemon=True)
        t.start()
    
    def register(self, wasConnected: bool, pman: PluginManager):
        self._pman = pman
        for rec in self._window_message_receivers:
            rec.register(wasConnected=wasConnected)
    
    def shutdown(self):
        try:
            for rec in self._window_message_receivers:
                rec.shutdown()
        except:
            self._log.exception("Shutdown of WindowEvent extension failed!")
        self._log.debug("WindowEvent wait4shutdown...")
        self._shutdown = True
        self._window_pump_thread.join()
    
    def sendUpdate(self, force=True):
        try:
            for rec in self._window_message_receivers:
                rec.sendUpdate(force=force)
        except:
            self._log.exception("sendUpdate()")
Example #13
0
 def __init__(self, conf: conf.BasicConfig):
     self.__ids = []
     self.c = PluginConfig(conf, PluginLoader.getConfigKey())
Example #14
0
 def getPlugin(client: mclient.Client, opts: conf.BasicConfig,
               logger: logging.Logger, device_id: str):
     from Mods.udev.Main import UdevPlugin as pl
     return pl(client, PluginConfig(opts, PluginLoader.getConfigKey()),
               logger, device_id)
Example #15
0
 def __init__(self, log: logging.Logger, config: PluginConfig, _MAX_VALUE = 255):
     self._config = PluginConfig(config, "valve")
     self.hotter = Pin(self._config["hotter"], PinDirection.OUT, init=0)
     self.colder = Pin(self._config["colder"], PinDirection.OUT, init=0)
     self._old_val = 0
     self._MAX_VALUE = _MAX_VALUE
Example #16
0
class PiMotionMain(threading.Thread):

    _motionStream = etc.NullOutput()
    _webStream = etc.NullOutput()
    _circularStream = None
    _inMotion = None
    _pluginManager = None
    _rtsp_server = None
    _http_server = None
    _rtsp_split = None
    _jsonOutput = None
    _motion_topic = None
    _debug_topic = None
    _do_record_topic = None
    _lastState = {"motion": 0, "x": 0, "y": 0, "val": 0, "c": 0}
    _analyzer = None
    _postRecordTimer = None
    _pilQueue = None
    _pilThread = None
    _http_out = None
    _sendDebug = False
    _splitStream = None
    _record_factory = None
    _snapper = None
    _annotation_updater = None
    _err_topics = None
    _brightness_topic = None
    __last_brightness = nan

    bitrate = 17000000
    _area = 25  # number of connected MV blocks (each 16x16 pixels) to count as a moving object
    _frames = 4  # number of frames which must contain movement to trigger

    def __init__(self, client: mclient.Client, opts: BasicConfig,
                 logger: logging.Logger, device_id: str):
        threading.Thread.__init__(self)
        self.__client = client
        self.__logger = logger.getChild("PiMotion")
        self._config = PluginConfig(opts, "PiMotion")
        self._device_id = device_id

        self.__logger.debug("PiMotion.__init__()")

        self.name = "PiCamera"
        self.__logger.debug("PiMotion.register()")
        self._doExit = False
        self._camera = None

        path = self._config.get("record/path", "~/Videos")
        if not path.endswith("/"):
            path += "/"
        path = "{}/aufnahmen/".format(path)
        pathlib.Path(path).mkdir(parents=True, exist_ok=True)

        path = self._config.get("record/path", "~/Videos")
        if not path.endswith("/"):
            path += "/"
        path = "{}/magnitude/".format(path)
        pathlib.Path(path).mkdir(parents=True, exist_ok=True)
        self._image_font = ImageFont.truetype(font=self._config.get(
            "font", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"),
                                              size=9,
                                              encoding="unic")

        self._pilQueue = queue.Queue(5)
        self._splitStream = None
        self._err_topics = None
        self.was_errored = True
        self.set_do_record(self._config.get("record/enabled", True))

        self.start()
        self._pilThread = threading.Thread(target=self.pil_magnitude_save,
                                           name="MagSave")
        self._pilThread.start()

    def set_do_record(self, recording: bool):
        self.__logger.info(
            "Config Wert aufnehmen wird auf {} gesetzt".format(recording))
        self._config["record/enabled"] = recording

    def register(self, wasConnected=False):
        # Setup MQTT motion binary_sensor
        sensorName = self._config["motion/sensorName"]
        uid_motion = "binary_sensor.piMotion-{}-{}".format(
            self._device_id, sensorName)
        self._motion_topic = self._config._main.get_autodiscovery_topic(
            autodisc.Component.BINARY_SENROR, sensorName,
            autodisc.BinarySensorDeviceClasses.MOTION)
        motion_payload = self._motion_topic.get_config_payload(
            sensorName,
            "",
            unique_id=uid_motion,
            value_template="{{ value_json.motion }}",
            json_attributes=True)
        if self._motion_topic.config is not None:
            self.__client.publish(self._motion_topic.config,
                                  payload=motion_payload,
                                  qos=0,
                                  retain=True)

        errName = "{} Kamera Fehler".format(sensorName)
        self._err_topics = self._config.get_autodiscovery_topic(
            autodisc.Component.BINARY_SENROR, errName,
            autodisc.BinarySensorDeviceClasses.PROBLEM)
        self._err_topics.register(
            self.__client,
            errName,
            "",
            value_template="{{value_json.err}}",
            json_attributes=True,
            unique_id="cam.main.error.{}".format(
                self._config._main.get_client_config().id))
        self.__client.publish(self._err_topics.state,
                              json.dumps({
                                  "err": 1,
                                  "Grund": "Starting..."
                              }))

        self._brightness_topic = self._config.get_autodiscovery_topic(
            autodisc.Component.SENSOR, "Bildhelligkeit",
            autodisc.SensorDeviceClasses.ILLUMINANCE)

        self._brightness_topic.register(
            self.__client,
            "Bildhelligkeit",
            "pxLux",
            value_template="{{value_json.brightness}}",
            json_attributes=True,
            unique_id="cam.main.bright.{}".format(
                self._config._main.get_client_config().id))
        self.was_errored = True

    def set_pluginManager(self, p: pm.PluginManager):
        self._pluginManager = p

    def stop(self):
        self._doExit = True
        if self._record_factory:
            self._record_factory.destroy()
        if self._pilQueue is not None and self._pilThread is not None:
            self.__logger.info("Stoppe PIL queue...")
            try:
                self._pilQueue.put((None, None), block=False)
            except queue.Full:
                pass
        if self._rtsp_server is not None:
            self._rtsp_server.stopServer()
        if self._http_server is not None:
            self._http_server.stop()
        self.__client.publish(self._motion_topic.ava_topic,
                              "offline",
                              retain=True)

        if self._analyzer is not None:
            self._analyzer.stop_queue()

        if self._pilQueue is not None and self._pilThread is not None:
            self.__logger.info("Warte auf PIL Thread...")
            self._pilThread.join(10)
            self._pilQueue = None
        if self._http_out is not None:
            self._http_out.flush()

    def setupAnalyzer(self, camera: cam.PiCamera):
        anal = analyzers.Analyzer(camera,
                                  logger=self.__logger.getChild("Analyzer"),
                                  config=self._config,
                                  fps=self._config["camera/fps"],
                                  postSecs=self._config.get(
                                      "motion/recordPost", 1))
        # SET SETTINGS
        anal.frameToTriggerMotion = self._config.get("motion/motion_frames", 4)
        anal.framesToNoMotion = self._config.get("motion/still_frames", 4)
        anal.blockMinNoise = self._config.get("motion/blockMinNoise", 0)
        anal.countMinNoise = self._config.get("motion/frameMinNoise", 0)
        anal.countMaxNoise = self._config.get("motion/frameMaxNoise", 0)
        anal.lightDiffBlock = self._config.get("motion/lightDiffBlock", -1)
        anal.zeromap_py = self._config.get("zeroMap", {
            "enabled": False,
            "isBuilding": False,
            "dict": None
        })
        anal.disableAnalyzing = not self._config.get("motion/doAnalyze", True)
        # SET CALLBACKS
        anal.motion_call = lambda motion, data, mes: self.motion(
            motion, data, mes)
        anal.motion_data_call = lambda data, changed: self.motion_data(
            data, changed)
        anal.pil_magnitude_save_call = lambda img, data: self.pil_magnitude_save_call(
            img, data)
        anal.cal_getMjpeg_Frame = self.getMjpegFrame
        self._analyzer = anal

    def setupRTSP(self):
        factory = PiCameraMediaFactory(
            fps=self._config["camera/fps"],
            CamName=self._config["motion/sensorName"],
            log=self.__logger.getChild("RTSP_Factory"),
            splitter=self.
            _splitStream  #,wh=(self._config["camera/width"], self._config["camera/height"])
        )

        server = GstServer(factory=factory, logger=self.__logger)
        server.runServer()
        self._rtsp_server = server

    def setupRecordFactory(self) -> PreRecordBuffer:
        factory = PreRecordBuffer(
            secs_pre=self._config["motion/recordPre"],
            fps=self._config["camera/fps"],
            camName=self._config["motion/sensorName"],
            logger=self.__logger,
            splitter=self._splitStream,
            path=pathlib.Path(self._config["record/path"]),
            wh=(self._config["camera/width"], self._config["camera/height"]))
        factory.start()
        self._record_factory = factory
        return factory

    def setupHttpServer(self):
        self.__logger.info("Aktiviere HTTP...")
        http_out = CameraSplitter(self._camera, self.__logger, 1, True)
        self._http_out = http_out

        self._jsonOutput = httpc.StreamingJsonOutput()
        address = (self._config.get("http/addr", "0.0.0.0"),
                   self._config.get("http/port", 8083))
        streamingHandle = httpc.makeStreamingHandler(http_out,
                                                     self._jsonOutput)
        streamingHandle.logger = self.__logger.getChild("HTTPHandler")
        streamingHandle.meassure_call = lambda s, i: self.meassure_call(i)
        streamingHandle.fill_setting_html = lambda s, html: self.fill_settings_html(
            html)
        streamingHandle.update_settings_call = lambda s, a, b, c, d, e, f: self.update_settings_call(
            a, b, c, d, e, f)
        streamingHandle.jpegUpload_call = lambda s, d: self.parseTrainingPictures(
            d)
        streamingHandle.set_anal_onhold = lambda s, x: self.set_anal_onhold(x)
        streamingHandle.save_snapshot = lambda s: self.takeSnapshot()

        server = httpc.StreamingServer(address, streamingHandle)
        server.logger = self.__logger.getChild("HTTP_srv")
        self._http_server = server

    def run(self):
        import time
        time.sleep(5)
        self.__logger.debug("PiMotion.run()")
        with cam.PiCamera(clock_mode='raw',
                          framerate=self._config.get("camera/fps",
                                                     23)) as camera:
            self._camera = camera
            self._splitStream = CameraSplitter(camera=camera,
                                               log=self.__logger)
            # Init Kamera
            camera.resolution = (self._config["camera/width"],
                                 self._config["camera/height"])
            camera.video_denoise = self._config.get("camera/denoise", True)
            self.__logger.debug("Kamera erstellt")

            camera.annotate_background = cam.Color('black')
            camera.annotate_text = dt.datetime.now().strftime(
                'Gestartet am %d.%m.%Y um %H:%M:%S')

            self.setupAnalyzer(camera)

            with self._analyzer:
                self.__logger.debug("Analyzer erstellt")

                if self._config.get("rtsp/enabled", True):
                    self.setupRTSP()

                self.setupRecordFactory()

                self._splitStream.splitter_port = 1
                camera.start_recording(
                    self._splitStream,
                    format='h264',
                    profile='high',
                    level='4.1',
                    splitter_port=1,
                    motion_output=self._analyzer,
                    quality=25,
                    sps_timing=True,
                    intra_period=int(self._config.get("camera/fps", 23) * 0.5),
                    sei=True,
                    inline_headers=True,
                    bitrate=self.bitrate)

                if self._config.get("http/enabled", False):
                    self.setupHttpServer()

                    camera.start_recording(self._http_out,
                                           format='mjpeg',
                                           splitter_port=2)
                    t = threading.Thread(name="http_server",
                                         target=self._http_server.run)
                    self.__logger.info("Starte HTTP...")
                    t.start()
                # Und jetzt einfach warten

                def first_run():
                    time.sleep(2)
                    self.__logger.info(
                        "Stream wird sich normalisiert haben. Queue wird angeschlossen..."
                    )
                    self._analyzer.run_queue()
                    self.update_anotation()

                threading.Thread(target=first_run,
                                 name="Analyzer bootstrap",
                                 daemon=True).start()

                exception_raised = False
                sleep_seconds = 2
                while not self._doExit:
                    try:
                        camera.wait_recording(sleep_seconds, splitter_port=1)
                        if self._analyzer.disableAnalyzing:
                            pps = self._splitStream.written / sleep_seconds
                            self._splitStream.written = 0
                        else:
                            pps = self._analyzer.processed / sleep_seconds
                            self._analyzer.processed = 0
                        fps = self._config.get("camera/fps", 23)
                        if int(fps) != int(pps):
                            self.__logger.warning(
                                "Pro Sekunde verarbeitet: %d, sollte aber %d sein",
                                pps, fps)

                        if pps == 0:
                            self.was_errored = True
                            if self.__client is not None:
                                self.__client.publish(
                                    self._err_topics.state,
                                    json.dumps({
                                        "err": 1,
                                        "Grund": "FPS ist 0"
                                    }))
                            import sys, traceback
                            with open("/tmp/piMotion_last_traces", "w") as f:
                                for thread_id, frame in sys._current_frames(
                                ).items():
                                    print('\n--- Stack for thread {t} ---'.
                                          format(t=thread_id),
                                          file=f)
                                    traceback.print_stack(frame, file=f)

                        elif self.was_errored:
                            if self.__client is not None:
                                self.__client.publish(
                                    self._err_topics.state,
                                    json.dumps({
                                        "err": 0,
                                        "Grund": "Fehler verschwunden"
                                    }))
                                self.was_errored = False
                        self.sendBrightness()
                    except Exception as e:
                        self.__logger.exception("Kamera Fehler")
                        self._doExit = True
                        exception_raised = True
                        self._analyzer.stop_queue(e)

                if not exception_raised:
                    self._analyzer.stop_queue()
        try:
            self._http_server.stop()
        except:
            pass
        try:
            self._rtsp_server.stopServer()
        except:
            pass
        try:
            camera.stop_recording(splitter_port=2)
        except:
            pass
        try:
            camera.stop_recording()
        except:
            pass
        try:
            self._http_out.shutdown()
        except:
            pass
        self._camera = None

    def update_anotation(self, aps=0):
        self._annotation_updater = None
        if self._camera is not None:
            txt_motion = dt.datetime.now().strftime(
                '%d.%m.%Y %H:%M:%S REC'
            ) if self._inMotion else dt.datetime.now().strftime(
                '%d.%m.%Y %H:%M:%S STILL')

            self._camera.annotate_text = txt_motion
            self._annotation_updater = threading.Timer(
                interval=1, function=self.update_anotation)
            self._annotation_updater.start()

            #self._camera.annotate_text = text1 + " {} {}APS {} {} {} {}".format(
            #   txt_motion, aps,
            #   self._lastState["x"], self._lastState["y"], self._lastState["val"], self._lastState["c"]
            #)

    def meassure_call(self, i):
        if i == 0:
            self.meassure_minimal_blocknoise()
        elif i == 1:
            self._analyzer.trainZeroMap()
        elif i == 2:
            self._analyzer.trainZeroMap(True)

    def meassure_minimal_blocknoise(self):
        self.__logger.info("Starte neue Kalibrierung...")
        self._analyzer.states["still_frames"] = 0
        self._analyzer._calibration_running = True
        self._analyzer.framesToNoMotion *= 15

    def stop_record(self):
        self._postRecordTimer = None
        self._record_factory.stop_recording()
        try:
            self.motion(False, self._lastState, False, True)
        except KeyError:
            self.__logger.exception("Sending no motion failed!")

    def do_record(self, record: bool, stopInsta=False):
        if record:
            if self._config.get("record/enabled", True):
                path = self._config.get("record/path", "~/Videos")
                if not path.endswith("/"):
                    path += "/"
                path = "{}/aufnahmen/{}.h264".format(
                    path,
                    dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
                if self._postRecordTimer is None:
                    self._record_factory.record()
                    self._postRecordTimer = ResettableTimer(
                        interval=self._config.get("motion/recordPost", 1),
                        function=self.stop_record,
                        autorun=False)

                    if self._config.get("motion/takeSnapshot", False):
                        self._snapper = threading.Timer(
                            interval=self._config.get(
                                "motion/takeSnapshotSeconds", 2),
                            function=self.takeSnapshot)
                        self._snapper.name = "PiMotionSnapshot"
                        self._snapper.start()
                elif self._postRecordTimer is not None:
                    self._postRecordTimer.cancel()
                    self.__logger.debug("Aufnahme timer wird zurückgesetzt")
        else:
            self.__logger.info("No Motion")
            if self._postRecordTimer is not None:
                if stopInsta:
                    self._postRecordTimer._interval = 1
                    self._postRecordTimer.reset()
                self._postRecordTimer.reset()

                self.__logger.debug(
                    "Aufnahme wird in {} Sekunden beendet.".format(
                        self._config.get("motion/recordPost", 1)))
            else:
                self.__logger.info("Kein Timer vorhanden. Stoppe sofort")
                self.stop_record()

    def takeSnapshot(self):
        if self._snapper is not None:
            self._snapper.cancel()
            self._snapper = None
        path = self._config.get("record/path", "~/Videos")
        if not path.endswith("/"):
            path += "/"
        path = "{}/aufnahmen/{}.jpeg".format(
            path,
            dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
        self._camera.capture(path, "jpeg", thumbnail=(64, 48, 35))

    def motion(self,
               motion: bool,
               data: dict,
               wasMeassureing: bool,
               delayed=False):
        if wasMeassureing:
            self._config["motion/blockMinNoise"] = self._analyzer.blockMaxNoise
            self._config["motion/frameMinNoise"] = self._analyzer.countMinNoise
            self._config["zeroMap"] = self._analyzer.zeromap_py
            self._config.save()

        if not delayed:
            self.do_record(motion)

        if not motion and not delayed:
            #delay the stop motion
            self._lastState = data
            return

        last = self._inMotion
        self._inMotion = motion

        self.motion_data(data=data, changed=last is not motion)
        #self.set_do_record(motion)

    def motion_data(self, data: dict, changed=False):
        # x y val count
        if data["type"] == "MotionDedector":
            self._lastState = {
                "motion": data["motion"],
                "val": data["val"],
                "type": data["type"],
                "brightness": data["brightness"],
                "lightDiff": data["brightness_change"]
            }
        elif data["type"] == "hotblock":
            self._lastState = {
                "motion": 1 if self._inMotion else 0,
                "x": data["hotest"][0],
                "y": data["hotest"][1],
                "val": data["hotest"][2],
                "c": data["noise_count"],
                "dbg_on": self._sendDebug,
                "ext": data["extendet"],
                "brightness": data["brightness"],
                "lightDiff": data["brightness_change"],
                "type": "hotBlock"
            }
        elif data["type"] == "brightness":
            self._lastState["brightness"] = data["brightness"]
            self._lastState["lightDiff"] = data["brightness_change"]
            self.sendBrightness()
        self._jsonOutput.write(self._lastState)
        if self._analyzer is not None and not self._analyzer._calibration_running:
            self.sendStates(changed=changed)
        #self.update_anotation()

    def sendBrightness(self):
        if self.__last_brightness != self._lastState.get(
                "brightness", nan) and not isnan(
                    self._lastState.get("brightness", nan)):
            if self.__client is None:
                return
            self.__last_brightness = self._lastState.get("brightness", nan)
            self.__client.publish(
                self._brightness_topic.state,
                json.dumps({
                    "brightness": self._lastState.get("brightness", nan),
                    "diff": self._lastState.get("lightDiff", 0)
                }))

    def sendStates(self, changed=None):
        if self.__client is None:
            return
        if changed is None:
            self.__client.publish(self._motion_topic.state,
                                  json.dumps(self._lastState))
            self.__client.publish(self._debug_topic.state,
                                  json.dumps(self._lastState))
            self.sendBrightness()
        elif changed:
            self.__client.publish(self._motion_topic.state,
                                  json.dumps(self._lastState))
            self.sendBrightness()
        elif self._sendDebug:
            self.__client.publish(self._debug_topic.state,
                                  json.dumps(self._lastState))

    def pil_magnitude_save_call(self, d, data: dict):
        if self._pilQueue is not None:
            try:
                self._pilQueue.put_nowait((d, data))
            except queue.Full:
                pass

    def getMjpegFrame(self, block=True, timeout=-1):
        try:
            q = queue.Queue(1)

            def push_queue(frame, extendet, eof=False):
                try:
                    q.put_nowait(frame)
                except queue.Full:
                    pass

            aid = self._http_out.add(push_queue)
            frame = None
            try:
                frame = q.get(block=block, timeout=timeout)
            except queue.Empty:
                pass
            self._http_out.remove(aid)
            return frame
        except AttributeError:
            return None

    def pil_magnitude_save(self):
        while True:
            try:
                a, data = self._pilQueue.get(timeout=5)
            except queue.Empty:
                if self._doExit:
                    self.__logger.warning(
                        "PIL Magnitude Save Thread wird beendet")
                    return
                continue
            if self._doExit or a is None and data is None:
                self.__logger.warning("PIL Magnitude Save Thread wird beendet")
                return
            frame = self.getMjpegFrame(block=True, timeout=2)
            background = None
            try:
                if frame is not None:
                    background = Image.open(io.BytesIO(frame))
                else:
                    self.__logger.warning("Snapshot is None")
            except IOError:
                self.__logger.warning("Snapshot IOError")
            d = np.sqrt(
                np.square(a['x'].astype(np.float)) +
                np.square(a['y'].astype(np.float))).clip(0,
                                                         255).astype(np.uint8)
            Bimg = Image.fromarray(d)
            img = Bimg.convert(mode="RGB", dither=Image.FLOYDSTEINBERG)
            #self.__logger.debug(data)
            if data is not None:
                ig = ImageDraw.Draw(img)
                ig.point(
                    [(data["object"][4]["col"], data["object"][4]["row"])],
                    fill=(255, 0, 0, 200))
            path = pathlib.Path(self._config.get("record/path", "~/Videos"))
            path = path.joinpath(
                "magnitude", "{}.jpeg".format(
                    dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
            path = path.expanduser().absolute()
            self.__logger.debug('Writing %s' % path)
            if background is not None:
                self.__logger.debug("Habe Snapshot. Vermische Bilder...")
                img = img.resize((self._config["camera/width"],
                                  self._config["camera/height"]))
                foreground = img.convert("RGBA")
                background = background.convert("RGBA")
                img = Image.blend(background, foreground, 0.5)

            exif_bytes = None
            if data is not None:
                draw = ImageDraw.Draw(img)
                draw.text(
                    (0, 0),
                    "X: {} Y: {} VAL: {} C: {}".format(data["hotest"][0],
                                                       data["hotest"][1],
                                                       data["hotest"][2],
                                                       data["noise_count"]),
                    fill=(255, 255, 0, 155),
                    font=self._image_font)
                draw.text((0, 20),
                          "R: {} C: {}".format(data["object"][4]["row"],
                                               data["object"][4]["col"]),
                          fill=(255, 255, 0, 155),
                          font=self._image_font)
                draw.text((0, 40),
                          "B: {} Zdata: {}".format(data.get("brightness", -1),
                                                   data.get("zmdata",
                                                            "KEINE")),
                          fill=(255, 255, 0, 155),
                          font=self._image_font)
                draw.text((0, 60),
                          "BDiff: {}".format(data["lightDiff"]),
                          fill=(255, 255, 0, 155),
                          font=self._image_font)
                draw.text((0, 80),
                          dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                          fill=(255, 255, 0, 155),
                          font=self._image_font)

            with path.open(mode="wb") as file:
                if data is not None:
                    cimg = img.convert('RGB')
                    bio = io.BytesIO()
                    cimg.save(bio, "jpeg")

                    encoded = data  #### OK Daten sind zu groß für exif villeicht anhängen?
                    extendet_data = {}
                    try:
                        extendet_data = data.get("exif_zeromap", None)
                        if extendet_data is not None:
                            del data["exif_zeromap"]
                            data["extendet_data_appendet"] = True
                        encoded = json.dumps(data)
                        user_comment = piexif.helper.UserComment.dump(
                            encoded, encoding="unicode")

                        bio.seek(0)
                        exif_dict = piexif.load(bio.read())
                        exif_dict["Exif"][
                            piexif.ExifIFD.UserComment] = user_comment
                        exif_bytes = piexif.dump(exif_dict)
                    except:
                        self.__logger.exception("exif_json failed")

                    if exif_bytes is not None:
                        bio = io.BytesIO()
                        cimg.save(bio, "jpeg", exif=exif_bytes)
                    if extendet_data is not None and len(
                            extendet_data.keys()) > 0:
                        bio.write(b"=======EXTENDET_DATA=======")
                        js_Data = json.dumps(extendet_data)
                        bio.write(js_Data.encode("utf-8"))

                    bio.flush()
                    bio.seek(0)
                    shutil.copyfileobj(bio, file)
                    file.flush()
                    file.close()

    def getExtenetData(self, data: io.BytesIO):
        data.seek(0)
        buf = data.read()
        buf = buf.split(b"=======EXTENDET_DATA=======")
        if len(buf) > 1:
            js = json.loads(buf[1])
            return js
        return {}

    def parseTrainingPictures(self, data: io.BytesIO):
        if data is None:
            self._analyzer.trainZeroMap(data=False)
            return
        try:
            data.seek(0)
            exif_dict = piexif.load(data.read())
            ucb = exif_dict["Exif"][piexif.ExifIFD.UserComment]
            ucr = piexif.helper.UserComment.load(ucb)
            ucj = json.loads(ucr)
            if ucj.get("extendet_data_appendet", False):
                ucj["exif_zeromap"] = self.getExtenetData(data)
                self._analyzer.trainZeroMap(data=ucj)
        except:
            self.__logger.exception("parseTrainingPictures()")

    def fill_settings_html(self, html: str):
        cv = self._camera.color_effects
        if cv is None:
            cv = (-1, -1)
        html = html.format(
            self._analyzer.countMaxNoise, self._analyzer.countMinNoise,
            self._analyzer.blockMinNoise, self._analyzer.frameToTriggerMotion,
            self._analyzer.framesToNoMotion, self._analyzer.lightDiffBlock,
            self._camera.shutter_speed, self._camera.exposure_speed,
            self._camera.exposure_mode, cv[0], cv[1], self._camera.iso)
        return html

    def update_settings_call(self, countMaxNoise, countMinNoise, blockMinNoise,
                             frameToTriggerMotion, framesToNoMotion,
                             lightDiff):
        self.__logger.debug("HTTP Einstellungen werden gespeichert...")
        self._analyzer.countMaxNoise = countMaxNoise
        self._analyzer.countMinNoise = countMinNoise
        self._analyzer.blockMinNoise = blockMinNoise
        self._analyzer.frameToTriggerMotion = frameToTriggerMotion
        self._analyzer.framesToNoMotion = framesToNoMotion
        self._analyzer.lightDiffBlock = lightDiff

        self._config["motion/motion_frames"] = frameToTriggerMotion
        self._config["motion/still_frames"] = framesToNoMotion
        self._config["motion/blockMinNoise"] = blockMinNoise
        self._config["motion/frameMinNoise"] = countMinNoise
        self._config["motion/frameMaxNoise"] = countMaxNoise
        self._config["motion/lightDiffBlock"] = lightDiff
        self._config.save()

    def set_anal_onhold(self, on_hold=None) -> bool:
        if on_hold is not None:
            self._analyzer._on_hold = on_hold
        return self._analyzer._on_hold
Example #17
0
class win32Systray:
    _shutdown = False
    _icon_rdy = False
    _pman: PluginManager = None

    _menuItemsInit: list[pystray.MenuItem] = []
    _menuItemsDynamic: list[pystray.MenuItem] = []

    def image(self, width=64, height=64) -> tuple[Image.Image, Colors]:
        """Generates an icon image.
        :return: the tuple ``(image, colors)``, where  ``image`` is a
            *PIL* image and ``colors`` is a tuple containing the colours as
            *PIL* colour names, suitable for printing; the stringification of
            the tuple is also suitable for printing
        """

        colors = Colors(("blue", "white"))
        img = Image.new('RGB', (width, height), colors[0])
        dc = ImageDraw.Draw(img)

        dc.rectangle((width // 2, 0, width, height // 2), fill=colors[1])
        dc.rectangle((0, height // 2, width // 2, height), fill=colors[0])

        return img, colors

    def icon_created(self, icon):
        self._icon_rdy = True
        self._sicon.visible = True
        self._log.info("Systray Icon wird jetzt angezeigt.")

    def killPluginManager(self):
        if self._pman is not None:
            self._pman.shutdown()
        self._sicon.notify(
            "Interner Fehler (PluginManager nicht gesetzt), kann nicht normal beendet werden. Versuche exit()"
        )
        exit(1)

    def sicon_thread(self):
        self._log.debug("Icon image wird gezeichnet...")
        img, colors = self.image()

        self._log.debug("Erstelle initales Menü...")
        items: list[pystray.MenuItem] = []
        items.append(pystray.MenuItem("Warte auf verbindung...", None))
        items.append(pystray.Menu.SEPARATOR)
        items.append(pystray.MenuItem("Beenden", self.killPluginManager))
        self._menuItemsInit = tuple(items)

        self._menu = pystray.Menu(*items)

        self._log.debug("Beginne mit dem mainloop von pysystray...")
        self._sicon = pystray.Icon("mqttScripts", icon=img, menu=self._menu)
        self._sicon.run(setup=self.icon_created)

    def __init__(self, config: PluginConfig, log: Logger) -> None:
        self._config = PluginConfig(config, "systray")
        self._log = log.getChild("STRAY")

        self.icon_thread = Thread(name="systray",
                                  daemon=False,
                                  target=self.sicon_thread)
        self.icon_thread.start()

    @staticmethod
    def getNewDeviceEntry():
        from Tools import ConsoleInputTools
        config = {}
        name = ConsoleInputTools.get_input("Name für den Eintrag?")
        config["type"] = "switch" if ConsoleInputTools.get_bool_input(
            "Soll der eintrag als Schalter fungieren?", False) else "action"
        config["confirm"] = ConsoleInputTools.get_bool_input(
            "Auswählen des Eintrages bestätigen?", False)

        return name, config

    def generateDeviceIems(self):
        citems: dict = self._config.get("itemList", {})
        mitems: list[pystray.MenuItem] = []
        for name, entry in citems.items():
            ctype = entry.get("type", "action")
            if ctype == "action":
                bsensor = BinarySensor.BinarySensor(
                    self._log, self._pman, name,
                    BinarySensorDeviceClasses.GENERIC_SENSOR)
                bs = SensorDeviceItem(name,
                                      bsensor,
                                      confirm=entry.get("confirm", False))
                bsensor.register()
                mitems.append(bs)
            elif ctype == "switch":
                sw = SwitchDeviceItem(name, entry.get("wasOn", False),
                                      self._config, self._log, self._pman)
                sw._confirm = entry.get("confirm", False)
                mitems.append(sw)
        mitems.append(pystray.Menu.SEPARATOR)
        mitems.append(pystray.MenuItem("Beenden", self.killPluginManager))
        self._menu = pystray.Menu(*mitems)
        self._sicon.menu = self._menu
        self._menuItemsDynamic = mitems

    def updateDeviceItems(self):
        for mis in self._menuItemsDynamic:
            if isinstance(mis, SwitchDeviceItem):
                sdi: SwitchDeviceItem = mis
                sdi.device_action()

    def register(self, wasConnected: bool, pman: PluginManager):
        self._pman = pman
        if self._icon_rdy:
            self._sicon.notify(
                "Verbindung mit MQTT Broker {}hergestellt".format(
                    "wieder" if wasConnected else ""))

        self.generateDeviceIems()

    def sendUpdate(self, force=True):
        if force:
            self.updateDeviceItems()

    def disconnected(self):
        self._menu.items = self._menuItemsInit
        self._sicon.notify("Verbindung zum MQTT Server unterbrochen!")

    def shutdown(self):
        self._shutdown = True
        if self._icon_rdy:
            self._sicon.stop()