def get_state(self) -> str:
        """Get device state.

        Returns:
            "on", "off", or "unknown"
            If fake_state is set to true, it does not exec a query for status,
            it returns the previous status stored.

        """
        if self.fake_state:
            logger.info(f"ZwavePlugin: return fake {self.state} ")
            return self.state

        url = ("http://" + self.zwave_host + ":" + str(self.zwave_port) +
               "/JS/Run/controller.devices.get('" + self.zwave_device +
               "').get('metrics:level')")
        logger.info(f"ZwavePlugin: Getting {url} ")

        try:
            resp = requests.get(url, auth=(self.zwave_user, self.zwave_pass))
        except Exception as e:
            logger.error(f"ZwavePlugin: {e}")
            return "unknown"

        if resp.status_code == 200:
            if resp.text == '"off"' or resp.text == '"on"':
                return resp.text.strip('"')

        logger.error(f"ZwavePlugin: {resp.status_code} {resp.text} ")
        return "unknown"
Exemple #2
0
 def _toggle(self, state: bool) -> None:
     "Run the TOGGLE command.  Returns true if command succeeded"
     GPIO.output(self.output_pin, False)
     logger.info(f"{self.name}: Turned to --> {not self.state}")
     sleep(0.1)
     GPIO.output(self.output_pin, True)
     logger.info(f"{self.name}: Turned back to --> {self.state}")
     self.state = state
Exemple #3
0
 def close(self) -> None:
     "Shut down cleanly"
     self.loop_running = False
     self.loop.run_until_complete(self.task)
     self.set_state(False, "shutdown")
     FauxmoGpioPlugin._num_instances -= 1
     if (FauxmoGpioPlugin._num_instances == 0):
         GPIO.cleanup()
     logger.info(f"{self.name}: Shutdown complete")
Exemple #4
0
    def data_received(self, data: bytes) -> None:
        """Decode incoming data.

        Args:
            data: Incoming message, either setup request or action request
        """
        msg = data.decode()

        logger.debug(f"Received message:\n{msg}")
        if msg.startswith('GET /setup.xml HTTP/1.1'):
            logger.info("setup.xml requested by Echo")
            self.handle_setup()

        elif msg.startswith('POST /upnp/control/basicevent1 HTTP/1.1'):
            self.handle_action(msg)
Exemple #5
0
    def set_state(self, state: bool, reason: str = "unspecified") -> None:
        "Set the plugin into the given state"
        if (state == self.state):
            return
        self.state = state
        if self.output_pin:
            GPIO.output(self.output_pin, self.state)
        elif state:
            _run_cmd(self.output_cmds[0])
        else:
            _run_cmd(self.output_cmds[1])

        if (self.notification_pin):
            GPIO.output(self.notification_pin, self.state)

        newval = "ON" if self.state else "OFF"
        logger.info(f"{self.name}: Turned {newval} on {reason}")
Exemple #6
0
    async def timer(self):
        "Timer loop to watch for schedule events."

        while (self.loop_running):
            if datetime.now(self.timezone).date() > self.sched_reset_for:
                self.reset_schedule()

            # process any schedule events
            if self.state:
                now = datetime.now(self.timezone).time()
                for e in self.schedule:
                    if now > e['time'] and not e['processed']:
                        self.set_pair_state(e['value'])
                        e['processed'] = True

            # only sleep 1 sec, otherwise teardown time is too long
            await asyncio.sleep(1)

        logger.info(f"{self.name}: timer exiting")
    def _ZwaveCmd(self, cmd: str) -> bool:

        url = ("http://" + self.zwave_host + ":" + str(self.zwave_port) +
               "/ZAutomation/api/v1/devices/" + self.zwave_device +
               "/command/" + cmd)
        logger.info(f"ZwavePlugin: Getting {url} ")

        try:
            resp = requests.get(url, auth=(self.zwave_user, self.zwave_pass))
        except Exception as e:
            logger.error(f"ZwavePlugin: {e}")
            return False

        if resp.status_code == 200:
            if resp.text == response_ok:
                self.state = cmd
                return True

        logger.error(f"ZwavePlugin: {resp.status_code} {resp.text} ")
        return False
    def __init__(
        self,
        *,
        name: str,
        port: int,
        device: str,
        zwave_host: str = "localhost",
        zwave_port: int = 8083,
        zwave_user: str = "admin",
        zwave_pass: str = None,
        fake_state: bool = False,
        state: str = "unknown",
    ) -> None:
        """Initialize a ZwaveAPIPlugin instance.

        Args:
            zwave_host: IP address or dns name of zway-server
            zwave_port: TCP port running zway-server (default 8083)
            zwave_user: Zwave user
            zwave_pass: Zwave user password
            fake_state: Set to true for it does not exec a query for status,
                        it returns the previous status stored
            state:      Initial device status

        """
        self.zwave_host = zwave_host
        self.zwave_port = zwave_port
        self.zwave_user = zwave_user
        self.zwave_pass = zwave_pass
        self.zwave_device = device
        self.fake_state = fake_state

        logger.info(f"ZwavePlugin: {ZwavePlugin_version} " +
                    "name: {name} device: {device} " +
                    "port: {port} fake_state: {fake_state}")

        super().__init__(name=name, port=port)
