Exemple #1
0
    def start(self, ipc_queue):
        ipc_thread = Thread(target=self.listen_to_ipc_queue,
                            args=(ipc_queue, ),
                            daemon=True)
        ipc_thread.start()

        connection_thread = Thread(target=self.check_nuimo_connection,
                                   daemon=True)
        connection_thread.start()

        self.manager = ControllerManager(self.ble_adapter_name)
        self.manager.is_adapter_powered = True

        self.controller = Controller(self.mac_address, self.manager)
        self.controller.listener = self
        self.set_active_component()
        logger.info("Connecting to Nuimo controller %s",
                    self.controller.mac_address)
        self.controller.connect()
        try:
            self.manager.run()
        except KeyboardInterrupt:
            logger.info("Nuimo app received SIGINT %s",
                        self.controller.mac_address)
            self.stop()
Exemple #2
0
    def start(self, ipc_queue):

        logger.debug("Started a dedicated nuimo control process for %s" %
                     self.mac_address)

        ipc_thread = Thread(target=self.listen_to_ipc_queue,
                            args=(ipc_queue, ),
                            name="ipc_thread",
                            daemon=True)
        ipc_thread.start()

        logger.debug("Using adapter (self.ble_adapter_name): %s" %
                     self.ble_adapter_name)
        import subprocess
        output = subprocess.check_output("hciconfig")
        logger.debug("Adapter (from hciconfig): %s " % str(output.split()[0]))

        self.manager = ControllerManager(self.ble_adapter_name)
        self.manager.is_adapter_powered = True
        logger.debug("Powering on BT adapter")

        devices_known_to_bt_module = [
            device.mac_address for device in self.manager.devices()
        ]
        if self.mac_address not in devices_known_to_bt_module:
            # The Nuimo needs to had been discovered by the bt module
            # at some point before we can do:
            #    self.controller = Controller(self.mac_address, self.manager)
            #
            # and expect it to connect succesfully. If it isn't present
            # discovery needs to be redone until the Nuimo reapears.
            logger.debug(
                "%s not in discovered devices of the bt module. Starting discovery"
                % self.mac_address)
            self.manager.start_discovery()
            while self.mac_address not in devices_known_to_bt_module:
                time.sleep(3)
                devices_known_to_bt_module = [
                    device.mac_address for device in self.manager.devices()
                ]
                logger.debug("Still haven't found %s" % self.mac_address)
            logger.debug("Found it. Stopping discovery")
            self.manager.stop_discovery()

        self.controller = Controller(self.mac_address, self.manager)
        self.controller.listener = self
        self.set_active_component()
        logger.info("Connecting to Nuimo controller %s",
                    self.controller.mac_address)
        self.controller.connect()

        try:
            self.manager.run()
        except KeyboardInterrupt:
            logger.info("Nuimo app received SIGINT %s",
                        self.controller.mac_address)
            self.stop()
Exemple #3
0
    def __init__(self, ha_api_url, ble_adapter_name, mac_address, components):
        super().__init__()

        self.components = []
        self.active_component = None
        self.set_components(components)

        self.manager = ControllerManager(ble_adapter_name)
        self.manager.is_adapter_powered = True

        self.controller = Controller(mac_address, self.manager)
        self.controller.listener = self
Exemple #4
0
def get_update_service(request):  # pragma: no cover,
    nuimo_app_config_path = request.registry.settings['nuimo_app_config_path']
    is_updated = False
    with open(nuimo_app_config_path, 'r') as f:
        config = yaml.load(f)
    adapter_name = request.registry.settings.get('bluetooth_adapter_name', 'hci0')
    manager = ControllerManager(adapter_name=adapter_name)
    for mac_address in config['nuimos']:
        # check Nuimo connection Status
        controller = Controller(mac_address=mac_address, manager=manager)
        is_updated = True if config['nuimos'][mac_address]['is_connected'] != controller.is_connected() else is_updated
        if is_updated:
            return get_configured_nuimos(request)
        if controller.is_connected():
            global prev_battery_level
            prev_nuimo_battery_level = prev_battery_level.get(mac_address, None)
            is_updated = True if prev_nuimo_battery_level and prev_nuimo_battery_level != get_nuimo_battery_level(mac_address) else is_updated
            prev_battery_level[mac_address] = get_nuimo_battery_level(mac_address)
            if is_updated:
                return get_configured_nuimos(request)

        # check if New Sonos Groups have been created
        components = config['nuimos'][mac_address].get('components', [])
        is_updated = check_if_sonos_is_updated(components)
        if is_updated:
            return get_configured_nuimos(request)

    return {'is_updated': is_updated}
