Esempio n. 1
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)
Esempio n. 2
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, mac_address, ha_api, ble_adapter_name):
        super().__init__()

        self.components = []
        self.active_component = None

        self.controller = Controller(adapter_name=ble_adapter_name, mac_address=mac_address)
        self.controller.listener = self
        self.controller.connect()

        self.rotation_value = 0
        self.action_in_progress = None

        self.ha = ha_api

    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_icon()
            return

        if event.gesture == Gesture.ROTATION:
            action = self.process_rotation(event.value)
        else:
            action = self.process_gesture(event.gesture)

        if action:
            self.execute_action(action)

    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_rotation(self, rotation_value):
        self.rotation_value += rotation_value

        if not self.action_in_progress:
            action = self.active_component.rotation(self.rotation_value)
            self.rotation_value = 0
            self.action_in_progress = action
            return action

    def process_gesture(self, gesture):
        action = None

        if gesture == Gesture.BUTTON_PRESS:
            action = self.active_component.button_press()

        elif gesture == Gesture.SWIPE_LEFT:
            action = self.active_component.swipe_left()

        elif gesture == Gesture.SWIPE_RIGHT:
            action = self.active_component.swipe_right()

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

        return action

    def execute_action(self, action):
        def call_service_callback(entity_id, response):
            logger.debug("service_call response for %s:", entity_id)
            logger.debug(pformat(response))

            status = response["success"]
            action.entity_updated(entity_id, status)

            # check if action has been already applied to all entities
            if action.is_complete():
                if action.is_successful():
                    matrix_config = action.led_matrix_config
                else:
                    matrix_config = LEDMatrixConfig(icons.ERROR)

                self.update_led_matrix(matrix_config)

                self.action_in_progress = None

        for entity_id in action.entity_ids:
            attributes = {"entity_id": entity_id}
            attributes.update(action.extra_args)

            callback = partial(call_service_callback, entity_id)
            self.ha.call_service(action.domain, action.service, attributes, callback)

    def register_component(self, component):
        def set_state(state):
            component.set_state(state)
            self.components.append(component)
            logger.debug("New component registered: %s initial state:", component.name)
            logger.debug(pformat(state))

            # register a state_changed callback that is called
            # every time there's a state changed event for any of
            # entities known by the component
            self.ha.register_state_listener(component.entity_ids, component.state_changed)

            # show active component if we can
            if not self.active_component and self.components:
                self.set_active_component()

        self.ha.get_state(component.entity_ids, set_state)

    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:
            logger.debug("active component: %s", active_component.name)

            if self.active_component:
                self.ha.unregister_state_listener(self.active_component.entity_ids)

            self.active_component = active_component
            self.ha.register_state_listener(self.active_component.entity_ids, self.state_changed)

            if self.controller.is_connected():
                self.show_active_component()

    def state_changed(self, state):
        """
        Gets called whenever state changes in any device within
        currently active component group.

        """
        logger.debug("state_changed:")
        logger.debug(pformat(state))

    def show_active_component(self):
        if self.active_component:
            index = self.components.index(self.active_component)
            icon = icons.icon_with_index(self.active_component.ICON, index)
        else:
            icon = icons.ERROR

        self.update_led_matrix(LEDMatrixConfig(icon))

    def show_error_icon(self):
        self.update_led_matrix(LEDMatrixConfig(icons.ERROR))

    def update_led_matrix(self, matrix_config):
        self.controller.display_matrix(
            matrix_config.matrix,
            fading=matrix_config.fading,
            ignore_duplicates=matrix_config.ignore_duplicates,
        )

    def quit(self):
        if self.controller.is_connected():
            self.controller.disconnect()
Esempio n. 3
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