Exemple #9
0
    def data_received(self, data: bytes) -> None:
        """Decode incoming data.

        Args:
            data: Incoming message, either setup request or action request
        """
        msg = data.decode()

        logger.debug(f"Received message:\n{msg}")
        if msg.startswith("GET /setup.xml HTTP/1.1"):
            logger.info("setup.xml requested by Echo")
            self.handle_setup()
        elif "/eventservice.xml" in msg:
            logger.info("eventservice.xml request by Echo")
            self.handle_event()
        elif "/metainfoservice.xml" in msg:
            logger.info("metainfoservice.xml request by Echo")
            self.handle_metainfo()
        elif msg.startswith("POST /upnp/control/basicevent1 HTTP/1.1"):
            logger.info("request BasicEvent1")
            self.handle_action(msg)
Exemple #10
0
 def on(self) -> bool:
     self.state = True
     logger.info(f"{self.name}: Turned ON")
     return True
Exemple #11
0
def main(config_path_str: str = None, verbosity: int = 20) -> None:
    """Run the main fauxmo process.

    Spawns a UDP server to handle the Echo's UPnP / SSDP device discovery
    process as well as multiple TCP servers to respond to the Echo's device
    setup requests and handle its process for turning devices on and off.

    Args:
        config_path_str: Path to config file. If not given will search for
                         `config.json` in cwd, `~/.fauxmo/`, and
                         `/etc/fauxmo/`.
        verbosity: Logging verbosity, defaults to 20
    """
    logger.setLevel(verbosity)
    logger.info(f"Fauxmo version {__version__}")
    logger.debug(sys.version)

    if config_path_str:
        config_path = pathlib.Path(config_path_str)
    else:
        for config_dir in ('.', "~/.fauxmo", "/etc/fauxmo"):
            config_path = pathlib.Path(config_dir) / 'config.json'
            if config_path.is_file():
                logger.info(f"Using config: {config_path}")
                break

    try:
        config = json.loads(config_path.read_text())
    except FileNotFoundError:
        logger.error("Could not find config file in default search path. Try "
                     "specifying your file with `-c`.\n")
        raise

    # Every config should include a FAUXMO section
    fauxmo_config = config.get("FAUXMO")
    fauxmo_ip = get_local_ip(fauxmo_config.get("ip_address"))

    ssdp_server = SSDPServer()
    servers = []

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    if verbosity < 20:
        loop.set_debug(True)
        logging.getLogger('asyncio').setLevel(logging.DEBUG)

    try:
        plugins = config['PLUGINS']
    except KeyError:
        # Give a meaningful message without a nasty traceback if it looks like
        # user is running a pre-v0.4.0 config.
        errmsg = ("`PLUGINS` key not found in your config.\n"
                  "You may be trying to use an outdated config.\n"
                  "If so, please review <https://github.com/n8henrie/fauxmo> "
                  "and update your config for Fauxmo >= v0.4.0.")
        print(errmsg)
        sys.exit(1)

    for plugin in plugins:

        modname = f"{__package__}.plugins.{plugin.lower()}"
        try:
            module = importlib.import_module(modname)

        # Will fail until https://github.com/python/typeshed/pull/1083 merged
        # and included in the next mypy release
        except ModuleNotFoundError:  # type: ignore
            path_str = config['PLUGINS'][plugin]['path']
            module = module_from_file(modname, path_str)

        PluginClass = getattr(module, plugin)  # noqa
        if not issubclass(PluginClass, FauxmoPlugin):
            raise TypeError(f"Plugins must inherit from {repr(FauxmoPlugin)}")

        # Pass along variables defined at the plugin level that don't change
        # per device
        plugin_vars = {
            k: v
            for k, v in config['PLUGINS'][plugin].items()
            if k not in {"DEVICES", "path"}
        }
        logger.debug(f"plugin_vars: {repr(plugin_vars)}")

        for device in config['PLUGINS'][plugin]['DEVICES']:
            logger.debug(f"device config: {repr(device)}")

            # Ensure port is `int`, set it if not given (`None`) or 0
            device["port"] = int(device.get('port', 0)) or find_unused_port()

            try:
                plugin = PluginClass(**plugin_vars, **device)
            except TypeError:
                logger.error(f"Error in plugin {repr(PluginClass)}")
                raise

            fauxmo = partial(Fauxmo, name=plugin.name, plugin=plugin)
            coro = loop.create_server(fauxmo, host=fauxmo_ip, port=plugin.port)
            server = loop.run_until_complete(coro)
            servers.append(server)

            ssdp_server.add_device(plugin.name, fauxmo_ip, plugin.port)

            logger.debug(f"Started fauxmo device: {repr(fauxmo.keywords)}")

    logger.info("Starting UDP server")

    # mypy will fail until https://github.com/python/typeshed/pull/1084 merged,
    # pulled into mypy, and new mypy released
    listen = loop.create_datagram_endpoint(
        lambda: ssdp_server,  # type: ignore
        sock=make_udp_sock())
    transport, _ = loop.run_until_complete(listen)  # type: ignore

    for signame in ('SIGINT', 'SIGTERM'):
        try:
            loop.add_signal_handler(getattr(signal, signame), loop.stop)

        # Workaround for Windows (https://github.com/n8henrie/fauxmo/issues/21)
        except NotImplementedError:
            if sys.platform == 'win32':
                pass
            else:
                raise

    loop.run_forever()

    # Will not reach this part unless SIGINT or SIGTERM triggers `loop.stop()`
    logger.debug("Shutdown starting...")
    transport.close()
    for idx, server in enumerate(servers):
        logger.debug(f"Shutting down server {idx}...")
        server.close()
        loop.run_until_complete(server.wait_closed())
    loop.close()