Exemple #5
0
def get_configured_nuimos(request):  # pragma: no cover,
    nuimo_app_config_path = request.registry.settings['nuimo_app_config_path']
    nuimos = []

    with open(nuimo_app_config_path, 'r+') as f:
        config = yaml.load(f)
        adapter_name = request.registry.settings.get('bluetooth_adapter_name', 'hci0')
        manager = ControllerManager(adapter_name=adapter_name)
        for mac_address in config['nuimos']:
            # check Nuimo connection Status
            controller = Controller(mac_address=mac_address, manager=manager)
            config['nuimos'][mac_address]['is_connected'] = controller.is_connected()

            # check if New Sonos Groups have been created
            components = config['nuimos'][mac_address].get('components', [])
            check_sonos_update(components)

        f.seek(0)  # We want to overwrite the config file with the new configuration
        f.truncate()
        yaml.dump(config, f, default_flow_style=False)

    with open(nuimo_app_config_path, 'r') as f:
        config = yaml.load(f)
        for mac_address in config['nuimos']:
            temp = config['nuimos'][mac_address]
            temp['mac_address'] = mac_address
            temp['battery_level'] = get_nuimo_battery_level(mac_address)
            nuimos.append(temp)

    return {'nuimos': nuimos}
Exemple #6
0
    def __init__(self, ha_api_url, ble_adapter_name, mac_address, components):
        super().__init__()

        self.components = components
        self.active_component = None

        self.manager = ControllerManager(ble_adapter_name)
        self.manager.is_adapter_powered = True

        self.controller = Controller(mac_address, self.manager)
        self.controller.listener = self
        self.controller.connect()

        for component in self.components:
            component.nuimo = self

        component = self.get_next_component()
        if component:
            self.set_active_component(component)
Exemple #7
0
def main(mac_address):
    manager = ControllerManager(adapter_name="hci0")
    controller = Controller(mac_address=mac_address, manager=manager)
    listener = NuimoListener(controller)
    controller.listener = listener
    controller.connect()

    try:
        manager.run()
    except KeyboardInterrupt:
        print("Stopping...")
        listener.stop()
        manager.stop()
Exemple #8
0
def get_configured_nuimos(request):  # pragma: no cover,
    nuimo_app_config_path = request.registry.settings['nuimo_app_config_path']
    nuimos = []

    try:
        with open(nuimo_app_config_path, 'r+') as f:
            config = yaml.load(f)
            adapter_name = request.registry.settings.get('bluetooth_adapter_name', 'hci0')
            manager = ControllerManager(adapter_name=adapter_name)
            for mac_address in config['nuimos']:
                # check Nuimo connection Status
                controller = Controller(mac_address=mac_address, manager=manager)
                config['nuimos'][mac_address]['is_connected'] = controller.is_connected()

                # check if New Sonos Groups have been created
                components = config['nuimos'][mac_address].get('components', [])
                check_sonos_update(components)

            f.seek(0)  # We want to overwrite the config file with the new configuration
            f.truncate()
            yaml.dump(config, f, default_flow_style=False)

        with open(nuimo_app_config_path, 'r') as f:
            config = yaml.load(f)
            for mac_address in config['nuimos']:
                temp = config['nuimos'][mac_address]
                temp['mac_address'] = mac_address
                temp['battery_level'] = get_nuimo_battery_level(mac_address)
                nuimos.append(temp)

    except FileNotFoundError as e:
        logger.error(e)

