def __init__(self) -> None: self.logging = Logging("events") self.rgb = RgbLedBlock() self.ir_block = IrBlock() self.ir_block.add_remote(IrNumericRemote()) #to demonstrate equal to self.ir_block.value.equal_to(RemoteKey("0"), True, self.light_off) Ble.value_remote.equal_to(RemoteKey("0"), True, self.light_off) #to demonstrate changed self.ir_block.value.updated(True, self.changed, self.ir_block.value) Ble.value_remote.updated(True, self.changed, Ble.value_remote)
class IrBlock(BlockBase): _no_data_ready = RemoteKey.get_default() def __init__(self, address=None, measurement_period: float=0.1): BlockBase.__init__(self, BlockTypes.ir, address) self.value = ActiveVariable(RemoteKey.get_default(), measurement_period, self._get_value) self._remotes = list() def _get_value(self): ready = self._tiny_read(_ir_data_ready_command, None, 1) if ready and ready[0]: data = self._tiny_read(_ir_data_command, None, 4) hex_addr = ''.join(['{:02x}'.format(b) for b in data[0:2]]) self.logging.debug("address ", hex_addr) raw_address = data[0] + (data[1] << 8) scan_code = data[2] #data[3] (repeat ) is not used yet remote = self._get_near_remote(raw_address) if remote: self.logging.info("scan_code:%d, raw_address:%d", scan_code, raw_address) return RemoteKey(remote.find_name_by_scan_code(scan_code), scan_code, remote.get_address()) return self._no_data_ready #None is reserved for the case that block do not answer def add_remote(self, remote_control:RemoteKeyboardBase): self._remotes.append(remote_control) def _get_near_remote(self, received) -> RemoteKeyboardBase: for remote in self._remotes: address = remote.get_address() count = 0 for index in range(16): if (address >> index) & 0x01 != (received >> index) & 0x01: count += 1 if count <= remote.address_bit_tolerance: return remote return None
def process_remote_key(cls, scan_code, key_name): if not cls._keyboard: from remote_control.virtual_keyboard import VirtualKeyboard cls._keyboard = VirtualKeyboard() cls.value_remote.set( RemoteKey(key_name, scan_code, cls._keyboard.get_address()))
def __init__(self) -> None: self.speed = 0 self.pwm = 50 Ble.value_remote.equal_to(RemoteKey("a"), True, self.pwm_down) Ble.value_remote.equal_to(RemoteKey("d"), True, self.pwm_up) Ble.value_remote.equal_to(RemoteKey("w"), True, self.speed_up) Ble.value_remote.equal_to(RemoteKey("s"), True, self.slow_down) Ble.value_remote.equal_to(RemoteKey("z"), True, self.stop) Ble.value_remote.equal_to(RemoteKey("o"), True, self.full_speed_up) Ble.value_remote.equal_to(RemoteKey("l"), True, self.full_speed_down) self.motor_driver_front = MotorDriverBlock(0x20) #self.motor_driver_front.change_block_address(0x20) self.motor_driver_front.turn_clockwise(MotorDriverBlock.motor1_id) self.motor_driver_front.turn_opposite(MotorDriverBlock.motor2_id) self.motor_driver_rear = MotorDriverBlock(0x21) #self.motor_driver_rear.change_block_address(0x21) self.motor_driver_rear.turn_clockwise(MotorDriverBlock.motor1_id) self.motor_driver_rear.turn_opposite(MotorDriverBlock.motor2_id) self.motor_driver_front.sensor_power_on() self.motor_driver_rear.sensor_power_on() self.motor_driver_front.reset_sensor_counter( MotorDriverBlock.motor1_id) self.motor_driver_front.reset_sensor_counter( MotorDriverBlock.motor2_id) self.motor_driver_rear.reset_sensor_counter(MotorDriverBlock.motor1_id) self.motor_driver_rear.reset_sensor_counter(MotorDriverBlock.motor2_id) Planner.repeat(1, self.print_counters)
def _get_value(self): ready = self._tiny_read(_ir_data_ready_command, None, 1) if ready and ready[0]: data = self._tiny_read(_ir_data_command, None, 4) hex_addr = ''.join(['{:02x}'.format(b) for b in data[0:2]]) self.logging.debug("address ", hex_addr) raw_address = data[0] + (data[1] << 8) scan_code = data[2] #data[3] (repeat ) is not used yet remote = self._get_near_remote(raw_address) if remote: self.logging.info("scan_code:%d, raw_address:%d", scan_code, raw_address) return RemoteKey(remote.find_name_by_scan_code(scan_code), scan_code, remote.get_address()) return self._no_data_ready #None is reserved for the case that block do not answer
def __init__(self) -> None: self.logging = Logging("events") self.chassis = Chassis(power_measurement_period=0.3) self.rgb = RgbLedBlock() self.display = DisplayBlock() self.distance = DistanceBlock(measurement_period=0.1) self.button = ButtonBlock(measurement_period=0.1) self.ir_block = IrBlock() self.ir_block.add_remote(IrNumericRemote()) self.button.value.equal_to(True, True, self.change_patrol) self.near_barrier_event = None self.far_bariier_event = None self.enough_space_event = None self.open_space_event = None self.patrol = False self.display.contrast(0) Ble.value_remote.equal_to(RemoteKey("a"), True, self.turn_left) Ble.value_remote.equal_to(RemoteKey("d"), True, self.turn_right) Ble.value_remote.equal_to(RemoteKey("w"), True, self.speed_up) Ble.value_remote.equal_to(RemoteKey("s"), True, self.slow_down) Ble.value_remote.equal_to(RemoteKey("z"), True, self.stop) Ble.value_remote.equal_to(RemoteKey("x"), True, self.reverse) Ble.value_remote.equal_to(RemoteKey("p"), True, self.change_patrol) self.ir_block.value.equal_to(IrNumericRemote.key_left, True, self.turn_left) self.ir_block.value.equal_to(IrNumericRemote.key_right, True, self.turn_right) self.ir_block.value.equal_to(IrNumericRemote.key_top, True, self.speed_up) self.ir_block.value.equal_to(IrNumericRemote.key_bottom, True, self.slow_down) self.ir_block.value.equal_to(IrNumericRemote.key_ok, True, self.stop) self.ir_block.value.equal_to(IrNumericRemote.key_hash, True, self.reverse) self.ir_block.value.equal_to(IrNumericRemote.key_star, True, self.change_patrol) self.counter = 0 Planner.repeat(0.5, self.print_power_info) PowerMgmt.set_plan(PowerPlan.get_max_performance_plan()) self.current_smoother = SmoothedVariable( 3, SmoothingType.average, self.chassis.power.battery_current_mA) self.current_smoother.more_than(600, True, self.stop_for_a_while, 1) self.heart_beat = False
def __init__(self) -> None: self.logging = Logging("events") self.chassis = Chassis(0x20, 0x21) Ble.value_remote.equal_to(RemoteKey("a"), True, self.turn_left) Ble.value_remote.equal_to(RemoteKey("d"), True, self.turn_right) Ble.value_remote.equal_to(RemoteKey("w"), True, self.speed_up) Ble.value_remote.equal_to(RemoteKey("s"), True, self.slow_down) Ble.value_remote.equal_to(RemoteKey("z"), True, self.stop) Ble.value_remote.equal_to(RemoteKey("x"), True, self.reverse) Planner.repeat(1, self.print_power_info)
def __init__(self): self.value = ActiveVariable(RemoteKey.get_default())
class IrNumericRemote(RemoteKeyboardBase): """ this class describes a cheap remote control with a numeric keyboard and a navigation cross """ address = 65280 key_1 = RemoteKey("1", 69, address) key_2 = RemoteKey("2", 70, address) key_3 = RemoteKey("3", 71, address) key_4 = RemoteKey("4", 68, address) key_5 = RemoteKey("5", 64, address) key_6 = RemoteKey("6", 67, address) key_7 = RemoteKey("7", 7, address) key_8 = RemoteKey("8", 21, address) key_9 = RemoteKey("9", 9, address) key_0 = RemoteKey("0", 25, address) key_star = RemoteKey("*", 22, address) key_hash = RemoteKey("#", 13, address) key_ok = RemoteKey(SpecialKeys.ok, 28, address) key_left = RemoteKey(SpecialKeys.left, 8, address) key_right = RemoteKey(SpecialKeys.right, 90, address) key_up = RemoteKey(SpecialKeys.up, 24, address) key_down = RemoteKey(SpecialKeys.down, 82, address) _keys = ( key_1, key_2, key_3, key_4, key_5, key_6, key_7, key_8, key_9, key_0, key_star, key_hash, key_ok, key_left, key_right, key_up, key_down, ) def get_address(self): return self.address def find_name_by_scan_code(self, scan_code): for key in self._keys: if key.scan_code == scan_code: return key.name return None
def __init__(self, address=None, measurement_period: float=0.1): BlockBase.__init__(self, BlockTypes.ir, address) self.value = ActiveVariable(RemoteKey.get_default(), measurement_period, self._get_value) self._remotes = list()
class Ble(): _ble = None _shell = None _remote_value = None _keyboard = None _command_handle = None _log_handle = None _keyboard_handle = None _initial_time_up = None _running_time_up = None _time_down = None _time_to_power_save = None value_remote: ActiveVariable = ActiveVariable(RemoteKey.get_default()) @classmethod def init(cls) -> None: PowerMgmt.register_management_change_callback( cls._set_power_save_timeouts) cls._set_power_save_timeouts( PowerMgmt.get_plan()) # to be set defaults Planner.plan(cls._check_time_to_power_save, True) Logging.add_logger(BleLogger) cls._start_ble( ) #to be allocated big blocks in the beginning it should prevent memory fragmentation cls.just_initialized = True @classmethod def _set_power_save_timeouts(cls, power_plan: PowerPlan): cls._initial_time_up = power_plan.ble_plan.initial_time_up cls._running_time_up = power_plan.ble_plan.running_time_up cls._time_down = power_plan.ble_plan.time_down if cls._time_to_power_save != 0: #if ble connection is not established already cls._time_to_power_save = cls._initial_time_up @classmethod def _check_time_to_power_save(cls, wake_up): #micropython.mem_info(False) go_to_power_save = False try: #to do not be _check_time_to_power_save interupted due to a BLE issue if wake_up: PowerMgmt.block_power_save() if not cls._time_to_power_save: #can be reset from constructor cls._time_to_power_save = cls._running_time_up if cls.just_initialized: cls.just_initialized = False else: cls._start_ble() #cls._advertise() #just for sure it is turned on print("BLE power-save blocked") else: if cls._time_to_power_save: #if time was not reset externally (e.g. ble connection) cls._time_to_power_save -= 1 if cls._time_to_power_save == 0: #if time has been reset by decreasing go_to_power_save = True # ble is disabled automatically when power save is activated and is enabled again when the program runs again # but it is not reliable (advertisement si not started) - lets do it manually #cls.disconnect() cls._stop_ble() PowerMgmt.unblock_power_save() print("BLE power-save allowed") except Exception as error: print("BLE error") #pylint: disable=no-member ;implemented in micropython sys.print_exception(error) #micropython.mem_info(0) delay = cls._time_down if go_to_power_save else 1 Planner.postpone(delay, cls._check_time_to_power_save, go_to_power_save) @classmethod def _start_ble(cls): cls._ble = None cls._ble = bluetooth.BLE() cls._ble.active(True) #cls._ble.config(rxbuf=_BMS_MTU) cls._ble.irq(cls._irq) #cls._ble.config(mtu=_BMS_MTU) ((cls._command_handle, cls._log_handle, cls._keyboard_handle), ) = cls._ble.gatts_register_services( (_HUGO_SERVICE, )) cls._connections = set() cls._payload = cls.advertising_payload( name="HuGo", services=[_HUGO_SERVICE], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER) cls._advertise() @classmethod def _stop_ble(cls): cls.disconnect() cls._ble.active(False) @classmethod def get_shell( cls ): #saving RAM shell and remote_values are ussually not loaded together if not cls._shell: from basal.shell import Shell cls._shell = Shell return cls._shell @classmethod def get_remote_value(cls): if not cls._remote_value: from basal.remote_value import RemoteValue cls._remote_value = RemoteValue return cls._remote_value @classmethod def process_remote_key(cls, scan_code, key_name): if not cls._keyboard: from remote_control.virtual_keyboard import VirtualKeyboard cls._keyboard = VirtualKeyboard() cls.value_remote.set( RemoteKey(key_name, scan_code, cls._keyboard.get_address())) @classmethod def process_command(cls, conn_handle, value_handle, command, data): if command < ble_ids.cmd_shell_last: command_solver = cls.get_shell() else: command_solver = cls.get_remote_value() ret_data = command_solver.command_request(command, data) if ret_data is not None: cls._ble.gatts_notify(conn_handle, value_handle, ret_data) @classmethod def _irq(cls, event, data): # Track connections so we can send notifications. if event == _IRQ_CENTRAL_CONNECT: conn_handle, _, _ = data #NOTE: use when mtu is necessary to change connected = False for _ in range(3): #sometimes attempts to exchange mtu fails try: cls._ble.gattc_exchange_mtu(conn_handle) connected = True cls._time_to_power_save = 0 # disable power save while a connection is active break except Exception: print("Error: gattc_exchange_mtu failed") if connected: cls._connections.add(conn_handle) print("BLE new connection: " + str(conn_handle)) else: cls._ble.gap_disconnect(conn_handle) elif event == _IRQ_CENTRAL_DISCONNECT: print("disconnect") conn_handle, _, _ = data cls._connections.remove(conn_handle) if not cls._connections: cls._time_to_power_save = cls._initial_time_up # enable power save with initial time out - to be possible to reconnect print("BLE disconnected " + str(conn_handle)) # Start advertising again to allow a new connection. cls._advertise() elif event == _IRQ_GATTS_INDICATE_DONE: conn_handle, value_handle, _status = data elif event == _IRQ_GATTS_WRITE: conn_handle, value_handle = data value = cls._ble.gatts_read(value_handle) if value_handle == cls._command_handle and value and len( value) > 0: command = value[0] data = value[1:] Planner.plan(cls.process_command, conn_handle, value_handle, command, data) if value_handle == cls._keyboard_handle: scan_code = int.from_bytes(value[0:2], "big", True) key_name = value[2:].decode("utf-8") print("scan_code: {}, key_name: {}".format( str(scan_code), str(key_name))) Planner.plan(cls.process_remote_key, scan_code, key_name) elif event == _IRQ_MTU_EXCHANGED: _conn_handle, mtu = data print("_IRQ_MTU_EXCHANGED mtu:", mtu) elif event == _IRQ_SET_SECRET: sec_type, key, value = data print("_IRQ_SET_SECRET sec_type:", sec_type, " key:", key, "value", value) else: print("unhandled event: " + str(event)) @classmethod def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0): payload = bytearray() def _append(adv_type, value): nonlocal payload payload += struct.pack("BB", len(value) + 1, adv_type) + value _append( _ADV_TYPE_FLAGS, struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)), ) if name: _append(_ADV_TYPE_NAME, name) if services: _append(_ADV_TYPE_UUID16_COMPLETE, b"HuGo") # See org.bluetooth.characteristic.gap.appearance.xml if appearance: _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance)) return payload @classmethod def _advertise(cls, interval_us=100000): cls._ble.gap_advertise(interval_us, adv_data=cls._payload) @classmethod def disconnect(cls): for connection in cls._connections: cls._ble.gap_disconnect(connection) @classmethod def notify_log(cls, message): try: for connection in cls._connections: cls._ble.gatts_notify(connection, cls._log_handle, message) except Exception as error: print("notify_log error: ", error)