Exemple #12
0
def main(config_path=None, verbosity=20):
    """Runs the main fauxmo process

    Spawns a UDP server to handle the Echo's UPnP / SSDP device discovery
    process as well as multiple TCP servers to respond to the Echo's device
    setup requests and handle its process for turning devices on and off.

    Kwargs:
        config_path (str): Path to config file. If not given will search for
                           `config.json` in cwd, `~/.fauxmo/`, and
                           `/etc/fauxmo/`.
        verbosity (int): Logging verbosity, defaults to 20
    """

    logger.setLevel(verbosity)

    logger.debug(sys.version)

    if not config_path:
        config_dirs = ['.', os.path.expanduser("~/.fauxmo"), "/etc/fauxmo"]
        for config_dir in config_dirs:
            config_path = os.path.join(config_dir, 'config.json')
            if os.path.isfile(config_path):
                logger.info("Using config: {}".format(config_path))
                break

    try:
        with open(config_path) as config_file:
            config = json.load(config_file)
    except FileNotFoundError:
        logger.error("Could not find config file in default search path. "
                     "Try specifying your file with `-c` flag.\n")
        raise

    # Every config should include a FAUXMO section
    fauxmo_config = config.get("FAUXMO")
    fauxmo_ip = get_local_ip(fauxmo_config.get("ip_address"))

    ssdp_server = SSDPServer()
    servers = []

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.set_debug(True)

    # Initialize Fauxmo devices
    for device in config.get('DEVICES'):
        name = device.get('description')
        port = int(device.get("port"))
        action_handler = RESTAPIHandler(**device.get("handler"))

        fauxmo = partial(Fauxmo, name=name, action_handler=action_handler)
        coro = loop.create_server(fauxmo, host=fauxmo_ip, port=port)
        server = loop.run_until_complete(coro)
        servers.append(server)

        ssdp_server.add_device(name, fauxmo_ip, port)

        logger.debug(fauxmo.keywords)

    # Initialize Home Assistant devices if config exists and enable is True
    if config.get("HOMEASSISTANT", {}).get("enable") is True:
        hass_config = config.get("HOMEASSISTANT")

        hass_host = hass_config.get("host")
        hass_password = hass_config.get("password")
        hass_port = hass_config.get("port")

        for device in hass_config.get('DEVICES'):
            name = device.get('description')
            device_port = device.get("port")
            entity = device.get("entity_id")
            action_handler = HassAPIHandler(host=hass_host,
                                            password=hass_password,
                                            entity=entity,
                                            port=hass_port)
            fauxmo = partial(Fauxmo, name=name, action_handler=action_handler)
            coro = loop.create_server(fauxmo, host=fauxmo_ip, port=device_port)
            server = loop.run_until_complete(coro)
            servers.append(server)

            ssdp_server.add_device(name, fauxmo_ip, device_port)

            logger.debug(fauxmo.keywords)

    logger.info("Starting UDP server")

    listen = loop.create_datagram_endpoint(lambda: ssdp_server,
                                           local_addr=('0.0.0.0', 1900),
                                           family=socket.AF_INET)
    transport, protocol = loop.run_until_complete(listen)

    for signame in ('SIGINT', 'SIGTERM'):
        loop.add_signal_handler(getattr(signal, signame), loop.stop)

    loop.run_forever()

    # Will not reach this part unless SIGINT or SIGTERM triggers `loop.stop()`
    logger.debug("Shutdown starting...")
    transport.close()
    for idx, server in enumerate(servers):
        logger.debug("Shutting down server {}...".format(idx))
        server.close()
        loop.run_until_complete(server.wait_closed())
    loop.close()