#   [Alan] 'nuimo' in this context represents all the devices
#     controlled by a pyisical nuimo + the state of that nuimo.
#     Example with single nuimo:
#    (Epdb) nuimos
#    [{'mac_address': 'e0:88:72:c4:49:c2', 'name': 'My Nuimo', 'is_connected': True, 'components': [{'id': '69d965ea-1978-4db4-8b3e-0b63742ed31d', 'is_reachable': True, 'room_name': 'Devs 1', 'type': 'sonos', 'device_ids': ['7828ca17171e01400'], 'ip_address': '10.10.10.114', 'name': '10.10.10.114 - Sonos One'}], 'battery_level': 100}]

    return {'nuimos': nuimos}
Exemple #9
0
def main(config_file_path=DEFAULT_CONFIG_FILE_PATH):
    if len(sys.argv) > 1:
        config_file_path = sys.argv[-1]

    config, component_config = read_config(config_file_path)

    log_level = getattr(logging, config["logging_level"],
                        DEFAULT_LOGGING_LEVEL)
    log_format = "%(asctime)s %(levelname)s %(name)s %(message)s"
    logging.basicConfig(level=log_level, format=log_format)
    logging.getLogger("urllib3.connectionpool").setLevel(logging.CRITICAL)

    logger.info("Using configuration from: %s", config_file_path)

    manager = ControllerManager()

    ha_url = config["ha_api_url"]
    ha_api = HAListener("ws://{}".format(ha_url))
    ha_api.start()

    nuimo_app = None
    controller_mac_address = config.get("controller_mac_address")
    if controller_mac_address:
        ble_adapter_name = config.get("ble_adapter_name",
                                      DEFAULT_BLE_ADAPTER_NAME)
        nuimo_app = NuimoApp(controller_mac_address, ha_api, ble_adapter_name)

    for cid in component_config.sections():
        cfg = component_config[cid]
        component_class = getattr(components, cfg["component"])
        entities = split_entities(cfg["entities"])
        nuimo_app.register_component(component_class(cfg["name"], entities))

    try:
        manager.run()
    except (KeyboardInterrupt, errors.NuimoControllerConnectionError):
        logger.debug("Stopping...")
        manager.stop()
        ha_api.stop()
        if nuimo_app:
            nuimo_app.quit()
