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