Exemple #13
0
    def handle_action(self, msg: str) -> None:
        """Execute `on`, `off`, or `get_state` method of plugin.

        Args:
            msg: Body of the Echo's HTTP request to trigger an action

        """
        logger.debug(f"Handling action for plugin type {self.plugin}")

        success = False
        soap_format = (
            '<s:Envelope '
            'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" '
            's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
            '<s:Body>'
            '<u:{action}BinaryStateResponse '
            'xmlns:u="urn:Belkin:service:basicevent:1">'
            '<BinaryState>{state_int}</BinaryState>'
            '</u:{action}BinaryStateResponse>'
            '</s:Body>'
            '</s:Envelope>').format

        command_format = (
            'SOAPACTION: "urn:Belkin:service:basicevent:1#{}BinaryState"'
        ).format

        soap_message: str = None
        action: str = None
        state_int: int = None

        if command_format("Get") in msg:
            logger.info(f"Attempting to get state for {self.plugin.name}")

            action = "Get"

            try:
                state = self.plugin.get_state()
            except AttributeError:
                logger.warning(f"Plugin {self.plugin.__module__} has not "
                               "implemented a `get_state` method.")
            else:
                logger.info(f"{self.plugin.name} state: {state}")

                success = True
                state_int = int(state.lower() == "on")

        elif command_format("Set") in msg:
            action = "Set"

            if '<BinaryState>0</BinaryState>' in msg:
                logger.info(f"Attempting to turn off {self.plugin.name}")
                state_int = 0
                success = self.plugin.off()

            elif '<BinaryState>1</BinaryState>' in msg:
                logger.info(f"Attempting to turn on {self.plugin.name}")
                state_int = 1
                success = self.plugin.on()

            else:
                logger.warning(f"Unrecognized request:\n{msg}")

        if success:
            date_str = formatdate(timeval=None, localtime=False, usegmt=True)
            soap_message = soap_format(action=action, state_int=state_int)
            response = '\n'.join([
                'HTTP/1.1 200 OK',
                f'CONTENT-LENGTH: {len(soap_message)}',
                'CONTENT-TYPE: text/xml charset="utf-8"',
                f'DATE: {date_str}',
                'EXT:',
                'SERVER: Unspecified, UPnP/1.0, Unspecified',
                'X-User-Agent: Fauxmo',
                'CONNECTION: close\n',
                f'{soap_message}',
            ])
            logger.debug(response)
            self.transport.write(response.encode())
        else:
            errmsg = (f"Unable to complete command for {self.plugin.name}:\n"
                      f"{msg}")
            logger.warning(errmsg)
        self.transport.close()