Exemple #10
0
class NuimoApp(NuimoControllerListener):
    TOUCH_GESTURES = [
        Gesture.TOUCH_LEFT,
        Gesture.TOUCH_RIGHT,
        Gesture.TOUCH_BOTTOM,
    ]

    INTERNAL_GESTURES = [
        Gesture.SWIPE_UP,
        Gesture.SWIPE_DOWN,
    ] + TOUCH_GESTURES

    GESTURES_TO_IGNORE = [
        Gesture.BUTTON_RELEASE,
    ]

    def __init__(self, ha_api_url, ble_adapter_name, mac_address, components):
        super().__init__()

        self.components = []
        self.active_component = None
        self.set_components(components)

        self.manager = ControllerManager(ble_adapter_name)
        self.manager.is_adapter_powered = True

        self.controller = Controller(mac_address, self.manager)
        self.controller.listener = self

    def set_components(self, components):
        previously_active = self.active_component
        if self.active_component:
            self.active_component.stop()

        for component in components:
            component.nuimo = self
        self.components = components

        if previously_active:
            for component in components:
                if previously_active.component_id == component.component_id:
                    self.set_active_component(component)
                    break

    def start(self):
        self.set_active_component()
        logger.info("Connecting to Nuimo controller %s",
                    self.controller.mac_address)
        self.controller.connect()
        try:
            self.manager.run()
        except KeyboardInterrupt:
            logger.info("Stopping... Nuimo controller %s",
                        self.controller.mac_address)
            self.stop()

    def stop(self):
        if self.active_component:
            self.active_component.stop()

        self.controller.disconnect()
        self.manager.stop()

    def process_gesture_event(self, event):
        if event.gesture in self.GESTURES_TO_IGNORE:
            logger.debug("Ignoring gesture event: %s", event)
            return

        logger.debug("Processing gesture event: %s", event)

        if event.gesture in self.INTERNAL_GESTURES:
            self.process_internal_gesture(event.gesture)
            return

        if not self.active_component:
            logger.warn("Ignoring event, no active component")
            self.show_error_matrix()
            return

        if self.active_component.stopped:
            logger.warn("Ignoring event, component is not running")
            self.show_error_matrix()
            return

        self.process_gesture(event.gesture, event.value)

    def process_internal_gesture(self, gesture):
        if gesture == Gesture.SWIPE_UP:
            component = self.get_prev_component()
            if component:
                self.set_active_component(component)

        elif gesture == Gesture.SWIPE_DOWN:
            component = self.get_next_component()
            if component:
                self.set_active_component(component)

        elif gesture in self.TOUCH_GESTURES:
            # Fall-through to show active component...
            pass

        self.show_active_component()

    def process_gesture(self, gesture, delta):
        if gesture == Gesture.ROTATION:
            self.active_component.on_rotation(
                delta / 1800
            )  # 1800 is the amount of all ticks for a full ring rotation

        if gesture == Gesture.BUTTON_PRESS:
            self.active_component.on_button_press()

        elif gesture == Gesture.SWIPE_LEFT:
            self.active_component.on_swipe_left()

        elif gesture == Gesture.SWIPE_RIGHT:
            self.active_component.on_swipe_right()

        elif gesture == Gesture.LONGTOUCH_LEFT:
            self.active_component.on_longtouch_left()

        elif gesture == Gesture.LONGTOUCH_BOTTOM:
            self.active_component.on_longtouch_bottom()

        elif gesture == Gesture.LONGTOUCH_RIGHT:
            self.active_component.on_longtouch_right()

        else:
            # TODO handle all remaining gestures...
            pass

    def get_prev_component(self):
        if not self.components:
            return None

        if self.active_component:
            index = self.components.index(self.active_component)
            return self.components[index - 1]
        else:
            return self.components[0]

    def get_next_component(self):
        if not self.components:
            return None

        if self.active_component:
            index = self.components.index(self.active_component)
            try:
                return self.components[index + 1]
            except IndexError:
                return self.components[0]
        else:
            return self.components[0]

    def set_active_component(self, component=None):
        active_component = None

        if component:
            active_component = component
        elif self.components:
            active_component = self.components[0]

        if active_component:
            if self.active_component:
                logger.debug("Stopping component: %s",
                             self.active_component.component_id)
                self.active_component.stop()

            logger.debug("Activating component: %s",
                         active_component.component_id)
            self.active_component = active_component
            self.active_component.start()

    def show_active_component(self):
        if self.active_component:
            index = self.components.index(self.active_component)
            matrix = matrices.matrix_with_index(self.active_component.MATRIX,
                                                index)
        else:
            matrix = matrices.ERROR

        self.display_matrix(matrix)

    def show_error_matrix(self):
        self.display_matrix(matrices.ERROR)

    def display_matrix(self, matrix, **kwargs):
        self.controller.display_matrix(LedMatrix(matrix), **kwargs)