Exemple #14
0
 def off(self) -> bool:
     self.state = False
     logger.info(f"{self.name}: Turned OFF")
     return True
Exemple #15
0
    def __init__(self,
                 name: str,
                 port: int,
                 paired_device: str,
                 schedule_events: list,
                 timezone: str = "UTC",
                 latitude: float = None,
                 longitude: float = None,
                 initial_state: bool = True) -> None:
        """Initialize a SchedulerPlugin instance.

        Args:
            name: Name for this Fauxmo device

            port: Port on which to run this Fauxmo device

            paired_device: Name of the Fauxmo device controlled by
              this schedule instance. The plugin for the paired device
              must also derive from PairedFauxmoPlugin.

            schedule_events: A list of dicts of the form
                  {"trigger": time-str, "random": int, "value": bool}

              time-str should follow the pattern HH:MM[:SS] (with
              hours in 24-hour format), or the patterns "sunrise[+N]",
              "sunrise[-N]", "sunset[+N]" or "sunset[-N]" (where N, if
              specified, is the number of minutes offset from sunrise
              or sunset).

              random, if specified, will cause the actual schedule
                 event to happen randomly between 0 and N minutes
                 after the time specified by time-str.

              value is true to turn on, false to turn off.

            timezone: The local timezone; if not specified, UTC is
              assumed.

            latitude, longitude: Coordinates on which sunrise and
              sunset should be calculated. Must be specified if any
              schedule triggers use sunrise or sunset.

            initial_state: A bool indicating whether the schedule
              is ON or OFF at startup. If not specified, ON is assumed.
        """
        self.state = initial_state
        self.timezone = pytz.timezone(timezone)
        self.latitude = latitude
        self.longitude = longitude

        # Internally, we maintain the self.schedule list, which is a
        # list of schedule events. Each element in the list is a dict
        # of the form specified in the comment for _parse_sched_entry
        self.schedule = []
        if schedule_events:
            for e in schedule_events:
                self.schedule.append(self._parse_sched_entry(e))
        self.reset_schedule()
        logger.info(f"{name} parsed schedule:" + repr(self.schedule))

        self.is_schedule_on = bool(self.schedule)

        self.loop = asyncio.get_event_loop()
        self.loop_running = True
        self.task = self.loop.create_task(self.timer())

        super().__init__(name=name,
                         port=port,
                         paired_device_name=paired_device)
        logger.info(f"Fauxmo schedule device {self.name} initialized")
Exemple #16
0
 def close(self) -> None:
     "Shut down cleanly"
     self.loop_running = False
     self.loop.run_until_complete(self.task)
     logger.info(f"{self.name}: Shutdown complete")
Exemple #17
0
    async def gpio_timer(self):
        """Timer loop to receive switch events. If input_pin is not configured,
        this loop will not run."""

        if self.long_press_interval:
            lp_interval = timedelta(milliseconds=self.long_press_interval)
        else:
            # setting this to 10 minutes is equivalent to disabling it!
            lp_interval = timedelta(seconds=600)

        press_tm = None

        notif_tog_tm = datetime.now()
        notif_delta = 0
        local_is_schedule_on = not self.is_schedule_on()

        while (self.loop_running):
            if GPIO.input(self.input_pin):  # button is depressed
                if not press_tm:
                    press_tm = datetime.now()
                    notif_tog_tm = datetime.now()
                    notif_delta = (40, 80)  # on time in msec, off time

                if (datetime.now() - press_tm) > lp_interval:
                    notif_delta = 0
                    if (self.notification_pin):
                        GPIO.output(self.notification_pin, True)

            elif press_tm:  # button has been released
                if (datetime.now() - press_tm) < timedelta(milliseconds=50):
                    logger.info(f"{self.name}: very short press, ignoring")
                elif (datetime.now() - press_tm) < lp_interval:  # short press
                    self.set_state(not self.state, "button press")
                else:  # long press
                    self.trigger_long_press()
                press_tm = None
                if self.is_schedule_on():
                    notif_delta = self.schedule_notification_interval
                else:
                    notif_delta = 0
                if (self.notification_pin):
                    GPIO.output(self.notification_pin, self.state)

            if self.is_schedule_on() != local_is_schedule_on:
                if self.is_schedule_on():
                    notif_delta = self.schedule_notification_interval
                    local_is_schedule_on = True
                else:
                    notif_delta = 0
                    local_is_schedule_on = False

            if (notif_delta and self.notification_pin
                    and datetime.now() >= notif_tog_tm):
                cur_val = GPIO.input(self.notification_pin)
                if type(notif_delta) is tuple:
                    delta = notif_delta[cur_val]
                else:
                    delta = notif_delta
                notif_tog_tm = datetime.now() + timedelta(milliseconds=delta)
                GPIO.output(self.notification_pin, not cur_val)

            await asyncio.sleep(0.02)

        logger.info(f"{self.name}: gpio_timer exiting")
Exemple #18
0
    def __init__(self,
                 name: str,
                 port: int,
                 type: str = None,
                 state: int = None,
                 output_pin: int = None,
                 output_cmds: list = None,
                 input_pin: int = None,
                 input_pull_dir: str = None,
                 notification_pin: int = None,
                 long_press_interval: int = None,
                 long_press_action: str = None) -> None:
        """Initialize a FauxmoGpioPlugin instance.

        Args:
            name: Name for this Fauxmo device

            port: Port on which to run this instance

            --- must specify one of the following ---

            output_pin: RPi.GPIO pin (using BOARD numbering) to control

            output_cmds: a 2-element string array; first command will
              be run to turn the device "on", second will be run to
              turn it off

            --- from here down the args are optional ---

            input_pin: RPi.GPIO pin (using BOARD numbering) which maps
              to a momentary-contact input switch. When a rising edge
              is detected on this pin, the state of the device will be
              toggled. Default is for the input_pin to not be
              configured.

            input_pull_dir: Either "Down" or "Up". Default is Down.

            notification_pin: RPi.GPIO pin (using BOARD numbering)
              which maps to an LED. The LED will be used for user
              feedback while pressing buttons, and to indicate whether
              the schedule is set or not.

            long_press_interval: duration, in milliseconds, for the
              user to hold down the switch in order to trigger a long
              press. If set, holding the button down for this interval
              will cause the configured long_press_action to be
              executed. If not specified, no long press behavior will
              be recognized.

            long_press_action: the action to be taken when a long
              press occurs. Must be specified if long_press_interval
              is set. Can be either the special string
              "toggle_paired_device", or a command to be run.
        """
        if (state is not None):
            self.state = state
        else:
            self.state = False  # True = on, False = off

        # Don't need to validate the output_pin, input_pin etc;
        # RPi.GPIO will throw ValueError if a pin is illegal

        if (type == "toggle"):
            self.toggle = True
        else:
            self.toggle = False

        self.output_pin = output_pin
        self.output_cmds = output_cmds

        if self.output_pin is None:
            if self.output_cmds is None or len(self.output_cmds) != 2:
                raise ValueError("Must specify output_pin, or output_cmds")
        else:
            if self.output_cmds is not None:
                raise ValueError("Cannot specify both output_pin and "
                                 "output_cmds")

        self.input_pin = input_pin
        self.notification_pin = notification_pin

        if (not input_pull_dir or input_pull_dir.lower() == "down"):
            self.input_pull_dir = GPIO.PUD_DOWN
        elif input_pull_dir.lower() == "up":
            self.input_pull_dir = GPIO.PUD_UP
        else:
            raise ValueError(f"input_pull_dir must be either Up or Down, "
                             "not {input_pull_dir}")

        self.long_press_interval = long_press_interval
        self.long_press_action = long_press_action

        if self.long_press_interval is not None and \
           self.long_press_action is None:
            raise ValueError("long_press_action required but not found!")

        # in msec, how fast the notification light should pulse when
        # the schedule is set. First number is on time, second is off time
        self.schedule_notification_interval = (50, 1500)

        self.gpio_setup()

        self.loop = asyncio.get_event_loop()
        self.loop_running = True
        if self.input_pin:
            self.task = self.loop.create_task(self.gpio_timer())

        FauxmoGpioPlugin._num_instances += 1

        super().__init__(name=name, port=port)
        logger.info(f"Fauxmo GPIO device {self.name} initialized")