Exemple #11
0
class NuimoApp(NuimoControllerListener):
    TOUCH_GESTURES = [
        Gesture.TOUCH_LEFT,
        Gesture.TOUCH_RIGHT,
        Gesture.TOUCH_BOTTOM,
    ]

    INTERNAL_GESTURES = [
        Gesture.SWIPE_UP,
        Gesture.SWIPE_DOWN,
    ] + TOUCH_GESTURES

    GESTURES_TO_IGNORE = [
        Gesture.BUTTON_RELEASE,
    ]

    def __init__(self, ble_adapter_name, mac_address, components):
        super().__init__()

        logger.debug("Initialising NuimoApp for %s" % mac_address)
        self.components = []
        self.active_component = None
        component_instances = get_component_instances(components, mac_address)
        self.set_components(component_instances)
        logger.info("Components associated with this Nuimo: %s" % components)

        self.manager = None
        self.ble_adapter_name = ble_adapter_name
        self.controller = None
        self.mac_address = mac_address
        self.battery_level = None

        # memory map using mmap to store nuimo battery level
        fd = os.open('/tmp/' + self.mac_address.replace(':', '-'),
                     os.O_CREAT | os.O_TRUNC | os.O_RDWR)
        assert os.write(fd, b'\x00' * mmap.PAGESIZE) == mmap.PAGESIZE
        buf = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
        self.bl = ctypes.c_int.from_buffer(buf)
        self.bl.value = 0
        offset = struct.calcsize(self.bl._type_)
        assert buf[offset] == 0

    def set_components(self, components):
        previously_active = self.active_component
        if self.active_component:
            self.active_component.stop()
            self.active_component = None

        for component in components:
            component.nuimo = self
        self.components = components

        if previously_active:
            for component in components:
                if previously_active.component_id == component.component_id:
                    self.set_active_component(component)
                    break

        if self.active_component is None:
            self.set_active_component()

    def start(self, ipc_queue):

        logger.debug("Started a dedicated nuimo control process for %s" %
                     self.mac_address)

        ipc_thread = Thread(target=self.listen_to_ipc_queue,
                            args=(ipc_queue, ),
                            name="ipc_thread",
                            daemon=True)
        ipc_thread.start()

        logger.debug("Using adapter (self.ble_adapter_name): %s" %
                     self.ble_adapter_name)
        import subprocess
        output = subprocess.check_output("hciconfig")
        logger.debug("Adapter (from hciconfig): %s " % str(output.split()[0]))

        self.manager = ControllerManager(self.ble_adapter_name)
        self.manager.is_adapter_powered = True
        logger.debug("Powering on BT adapter")

        devices_known_to_bt_module = [
            device.mac_address for device in self.manager.devices()
        ]
        if self.mac_address not in devices_known_to_bt_module:
            # The Nuimo needs to had been discovered by the bt module
            # at some point before we can do:
            #    self.controller = Controller(self.mac_address, self.manager)
            #
            # and expect it to connect succesfully. If it isn't present
            # discovery needs to be redone until the Nuimo reapears.
            logger.debug(
                "%s not in discovered devices of the bt module. Starting discovery"
                % self.mac_address)
            self.manager.start_discovery()
            while self.mac_address not in devices_known_to_bt_module:
                time.sleep(3)
                devices_known_to_bt_module = [
                    device.mac_address for device in self.manager.devices()
                ]
                logger.debug("Still haven't found %s" % self.mac_address)
            logger.debug("Found it. Stopping discovery")
            self.manager.stop_discovery()

        self.controller = Controller(self.mac_address, self.manager)
        self.controller.listener = self
        self.set_active_component()
        logger.info("Connecting to Nuimo controller %s",
                    self.controller.mac_address)
        self.controller.connect()
        try:
            self.manager.run()
        except KeyboardInterrupt:
            logger.info("Nuimo app received SIGINT %s",
                        self.controller.mac_address)
            self.stop()

    def stop(self):
        logger.info("Stopping nuimo app of %s ...",
                    self.controller.mac_address)
        if self.active_component:
            self.active_component.stop()

        self.controller.disconnect()
        self.is_app_disconnection = True
        logger.info("Disconnected from Nuimo controller %s",
                    self.controller.mac_address)
        self.manager.stop()
        logger.debug("self manager stop %s", self.controller.mac_address)

    def process_gesture_event(self, event):
        if event.gesture in self.GESTURES_TO_IGNORE:
            logger.debug("Ignoring gesture event: %s", event)
            return

        logger.debug("Processing gesture event: %s", event)

        if event.gesture in self.INTERNAL_GESTURES:
            self.process_internal_gesture(event.gesture)
            return

        if event.gesture == Gesture.BATTERY_LEVEL:
            logger.info("gesture BATTERY LEVEL %d", event.value)
            self.battery_level = event.value
            self.update_battery_level()
            return

        if not self.active_component:
            logger.warn("Ignoring event, no active component")
            self.show_error_matrix()
            return

        if self.active_component.stopped:
            logger.warn("Ignoring event, component is not running")
            self.show_error_matrix()
            return

        if self.active_component.ip_address is not None:
            self.process_gesture(event.gesture, event.value)
            return

        # Process gestures for devices having no IP address in nuimo_app.cfg
        self.process_gesture_event(event.gesture, event.value)

    def process_internal_gesture(self, gesture):
        if gesture == Gesture.SWIPE_UP:
            component = self.get_prev_component()
            if component:
                self.set_active_component(component)

        elif gesture == Gesture.SWIPE_DOWN:
            component = self.get_next_component()
            if component:
                self.set_active_component(component)

        elif gesture in self.TOUCH_GESTURES:
            # Fall-through to show active component...
            pass

        self.show_active_component()

    def process_gesture(self, gesture, delta):
        if gesture == Gesture.ROTATION:
            self.active_component.on_rotation(
                delta / 1800
            )  # 1800 is the amount of all ticks for a full ring rotation

        if gesture == Gesture.BUTTON_PRESS:
            self.active_component.on_button_press()

        elif gesture == Gesture.SWIPE_LEFT:
            self.active_component.on_swipe_left()

        elif gesture == Gesture.SWIPE_RIGHT:
            self.active_component.on_swipe_right()

        elif gesture == Gesture.LONGTOUCH_LEFT:
            self.active_component.on_longtouch_left()

        elif gesture == Gesture.LONGTOUCH_BOTTOM:
            self.active_component.on_longtouch_bottom()

        elif gesture == Gesture.LONGTOUCH_RIGHT:
            self.active_component.on_longtouch_right()

        else:
            # TODO handle all remaining gestures...
            pass

    def get_prev_component(self):
        if not self.components:
            return None

        if self.active_component:
            index = self.components.index(self.active_component)
            return self.components[index - 1]
        else:
            return self.components[0]

    def get_next_component(self):
        if not self.components:
            return None

        if self.active_component:
            index = self.components.index(self.active_component)
            try:
                return self.components[index + 1]
            except IndexError:
                return self.components[0]
        else:
            return self.components[0]

    def set_active_component(self, component=None):
        active_component = None

        if component:
            active_component = component
        elif self.components:
            active_component = self.components[0]

        if active_component:
            if self.active_component:
                logger.debug("Stopping component: %s",
                             self.active_component.component_id)
                self.active_component.stop()

            logger.debug("Activating component: %s",
                         active_component.component_id)
            self.active_component = active_component
            self.active_component.start()

    def show_active_component(self):
        if self.active_component:
            index = self.components.index(self.active_component)
            matrix = matrices.matrix_with_index(self.active_component.MATRIX,
                                                index)
        else:
            matrix = matrices.ERROR

        self.display_matrix(matrix)

    def show_error_matrix(self):
        self.display_matrix(matrices.ERROR)

    def display_matrix(self, matrix, **kwargs):
        self.controller.display_matrix(LedMatrix(matrix), **kwargs)

    def listen_to_ipc_queue(self, ipc_queue):
        """
        Checks an inter-process queue for new messages. The messages have a simple custom format
        containing the name of one of the defined methods to call and in some cases additional arguments.

        This is required because this NuimoApp instance is executed in its own process (because gatt-python
        doesn't handle multiple devices in a single thread correctly) and it needs to be notified of changes
        and when to quit.
        """

        logger.debug("Started the ipc_queue listener")

        while True:
            msg = ipc_queue.get()
            if msg['method'] == 'set_components':
                components = msg['components']
                logger.info("IPC set_components() received: %s mac = %s",
                            components, self.controller.mac_address)
                component_instances = get_component_instances(
                    components, self.controller.mac_address)
                self.set_components(component_instances)
            elif msg['method'] == 'stop':
                logger.info("IPC stop() received %s",
                            self.controller.mac_address)
                self.stop()
                return

    def update_battery_level(self):
        self.bl.value = self.battery_level