Exemple #19
0
    def handle_action(self, msg: str) -> None:
        """Execute `on`, `off`, or `get_state` method of plugin.

        Args:
            msg: Body of the Echo's HTTP request to trigger an action

        """
        logger.debug(f"Handling action for plugin type {self.plugin}")

        soap_format = (
            "<s:Envelope "
            'xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" '
            's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
            "<s:Body>"
            "<u:{action}{action_type}Response "
            'xmlns:u="urn:Belkin:service:basicevent:1">'
            "<{action_type}>{return_val}</{action_type}>"
            "</u:{action}{action_type}Response>"
            "</s:Body>"
            "</s:Envelope>").format

        command_format = (
            'SOAPACTION: "urn:Belkin:service:basicevent:1#{}"').format

        soap_message: str = None
        action: str = None
        action_type: str = None
        return_val: str = None
        success: bool = False

        if command_format("GetBinaryState").casefold() in msg.casefold():
            logger.info(f"Attempting to get state for {self.plugin.name}")

            action = "Get"
            action_type = "BinaryState"

            state = self.plugin.get_state().casefold()
            logger.info(f"{self.plugin.name} state: {state}")

            if state in ["off", "on"]:
                success = True
                return_val = str(int(state.lower() == "on"))

        elif command_format("SetBinaryState").casefold() in msg.casefold():
            action = "Set"
            action_type = "BinaryState"

            if "<BinaryState>0</BinaryState>" in msg:
                logger.info(f"Attempting to turn off {self.plugin.name}")
                return_val = "0"
                success = self.plugin.off()

            elif "<BinaryState>1</BinaryState>" in msg:
                logger.info(f"Attempting to turn on {self.plugin.name}")
                return_val = "1"
                success = self.plugin.on()

            else:
                logger.warning(f"Unrecognized request:\n{msg}")

        elif command_format("GetFriendlyName").casefold() in msg.casefold():
            action = "Get"
            action_type = "FriendlyName"
            return_val = self.plugin.name
            success = True
            logger.info(f"{self.plugin.name} returning friendly name")

        if success:
            soap_message = soap_format(action=action,
                                       action_type=action_type,
                                       return_val=return_val)

            response = self.add_http_headers(soap_message)
            logger.debug(response)
            self.transport.write(response.encode())
        else:
            errmsg = (
                f"Unable to complete command for {self.plugin.name}:\n{msg}")
            logger.warning(errmsg)
        self.transport.close()
Exemple #20
0
Provides console_script via argparse.
"""

import argparse
import sys

from fauxmo import __version__, logger
from fauxmo.fauxmo import main

try:
    import uvloop
except ImportError:
    pass
else:
    logger.info("Using uvloop")
    uvloop.install()


def cli() -> None:
    """Parse command line options, provide entry point for console scripts."""
    arguments = sys.argv[1:]
    parser = argparse.ArgumentParser(
        description="Emulate Belkin Wemo devices for use with Amaazon Echo"
    )
    parser.add_argument(
        "-v",
        "--verbose",
        help="increase verbosity (may repeat up to -vvv)",
        action="count",
        default=0,