From 3fe755468aaf23da176299fcc65844d3b4cc96ff Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 13 Mar 2024 10:50:49 +0100 Subject: [PATCH 01/46] Testing bleak as a replacement for bluepy write rx with UUID add badge_bleak add badge_bleak add badge_bleak add badge_bleak add badge_bleak add badge_bleak rewrite utilities using bleak add request handler decorators add request handler decorators add request handler decorators add request handler decorators add request handler decorators rewrite utilities using bleak add request handler decorators --- .gitignore | 4 + BadgeFramework/badge.py | 592 ++++++++++-------- BadgeFramework/badge_protocol.py | 6 +- BadgeFramework/bleak_test.py | 56 ++ BadgeFramework/bluepy/badge.py | 316 ++++++++++ .../{ => bluepy}/badge_connection.py | 0 .../{ => bluepy}/ble_badge_connection.py | 0 .../{ => bluepy}/hub_connection_V1.py | 0 .../{ => bluepy}/hub_utilities_V1.py | 23 +- BadgeFramework/{ => bluepy}/scan_all.py | 0 BadgeFramework/bluepy_test.py | 126 ++++ BadgeFramework/hub_V1.py | 229 ++++++- BadgeFramework/hub_utilities.py | 173 +++++ BadgeFramework/mappings2.csv | 50 ++ BadgeFramework/utils.py | 45 ++ 15 files changed, 1351 insertions(+), 269 deletions(-) create mode 100644 BadgeFramework/bleak_test.py create mode 100644 BadgeFramework/bluepy/badge.py rename BadgeFramework/{ => bluepy}/badge_connection.py (100%) rename BadgeFramework/{ => bluepy}/ble_badge_connection.py (100%) rename BadgeFramework/{ => bluepy}/hub_connection_V1.py (100%) rename BadgeFramework/{ => bluepy}/hub_utilities_V1.py (87%) rename BadgeFramework/{ => bluepy}/scan_all.py (100%) create mode 100644 BadgeFramework/bluepy_test.py create mode 100644 BadgeFramework/hub_utilities.py create mode 100644 BadgeFramework/mappings2.csv create mode 100644 BadgeFramework/utils.py diff --git a/.gitignore b/.gitignore index 3446e37..59f440f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ _build/* __pycache/ .vscode/c_cpp_properties.json .vscode/settings.json +**/.DS_Store +BadgeFramework/rotation/rpi_sync.sh +.idea +*.log diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 4a059f6..eda67a2 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -1,9 +1,17 @@ from __future__ import division, absolute_import, print_function + +import sys +import functools import time import logging -import sys import struct import Queue +from collections.abc import Callable + +from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic +from typing import Optional, Final +import badge_protocol as bp +import utils DEFAULT_SCAN_WINDOW = 250 DEFAULT_SCAN_INTERVAL = 1000 @@ -14,8 +22,8 @@ DEFAULT_MICROPHONE_MODE = 0 #Valid options: 0=Stereo, 1=Mono -from badge_protocol import * - +CONNECTION_RETRY_TIMES = 10 +DUPLICATE_TIME_INTERVAL = 2 logger = logging.getLogger(__name__) # -- Helper methods used often in badge communication -- @@ -23,297 +31,365 @@ # We generally define timestamp_seconds to be in number of seconds since UTC epoch # and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. -# Returns the current timestamp as two parts - seconds and milliseconds -def get_timestamps(): - return get_timestamps_from_time(time.time()) -# Returns the given time as two parts - seconds and milliseconds -def get_timestamps_from_time(t): +def get_timestamps_from_time(t=None) -> (int, int): + """Returns the given time as two parts - seconds and milliseconds""" + if t is None: + t = time.time() timestamp_seconds = int(t) timestamp_fraction_of_second = t - timestamp_seconds timestamp_ms = int(1000 * timestamp_fraction_of_second) - return (timestamp_seconds, timestamp_ms) + return timestamp_seconds, timestamp_ms # Convert badge timestamp representation to python representation -def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): - return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) - - -# Represents an OpenBadge currently connected via the BadgeConnection 'connection'. -# The 'connection' should already be connected when it is used to initialize this class. -# Implements methods that allow for interaction with that badge. -class OpenBadge(object): - def __init__(self, connection): - self.connection = connection - self.status_response_queue = Queue.Queue() - self.start_microphone_response_queue = Queue.Queue() - self.start_scan_response_queue = Queue.Queue() - self.start_imu_response_queue = Queue.Queue() - self.free_sdc_space_response_queue = Queue.Queue() - self.sdc_errase_all_response_queue = Queue.Queue() - self.get_imu_data_response_queue = Queue.Queue() - +# def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): +# return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) + + +# class OpenBadge(object): +# def __init__(self, connection): +# self.connection = connection +# self.status_response_queue = Queue.Queue() +# self.start_microphone_response_queue = Queue.Queue() +# self.start_scan_response_queue = Queue.Queue() +# self.start_imu_response_queue = Queue.Queue() +# self.free_sdc_space_response_queue = Queue.Queue() +# self.sdc_errase_all_response_queue = Queue.Queue() +# self.get_imu_data_response_queue = Queue.Queue() + +# TODO: check erase all and get imu data function +def bp_timestamp_from_time(t=None) -> bp.Timestamp: + ts = bp.Timestamp() + ts.seconds, ts.ms = get_timestamps_from_time(t) + return ts + + +def badge_disconnected(b: BleakClient) -> None: + """disconnection callback""" + print(f"Warning: disconnected badge") + + +def request_handler(device_id, action_desc): + def request_handler_decorator(func): + @functools.wraps(func) + async def wrapper(*args, **kwargs): + try: + value = await func(*args, **kwargs) + return value + except Exception as err: + error_desc = "Could not {} for participant {}, error: {}" + raise Exception(error_desc.format(action_desc, str(device_id), str(err))) + return wrapper + return request_handler_decorator + + +def request_handler_marker(action_desc): + def wrapper(func): + func._handler = True # Mark the function to be repeated + func._action_desc = action_desc + return func + return wrapper + + +class OpenBadgeMeta: + def __init__(self, device: BLEDevice or int, mac_address: str = None): + # self.device = device + if isinstance(device, BLEDevice): + self.device_id = utils.get_device_id(device) + self.address = device.address + elif isinstance(device, int): + self.device_id = device + self.address = mac_address + else: + raise TypeError + self._decorate_methods() + + def _decorate_methods(self): + # Automatically decorate methods marked with @repeat_method + for attr_name in dir(self): + attr = getattr(self, attr_name, None) + if callable(attr) and getattr(attr, '_handler', False): + action_desc = getattr(attr, '_action_desc', '[unknown operation]') + decorated = request_handler(self.device_id, action_desc)(attr) + setattr(self, attr_name, decorated) + + +class OpenBadge(OpenBadgeMeta): + """Represents an OpenBadge and implements methods that allow for interaction with that badge.""" + def __init__(self, device: BLEDevice or int, mac_address: str = None): + super().__init__(device, mac_address) + self.client = BleakClient(self.address, disconnected_callback=badge_disconnected) + # self.rx_message = b'' + self.rx_list = [] + + async def __aenter__(self): + for _ in range(CONNECTION_RETRY_TIMES): + try: + await self.client.connect(timeout=10) + await self.client.start_notify(utils.RX_CHAR_UUID, self.received_callback) + return self + except Exception as e: + pass + raise TimeoutError(f'Failed to connect to device after {CONNECTION_RETRY_TIMES} attempts.') + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.client.disconnect() # Helper function to send a BadgeMessage `command_message` to a device, expecting a response # of class `response_type` that is a subclass of BadgeMessage, or None if no response is expected. - def send_command(self, command_message, response_type): - expected_response_length = response_type.length() if response_type else 0 - serialized_command = command_message.serialize_message() - logger.debug( - "Sending: {}, Raw: {}".format( - command_message, serialized_command.encode("hex") - ) - ) - serialized_response = self.connection.send( - serialized_command, response_len=expected_response_length - ) + @property + def is_connected(self) -> bool: + return self.client.is_connected - if expected_response_length > 0: - response = response_type.deserialize_message(serialized_response) - logger.info("Recieved response {}".format(response)) - return response - else: - logger.info("No response expected, transmission successful.") - return True + def badge_disconnected(self, b: BleakClient) -> None: + """disconnection callback""" + print(f"Warning: disconnected badge") - def send_request(self, request_message): + @staticmethod + def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: + """add serialized header to message""" serialized_request = request_message.encode() - # Adding length header: serialized_request_len = struct.pack(" None: + """send message to client""" + await client.write_gatt_char(utils.TX_CHAR_UUID, message, response=True) + + @staticmethod + async def receive(client: BleakClient) -> bytes or bytearray: + """receive message from client""" + response_rx = b'' + for x in range(10): + # if len(response_rx) > 0: + # break + response_rx = await client.read_gatt_char(utils.RX_CHAR_UUID) + return response_rx + + @staticmethod + def message_is_duplicated(message_list: list, new_message: dict) -> bool: + """check if message is duplicated with existing message list""" + if len(message_list) == 0: + return False + last_message = message_list[-1] + time_duplicated = abs(last_message['time'] - new_message['time']) < DUPLICATE_TIME_INTERVAL + message_duplicated = last_message['message'] == new_message['message'] + return time_duplicated and message_duplicated + + def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray): + """callback function for receiving message. Note that this must be used in combination with the + 'receive' function to work properly.""" + if len(message) > 0: + new_message = {'time': time.time(), 'message': message} + if not self.message_is_duplicated(self.rx_list, new_message): + print(f"RX changed {sender}: {message}" + str(time.time())) + # self.rx_message = message + self.rx_list.append(new_message) + + # TODO: figure out how to implement the queue options + # queue_options = { + # Response_status_response_tag: self.status_response_queue, + # Response_start_microphone_response_tag: self.start_microphone_response_queue, + # Response_start_scan_response_tag: self.start_scan_response_queue, + # Response_start_imu_response_tag: self.start_imu_response_queue, + # Response_free_sdc_space_response_tag: self.free_sdc_space_response_queue, + # Response_sdc_errase_all_response_tag: self.sdc_errase_all_response_queue, + # Response_get_imu_data_response_tag: self.get_imu_data_response_queue, + # } + # response_options = { + # Response_status_response_tag: response_message.type.status_response, + # Response_start_microphone_response_tag: response_message.type.start_microphone_response, + # Response_start_scan_response_tag: response_message.type.start_scan_response, + # Response_start_imu_response_tag: response_message.type.start_imu_response, + # Response_free_sdc_space_response_tag: response_message.type.free_sdc_space_response, + # Response_sdc_errase_all_response_tag: response_message.type.sdc_errase_all_response, + # Response_get_imu_data_response_tag: response_message.type.get_imu_data_response + # } + # queue_options[response_message.type.which].put( + # response_options[response_message.type.which] + # ) + + async def request_response(self, message: bp.Request, require_response: Optional[bool] = True): + """request response from client""" + serialized_request = self.add_serialized_header(message) + # logger.debug("Sending: {}, Raw: {}".format(message, serialized_request.hex())) + await self.send(self.client, serialized_request) + response = await self.receive(self.client) + return response if require_response else None + + @staticmethod + def decode_response(response: bytearray or bytes): + """decode response from client. First two bytes represent the length.""" + response_len = struct.unpack(" bp.StatusResponse: + """Sends a status request to this Badge. Optional fields new_id and new_group number will set + the badge's id and group number. They must be sent together. Returns a StatusResponse() + representing badge's response.""" + request = bp.Request() + request.type.which = bp.Request_status_request_tag + request.type.status_request = bp.StatusRequest() + request.type.status_request.timestamp = bp_timestamp_from_time(t) if not ((new_id is None) or (new_group_number is None)): - request.type.status_request.badge_assignement = BadgeAssignement() + request.type.status_request.badge_assignement = bp.BadgeAssignement() request.type.status_request.badge_assignement.ID = new_id request.type.status_request.badge_assignement.group = new_group_number request.type.status_request.has_badge_assignement = True - self.send_request(request) - - # Clear the queue before receiving - with self.status_response_queue.mutex: - self.status_response_queue.queue.clear() - - while self.status_response_queue.empty(): - self.receive_response() - - return self.status_response_queue.get() - - # Sends a request to the badge to start recording microphone data. - # Returns a StartRecordResponse() representing the badges response. - def start_microphone(self, t=None, mode=DEFAULT_MICROPHONE_MODE): - if t is None: - (timestamp_seconds, timestamp_ms) = get_timestamps() - else: - (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) - #print("MODE ",mode) - request = Request() - request.type.which = Request_start_microphone_request_tag - request.type.start_microphone_request = StartMicrophoneRequest() - request.type.start_microphone_request.timestamp = Timestamp() - request.type.start_microphone_request.timestamp.seconds = timestamp_seconds - request.type.start_microphone_request.timestamp.ms = timestamp_ms + await self.request_response(request) + return self.deal_response().type.status_response + + async def set_id_at_start(self, badge_id, group_number): + try: + await self.get_status(new_id=badge_id, new_group_number=group_number) + except Exception as err: + raise Exception(f"Could not set id {badge_id}, error:" + str(err)) + + @request_handler_marker(action_desc='start microphone') + async def start_microphone(self, t=None, mode=DEFAULT_MICROPHONE_MODE) -> bp.StartMicrophoneResponse: + """Sends a request to the badge to start recording microphone data. Returns a StartRecordResponse() + representing the badges' response.""" + request = bp.Request() + request.type.which = bp.Request_start_microphone_request_tag + request.type.start_microphone_request = bp.StartMicrophoneRequest() + request.type.start_microphone_request.timestamp = bp_timestamp_from_time(t) request.type.start_microphone_request.mode = mode - self.send_request(request) - - with self.start_microphone_response_queue.mutex: - self.start_microphone_response_queue.queue.clear() - - while self.start_microphone_response_queue.empty(): - self.receive_response() - - return self.start_microphone_response_queue.get() - - # Sends a request to the badge to stop recording. - # Returns True if request was successfuly sent. - def stop_microphone(self): - - request = Request() - request.type.which = Request_stop_microphone_request_tag - request.type.stop_microphone_request = StopMicrophoneRequest() - - self.send_request(request) - - # Sends a request to the badge to start performing scans and collecting scan data. - # window_miliseconds and interval_miliseconds controls radio duty cycle during scanning (0 for firmware default) - # radio is active for [window_miliseconds] every [interval_miliseconds] - # Returns a StartScanningResponse() representing the badge's response. - def start_scan( - self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DEFAULT_SCAN_INTERVAL - ): - if t is None: - (timestamp_seconds, timestamp_ms) = get_timestamps() - else: - (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) - - request = Request() - request.type.which = Request_start_scan_request_tag - request.type.start_scan_request = StartScanRequest() - request.type.start_scan_request.timestamp = Timestamp() - request.type.start_scan_request.timestamp.seconds = timestamp_seconds - request.type.start_scan_request.timestamp.ms = timestamp_ms + await self.request_response(request) + return self.deal_response().type.start_microphone_response + + @request_handler_marker(action_desc='stop microphone') + async def stop_microphone(self) -> None: + """Sends a request to the badge to stop recording.""" + request = bp.Request() + request.type.which = bp.Request_stop_microphone_request_tag + request.type.stop_microphone_request = bp.StopMicrophoneRequest() + + await self.request_response(request, require_response=False) + return None + + @request_handler_marker(action_desc='start scan') + async def start_scan(self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DEFAULT_SCAN_INTERVAL)\ + -> bp.StartScanResponse: + """Sends a request to the badge to start performing scans and collecting scan data. + window_miliseconds and interval_miliseconds controls radio duty cycle during scanning (0 for firmware default) + radio is active for [window_miliseconds] every [interval_miliseconds] + Returns a StartScanningResponse() representing the badge's response.""" + request = bp.Request() + request.type.which = bp.Request_start_scan_request_tag + request.type.start_scan_request = bp.StartScanRequest() + request.type.start_scan_request.timestamp = bp_timestamp_from_time(t) request.type.start_scan_request.window = window_ms request.type.start_scan_request.interval = interval_ms - self.send_request(request) - - # Clear the queue before receiving - with self.start_scan_response_queue.mutex: - self.start_scan_response_queue.queue.clear() - - while self.start_scan_response_queue.empty(): - self.receive_response() - - return self.start_scan_response_queue.get() - - # Sends a request to the badge to stop scanning. - # Returns True if request was successfuly sent. - def stop_scan(self): - - request = Request() - request.type.which = Request_stop_scan_request_tag - request.type.stop_scan_request = StopScanRequest() - - self.send_request(request) - - def start_imu( - self, - t=None, - acc_fsr=DEFAULT_IMU_ACC_FSR, - gyr_fsr=DEFAULT_IMU_GYR_FSR, - datarate=DEFAULT_IMU_DATARATE, - ): - if t is None: - (timestamp_seconds, timestamp_ms) = get_timestamps() - else: - (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) - - request = Request() - request.type.which = Request_start_imu_request_tag - request.type.start_imu_request = StartImuRequest() - request.type.start_imu_request.timestamp = Timestamp() - request.type.start_imu_request.timestamp.seconds = timestamp_seconds - request.type.start_imu_request.timestamp.ms = timestamp_ms + await self.request_response(request) + return self.deal_response().type.start_scan_response + + @request_handler_marker(action_desc='stop scan') + async def stop_scan(self) -> None: + """Sends a request to the badge to stop scanning.""" + request = bp.Request() + request.type.which = bp.Request_stop_scan_request_tag + request.type.stop_scan_request = bp.StopScanRequest() + + await self.request_response(request) + return self.deal_response() + + @request_handler_marker(action_desc='start imu') + async def start_imu(self, t=None, acc_fsr=DEFAULT_IMU_ACC_FSR, gyr_fsr=DEFAULT_IMU_GYR_FSR, + datarate=DEFAULT_IMU_DATARATE) -> bp.StartImuResponse: + """Sends a request to the badge to start IMU. Returns the response object.""" + request = bp.Request() + request.type.which = bp.Request_start_imu_request_tag + request.type.start_imu_request = bp.StartImuRequest() + request.type.start_imu_request.timestamp = bp_timestamp_from_time(t) request.type.start_imu_request.acc_fsr = acc_fsr request.type.start_imu_request.gyr_fsr = gyr_fsr request.type.start_imu_request.datarate = datarate - self.send_request(request) - - # Clear the queue before receiving - with self.start_imu_response_queue.mutex: - self.start_imu_response_queue.queue.clear() - - while self.start_imu_response_queue.empty(): - self.receive_response() - - return self.start_imu_response_queue.get() - - def stop_imu(self): - - request = Request() - request.type.which = Request_stop_imu_request_tag - request.type.stop_imu_request = StopImuRequest() - - self.send_request(request) - - # Send a request to the badge to light an led to identify its self. - # If duration_seconds == 0, badge will turn off LED if currently lit. - # Returns True if request was successfuly sent. - def identify(self, duration_seconds=10): - - request = Request() - request.type.which = Request_identify_request_tag - request.type.identify_request = IdentifyRequest() + await self.request_response(request) + return self.deal_response().type.start_imu_response + + @request_handler_marker(action_desc='stop imu') + async def stop_imu(self) -> None: + """Sends a request to the badge to stop IMU.""" + request = bp.Request() + request.type.which = bp.Request_stop_imu_request_tag + request.type.stop_imu_request = bp.StopImuRequest() + + await self.request_response(request) + return None + + @request_handler_marker(action_desc='identify') + async def identify(self, duration_seconds=10) -> bool: + """Send a request to the badge to light an LED to identify its self. + If duration_seconds == 0, badge will turn off LED if currently lit. + Returns True if request was successfully sent.""" + request = bp.Request() + request.type.which = bp.Request_identify_request_tag + request.type.identify_request = bp.IdentifyRequest() request.type.identify_request.timeout = duration_seconds - self.send_request(request) - + await self.request_response(request) + self.deal_response() return True - def restart(self): - - request = Request() - request.type.which = Request_restart_request_tag - request.type.restart_request = RestartRequest() - - self.send_request(request) + @request_handler_marker(action_desc='restart') + async def restart(self) -> bool: + """Sends a request to the badge to restart the badge. Returns True if request was successfully sent.""" + request = bp.Request() + request.type.which = bp.Request_restart_request_tag + request.type.restart_request = bp.RestartRequest() + await self.request_response(request) + self.deal_response() return True - def get_free_sdc_space(self): + @request_handler_marker(action_desc='get free sdc space') + async def get_free_sdc_space(self) -> bp.FreeSDCSpaceResponse: + """Sends a request to the badge to get a free sdc space. Returns the response object.""" + request = bp.Request() + request.type.which = bp.Request_free_sdc_space_request_tag + request.type.free_sdc_space_request = bp.FreeSDCSpaceRequest() - request = Request() - request.type.which = Request_free_sdc_space_request_tag - request.type.free_sdc_space_request = FreeSDCSpaceRequest() + await self.request_response(request) + return self.deal_response().type.free_sdc_space_response - self.send_request(request) + async def start_recording_all_sensors(self): + await self.get_status() + await self.start_scan() + await self.start_microphone() + await self.start_imu() - # Clear the queue before receiving - with self.free_sdc_space_response_queue.mutex: - self.free_sdc_space_response_queue.queue.clear() - - while self.free_sdc_space_response_queue.empty(): - self.receive_response() + async def stop_recording_all_sensors(self): + await self.stop_scan() + await self.stop_microphone() + await self.stop_imu() + #TODO: + # while self.free_sdc_space_response_queue.empty(): + # self.receive_response() - return self.free_sdc_space_response_queue.get() + # return self.free_sdc_space_response_queue.get() def sdc_errase_all(self): @@ -349,4 +425,22 @@ def get_imu_data(self): while self.get_imu_data_response_queue.empty(): self.receive_response() - return self.get_imu_data_response_queue.get() \ No newline at end of file + return self.get_imu_data_response_queue.get() + @staticmethod + def print_help(): + print(" Available commands:") + print(" status ") + print(" start_all_sensors") + print(" stop_all_sensors") + print(" start_microphone") + print(" stop_microphone") + print(" start_scan") + print(" stop_scan") + print(" start_imu") + print(" stop_imu") + print(" identify") + print(" restart") + print(" get_free_space") + print(" help") + print(" All commands use current system time as transmitted time.") + sys.stdout.flush() diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index aa61d33..724d376 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -21,15 +21,19 @@ Response_sdc_errase_all_response_tag = 32 Response_get_imu_data_response_tag = 34 + class _Ostream: def __init__(self): self.buf = b'' + def write(self, data): self.buf += data + class _Istream: def __init__(self, buf): self.buf = buf + def read(self, l): if(l > len(self.buf)): raise Exception("Not enough bytes in Istream to read") @@ -39,8 +43,8 @@ def read(self, l): # print("_Istream:",i) return ret -class Timestamp: +class Timestamp: def __init__(self): self.reset() diff --git a/BadgeFramework/bleak_test.py b/BadgeFramework/bleak_test.py new file mode 100644 index 0000000..5d779e2 --- /dev/null +++ b/BadgeFramework/bleak_test.py @@ -0,0 +1,56 @@ +import asyncio +import time +import logging +import utils +from bleak import BleakScanner +from badge import OpenBadge + + +async def synchronize_device(open_badge: OpenBadge, logger: logging.Logger) -> None: + # TODO: pass in the universal time code here? + status = await open_badge.get_status() + logger.info(f"Status received for the following midge: {open_badge.id}.") + # TODO This is not actually the timestamp before, find how to get it. + logger.debug(f"Device timestamp before sync - seconds: {status.timestamp.seconds}, ms:{status.timestamp.ms}.") + if status.imu_status == 0: + logger.info(f"IMU is not recording for participant {open_badge.id}.") + if status.microphone_status == 0: + logger.info(f"Mic is not recording for participant {open_badge.id}.") + if status.scan_status == 0: + logger.info(f"Scan is not recording for participant {open_badge.id}.") + if status.clock_status == 0: + logger.info(f"Can't sync for participant {open_badge.id}.") + + +async def main(): + logger = utils.get_logger('bleak_logger') + # Find all devices + # using both BLEdevice and advertisement data here since it has more information. Also mutes the warning. + devices = await BleakScanner.discover(timeout=10.0, return_adv=True) + + # Filter out the devices that are not the midge + devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] + + # Print Id to see if it matches with any devices in the csv file. + for ble_device, adv_data in devices: + device_id = utils.get_device_id(ble_device) + print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") + + for ble_device, adv_data in devices: + # open_badge = OpenBadge(ble_device) + # space = await open_badge.get_free_sdc_space() + async with OpenBadge(ble_device) as open_badge: + # space = await open_badge.get_free_sdc_space() + out = await open_badge.get_status(t=40) + start = await open_badge.start_microphone() + time.sleep(15) + async with OpenBadge(ble_device) as open_badge: + stop = await open_badge.stop_microphone() + # await synchronize_device(open_badge, logger) + c = 9 + print('completed') + # Connect to the midge + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/BadgeFramework/bluepy/badge.py b/BadgeFramework/bluepy/badge.py new file mode 100644 index 0000000..cef7beb --- /dev/null +++ b/BadgeFramework/bluepy/badge.py @@ -0,0 +1,316 @@ +from __future__ import division, absolute_import, print_function +import time +import logging +import sys +import struct +import queue +from typing import Optional, Final +from badge_protocol import Request as mRequest + +DEFAULT_SCAN_WINDOW: Final[int] = 250 +DEFAULT_SCAN_INTERVAL: Final[int] = 1000 + +DEFAULT_IMU_ACC_FSR: Final[int] = 4 # Valid ranges: 2, 4, 8, 16 +DEFAULT_IMU_GYR_FSR: Final[int] = 1000 # Valid ranges: 250, 500, 1000, 2000 +DEFAULT_IMU_DATARATE: Final[int] = 50 + +DEFAULT_MICROPHONE_MODE: Final[int] = 1 # Valid options: 0=Stereo, 1=Mono + +from badge_protocol import * + +logger = logging.getLogger(__name__) + +# -- Helper methods used often in badge communication -- + +# We generally define timestamp_seconds to be in number of seconds since UTC epoch +# and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. + +# Returns the current timestamp as two parts - seconds and milliseconds +def get_timestamps(): + return get_timestamps_from_time(time.time()) + + +# Returns the given time as two parts - seconds and milliseconds +def get_timestamps_from_time(t): + timestamp_seconds = int(t) + timestamp_fraction_of_second = t - timestamp_seconds + timestamp_ms = int(1000 * timestamp_fraction_of_second) + return (timestamp_seconds, timestamp_ms) + + +# Convert badge timestamp representation to python representation +def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): + return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) + + +# Represents an OpenBadge currently connected via the BadgeConnection 'connection'. +# The 'connection' should already be connected when it is used to initialize this class. +# Implements methods that allow for interaction with that badge. +class OpenBadge(object): + def __init__(self, connection): + self.connection = connection + self.status_response_queue = queue.Queue() + self.start_microphone_response_queue = queue.Queue() + self.start_scan_response_queue = queue.Queue() + self.start_imu_response_queue = queue.Queue() + self.free_sdc_space_response_queue = queue.Queue() + + # Helper function to send a BadgeMessage `command_message` to a device, expecting a response + # of class `response_type` that is a subclass of BadgeMessage, or None if no response is expected. + def send_command(self, command_message, response_type): + expected_response_length = response_type.length() if response_type else 0 + + serialized_command = command_message.serialize_message() + logger.debug( + "Sending: {}, Raw: {}".format( + command_message, serialized_command.hex() + ) + ) + serialized_response = self.connection.send( + serialized_command, response_len=expected_response_length + ) + + if expected_response_length > 0: + response = response_type.deserialize_message(serialized_response) + logger.info("Recieved response {}".format(response)) + return response + else: + logger.info("No response expected, transmission successful.") + return True + + def send_request(self, request_message: mRequest): + print(type(request_message)) + serialized_request = request_message.encode() + + # Adding length header: + serialized_request_len = struct.pack(" 0: + while True: + while not self.queue.empty(): + rx_message += self.queue.get().to_bytes(1, byteorder="big") + if len(rx_message) == rx_bytes_expected: + return rx_message + + conn.waitForNotifications(5.0) + + def send(self, conn, tx: btle.Characteristic, message, response_len=0): + rx_message = b"" + rx_bytes_expected = response_len + + tx.write(message, withResponse=True) + conn.waitForNotifications(5.0) + # if True: + # while True: + while not self.queue.empty(): + rx_message += self.queue.get().to_bytes(1, byteorder="big") + if len(rx_message) >= 18: + break + return rx_message + # if len(rx_message) == rx_bytes_expected: + # return rx_message + + +class SimpleDelegate2(btle.DefaultDelegate): + def __init__(self, badge: SimpleBadge): + btle.DefaultDelegate.__init__(self) + self.badge = badge + + def handleNotification(self, cHandle, data): + print(f'handle: {cHandle}, data:{data}') + self.badge.receive(data) + + +def main(): + df = pandas.read_csv("mappings_allBackup.csv") + # df = pd.read_csv("mappings_all.csv") + devices_id = [29] # test with 1 midge + df = df[df["Participant Id"].isin(devices_id)] + for _, row in df.iterrows(): + current_participant: int = row["Participant Id"] + current_mac: str = row["Mac Address"] + conn = Peripheral(current_mac, btle.ADDR_TYPE_RANDOM) + # self.conn = Peripheral2(self.ble_device, btle.ADDR_TYPE_RANDOM) + + # Find the UART service and its characteristics. + uart = conn.getServiceByUUID(UART_SERVICE_UUID) + rx = uart.getCharacteristics(RX_CHAR_UUID)[0] + tx = uart.getCharacteristics(TX_CHAR_UUID)[0] + + # Turn on notification of RX characteristics + logger.debug("Subscribing to RX characteristic changes...") + CONFIG_HANDLE = 0x0013 + # was wrong 0x000c + get_free_sdc_space = b'\x01\x00\x1e' + start_microphone = b'\x08\x00\x02\xc6\xa2\xf9ek\x02\x01' + stop_microphone = b'\x01\x00\x03' + val = struct.pack(" ") + sys.stdout.flush() + command = sys.stdin.readline()[:-1] + if command == "exit": + break + + # Check valid input id + try: + midge_id = int(command) + except ValueError: + logger.warning('Invalid id! Only integers are allowed.') + continue + + try: + current_mac_addr = df.loc[df["Participant Id"] == midge_id]["Mac Address"] + current_mac_addr = current_mac_addr.values[0] + except Exception as e: + logger.info('Mac address for the midge ' + str(midge_id) + ' is not found.') + continue + + # try connection + try: + # TODO: uncomment the connection function + # cur_connection = Connection(int(command), current_mac_addr) + logger.info('placeholder for connection') + except Exception as error: + logger.warning("While connecting to midge " + str(command) + + ", following error occurred:" + str(error)) + sys.stdout.flush() + continue + + single_sensor_cmd(logger, midge_id) + + +def single_sensor_cmd(logger, midge_id): + while True: + logger.info("Connected to the midge " + str(midge_id) + "." + + " For available commands, please type help.") + sys.stdout.flush() + # sys.stdout.write("Now connected to the midge\n > ") + command = sys.stdin.readline()[:-1] + command_args = command.split(" ") + if command == "exit": + # TODO: uncomment the disconnect function + # cur_connection.disconnect() + logger.info("Disconnected from the midge.") + break + elif command == "help": + # TODO: uncomment the print help function + # cur_connection.print_help() + pass + else: + handle_function_choice(logger) + + +def handle_function_choice(logger): + try: + # TODO: uncomment the choose function + # out = choose_function(cur_connection, command_args[0]) + # if out is not None: + # logger.info("Midge returned following status: " + str(out)) + sys.stdout.flush() + except Exception as error: + logger.warning(str(error)) + sys.stdout.flush() + + +def synchronization_cmd(logger): + logger.info("Synchronization is starting. Please wait till it ends") + # TODO: uncomment the synchronization function + # synchronise_and_check_all_devices(df) + logger.info("Synchronization is finished.") + sys.stdout.flush() + + +async def command_line(): + df = pd.read_csv("mappings2.csv") + logger = get_logger("hub_main") + recording_started = False + while True: + logger.info("Interactive shell of midge hub. Type 'help' for options.") + sys.stdout.write("> ") + sys.stdout.flush() + command = sys.stdin.readline()[:-1] + if command == "start": + if not recording_started: + recording_started = True + await start_recording_cmd(logger, df) + else: + logger.warning("Recording already started.") + elif command == "stop": + if recording_started: + await stop_recording_cmd(logger, df) + recording_started = False + else: + logger.warning("Cannot stop. Recording not started.") + elif command == "sync": + if recording_started: + synchronization_cmd(logger) + else: + logger.warning("Cannot synchronize. Recording not started.") + elif command == "int": + interactive_cmd(logger, df) + elif command == "help": + # TODO: add help + logger.info("help placeholder") + elif command == "exit": + break + else: + logger.warning('Command not recognized.') + logger.info("Interactive session is finished.") + + +def main(): + df = pd.read_csv("mappings2.csv") + logger = get_logger("hub_main") while True: print("Type start to start data collection or stop to finish data collection.") sys.stdout.write("> ") sys.stdout.flush() command = sys.stdin.readline()[:-1] if command == "start": - start_recording_all_devices(df) + logger.info("Connecting to the midges for starting the recordings.") + # TODO: uncomment the recording function + # start_recording_all_devices(df) + logger.info("Loop for starting the devices is finished.") while True: ti = timeout_input(poll_period=0.05) - s = ti.input(prompt='Type int if you would like to enter interactive shell.\n'+'>', timeout=10.0, - extend_timeout_with_input=False, require_enter_to_confirm=True) + s = ti.input( + prompt="Type int if you would like to enter interactive shell.\n" + + ">", + timeout=10.0, + extend_timeout_with_input=False, + require_enter_to_confirm=True, + ) if s == "int": print("Welcome to the interactive shell. Please type the id of the Midge you want to connect.") print("Type exit if you would like to stop recording for all devices.") @@ -24,14 +184,25 @@ if command == "exit": print("Stopping the recording of all devices.") sys.stdout.flush() - stop_recording_all_devices(df) - print("Devices are stopped.") + # TODO: uncomment the stop recording function + # stop_recording_all_devices(df) + logger.info("Devices are stopped.") sys.stdout.flush() break command_args = command.split(" ") current_mac_addr= (df.loc[df['Participant Id'] == int(command)]['Mac Address']).values[0] try: - cur_connection = Connection(int(command),current_mac_addr) + current_mac_addr = ( + df.loc[df["Participant Id"] == int(command)]["Mac Address"] + ).values[0] + except Exception: + logger.info('Mac address for the midge ' + str(command) + + ' is not found.') + continue + try: + # TODO: uncomment the connection function + # cur_connection = Connection(int(command), current_mac_addr) + logger.info('placeholder for connection') except Exception as error: print (str(error)) sys.stdout.flush() @@ -43,23 +214,29 @@ command = sys.stdin.readline()[:-1] command_args = command.split(" ") if command == "exit": - cur_connection.disconnect() + # TODO: uncomment the disconnect function + # cur_connection.disconnect() + logger.info("Disconnected from the midge.") break try: - out = choose_function(cur_connection,command_args[0]) - if out != None: - print (out) - sys.stdout.flush() + # TODO: uncomment the choose function + # out = choose_function(cur_connection, command_args[0]) + # if out is not None: + # logger.info("Midge returned following" + # + " status: " + str(out)) + sys.stdout.flush() except Exception as error: print (str(error)) print(" Command not found!") sys.stdout.flush() - cur_connection.print_help() + # TODO: uncomment the print help function + # cur_connection.print_help() continue else: - print('Synchronisation is starting. Please wait till it ends.') - synchronise_and_check_all_devices(df) - print('Synchronisation is finished.') + logger.info("Synchronisation is starting. Please wait till it ends") + # TODO: uncomment the synchronization function + # synchronise_and_check_all_devices(df) + logger.info("Synchronisation is finished.") sys.stdout.flush() elif command == "stop": print("Stopping data collection.") @@ -69,3 +246,19 @@ print("Command not found, please type start or stop to start or stop data collection.") sys.stdout.flush() + +async def main_v2(): + # print(os.getcwd()) + df = pd.read_csv('mappings2.csv') + # await synchronise_and_check_all_devices(df) + await start_recording_all_devices(df) + time.sleep(10) + await stop_recording_all_devices(df) + + +if __name__ == "__main__": + os.chdir('/home/zonghuan/tudelft/projects/spcl_app/BadgeFramework') + asyncio.run(main_v2()) + # main() + # df = pd.read_csv('mappings2.csv') + # asyncio.run(command_line()) diff --git a/BadgeFramework/hub_utilities.py b/BadgeFramework/hub_utilities.py new file mode 100644 index 0000000..84ae228 --- /dev/null +++ b/BadgeFramework/hub_utilities.py @@ -0,0 +1,173 @@ +from badge import OpenBadge +import sys +import tty +import termios +import logging +import time +import utils +from bleak import BleakScanner, BleakClient, BLEDevice + + +def get_logger(name): + log_format_file = '%(asctime)s %(levelname)5s %(message)s' + log_format_console = '%(message)s' + logging.basicConfig(level=logging.DEBUG, + format=log_format_file, + filename="data_collection.log", + filemode='w') + console = logging.StreamHandler() + console.setLevel(logging.INFO) + console.setFormatter(logging.Formatter(log_format_console)) + logging.getLogger(name).addHandler(console) + return logging.getLogger(name) + + +logger = get_logger("hub_utilities") + + +def choose_function(badge: OpenBadge, user_input): + chooser = { + "help": badge.print_help, + "status": badge.get_status, + "start_all_sensors": badge.start_recording_all_sensors, + "stop_all_sensors": badge.stop_recording_all_sensors, + "start_microphone": badge.start_microphone, + "stop_microphone": badge.stop_microphone, + "start_scan": badge.start_scan, + "stop_scan": badge.stop_scan, + "start_imu": badge.start_imu, + "stop_imu": badge.stop_imu, + "identify": badge.identify, + "restart": badge.restart, + "get_free_space": badge.get_free_sdc_space, + } + func = chooser.get(user_input, lambda: "Invalid command!") + logger.info("Following command is entered: " + user_input + ".") + try: + out = func() + return out + except Exception as error: + logger.info("Error: " + str(error)) + return + + +async def start_recording_all_devices(df): + for _, row in df.iterrows(): + device_id: int = row["Participant Id"] + device_addr: str = row["Mac Address"] + use_current_device: bool = row["Use"] + if not use_current_device: + continue + try: + async with OpenBadge(device_id, device_addr) as open_badge: + # await open_badge.set_id_at_start() + await open_badge.start_recording_all_sensors() + except Exception as error: + logger.info(f"Sensors for midge {device_id} are not started with the following error: {str(error)}") + continue + print('completed') + + +async def stop_recording_all_devices(df): + for _, row in df.iterrows(): + device_id: int = row["Participant Id"] + device_addr: str = row["Mac Address"] + use_current_device: bool = row["Use"] + if not use_current_device: + continue + try: + async with OpenBadge(device_id, device_addr) as open_badge: + await open_badge.stop_recording_all_sensors() + except Exception as error: + logger.info(f"Sensors for midge {str(device_id)} are not stopped with the following error: {str(error)}") + continue + print('completed') + + +# async def synchronise_one_device(df): + + +async def synchronise_and_check_all_devices(df): + for _, row in df.iterrows(): + device_id: int = row["Participant Id"] + device_addr: str = row["Mac Address"] + use_current_device: bool = row["Use"] + if not use_current_device: + continue + try: + async with OpenBadge(device_id, device_addr) as open_badge: + out = await open_badge.get_status() + logger.info("Status received for the following midge:" + str(device_id) + ".") + # TODO This is not actually the timestamp before, find how to get it. + logger.debug("Device timestamp before sync - seconds:" + + str(out.timestamp.seconds) + ", ms:" + + str(out.timestamp.ms) + ".") + error_message = "{} is not recording for participant " + str(device_id) + "." + if out.imu_status == 0: + logger.info(error_message.format("IMU")) + if out.microphone_status == 0: + logger.info(error_message.format("Mic")) + if out.scan_status == 0: + logger.info(error_message.format("Scan")) + if out.clock_status == 0: + logger.info("Cant sync for participant " + str(device_id) + ".") + except Exception as error: + logger.info(f"Status check for midge {str(device_id)} returned the following error: {str(error)}") + # sys.stdout.flush() + continue + print('completed') + + +class timeout_input(object): + def __init__(self, poll_period=0.05): + self.poll_period = poll_period + + def _getch_nix(self): + from select import select + + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + [i, _, _] = select([sys.stdin.fileno()], [], [], self.poll_period) + if i: + ch = sys.stdin.read(1) + else: + ch = "" + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + # pass + return ch + + def input( + self, + prompt=None, + timeout=None, + extend_timeout_with_input=True, + require_enter_to_confirm=True, + ): + prompt = prompt or "" + sys.stdout.write(prompt) + sys.stdout.flush() + input_chars = [] + start_time = time.time() + received_enter = False + while (time.time() - start_time) < timeout: + c = self._getch_nix() + if c in ("\n", "\r"): + received_enter = True + break + elif c: + input_chars.append(c) + sys.stdout.write(c) + sys.stdout.flush() + if extend_timeout_with_input: + start_time = time.time() + sys.stdout.write("\n") + sys.stdout.flush() + captured_string = "".join(input_chars) + if require_enter_to_confirm: + return_string = captured_string if received_enter else "" + else: + return_string = captured_string + return return_string diff --git a/BadgeFramework/mappings2.csv b/BadgeFramework/mappings2.csv new file mode 100644 index 0000000..8011d41 --- /dev/null +++ b/BadgeFramework/mappings2.csv @@ -0,0 +1,50 @@ +,Participant Id,Mac Address,Data Frame Length,Use +0,1,d5:46:26:05:1f:2f,32.0,0 +1,2,c3:4c:d5:ba:b1:44,32.0,0 +2,3,e5:93:96:ca:ee:c4,,0 +3,4,f9:39:24:a9:04:f1,,0 +4,5,d2:fd:13:bd:81:39,,0 +5,6,de:94:80:39:25:be,24.0,0 +6,7,fb:f5:d5:84:a1:68,,0 +7,8,e2:6e:4e:21:f1:a4,,0 +8,9,f5:40:84:e2:9a:16,,0 +9,10,e8:44:59:c0:39:de,,0 +10,11,d7:11:de:c6:c8:e3,24.0,0 +11,12,fa:24:bd:55:c7:ab,,0 +12,13,d6:12:78:32:80:19,,0 +13,14,e8:03:31:16:ce:3a,24.0,0 +14,15,d8:ae:5b:aa:55:ae,,0 +15,16,f5:ef:4c:9b:55:de,,0 +16,17,c3:2b:78:b5:c2:4a,,0 +17,18,c4:3e:0b:bc:e6:92,,0 +18,19,d9:0d:2e:b7:cc:6b,,0 +19,20,e6:cc:57:a3:6b:57,,0 +20,21,c6:30:71:35:02:5a,,0 +21,22,fb:42:af:eb:ba:3c,,0 +22,23,d2:91:1c:b9:6f:5c,24.0,0 +23,24,c1:a4:56:be:7f:7e,,0 +24,25,f2:26:6c:f5:ca:e5,,0 +25,26,cb:14:eb:9a:5c:a3,,0 +26,27,db:03:30:a6:ee:86,,0 +27,28,fc:3f:62:28:17:b4,,0 +28,29,ce:22:14:de:40:38,,0 +29,30,e3:5d:06:9a:55:0f,,0 +30,31,d4:56:f4:e1:ef:f1,,0 +31,32,d2:4f:7c:01:93:0e,,0 +32,33,da:03:10:bb:61:f5,,1 +33,34,f5:e7:4d:77:4e:74,,0 +34,35,fd:f3:eb:4b:d0:8c,,0 +35,36,dc:bc:ed:19:1f:09,,0 +36,37,c5:61:a9:e9:83:ef,,0 +37,39,e7:03:db:e9:16:3b,,0 +38,40,fe:82:88:fa:36:62,,0 +39,41,dc:a4:f4:7e:45:fb,,0 +40,42,f6:01:ad:d1:18:23,,0 +41,43,e0:c3:d6:2e:ab:44,,0 +42,44,de:7f:7f:a2:9c:f5,,0 +43,45,fe:71:db:20:4f:34,,0 +44,46,dc:11:e6:20:81:d8,,0 +45,47,d9:98:8a:68:f0:89,,0 +46,48,f7:5a:78:fd:21:46,,0 +47,49,e9:59:12:26:a7:63,,1 +48,50,d0:c5:cc:ef:90:e2,,0 diff --git a/BadgeFramework/utils.py b/BadgeFramework/utils.py new file mode 100644 index 0000000..9abb103 --- /dev/null +++ b/BadgeFramework/utils.py @@ -0,0 +1,45 @@ +import uuid +import logging +import pandas +from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData +from typing import Optional, Final + +UART_SERVICE_UUID = uuid.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") +TX_CHAR_UUID = uuid.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") +RX_CHAR_UUID = uuid.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") +df_badge = pandas.read_csv("mappings_allBackup.csv") + + +def get_logger(name): + log_format_file = '%(asctime)s %(levelname)5s %(message)s' + log_format_console = '%(message)s' + logging.basicConfig(level=logging.DEBUG, + format=log_format_file, + filename="data_collection.log", + filemode='w') + console = logging.StreamHandler() + console.setLevel(logging.INFO) + console.setFormatter(logging.Formatter(log_format_console)) + logging.getLogger(name).addHandler(console) + return logging.getLogger(name) + + +def get_mac_address(df, badge_id: int) -> str: + mac_address = df[df['Participant Id'] == badge_id]['Mac Address'] + mac_address = list(mac_address)[0] + return mac_address + + +def is_spcl_midge(device: BLEDevice) -> bool: + return device.name == 'HDBDG' + + +def get_device_id(device: BLEDevice, addr_df: Optional[pandas.DataFrame] = df_badge) -> int: + address = device.address.lower() + try: + badge_id = addr_df[addr_df['Mac Address'] == address]['Participant Id'] + badge_id = badge_id.values[0] + except IndexError: + badge_id = -1000 + return badge_id From 65e6c10ac8115684a016e14e496e1a7bb4694557 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 19 Sep 2024 14:27:47 +0200 Subject: [PATCH 02/46] test bleak --- BadgeFramework/badge.py | 12 ++++++------ BadgeFramework/bleak_test.py | 18 +++++++++--------- BadgeFramework/utils.py | 2 +- led/led.c | 4 ++-- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index eda67a2..db2b7a1 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -5,7 +5,7 @@ import time import logging import struct -import Queue +import queue from collections.abc import Callable from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic @@ -153,11 +153,11 @@ def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: serialized_request = request_message.encode() # Adding length header: serialized_request_len = struct.pack(" None: # TODO: pass in the universal time code here? status = await open_badge.get_status() - logger.info(f"Status received for the following midge: {open_badge.id}.") + logger.info(f"Status received for the following midge: {open_badge.device_id}.") # TODO This is not actually the timestamp before, find how to get it. logger.debug(f"Device timestamp before sync - seconds: {status.timestamp.seconds}, ms:{status.timestamp.ms}.") if status.imu_status == 0: - logger.info(f"IMU is not recording for participant {open_badge.id}.") + logger.info(f"IMU is not recording for participant {open_badge.device_id}.") if status.microphone_status == 0: - logger.info(f"Mic is not recording for participant {open_badge.id}.") + logger.info(f"Mic is not recording for participant {open_badge.device_id}.") if status.scan_status == 0: - logger.info(f"Scan is not recording for participant {open_badge.id}.") + logger.info(f"Scan is not recording for participant {open_badge.device_id}.") if status.clock_status == 0: - logger.info(f"Can't sync for participant {open_badge.id}.") + logger.info(f"Can't sync for participant {open_badge.device_id}.") async def main(): @@ -42,10 +42,10 @@ async def main(): async with OpenBadge(ble_device) as open_badge: # space = await open_badge.get_free_sdc_space() out = await open_badge.get_status(t=40) - start = await open_badge.start_microphone() - time.sleep(15) - async with OpenBadge(ble_device) as open_badge: - stop = await open_badge.stop_microphone() + # start = await open_badge.start_microphone() + # time.sleep(15) + # async with OpenBadge(ble_device) as open_badge: + # stop = await open_badge.stop_microphone() # await synchronize_device(open_badge, logger) c = 9 print('completed') diff --git a/BadgeFramework/utils.py b/BadgeFramework/utils.py index 9abb103..4aeaabb 100644 --- a/BadgeFramework/utils.py +++ b/BadgeFramework/utils.py @@ -8,7 +8,7 @@ UART_SERVICE_UUID = uuid.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") TX_CHAR_UUID = uuid.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") RX_CHAR_UUID = uuid.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") -df_badge = pandas.read_csv("mappings_allBackup.csv") +df_badge = pandas.read_csv("mappings2.csv") def get_logger(name): diff --git a/led/led.c b/led/led.c index 70a6c49..b54d8dd 100644 --- a/led/led.c +++ b/led/led.c @@ -25,9 +25,9 @@ void led_init_success(void) // If initialization was successful, blink the green LED 3 times. for(uint8_t i = 0; i < 3; i++) { nrf_gpio_pin_write(LED, LED_ON); //turn on LED - nrf_delay_ms(100); + nrf_delay_ms(1000); nrf_gpio_pin_write(LED, LED_OFF); //turn off LED - nrf_delay_ms(100); + nrf_delay_ms(1000); } } From 043654008e339a66949525d4401a024077c493e6 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 23 Sep 2024 13:37:06 +0200 Subject: [PATCH 03/46] modify according to new hardware codes --- .gitignore | 1 + BadgeFramework/badge.py | 40 +- BadgeFramework/badge_protocol.py | 460 +----- BadgeFramework/bleak_test.py | 13 +- BadgeFramework/bluepy/badge_protocol_old.py | 1583 +++++++++++++++++++ BadgeFramework/utils.py | 2 +- 6 files changed, 1637 insertions(+), 462 deletions(-) create mode 100644 BadgeFramework/bluepy/badge_protocol_old.py diff --git a/.gitignore b/.gitignore index 59f440f..10c7cee 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ __pycache/ BadgeFramework/rotation/rpi_sync.sh .idea *.log +notes.txt diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index eda67a2..1fbe4a8 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -5,7 +5,7 @@ import time import logging import struct -import Queue +# import queue from collections.abc import Callable from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic @@ -153,11 +153,11 @@ def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: serialized_request = request_message.encode() # Adding length header: serialized_request_len = struct.pack(" None: async def receive(client: BleakClient) -> bytes or bytearray: """receive message from client""" response_rx = b'' - for x in range(10): - # if len(response_rx) > 0: - # break - response_rx = await client.read_gatt_char(utils.RX_CHAR_UUID) + for k1 in range(5): + for _ in range(5): + # if len(response_rx) > 0: + # break + response_rx = await client.read_gatt_char(utils.RX_CHAR_UUID) + time.sleep(3) return response_rx @staticmethod @@ -218,13 +220,15 @@ def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray) # queue_options[response_message.type.which].put( # response_options[response_message.type.which] # ) - + async def request_response(self, message: bp.Request, require_response: Optional[bool] = True): """request response from client""" serialized_request = self.add_serialized_header(message) # logger.debug("Sending: {}, Raw: {}".format(message, serialized_request.hex())) await self.send(self.client, serialized_request) response = await self.receive(self.client) + # response = 0 + # print('request_response ended') return response if require_response else None @staticmethod @@ -232,14 +236,26 @@ def decode_response(response: bytearray or bytes): """decode response from client. First two bytes represent the length.""" response_len = struct.unpack(" bp.StatusResponse: @@ -385,7 +401,7 @@ async def stop_recording_all_sensors(self): await self.stop_scan() await self.stop_microphone() await self.stop_imu() - #TODO: + #TODO: # while self.free_sdc_space_response_queue.empty(): # self.receive_response() diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index 724d376..6c2978a 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -10,16 +10,12 @@ Request_identify_request_tag = 27 Request_restart_request_tag = 29 Request_free_sdc_space_request_tag = 30 -Request_sdc_errase_all_request_tag = 31 -Request_get_imu_data_request_tag = 33 Response_status_response_tag = 1 Response_start_microphone_response_tag = 2 Response_start_scan_response_tag = 3 Response_start_imu_response_tag = 4 Response_free_sdc_space_response_tag = 5 -Response_sdc_errase_all_response_tag = 32 -Response_get_imu_data_response_tag = 34 class _Ostream: @@ -39,8 +35,6 @@ def read(self, l): raise Exception("Not enough bytes in Istream to read") ret = self.buf[0:l] self.buf = self.buf[l:] - #for i in ret: - # print("_Istream:",i) return ret @@ -618,65 +612,6 @@ def decode_internal(self, istream): self.reset() pass -class ErraseAllRequest: - - def __init__(self): - self.reset() - - def __repr__(self): - return str(self.__dict__) - - def reset(self): - pass - - def encode(self): - ostream = _Ostream() - self.encode_internal(ostream) - return ostream.buf - - def encode_internal(self, ostream): - pass - - - @classmethod - def decode(cls, buf): - obj = cls() - obj.decode_internal(_Istream(buf)) - return obj - - def decode_internal(self, istream): - self.reset() - pass - -class GetIMUDataRequest: - - def __init__(self): - self.reset() - - def __repr__(self): - return str(self.__dict__) - - def reset(self): - pass - - def encode(self): - ostream = _Ostream() - self.encode_internal(ostream) - return ostream.buf - - def encode_internal(self, ostream): - pass - - - @classmethod - def decode(cls, buf): - obj = cls() - obj.decode_internal(_Istream(buf)) - return obj - - def decode_internal(self, istream): - self.reset() - pass class Request: @@ -731,8 +666,6 @@ def reset(self): self.identify_request = None self.restart_request = None self.free_sdc_space_request = None - self.sdc_errase_all_request = None - self.get_imu_data_request = None pass def encode_internal(self, ostream): @@ -748,8 +681,6 @@ def encode_internal(self, ostream): 27: self.encode_identify_request, 29: self.encode_restart_request, 30: self.encode_free_sdc_space_request, - 31: self.encode_sdc_errase_all_request, - 33: self.encode_get_imu_data_request, } options[self.which](ostream) pass @@ -784,11 +715,6 @@ def encode_restart_request(self, ostream): def encode_free_sdc_space_request(self, ostream): self.free_sdc_space_request.encode_internal(ostream) - def encode_sdc_errase_all_request(self, ostream): - self.sdc_errase_all_request.encode_internal(ostream) - - def encode_get_imu_data_request(self, ostream): - self.get_imu_data_request.encode_internal(ostream) def decode_internal(self, istream): self.reset() @@ -804,8 +730,6 @@ def decode_internal(self, istream): 27: self.decode_identify_request, 29: self.decode_restart_request, 30: self.decode_free_sdc_space_request, - 31: self.decode_sdc_errase_all_request, - 33: self.decode_get_imu_data_request, } options[self.which](istream) pass @@ -850,14 +774,6 @@ def decode_free_sdc_space_request(self, istream): self.free_sdc_space_request = FreeSDCSpaceRequest() self.free_sdc_space_request.decode_internal(istream) - def decode_sdc_errase_all_request(self, istream): - self.sdc_errase_all_request = ErraseAllRequest() - self.sdc_errase_all_request.decode_internal(istream) - - def decode_get_imu_data_request(self, istream): - self.get_imu_data_request = ErraseAllRequest() - self.get_imu_data_request.decode_internal(istream) - class StatusResponse: @@ -872,9 +788,7 @@ def reset(self): self.microphone_status = 0 self.scan_status = 0 self.imu_status = 0 - self.battery_level = 0 - self.pdm_data = 0 - self.scan_data = 0 + self.time_delta = 0 self.timestamp = None pass @@ -889,9 +803,7 @@ def encode_internal(self, ostream): self.encode_scan_status(ostream) self.encode_imu_status(ostream) self.encode_timestamp(ostream) - self.encode_battery_level(ostream) - self.encode_pdm_data(ostream) - self.encode_scan_data(ostream) + self.encode_time_delta(ostream) pass def encode_clock_status(self, ostream): @@ -906,19 +818,14 @@ def encode_scan_status(self, ostream): def encode_imu_status(self, ostream): ostream.write(struct.pack(' len(self.buf)): + raise Exception("Not enough bytes in Istream to read") + ret = self.buf[0:l] + self.buf = self.buf[l:] + # for i in ret: + # print("_Istream:",i) + return ret + + +class Timestamp: + def __init__(self): + self.reset() + + def __repr__(self): + return str(self.__dict__) + + def reset(self): + self.seconds = 0 + self.ms = 0 + pass + + def encode(self): + ostream = _Ostream() + self.encode_internal(ostream) + return ostream.buf + + def encode_internal(self, ostream): + self.encode_seconds(ostream) + self.encode_ms(ostream) + pass + + def encode_seconds(self, ostream): + ostream.write(struct.pack(' Date: Mon, 23 Sep 2024 14:04:04 +0200 Subject: [PATCH 04/46] modify according to new hardware codes --- BadgeFramework/test.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index d3df97e..09ed996 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -29,19 +29,19 @@ def mic_test(badge, mode): else: print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") time.sleep(10) # reecording time mic = badge.get_status() # check if mic was enabled if (mic.microphone_status): - print(" mic enabled: PASS ") + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") badge.stop_microphone() # stop recording - + time.sleep(0.5) mic = badge.get_status() @@ -70,19 +70,19 @@ def mic_test(badge, mode): print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") time.sleep(10) # reecording time mic = badge.get_status() # check if mic was enabled if (mic.microphone_status): - print(" mic enabled: PASS ") + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") badge.stop_microphone() # stop recording - + time.sleep(0.5) mic = badge.get_status() @@ -236,4 +236,4 @@ def main(): connection.disconnect() if __name__ == "__main__": - main() \ No newline at end of file + main() \ No newline at end of file From 857e35f8923dcff46adf4ddf3769832087223026 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 26 Sep 2024 10:30:47 +0200 Subject: [PATCH 05/46] modify according to new hardware codes --- BadgeFramework/badge.py | 192 +++++++------------ BadgeFramework/badge_gui_bleak.py | 301 ++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+), 128 deletions(-) create mode 100644 BadgeFramework/badge_gui_bleak.py diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 1fbe4a8..8977c11 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -5,7 +5,6 @@ import time import logging import struct -# import queue from collections.abc import Callable from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic @@ -13,20 +12,27 @@ import badge_protocol as bp import utils -DEFAULT_SCAN_WINDOW = 250 -DEFAULT_SCAN_INTERVAL = 1000 +DEFAULT_SCAN_WINDOW: Final[int] = 250 +DEFAULT_SCAN_INTERVAL: Final[int] = 1000 -DEFAULT_IMU_ACC_FSR = 4 # Valid ranges: 2, 4, 8, 16 -DEFAULT_IMU_GYR_FSR = 1000 # Valid ranges: 250, 500, 1000, 2000 -DEFAULT_IMU_DATARATE = 50 +DEFAULT_IMU_ACC_FSR: Final[int] = 4 # Valid ranges: 2, 4, 8, 16 +DEFAULT_IMU_GYR_FSR: Final[int] = 1000 # Valid ranges: 250, 500, 1000, 2000 +DEFAULT_IMU_DATARATE: Final[int] = 50 -DEFAULT_MICROPHONE_MODE = 0 #Valid options: 0=Stereo, 1=Mono +DEFAULT_MICROPHONE_MODE: Final[int] = 1 # Valid options: 0=Stereo, 1=Mono -CONNECTION_RETRY_TIMES = 10 +CONNECTION_RETRY_TIMES = 15 DUPLICATE_TIME_INTERVAL = 2 + +DECODE_STATUS_RESPONSE = 1 +DECODE_START_MICROPHONE_RESPONSE = 2 +DECODE_START_SCAN_RESPONSE = 3 +DECODE_START_IMU_REQUEST = 4 +DECODE_FREE_SDC_SPACE_RESPONSE = 5 + logger = logging.getLogger(__name__) -# -- Helper methods used often in badge communication -- +# -- Helper methods used often in badge communication -- # We generally define timestamp_seconds to be in number of seconds since UTC epoch # and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. @@ -47,18 +53,6 @@ def get_timestamps_from_time(t=None) -> (int, int): # return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) -# class OpenBadge(object): -# def __init__(self, connection): -# self.connection = connection -# self.status_response_queue = Queue.Queue() -# self.start_microphone_response_queue = Queue.Queue() -# self.start_scan_response_queue = Queue.Queue() -# self.start_imu_response_queue = Queue.Queue() -# self.free_sdc_space_response_queue = Queue.Queue() -# self.sdc_errase_all_response_queue = Queue.Queue() -# self.get_imu_data_response_queue = Queue.Queue() - -# TODO: check erase all and get imu data function def bp_timestamp_from_time(t=None) -> bp.Timestamp: ts = bp.Timestamp() ts.seconds, ts.ms = get_timestamps_from_time(t) @@ -126,7 +120,7 @@ def __init__(self, device: BLEDevice or int, mac_address: str = None): async def __aenter__(self): for _ in range(CONNECTION_RETRY_TIMES): try: - await self.client.connect(timeout=10) + await self.client.connect(timeout=1000) await self.client.start_notify(utils.RX_CHAR_UUID, self.received_callback) return self except Exception as e: @@ -145,7 +139,7 @@ def is_connected(self) -> bool: def badge_disconnected(self, b: BleakClient) -> None: """disconnection callback""" - print(f"Warning: disconnected badge") + print(f"Warning: disconnected badge", b.address) @staticmethod def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: @@ -153,29 +147,34 @@ def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: serialized_request = request_message.encode() # Adding length header: serialized_request_len = struct.pack(" None: + # @staticmethod + async def send(self, client, message) -> None: """send message to client""" - await client.write_gatt_char(utils.TX_CHAR_UUID, message, response=True) - - @staticmethod - async def receive(client: BleakClient) -> bytes or bytearray: + trial_times = 0 + while trial_times < 5: + if client.is_connected: + await client.write_gatt_char(utils.TX_CHAR_UUID, message, response=True) + return + else: + await self.__aenter__() + trial_times += 1 + raise TimeoutError + + # @staticmethod + async def receive(self, client: BleakClient) -> bytes or bytearray: """receive message from client""" response_rx = b'' - for k1 in range(5): - for _ in range(5): - # if len(response_rx) > 0: - # break + for _ in range(2): + # if len(response_rx) > 0: + # break + if client.is_connected: response_rx = await client.read_gatt_char(utils.RX_CHAR_UUID) - time.sleep(3) + else: + await self.__aenter__() + time.sleep(1) return response_rx @staticmethod @@ -198,37 +197,12 @@ def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray) # self.rx_message = message self.rx_list.append(new_message) - # TODO: figure out how to implement the queue options - # queue_options = { - # Response_status_response_tag: self.status_response_queue, - # Response_start_microphone_response_tag: self.start_microphone_response_queue, - # Response_start_scan_response_tag: self.start_scan_response_queue, - # Response_start_imu_response_tag: self.start_imu_response_queue, - # Response_free_sdc_space_response_tag: self.free_sdc_space_response_queue, - # Response_sdc_errase_all_response_tag: self.sdc_errase_all_response_queue, - # Response_get_imu_data_response_tag: self.get_imu_data_response_queue, - # } - # response_options = { - # Response_status_response_tag: response_message.type.status_response, - # Response_start_microphone_response_tag: response_message.type.start_microphone_response, - # Response_start_scan_response_tag: response_message.type.start_scan_response, - # Response_start_imu_response_tag: response_message.type.start_imu_response, - # Response_free_sdc_space_response_tag: response_message.type.free_sdc_space_response, - # Response_sdc_errase_all_response_tag: response_message.type.sdc_errase_all_response, - # Response_get_imu_data_response_tag: response_message.type.get_imu_data_response - # } - # queue_options[response_message.type.which].put( - # response_options[response_message.type.which] - # ) - async def request_response(self, message: bp.Request, require_response: Optional[bool] = True): """request response from client""" serialized_request = self.add_serialized_header(message) # logger.debug("Sending: {}, Raw: {}".format(message, serialized_request.hex())) await self.send(self.client, serialized_request) response = await self.receive(self.client) - # response = 0 - # print('request_response ended') return response if require_response else None @staticmethod @@ -236,25 +210,25 @@ def decode_response(response: bytearray or bytes): """decode response from client. First two bytes represent the length.""" response_len = struct.unpack(" bp.Sta request.type.start_microphone_request.mode = mode await self.request_response(request) - return self.deal_response().type.start_microphone_response + return self.deal_response(response_type=DECODE_START_MICROPHONE_RESPONSE).type.start_microphone_response @request_handler_marker(action_desc='stop microphone') async def stop_microphone(self) -> None: @@ -302,6 +276,7 @@ async def stop_microphone(self) -> None: request.type.stop_microphone_request = bp.StopMicrophoneRequest() await self.request_response(request, require_response=False) + self.deal_response(response_type=-1) return None @request_handler_marker(action_desc='start scan') @@ -319,7 +294,7 @@ async def start_scan(self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DE request.type.start_scan_request.interval = interval_ms await self.request_response(request) - return self.deal_response().type.start_scan_response + return self.deal_response(response_type=DECODE_START_SCAN_RESPONSE).type.start_scan_response @request_handler_marker(action_desc='stop scan') async def stop_scan(self) -> None: @@ -329,7 +304,7 @@ async def stop_scan(self) -> None: request.type.stop_scan_request = bp.StopScanRequest() await self.request_response(request) - return self.deal_response() + return self.deal_response(response_type=-1) @request_handler_marker(action_desc='start imu') async def start_imu(self, t=None, acc_fsr=DEFAULT_IMU_ACC_FSR, gyr_fsr=DEFAULT_IMU_GYR_FSR, @@ -344,7 +319,7 @@ async def start_imu(self, t=None, acc_fsr=DEFAULT_IMU_ACC_FSR, gyr_fsr=DEFAULT_I request.type.start_imu_request.datarate = datarate await self.request_response(request) - return self.deal_response().type.start_imu_response + return self.deal_response(response_type=DECODE_START_IMU_REQUEST).type.start_imu_response @request_handler_marker(action_desc='stop imu') async def stop_imu(self) -> None: @@ -354,6 +329,7 @@ async def stop_imu(self) -> None: request.type.stop_imu_request = bp.StopImuRequest() await self.request_response(request) + self.deal_response(response_type=-1) return None @request_handler_marker(action_desc='identify') @@ -367,7 +343,7 @@ async def identify(self, duration_seconds=10) -> bool: request.type.identify_request.timeout = duration_seconds await self.request_response(request) - self.deal_response() + self.deal_response(response_type=-1) return True @request_handler_marker(action_desc='restart') @@ -378,7 +354,7 @@ async def restart(self) -> bool: request.type.restart_request = bp.RestartRequest() await self.request_response(request) - self.deal_response() + self.deal_response(response_type=-1) return True @request_handler_marker(action_desc='get free sdc space') @@ -389,7 +365,7 @@ async def get_free_sdc_space(self) -> bp.FreeSDCSpaceResponse: request.type.free_sdc_space_request = bp.FreeSDCSpaceRequest() await self.request_response(request) - return self.deal_response().type.free_sdc_space_response + return self.deal_response(response_type=DECODE_FREE_SDC_SPACE_RESPONSE).type.free_sdc_space_response async def start_recording_all_sensors(self): await self.get_status() @@ -401,47 +377,7 @@ async def stop_recording_all_sensors(self): await self.stop_scan() await self.stop_microphone() await self.stop_imu() - #TODO: - # while self.free_sdc_space_response_queue.empty(): - # self.receive_response() - - # return self.free_sdc_space_response_queue.get() - - - def sdc_errase_all(self): - - request = Request() - request.type.which = Request_sdc_errase_all_request_tag - request.type.sdc_errase_all_request = ErraseAllRequest() - - self.send_request(request) - - # Clear the queue before receiving - with self.sdc_errase_all_response_queue.mutex: - self.sdc_errase_all_response_queue.queue.clear() - - while self.sdc_errase_all_response_queue.empty(): - self.receive_response() - - return self.sdc_errase_all_response_queue.get() - - def get_imu_data(self): - - request = Request() - request.type.which = Request_get_imu_data_request_tag - request.type.get_imu_data_request = GetIMUDataRequest() - request.type.get_imu_data_request.timestamp = Timestamp() - - self.send_request(request) - - # Clear the queue before receiving - with self.get_imu_data_response_queue.mutex: - self.get_imu_data_response_queue.queue.clear() - - while self.get_imu_data_response_queue.empty(): - self.receive_response() - return self.get_imu_data_response_queue.get() @staticmethod def print_help(): print(" Available commands:") diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py new file mode 100644 index 0000000..b9efcb0 --- /dev/null +++ b/BadgeFramework/badge_gui_bleak.py @@ -0,0 +1,301 @@ +import tkinter as tk +from tkinter import ttk +import asyncio +import pandas as pd +from badge import OpenBadge +import random +from datetime import datetime +import numpy as np +import sys + + +SENSOR_ALL = 0 +SENSOR_MICROPHONE = 1 +SENSOR_IMU = 2 +SENSOR_SCAN = 3 +SENSOR_CHECK_STATUS = 100 + +SENSOR_START = 1 +SENSOR_STOP = 0 + +sensors = ['All', 'Mic', 'IMU', 'Scan'] +sensor_num = 3 + + +async def check_status(badge_id): + # Simulating the status check with a random on/off status + print(f"Checking status for badge {badge_id}...") + await asyncio.sleep(2) # Simulate delay + statuses = np.random.randint(2, size=3) + timestamp = datetime.now() # Get the current timestamp + print(f"Status for badge {badge_id} is {statuses} at {timestamp}.") + return statuses, timestamp + + +class RedirectText: + """Class to redirect stdout to a tkinter Text widget.""" + + def __init__(self, text_widget): + self.text_widget = text_widget + self.text_widget.config(state=tk.NORMAL) + + def write(self, string): + """Redirects the text to the Text widget.""" + self.text_widget.insert(tk.END, string) + self.text_widget.see(tk.END) # Auto-scroll to the bottom + self.text_widget.update_idletasks() + + def flush(self): + """Handle flush.""" + pass # Needed for compatibility with the print function + + +class BadgeMonitorApp(tk.Tk): + def __init__(self, badges): + super().__init__() + + self.title("Badge Status Monitor") + self.badges = pd.read_csv('mappings2.csv') + # self.badges = [] + + # Create a frame to contain the badge area and the terminal area + self.main_frame = ttk.Frame(self) + self.main_frame.grid(row=0, column=0, sticky="nsew") # Use grid for main_frame + + # Make the window resizable in both directions + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(0, weight=1) + + # Create a canvas to allow scrolling for the badge section + self.canvas = tk.Canvas(self.main_frame, borderwidth=0) + self.frame = ttk.Frame(self.canvas) + self.scrollbar = ttk.Scrollbar(self.main_frame, orient="vertical", command=self.canvas.yview) + self.canvas.configure(yscrollcommand=self.scrollbar.set) + + # Place the scrollbar and canvas in the window + self.scrollbar.grid(row=0, column=0, sticky="ns") + self.canvas.grid(row=0, column=1, sticky="nsew") + + # Bind mouse scroll to canvas + self.bind_mouse_scroll(self.canvas) + + # Make the badge section expand vertically + self.main_frame.grid_rowconfigure(0, weight=1) + + # Create a window within the canvas + self.canvas_frame = self.canvas.create_window((0, 0), window=self.frame, anchor="nw") + + # Configure the frame to resize with the canvas + self.frame.bind("", self.on_frame_configure) + + # Terminal on the right + self.terminal_frame = ttk.Frame(self.main_frame) + self.terminal_frame.grid(row=0, column=2, sticky="nsew") + + self.terminal_text = tk.Text(self.terminal_frame, wrap="word", state="normal", width=40, height=20) + self.terminal_text.pack(fill="both", expand=True) + + # Redirect stdout to terminal Text widget + self.stdout_redirector = RedirectText(self.terminal_text) + sys.stdout = self.stdout_redirector + + # Set column weights to control space allocation + self.main_frame.grid_columnconfigure(1, weight=3) # Badge area + self.main_frame.grid_columnconfigure(2, weight=1) # Terminal area + + self.timestamp_labels = {} + self.sensor_lights = {} + self.timestamps = {} + self.update_tasks = {} + + # Create rows for each badge + for idx, badge in enumerate(badges, start=1): + row_button, row_status = idx * 2 - 1, idx * 2 + badge_label = ttk.Label(self.frame, text=f"Badge {badge}") + badge_label.grid(row=row_button, column=0, padx=5, pady=5) + + check_button = ttk.Button(self.frame, text="Check Status", + command=lambda b=badge, s=100, m=1: self.schedule_async_task(b, s, m)) + check_button.grid(row=row_button, column=1, padx=70, pady=5) + + for s_idx, sensor in enumerate(sensors): + s_start_button = ttk.Button(self.frame, text=sensor+" Start", + command=lambda b=badge, s=s_idx, m=1: self.schedule_async_task(b, s, m)) + s_start_button.grid(row=row_button, column=2+2*s_idx, padx=5, pady=5) + + s_stop_button = ttk.Button(self.frame, text=sensor+" Stop", + command=lambda b=badge, s=s_idx, m=0: self.schedule_async_task(b, s, m)) + s_stop_button.grid(row=row_button, column=3+2*s_idx, padx=5, pady=5) + + sensor_light_canvases = [] + for s_idx, sensor in enumerate(sensors[1:], start=1): + sensor_light_canvas = tk.Canvas(self.frame, width=20, height=20) + sensor_light_canvas.grid(row=row_status, column=3+2*s_idx, padx=5, pady=5) + sensor_light = sensor_light_canvas.create_oval(5, 5, 15, 15, fill="red") + sensor_light_canvases.append((sensor_light_canvas, sensor_light)) + + sensor_label = ttk.Label(self.frame, text=sensor + ' status:') + sensor_label.grid(row=row_status, column=2+2*s_idx, padx=5, pady=5) + + # Create a label to display the last updated time and elapsed time + timestamp_label = ttk.Label(self.frame, text="Last updated: N/A") + timestamp_label.grid(row=row_status, column=1, padx=5, pady=5) + + # Create a label to display the elapsed time in seconds + elapsed_time_label = ttk.Label(self.frame, text="Elapsed: N/A") + elapsed_time_label.grid(row=row_status, column=2, padx=5, pady=5) + + # Store the canvas, light, sensor lights, timestamp label, and elapsed time label + self.timestamp_labels[badge] = (timestamp_label, elapsed_time_label) + self.sensor_lights[badge] = sensor_light_canvases + + def bind_mouse_scroll(self, widget): + """Bind the mouse scroll event to the canvas.""" + # Windows OS uses "", others use "" and "" + widget.bind_all("", self.on_mouse_wheel) + widget.bind_all("", self.on_mouse_wheel) + widget.bind_all("", self.on_mouse_wheel) + + def on_mouse_wheel(self, event): + """Handle the mouse scroll event.""" + if event.num == 4 or event.delta > 0: # Scroll up + self.canvas.yview_scroll(-1, "units") + elif event.num == 5 or event.delta < 0: # Scroll down + self.canvas.yview_scroll(1, "units") + + def on_frame_configure(self, event): + """Reset the scroll region to encompass the inner frame.""" + self.canvas.configure(scrollregion=self.canvas.bbox("all")) + + def schedule_async_task(self, badge_id, sensor_idx, mode): + # Schedule the coroutine to run asynchronously + if sensor_idx == SENSOR_CHECK_STATUS: + asyncio.create_task(self.async_check_status(badge_id)) + elif sensor_idx == SENSOR_MICROPHONE: + asyncio.create_task(self.async_microphone(badge_id, mode)) + elif sensor_idx == SENSOR_IMU: + asyncio.create_task(self.async_imu(badge_id, mode)) + elif sensor_idx == SENSOR_SCAN: + asyncio.create_task(self.async_scan(badge_id, mode)) + elif sensor_idx == SENSOR_ALL: + asyncio.create_task(self.async_microphone(badge_id, mode)) + asyncio.create_task(self.async_imu(badge_id, mode)) + asyncio.create_task(self.async_scan(badge_id, mode)) + + async def async_check_status(self, badge_id): + # Call the async function to check the status + sensor_statuses, timestamp = await check_status(badge_id) + + # Get the canvas and light object for the badge + timestamp_label, elapsed_time_label = self.timestamp_labels[badge_id] + + sensor_light_canvases = self.sensor_lights[badge_id] + for sensor_idx, (sensor_light_canvas, sensor_light) in enumerate(sensor_light_canvases): + sensor_color = "green" if sensor_statuses[sensor_idx] == 1 else "red" + sensor_light_canvas.itemconfig(sensor_light, fill=sensor_color) + + # Format the timestamp and update the timestamp label + formatted_time = timestamp.strftime("%Y-%m-%d %H:%M:%S") + timestamp_label.config(text=f"Last updated: {formatted_time}") + + # Update the timestamps dictionary with the current time + self.timestamps[badge_id] = timestamp + + # Cancel the previous update task if it exists + if badge_id in self.update_tasks: + self.update_tasks[badge_id].cancel() + + # Start a new task to update the elapsed time + task = asyncio.create_task(self.update_elapsed_time(badge_id, elapsed_time_label, timestamp)) + self.update_tasks[badge_id] = task + + @staticmethod + async def async_microphone(badge_id, mode): + if mode == SENSOR_START: + print(f"Starting microphone for badge {badge_id}...") + mode_desc = 'started' + # TODO: add Openbadge(...) + elif mode == SENSOR_STOP: + print(f"Stopping microphone for badge {badge_id}...") + mode_desc = 'stopped' + # TODO: add Openbadge(...) + else: + raise ValueError(f"Unknown mode {mode}") + await asyncio.sleep(1) # Simulate delay + timestamp = datetime.now() # Get the current timestamp + print(f'Badge {badge_id} microphone {mode_desc} successfully.') + return timestamp + + @staticmethod + async def async_imu(badge_id, mode): + if mode == SENSOR_START: + print(f"Starting imu for badge {badge_id}...") + mode_desc = 'started' + # TODO: add Openbadge(...) + elif mode == SENSOR_STOP: + print(f"Stopping imu for badge {badge_id}...") + mode_desc = 'stopped' + # TODO: add Openbadge(...) + else: + raise ValueError(f"Unknown mode {mode}") + await asyncio.sleep(1) # Simulate delay + timestamp = datetime.now() # Get the current timestamp + print(f'Badge {badge_id} imu {mode_desc} successfully.') + return timestamp + + @staticmethod + async def async_scan(badge_id, mode): + if mode == SENSOR_START: + print(f"Starting scan for badge {badge_id}...") + mode_desc = 'started' + # TODO: add Openbadge(...) + elif mode == SENSOR_STOP: + print(f"Stopping scan for badge {badge_id}...") + mode_desc = 'stopped' + # TODO: add Openbadge(...) + else: + raise ValueError(f"Unknown mode {mode}") + await asyncio.sleep(1) # Simulate delay + timestamp = datetime.now() # Get the current timestamp + print(f'Badge {badge_id} scan {mode_desc} successfully.') + return timestamp + + def get_badge_address(self, badge_id: int): + badge = self.badges[self.badges['Participant Id'] == badge_id] + address = badge['Mac Address'].to_numpy()[0] # There should be a more elegant way to do this + return address + + @staticmethod + async def update_elapsed_time(badge_id, elapsed_time_label, last_update_time): + """Continuously update the elapsed time since the last status update.""" + try: + while True: + await asyncio.sleep(1) # Update every second + now = datetime.now() + elapsed_seconds = (now - last_update_time).total_seconds() + elapsed_time_label.config(text=f"Elapsed: {int(elapsed_seconds)}s") + except asyncio.CancelledError: + pass # Task was cancelled + + def run(self): + # Start the tkinter mainloop + self.mainloop() + + +# Main function that starts the tkinter app and runs the event loop +def run_tkinter_async(): + badges = list(range(1, 20)) # Badge numbers from 1 to 10 + app = BadgeMonitorApp(badges) + + async def main_loop(): + while True: + await asyncio.sleep(0.01) # Non-blocking sleep to allow tkinter to update + app.update_idletasks() + app.update() + + # Schedule the main loop in asyncio + asyncio.run(main_loop()) + + +if __name__ == "__main__": + run_tkinter_async() From 1736ff89ef804e8ff0a900f74430ed3aa7b9bc33 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 4 Oct 2024 17:39:47 +0200 Subject: [PATCH 06/46] modify according to new hardware codes --- BadgeFramework/badge_gui_bleak.py | 138 ++++++++++++------------------ 1 file changed, 56 insertions(+), 82 deletions(-) diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index b9efcb0..87f437d 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -3,10 +3,10 @@ import asyncio import pandas as pd from badge import OpenBadge -import random from datetime import datetime import numpy as np import sys +from functools import partial SENSOR_ALL = 0 @@ -15,21 +15,13 @@ SENSOR_SCAN = 3 SENSOR_CHECK_STATUS = 100 -SENSOR_START = 1 -SENSOR_STOP = 0 +SENSOR_START = True +SENSOR_STOP = False sensors = ['All', 'Mic', 'IMU', 'Scan'] -sensor_num = 3 - - -async def check_status(badge_id): - # Simulating the status check with a random on/off status - print(f"Checking status for badge {badge_id}...") - await asyncio.sleep(2) # Simulate delay - statuses = np.random.randint(2, size=3) - timestamp = datetime.now() # Get the current timestamp - print(f"Status for badge {badge_id} is {statuses} at {timestamp}.") - return statuses, timestamp +indicators_long = ['clock', 'microphone', 'imu', 'scan'] +indicators_short = ['Clock', 'Mic', 'IMU', 'Scan'] +sensor_num = len(indicators_short) class RedirectText: @@ -112,38 +104,38 @@ def __init__(self, badges): for idx, badge in enumerate(badges, start=1): row_button, row_status = idx * 2 - 1, idx * 2 badge_label = ttk.Label(self.frame, text=f"Badge {badge}") - badge_label.grid(row=row_button, column=0, padx=5, pady=5) + badge_label.grid(row=row_button, column=0, padx=80, pady=5) check_button = ttk.Button(self.frame, text="Check Status", command=lambda b=badge, s=100, m=1: self.schedule_async_task(b, s, m)) - check_button.grid(row=row_button, column=1, padx=70, pady=5) + check_button.grid(row=row_button, column=1, padx=20, pady=5) for s_idx, sensor in enumerate(sensors): - s_start_button = ttk.Button(self.frame, text=sensor+" Start", - command=lambda b=badge, s=s_idx, m=1: self.schedule_async_task(b, s, m)) + s_start_button = ttk.Button(self.frame, text=sensor+" Start", command= + lambda b=badge, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) s_start_button.grid(row=row_button, column=2+2*s_idx, padx=5, pady=5) - s_stop_button = ttk.Button(self.frame, text=sensor+" Stop", - command=lambda b=badge, s=s_idx, m=0: self.schedule_async_task(b, s, m)) + s_stop_button = ttk.Button(self.frame, text=sensor+" Stop", command= + lambda b=badge, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) s_stop_button.grid(row=row_button, column=3+2*s_idx, padx=5, pady=5) sensor_light_canvases = [] - for s_idx, sensor in enumerate(sensors[1:], start=1): + for s_idx, sensor in enumerate(indicators_short, start=1): sensor_light_canvas = tk.Canvas(self.frame, width=20, height=20) - sensor_light_canvas.grid(row=row_status, column=3+2*s_idx, padx=5, pady=5) + sensor_light_canvas.grid(row=row_status, column=1+2*s_idx, padx=5, pady=5) sensor_light = sensor_light_canvas.create_oval(5, 5, 15, 15, fill="red") sensor_light_canvases.append((sensor_light_canvas, sensor_light)) sensor_label = ttk.Label(self.frame, text=sensor + ' status:') - sensor_label.grid(row=row_status, column=2+2*s_idx, padx=5, pady=5) + sensor_label.grid(row=row_status, column=2*s_idx, padx=5, pady=5) # Create a label to display the last updated time and elapsed time timestamp_label = ttk.Label(self.frame, text="Last updated: N/A") - timestamp_label.grid(row=row_status, column=1, padx=5, pady=5) + timestamp_label.grid(row=row_status, column=0, padx=5, pady=5) # Create a label to display the elapsed time in seconds elapsed_time_label = ttk.Label(self.frame, text="Elapsed: N/A") - elapsed_time_label.grid(row=row_status, column=2, padx=5, pady=5) + elapsed_time_label.grid(row=row_status, column=1, padx=5, pady=5) # Store the canvas, light, sensor lights, timestamp label, and elapsed time label self.timestamp_labels[badge] = (timestamp_label, elapsed_time_label) @@ -167,24 +159,26 @@ def on_frame_configure(self, event): """Reset the scroll region to encompass the inner frame.""" self.canvas.configure(scrollregion=self.canvas.bbox("all")) - def schedule_async_task(self, badge_id, sensor_idx, mode): + def schedule_async_task(self, badge_id, sensor_idx, mode: bool): # Schedule the coroutine to run asynchronously + async_sensor = partial(self.async_sensor_operation, badge_id=badge_id, mode=mode) if sensor_idx == SENSOR_CHECK_STATUS: asyncio.create_task(self.async_check_status(badge_id)) elif sensor_idx == SENSOR_MICROPHONE: - asyncio.create_task(self.async_microphone(badge_id, mode)) + asyncio.create_task(async_sensor(sensor_name='microphone')) elif sensor_idx == SENSOR_IMU: - asyncio.create_task(self.async_imu(badge_id, mode)) + asyncio.create_task(async_sensor(sensor_name='imu')) elif sensor_idx == SENSOR_SCAN: - asyncio.create_task(self.async_scan(badge_id, mode)) + asyncio.create_task(async_sensor(sensor_name='scan')) elif sensor_idx == SENSOR_ALL: - asyncio.create_task(self.async_microphone(badge_id, mode)) - asyncio.create_task(self.async_imu(badge_id, mode)) - asyncio.create_task(self.async_scan(badge_id, mode)) + asyncio.create_task(async_sensor(sensor_name='microphone')) + asyncio.create_task(async_sensor(sensor_name='imu')) + asyncio.create_task(async_sensor(sensor_name='scan')) async def async_check_status(self, badge_id): # Call the async function to check the status - sensor_statuses, timestamp = await check_status(badge_id) + statuses, timestamp = await self.check_status(badge_id) + sensor_statuses = [getattr(statuses, s + '_status') for s in indicators_long] # Get the canvas and light object for the badge timestamp_label, elapsed_time_label = self.timestamp_labels[badge_id] @@ -209,58 +203,38 @@ async def async_check_status(self, badge_id): task = asyncio.create_task(self.update_elapsed_time(badge_id, elapsed_time_label, timestamp)) self.update_tasks[badge_id] = task - @staticmethod - async def async_microphone(badge_id, mode): - if mode == SENSOR_START: - print(f"Starting microphone for badge {badge_id}...") - mode_desc = 'started' - # TODO: add Openbadge(...) - elif mode == SENSOR_STOP: - print(f"Stopping microphone for badge {badge_id}...") - mode_desc = 'stopped' - # TODO: add Openbadge(...) - else: - raise ValueError(f"Unknown mode {mode}") - await asyncio.sleep(1) # Simulate delay - timestamp = datetime.now() # Get the current timestamp - print(f'Badge {badge_id} microphone {mode_desc} successfully.') - return timestamp + async def check_status(self, badge_id): + badge_addr = self.get_badge_address(badge_id) + print(f"Checking status for badge {badge_id}...") - @staticmethod - async def async_imu(badge_id, mode): - if mode == SENSOR_START: - print(f"Starting imu for badge {badge_id}...") - mode_desc = 'started' - # TODO: add Openbadge(...) - elif mode == SENSOR_STOP: - print(f"Stopping imu for badge {badge_id}...") - mode_desc = 'stopped' - # TODO: add Openbadge(...) - else: - raise ValueError(f"Unknown mode {mode}") - await asyncio.sleep(1) # Simulate delay - timestamp = datetime.now() # Get the current timestamp - print(f'Badge {badge_id} imu {mode_desc} successfully.') - return timestamp + # simulate + # await asyncio.sle(2) # Simulate delay + # statuses = np.random.randint(2, size=sensor_num) - @staticmethod - async def async_scan(badge_id, mode): - if mode == SENSOR_START: - print(f"Starting scan for badge {badge_id}...") - mode_desc = 'started' - # TODO: add Openbadge(...) - elif mode == SENSOR_STOP: - print(f"Stopping scan for badge {badge_id}...") - mode_desc = 'stopped' - # TODO: add Openbadge(...) - else: - raise ValueError(f"Unknown mode {mode}") - await asyncio.sleep(1) # Simulate delay + # real status + async with OpenBadge(badge_id, badge_addr) as open_badge: + statuses = await open_badge.get_status() + + timestamp = datetime.now() # Get the current timestamp + print(statuses) + print(f"Check status for badge {badge_id} completed.") + return statuses, timestamp + + async def async_sensor_operation(self, badge_id, sensor_name, mode: bool): + badge_addr = self.get_badge_address(badge_id) + mode_name = 'start' if mode == SENSOR_START else 'stop' + op_name = f'{mode_name}_{sensor_name}' + print(f"Executing: {mode_name} {sensor_name} for badge {badge_id}...") + async with OpenBadge(badge_id, badge_addr) as open_badge: + sensor_operation = getattr(open_badge, op_name) + return_message = await sensor_operation() + print(return_message) + # await asyncio.sleep(1) # Simulate delay timestamp = datetime.now() # Get the current timestamp - print(f'Badge {badge_id} scan {mode_desc} successfully.') + print(f'Badge {badge_id} {sensor_name} {mode_name} successfully.') return timestamp - def get_badge_address(self, badge_id: int): + def get_badge_address(self, badge_id: int) -> str: badge = self.badges[self.badges['Participant Id'] == badge_id] address = badge['Mac Address'].to_numpy()[0] # There should be a more elegant way to do this return address @@ -284,7 +258,7 @@ def run(self): # Main function that starts the tkinter app and runs the event loop def run_tkinter_async(): - badges = list(range(1, 20)) # Badge numbers from 1 to 10 + badges = list(range(1, 50)) # Badge numbers from 1 to 10 app = BadgeMonitorApp(badges) async def main_loop(): @@ -298,4 +272,4 @@ async def main_loop(): if __name__ == "__main__": - run_tkinter_async() + run_tkinter_async() \ No newline at end of file From 5f7f58a2a2b5c225ab29bb0c2655ba98c1a4a83a Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 28 Oct 2024 11:49:06 +0100 Subject: [PATCH 07/46] modify according to new hardware codes --- BadgeFramework/badge.py | 1 + BadgeFramework/badge_gui_bleak.py | 117 +++++++++++++++++++++--------- 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 8977c11..74c16ff 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -216,6 +216,7 @@ def deal_response(self, response_type): """deal response from client. Currently, this only involves decoding.""" # print('rx list:', self.rx_list) if response_type < 0: + # response_type < 0 means this response does not contain messages self.rx_list.pop(0) else: while True: diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index 87f437d..ce9ec81 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -14,10 +14,14 @@ SENSOR_IMU = 2 SENSOR_SCAN = 3 SENSOR_CHECK_STATUS = 100 +SENSOR_TIMEOUT = 15 SENSOR_START = True SENSOR_STOP = False +BADGES_ALL = -1000 +USE_ALL = False # True to use all badges in .csv file and False to use only marked badges + sensors = ['All', 'Mic', 'IMU', 'Scan'] indicators_long = ['clock', 'microphone', 'imu', 'scan'] indicators_short = ['Clock', 'Mic', 'IMU', 'Scan'] @@ -100,23 +104,39 @@ def __init__(self, badges): self.timestamps = {} self.update_tasks = {} + badge_label = ttk.Label(self.frame, text=f"ALL BADGES") + badge_label.grid(row=0, column=0, padx=80, pady=5) + check_button = ttk.Button(self.frame, text="Check Status", + command=lambda b=BADGES_ALL, s=SENSOR_CHECK_STATUS, + m=SENSOR_CHECK_STATUS: self.schedule_async_task(b, s, m)) + check_button.grid(row=0, column=1, padx=20, pady=5) + for s_idx, sensor in enumerate(sensors): + s_start_button = ttk.Button(self.frame, text=sensor + " Start", command= + lambda b=BADGES_ALL, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) + s_start_button.grid(row=0, column=2 + 2 * s_idx, padx=5, pady=5) + + s_stop_button = ttk.Button(self.frame, text=sensor + " Stop", command= + lambda b=BADGES_ALL, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) + s_stop_button.grid(row=0, column=3 + 2 * s_idx, padx=5, pady=5) + # Create rows for each badge + for idx, badge in enumerate(badges, start=1): - row_button, row_status = idx * 2 - 1, idx * 2 + row_button, row_status = idx * 2 + 1, idx * 2 + 2 badge_label = ttk.Label(self.frame, text=f"Badge {badge}") badge_label.grid(row=row_button, column=0, padx=80, pady=5) check_button = ttk.Button(self.frame, text="Check Status", - command=lambda b=badge, s=100, m=1: self.schedule_async_task(b, s, m)) + command=lambda b=badge, s=100, m=SENSOR_START: self.schedule_async_task(b, s, m)) check_button.grid(row=row_button, column=1, padx=20, pady=5) for s_idx, sensor in enumerate(sensors): s_start_button = ttk.Button(self.frame, text=sensor+" Start", command= - lambda b=badge, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) + lambda b=badge, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) s_start_button.grid(row=row_button, column=2+2*s_idx, padx=5, pady=5) s_stop_button = ttk.Button(self.frame, text=sensor+" Stop", command= - lambda b=badge, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) + lambda b=badge, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) s_stop_button.grid(row=row_button, column=3+2*s_idx, padx=5, pady=5) sensor_light_canvases = [] @@ -160,24 +180,40 @@ def on_frame_configure(self, event): self.canvas.configure(scrollregion=self.canvas.bbox("all")) def schedule_async_task(self, badge_id, sensor_idx, mode: bool): - # Schedule the coroutine to run asynchronously - async_sensor = partial(self.async_sensor_operation, badge_id=badge_id, mode=mode) + if badge_id == BADGES_ALL: + asyncio.create_task(self.async_task_all_badges(sensor_idx, mode, use_all=USE_ALL)) + else: + asyncio.create_task(self.async_task_sensors(badge_id, sensor_idx, mode)) + + async def async_task_all_badges(self, sensor_idx, mode: bool, use_all: bool): + for row_id in self.badges.index: + badge_id, use_flag = self.badges['Participant Id'][row_id], self.badges['Use'][row_id] + if use_flag or use_all: + await self.async_task_sensors(badge_id=badge_id, sensor_idx=sensor_idx, mode=mode) + + + async def async_task_sensors(self, badge_id: int, sensor_idx: int, mode: bool): if sensor_idx == SENSOR_CHECK_STATUS: - asyncio.create_task(self.async_check_status(badge_id)) + await self.async_check_status(badge_id) + # await self.async_check_status(badge_id=badge_id, mode=mode, sensor_name='status') elif sensor_idx == SENSOR_MICROPHONE: - asyncio.create_task(async_sensor(sensor_name='microphone')) + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='microphone') elif sensor_idx == SENSOR_IMU: - asyncio.create_task(async_sensor(sensor_name='imu')) + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='imu') elif sensor_idx == SENSOR_SCAN: - asyncio.create_task(async_sensor(sensor_name='scan')) + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='scan') elif sensor_idx == SENSOR_ALL: - asyncio.create_task(async_sensor(sensor_name='microphone')) - asyncio.create_task(async_sensor(sensor_name='imu')) - asyncio.create_task(async_sensor(sensor_name='scan')) + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='microphone') + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='imu') + await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='scan') + async def async_check_status(self, badge_id): # Call the async function to check the status - statuses, timestamp = await self.check_status(badge_id) + # statuses, timestamp = await self.check_status(badge_id) + statuses, timestamp = await self.async_sensor(badge_id=badge_id, mode=True, sensor_name='status') + if statuses is None: + return sensor_statuses = [getattr(statuses, s + '_status') for s in indicators_long] # Get the canvas and light object for the badge @@ -203,36 +239,45 @@ async def async_check_status(self, badge_id): task = asyncio.create_task(self.update_elapsed_time(badge_id, elapsed_time_label, timestamp)) self.update_tasks[badge_id] = task - async def check_status(self, badge_id): - badge_addr = self.get_badge_address(badge_id) - print(f"Checking status for badge {badge_id}...") - - # simulate - # await asyncio.sle(2) # Simulate delay - # statuses = np.random.randint(2, size=sensor_num) - - # real status - async with OpenBadge(badge_id, badge_addr) as open_badge: - statuses = await open_badge.get_status() + @staticmethod + def get_operation_name(mode_name: str, sensor_name: str) -> str: + if sensor_name in indicators_long: + return f'{mode_name}_{sensor_name}' + elif sensor_name == 'status': + return 'get_status' + elif sensor_name == 'sdc_space': + return 'get_free_sdc_space' + else: + raise ValueError + + async def async_sensor(self, badge_id, sensor_name, mode: bool): + # badge_addr = self.get_badge_address(badge_id) + mode_name = 'start' if mode == SENSOR_START else 'stop' + op_name = self.get_operation_name(mode_name, sensor_name) + badge_op_desc = f'Badge {badge_id} {op_name}' + print(f"Executing: {badge_op_desc}...") + try: + # await asyncio.wait_for(self.eternity(), timeout=1.0) + response = await asyncio.wait_for(self.async_sensor_operation(badge_id, op_name), timeout=SENSOR_TIMEOUT) + print(f'Info: {badge_op_desc} successfully.') + except asyncio.TimeoutError: + print(f"Warning: {badge_op_desc} has timed out! (>{SENSOR_TIMEOUT}s)") + response = None + except Exception as e: + print(e) + response = None + # await asyncio.sleep(1) # Simulate delay timestamp = datetime.now() # Get the current timestamp - print(statuses) - print(f"Check status for badge {badge_id} completed.") - return statuses, timestamp + return response, timestamp - async def async_sensor_operation(self, badge_id, sensor_name, mode: bool): + async def async_sensor_operation(self, badge_id, op_name): badge_addr = self.get_badge_address(badge_id) - mode_name = 'start' if mode == SENSOR_START else 'stop' - op_name = f'{mode_name}_{sensor_name}' - print(f"Executing: {mode_name} {sensor_name} for badge {badge_id}...") async with OpenBadge(badge_id, badge_addr) as open_badge: sensor_operation = getattr(open_badge, op_name) return_message = await sensor_operation() print(return_message) - # await asyncio.sleep(1) # Simulate delay - timestamp = datetime.now() # Get the current timestamp - print(f'Badge {badge_id} {sensor_name} {mode_name} successfully.') - return timestamp + return return_message def get_badge_address(self, badge_id: int) -> str: badge = self.badges[self.badges['Participant Id'] == badge_id] From f4bfc681219718cc36f61ec95713c05ca816e9cb Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 31 Oct 2024 13:40:53 +0100 Subject: [PATCH 08/46] modify according to new hardware codes --- BadgeFramework/badge.py | 22 +++++++++++++++++++-- BadgeFramework/bleak_test.py | 10 +++++----- BadgeFramework/mappings2.csv | 38 +++++++++++++++++++++++------------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 74c16ff..b985265 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -5,7 +5,8 @@ import time import logging import struct -from collections.abc import Callable +import ntplib +from datetime import datetime, timezone from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic from typing import Optional, Final @@ -32,12 +33,29 @@ logger = logging.getLogger(__name__) + +def get_ntp_time(ntp_server='pool.ntp.org'): + # Create an NTP client + client = ntplib.NTPClient() + + try: + # Query the NTP server + response = client.request(ntp_server) + + # Convert the response to datetime + ntp_time = datetime.fromtimestamp(response.tx_time, tz=timezone.utc) + print("NTP time:", ntp_time.strftime('%Y-%m-%d %H:%M:%S %Z')) + return ntp_time + + except Exception as e: + print("Could not connect to NTP server:", e) + return None + # -- Helper methods used often in badge communication -- # We generally define timestamp_seconds to be in number of seconds since UTC epoch # and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. - def get_timestamps_from_time(t=None) -> (int, int): """Returns the given time as two parts - seconds and milliseconds""" if t is None: diff --git a/BadgeFramework/bleak_test.py b/BadgeFramework/bleak_test.py index af26f6e..02cf9d3 100644 --- a/BadgeFramework/bleak_test.py +++ b/BadgeFramework/bleak_test.py @@ -36,19 +36,19 @@ async def main(): device_id = utils.get_device_id(ble_device) print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - for ble_device, adv_data in devices: + # for ble_device, adv_data in devices: # open_badge = OpenBadge(ble_device) # space = await open_badge.get_free_sdc_space() - async with OpenBadge(ble_device) as open_badge: - space = await open_badge.get_free_sdc_space() - print(space) + # async with OpenBadge(ble_device) as open_badge: + # space = await open_badge.get_free_sdc_space() + # print(space) # out = await open_badge.get_status(t=40) # start = await open_badge.start_microphone() # time.sleep(15) # async with OpenBadge(ble_device) as open_badge: # stop = await open_badge.stop_microphone() # await synchronize_device(open_badge, logger) - c = 9 + # c = 9 print('completed') # Connect to the midge diff --git a/BadgeFramework/mappings2.csv b/BadgeFramework/mappings2.csv index 8011d41..467f816 100644 --- a/BadgeFramework/mappings2.csv +++ b/BadgeFramework/mappings2.csv @@ -5,46 +5,56 @@ 3,4,f9:39:24:a9:04:f1,,0 4,5,d2:fd:13:bd:81:39,,0 5,6,de:94:80:39:25:be,24.0,0 -6,7,fb:f5:d5:84:a1:68,,0 +6,7,fb:f5:d5:84:a1:68,,1 7,8,e2:6e:4e:21:f1:a4,,0 8,9,f5:40:84:e2:9a:16,,0 9,10,e8:44:59:c0:39:de,,0 10,11,d7:11:de:c6:c8:e3,24.0,0 -11,12,fa:24:bd:55:c7:ab,,0 +11,12,fa:24:bd:55:c7:ab,,1 12,13,d6:12:78:32:80:19,,0 13,14,e8:03:31:16:ce:3a,24.0,0 14,15,d8:ae:5b:aa:55:ae,,0 15,16,f5:ef:4c:9b:55:de,,0 16,17,c3:2b:78:b5:c2:4a,,0 17,18,c4:3e:0b:bc:e6:92,,0 -18,19,d9:0d:2e:b7:cc:6b,,0 -19,20,e6:cc:57:a3:6b:57,,0 +18,19,d9:0d:2e:b7:cc:6b,,1 +19,20,e6:cc:57:a3:6b:57,,1 20,21,c6:30:71:35:02:5a,,0 21,22,fb:42:af:eb:ba:3c,,0 22,23,d2:91:1c:b9:6f:5c,24.0,0 -23,24,c1:a4:56:be:7f:7e,,0 -24,25,f2:26:6c:f5:ca:e5,,0 +23,24,c1:a4:56:be:7f:7e,,1 +24,25,f2:26:6c:f5:ca:e5,,1 25,26,cb:14:eb:9a:5c:a3,,0 26,27,db:03:30:a6:ee:86,,0 27,28,fc:3f:62:28:17:b4,,0 -28,29,ce:22:14:de:40:38,,0 +28,29,ce:22:14:de:40:38,,1 29,30,e3:5d:06:9a:55:0f,,0 30,31,d4:56:f4:e1:ef:f1,,0 -31,32,d2:4f:7c:01:93:0e,,0 +31,32,d2:4f:7c:01:93:0e,,1 32,33,da:03:10:bb:61:f5,,1 -33,34,f5:e7:4d:77:4e:74,,0 -34,35,fd:f3:eb:4b:d0:8c,,0 +33,34,f5:e7:4d:77:4e:74,,1 +34,35,fd:f3:eb:4b:d0:8c,,1 35,36,dc:bc:ed:19:1f:09,,0 -36,37,c5:61:a9:e9:83:ef,,0 +36,37,c5:61:a9:e9:83:ef,,1 37,39,e7:03:db:e9:16:3b,,0 38,40,fe:82:88:fa:36:62,,0 39,41,dc:a4:f4:7e:45:fb,,0 40,42,f6:01:ad:d1:18:23,,0 41,43,e0:c3:d6:2e:ab:44,,0 42,44,de:7f:7f:a2:9c:f5,,0 -43,45,fe:71:db:20:4f:34,,0 +43,45,fe:71:db:20:4f:34,,1 44,46,dc:11:e6:20:81:d8,,0 45,47,d9:98:8a:68:f0:89,,0 -46,48,f7:5a:78:fd:21:46,,0 -47,49,e9:59:12:26:a7:63,,1 +46,48,f7:5a:78:fd:21:46,,1 +47,49,e9:59:12:26:a7:63,,0 48,50,d0:c5:cc:ef:90:e2,,0 +49,51,d0:c5:cc:ef:90:e2,,0 +50,52,d0:c5:cc:ef:90:e2,,0 +51,53,d0:c5:cc:ef:90:e2,,0 +52,54,CB:58:7B:77:44:D6,,1 +53,55,FE:63:F0:A4:C8:F4,,1 +54,56,d0:c5:cc:ef:90:e2,,0 +55,57,D8:52:CD:32:50:B0,,1 +56,58,D1:0E:45:AC:97:DD,,1 +57,59,DD:09:38:B5:ED:8C,,1 +58,60,E4:9C:CC:8C:8F:DA,,1 \ No newline at end of file From 786b88481d6e34faab87c07a28c9bfeab6ccbde9 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 29 Nov 2024 09:59:38 +0100 Subject: [PATCH 09/46] develop GUI with bleak --- BadgeFramework/badge.py | 52 +++++++++++------- BadgeFramework/badge_gui_bleak.py | 91 +++++++++++++++++++++++-------- BadgeFramework/bleak_test.py | 38 +++++++++---- BadgeFramework/mappings2.csv | 4 +- 4 files changed, 128 insertions(+), 57 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index b985265..f7cd30e 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -34,28 +34,12 @@ logger = logging.getLogger(__name__) -def get_ntp_time(ntp_server='pool.ntp.org'): - # Create an NTP client - client = ntplib.NTPClient() - - try: - # Query the NTP server - response = client.request(ntp_server) - - # Convert the response to datetime - ntp_time = datetime.fromtimestamp(response.tx_time, tz=timezone.utc) - print("NTP time:", ntp_time.strftime('%Y-%m-%d %H:%M:%S %Z')) - return ntp_time - - except Exception as e: - print("Could not connect to NTP server:", e) - return None - # -- Helper methods used often in badge communication -- # We generally define timestamp_seconds to be in number of seconds since UTC epoch # and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. + def get_timestamps_from_time(t=None) -> (int, int): """Returns the given time as two parts - seconds and milliseconds""" if t is None: @@ -79,7 +63,8 @@ def bp_timestamp_from_time(t=None) -> bp.Timestamp: def badge_disconnected(b: BleakClient) -> None: """disconnection callback""" - print(f"Warning: disconnected badge") + # print(f"Warning: disconnected badge") + pass def request_handler(device_id, action_desc): @@ -157,7 +142,8 @@ def is_connected(self) -> bool: def badge_disconnected(self, b: BleakClient) -> None: """disconnection callback""" - print(f"Warning: disconnected badge", b.address) + # print(f"Warning: disconnected badge", b.address) + pass @staticmethod def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: @@ -211,7 +197,7 @@ def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray) if len(message) > 0: new_message = {'time': time.time(), 'message': message} if not self.message_is_duplicated(self.rx_list, new_message): - print(f"RX changed {sender}: {message}" + str(time.time())) + # print(f"RX changed {sender}: {message}" + str(time.time())) # self.rx_message = message self.rx_list.append(new_message) @@ -235,7 +221,8 @@ def deal_response(self, response_type): # print('rx list:', self.rx_list) if response_type < 0: # response_type < 0 means this response does not contain messages - self.rx_list.pop(0) + if len(self.rx_list) > 0: + self.rx_list.pop(0) else: while True: try: @@ -415,3 +402,26 @@ def print_help(): print(" help") print(" All commands use current system time as transmitted time.") sys.stdout.flush() + + +def display_current_time(): + try: + while True: + # Get current time with millisecond accuracy + current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + # current_time = time.time() + print(f"\r{current_time}", end='') # Use carriage return to overwrite the line + time.sleep(0.001) # Sleep for 1 millisecond + except KeyboardInterrupt: + print("\nStopped.") + + +def main(): + display_current_time() +# c = 0 +# # ntp_server_ip = "127.0.0.1" +# # get_ntp_time(ntp_server_ip) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index ce9ec81..4a28829 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -4,9 +4,11 @@ import pandas as pd from badge import OpenBadge from datetime import datetime -import numpy as np +# import numpy as np import sys -from functools import partial +import time +import ntplib +# from functools import partial SENSOR_ALL = 0 @@ -18,6 +20,8 @@ SENSOR_START = True SENSOR_STOP = False +CHECK_SYNC = True +CHECK_NO_SYNC = False BADGES_ALL = -1000 USE_ALL = False # True to use all badges in .csv file and False to use only marked badges @@ -47,7 +51,7 @@ def flush(self): class BadgeMonitorApp(tk.Tk): - def __init__(self, badges): + def __init__(self, ): super().__init__() self.title("Badge Status Monitor") @@ -108,7 +112,7 @@ def __init__(self, badges): badge_label.grid(row=0, column=0, padx=80, pady=5) check_button = ttk.Button(self.frame, text="Check Status", command=lambda b=BADGES_ALL, s=SENSOR_CHECK_STATUS, - m=SENSOR_CHECK_STATUS: self.schedule_async_task(b, s, m)) + m=CHECK_NO_SYNC: self.schedule_async_task(b, s, m)) check_button.grid(row=0, column=1, padx=20, pady=5) for s_idx, sensor in enumerate(sensors): s_start_button = ttk.Button(self.frame, text=sensor + " Start", command= @@ -119,26 +123,37 @@ def __init__(self, badges): lambda b=BADGES_ALL, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) s_stop_button.grid(row=0, column=3 + 2 * s_idx, padx=5, pady=5) - # Create rows for each badge + sync_button = ttk.Button(self.frame, text="Sync", + command=lambda b=BADGES_ALL, s=SENSOR_CHECK_STATUS, m=CHECK_SYNC: + self.schedule_async_task(b, s, m)) + sync_button.grid(row=0, column=10, padx=5, pady=5) - for idx, badge in enumerate(badges, start=1): + # Create rows for each badge + badges_list = list(range(1, len(self.badges) + 5)) + for idx, badge in enumerate(badges_list, start=1): row_button, row_status = idx * 2 + 1, idx * 2 + 2 badge_label = ttk.Label(self.frame, text=f"Badge {badge}") badge_label.grid(row=row_button, column=0, padx=80, pady=5) - check_button = ttk.Button(self.frame, text="Check Status", - command=lambda b=badge, s=100, m=SENSOR_START: self.schedule_async_task(b, s, m)) + check_button = ttk.Button(self.frame, text="Check", + command=lambda b=badge, s=SENSOR_CHECK_STATUS, m=CHECK_NO_SYNC: + self.schedule_async_task(b, s, m)) check_button.grid(row=row_button, column=1, padx=20, pady=5) for s_idx, sensor in enumerate(sensors): s_start_button = ttk.Button(self.frame, text=sensor+" Start", command= - lambda b=badge, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) + lambda b=badge, s=s_idx, m=SENSOR_START: self.schedule_async_task(b, s, m)) s_start_button.grid(row=row_button, column=2+2*s_idx, padx=5, pady=5) s_stop_button = ttk.Button(self.frame, text=sensor+" Stop", command= - lambda b=badge, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) + lambda b=badge, s=s_idx, m=SENSOR_STOP: self.schedule_async_task(b, s, m)) s_stop_button.grid(row=row_button, column=3+2*s_idx, padx=5, pady=5) + sync_button = ttk.Button(self.frame, text="Sync", + command=lambda b=badge, s=SENSOR_CHECK_STATUS, m=CHECK_SYNC: + self.schedule_async_task(b, s, m)) + sync_button.grid(row=row_button, column=10, padx=5, pady=5) + sensor_light_canvases = [] for s_idx, sensor in enumerate(indicators_short, start=1): sensor_light_canvas = tk.Canvas(self.frame, width=20, height=20) @@ -186,17 +201,14 @@ def schedule_async_task(self, badge_id, sensor_idx, mode: bool): asyncio.create_task(self.async_task_sensors(badge_id, sensor_idx, mode)) async def async_task_all_badges(self, sensor_idx, mode: bool, use_all: bool): + # await self.async_task_sensors(badge_id=1, sensor_idx=sensor_idx, mode=mode) for row_id in self.badges.index: - badge_id, use_flag = self.badges['Participant Id'][row_id], self.badges['Use'][row_id] + badge_id, use_flag = int(self.badges['Participant Id'][row_id]), bool(self.badges['Use'][row_id]) if use_flag or use_all: await self.async_task_sensors(badge_id=badge_id, sensor_idx=sensor_idx, mode=mode) - async def async_task_sensors(self, badge_id: int, sensor_idx: int, mode: bool): - if sensor_idx == SENSOR_CHECK_STATUS: - await self.async_check_status(badge_id) - # await self.async_check_status(badge_id=badge_id, mode=mode, sensor_name='status') - elif sensor_idx == SENSOR_MICROPHONE: + if sensor_idx == SENSOR_MICROPHONE: await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='microphone') elif sensor_idx == SENSOR_IMU: await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='imu') @@ -207,11 +219,15 @@ async def async_task_sensors(self, badge_id: int, sensor_idx: int, mode: bool): await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='imu') await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='scan') + if sensor_idx == SENSOR_CHECK_STATUS: + await self.async_check_status(badge_id, mode=mode) + else: + await self.async_check_status(badge_id, mode=CHECK_NO_SYNC) - async def async_check_status(self, badge_id): + async def async_check_status(self, badge_id, mode=CHECK_NO_SYNC): # Call the async function to check the status # statuses, timestamp = await self.check_status(badge_id) - statuses, timestamp = await self.async_sensor(badge_id=badge_id, mode=True, sensor_name='status') + statuses, timestamp = await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='status') if statuses is None: return sensor_statuses = [getattr(statuses, s + '_status') for s in indicators_long] @@ -256,9 +272,11 @@ async def async_sensor(self, badge_id, sensor_name, mode: bool): op_name = self.get_operation_name(mode_name, sensor_name) badge_op_desc = f'Badge {badge_id} {op_name}' print(f"Executing: {badge_op_desc}...") + try: # await asyncio.wait_for(self.eternity(), timeout=1.0) - response = await asyncio.wait_for(self.async_sensor_operation(badge_id, op_name), timeout=SENSOR_TIMEOUT) + response = await asyncio.wait_for(self.async_sensor_operation(badge_id, op_name, mode), + timeout=SENSOR_TIMEOUT) print(f'Info: {badge_op_desc} successfully.') except asyncio.TimeoutError: print(f"Warning: {badge_op_desc} has timed out! (>{SENSOR_TIMEOUT}s)") @@ -271,12 +289,15 @@ async def async_sensor(self, badge_id, sensor_name, mode: bool): timestamp = datetime.now() # Get the current timestamp return response, timestamp - async def async_sensor_operation(self, badge_id, op_name): + async def async_sensor_operation(self, badge_id, op_name, mode): badge_addr = self.get_badge_address(badge_id) async with OpenBadge(badge_id, badge_addr) as open_badge: sensor_operation = getattr(open_badge, op_name) - return_message = await sensor_operation() - print(return_message) + if op_name == 'get_status' and mode == CHECK_SYNC: + return_message = await sensor_operation(t=datetime.now().timestamp()) + else: + return_message = await sensor_operation() + # print(return_message) return return_message def get_badge_address(self, badge_id: int) -> str: @@ -303,8 +324,7 @@ def run(self): # Main function that starts the tkinter app and runs the event loop def run_tkinter_async(): - badges = list(range(1, 50)) # Badge numbers from 1 to 10 - app = BadgeMonitorApp(badges) + app = BadgeMonitorApp() async def main_loop(): while True: @@ -316,5 +336,28 @@ async def main_loop(): asyncio.run(main_loop()) +# def get_ntp_time(ntp_server='pool.ntp.org'): +# # Create an NTP client +# client = ntplib.NTPClient() +# +# try: +# # Query the NTP server +# response = client.request(ntp_server) +# +# # Convert the response to datetime +# ntp_time = datetime.fromtimestamp(response.tx_time, tz=timezone.utc) +# print("NTP time:", ntp_time.strftime('%Y-%m-%d %H:%M:%S %Z')) +# return ntp_time +# +# except Exception as e: +# print("Could not connect to NTP server:", e) +# return None + + +# Use a local NTP server (replace with your server IP if needed) +# ntp_server_ip = '192.168.1.100' # Example IP for a local NTP server +# ntp_time = get_ntp_time(ntp_server=ntp_server_ip) + + if __name__ == "__main__": run_tkinter_async() \ No newline at end of file diff --git a/BadgeFramework/bleak_test.py b/BadgeFramework/bleak_test.py index 02cf9d3..b205ae1 100644 --- a/BadgeFramework/bleak_test.py +++ b/BadgeFramework/bleak_test.py @@ -1,9 +1,9 @@ import asyncio -import time import logging import utils from bleak import BleakScanner from badge import OpenBadge +from datetime import datetime async def synchronize_device(open_badge: OpenBadge, logger: logging.Logger) -> None: @@ -22,6 +22,17 @@ async def synchronize_device(open_badge: OpenBadge, logger: logging.Logger) -> N logger.info(f"Can't sync for participant {open_badge.id}.") +def remove_decimal(number): + # Convert to string, remove the decimal point, and convert back to integer + no_decimal = int(str(number).replace('.', '')) + return no_decimal + + +def midge_timestamp(number: float): + a = int(number * 1000) + return int(str(a)[3:]) + + async def main(): logger = utils.get_logger('bleak_logger') # Find all devices @@ -36,14 +47,21 @@ async def main(): device_id = utils.get_device_id(ble_device) print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - # for ble_device, adv_data in devices: - # open_badge = OpenBadge(ble_device) - # space = await open_badge.get_free_sdc_space() - # async with OpenBadge(ble_device) as open_badge: - # space = await open_badge.get_free_sdc_space() - # print(space) - # out = await open_badge.get_status(t=40) - # start = await open_badge.start_microphone() + for ble_device, adv_data in devices: + # device_id = utils.get_device_id(ble_device) + async with OpenBadge(ble_device) as open_badge: + # async with OpenBadge(int(device_id), ble_device.address) as open_badge: + out = await open_badge.get_status(t=midge_timestamp(datetime.now().timestamp())) + print(datetime.now().timestamp()) + start = await open_badge.start_microphone() + print(out) + print(start) + print(remove_decimal(datetime.now().timestamp())) + # space = await open_badge.get_free_sdc_space() + # print(space) + # out = await open_badge.get_status(t=40.3356) + # print(out) + # # time.sleep(15) # async with OpenBadge(ble_device) as open_badge: # stop = await open_badge.stop_microphone() @@ -54,4 +72,4 @@ async def main(): if __name__ == "__main__": - asyncio.run(main()) + asyncio.run(main()) \ No newline at end of file diff --git a/BadgeFramework/mappings2.csv b/BadgeFramework/mappings2.csv index 467f816..0c87530 100644 --- a/BadgeFramework/mappings2.csv +++ b/BadgeFramework/mappings2.csv @@ -18,7 +18,7 @@ 16,17,c3:2b:78:b5:c2:4a,,0 17,18,c4:3e:0b:bc:e6:92,,0 18,19,d9:0d:2e:b7:cc:6b,,1 -19,20,e6:cc:57:a3:6b:57,,1 +19,20,e6:cc:57:a3:6b:57,,0 20,21,c6:30:71:35:02:5a,,0 21,22,fb:42:af:eb:ba:3c,,0 22,23,d2:91:1c:b9:6f:5c,24.0,0 @@ -26,7 +26,7 @@ 24,25,f2:26:6c:f5:ca:e5,,1 25,26,cb:14:eb:9a:5c:a3,,0 26,27,db:03:30:a6:ee:86,,0 -27,28,fc:3f:62:28:17:b4,,0 +27,28,fc:3f:62:28:17:b4,,1 28,29,ce:22:14:de:40:38,,1 29,30,e3:5d:06:9a:55:0f,,0 30,31,d4:56:f4:e1:ef:f1,,0 From 803562d945b107471e9bda03eab2ec0c54086a68 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 5 Dec 2024 18:06:37 +0100 Subject: [PATCH 10/46] develop GUI with bleak --- BadgeFramework/audio_parser_V0.py | 80 +++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 BadgeFramework/audio_parser_V0.py diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py new file mode 100644 index 0000000..d7f9ac2 --- /dev/null +++ b/BadgeFramework/audio_parser_V0.py @@ -0,0 +1,80 @@ +import numpy as np +from numpy.random import random +from scipy.io.wavfile import write +from pathlib import Path +import argparse +import os + +HIGH_SAMPLE_RATE = 8000 +LOW_SAMPLE_RATE = 1250 +s_rate = 5000 + +def deal_audio(fn): + data_folder = Path(fn) + for path_raw_input in sorted(data_folder.iterdir()): + print(path_raw_input, path_raw_input.suffix == "") + if (path_raw_input.is_file() and path_raw_input.suffix == "" and + ("MICLO" in path_raw_input.stem or "MICHI" in path_raw_input.stem or "audio" in path_raw_input.stem)): + + path_wav_output = path_raw_input.parent / (path_raw_input.stem + f"_{s_rate}.wav") + + # if path_raw_input.stem[4:6] == "LO": + # sample_rate = LOW_SAMPLE_RATE # Low frequency sampling + # raise NotImplementedError("Low sample rate does not work yet") + # elif path_raw_input.stem[4:6] == "HI": + # sample_rate = HIGH_SAMPLE_RATE # High frequency sampling + # else: + # raise RuntimeError("Unknown number of channels") + # + # if path_raw_input.stem[0] == "0": + # num_channels = 2 # Stereo + # elif path_raw_input.stem[0] == "1": + # num_channels = 1 # Mono + # else: + # raise RuntimeError("Unknown number of channels") + + sample_rate = HIGH_SAMPLE_RATE + num_channels = 2 + with path_raw_input.open("rb") as f: + raw_data = f.read() + + # 32-bit PCM uses numpy dtype int32 + audio_data = np.frombuffer(raw_data, dtype=np.int32) + + if num_channels == 2: + audio_data = audio_data.reshape(-1, 2) + + # Upsample the low sample rate to high sample rate by linear interpolation + if sample_rate == LOW_SAMPLE_RATE: + xp = np.linspace(0, audio_data.shape[0], audio_data.shape[0]) + x = np.linspace(0, audio_data.shape[0], audio_data.shape[0] * (HIGH_SAMPLE_RATE / LOW_SAMPLE_RATE)) + if num_channels == 1: + audio_data = np.interp(x, xp, audio_data) + else: + fp0 = np.interp(x, xp, audio_data[:, 0]) + fp1 = np.interp(x, xp, audio_data[:, 1]) + audio_data = np.stack([fp0, fp1], axis=1) + + print(audio_data.shape) + + # Save the audio data as a WAV file + write(filename=str(path_wav_output), rate=s_rate, data=audio_data) + + +def main(): + """ + Takes as input a folder with RAW audio files from the midge and saves them as .wav files. + """ + # parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') + # parser.add_argument('--fn', required=True, help='Please enter the path to the file') + # args = parser.parse_args() + midge_id = 54 + midge_folder = os.path.join('/home/zonghuan/tudelft/projects/datasets/new_collection/tech_pilot_1/midge_data/', + str(midge_id), '65535_1730466472') + deal_audio(fn=midge_folder) + + +if __name__ == '__main__': + main() + + From 1c1e136faa79b10c4dcda396093c1d10aeccbb43 Mon Sep 17 00:00:00 2001 From: Kevinlzh9802 Date: Thu, 5 Dec 2024 20:35:20 +0100 Subject: [PATCH 11/46] test modification at home --- BadgeFramework/audio_parser_V0.py | 1 + 1 file changed, 1 insertion(+) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index d7f9ac2..eaef317 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -72,6 +72,7 @@ def main(): midge_folder = os.path.join('/home/zonghuan/tudelft/projects/datasets/new_collection/tech_pilot_1/midge_data/', str(midge_id), '65535_1730466472') deal_audio(fn=midge_folder) + # add something at home if __name__ == '__main__': From 582bef7ad43ea7d68d8928ebd5b6aa81da8f23a3 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 6 Dec 2024 18:06:40 +0100 Subject: [PATCH 12/46] read data in imu and audio for tech pilot 11/01 --- BadgeFramework/audio_parser_V0.py | 244 +++++++++++++++++++++++------- BadgeFramework/imu_parser_V0.py | 106 +++++++++---- 2 files changed, 271 insertions(+), 79 deletions(-) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index eaef317..1eb2241 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -1,5 +1,4 @@ import numpy as np -from numpy.random import random from scipy.io.wavfile import write from pathlib import Path import argparse @@ -7,58 +6,146 @@ HIGH_SAMPLE_RATE = 8000 LOW_SAMPLE_RATE = 1250 -s_rate = 5000 +EMPIRICAL_RATE = 5000 +# s_rate = 5000 +audio_file_name_elements = ['MICLO', 'MICHI', 'audio'] + +def process_audio_file(path_raw_input, low_sample_rate, high_sample_rate): + """ + Process a single audio file, converting raw data into a WAV file with appropriate sample rate and channels. + """ + # Determine output path + + # Determine sample rate based on filename + # sample_rate = determine_sample_rate(path_raw_input.stem, low_sample_rate, high_sample_rate) + # num_channels = determine_num_channels(path_raw_input.stem) + + sample_rate = EMPIRICAL_RATE + num_channels = 1 + path_wav_output = path_raw_input.parent / (path_raw_input.stem + f"_{sample_rate}.wav") + + # Read raw data + with path_raw_input.open("rb") as f: + raw_data = f.read() + + # Convert raw data to numpy array + audio_data = np.frombuffer(raw_data, dtype=np.int32) + if num_channels == 2: + audio_data = audio_data.reshape(-1, 2) + + # Upsample if necessary + if sample_rate == low_sample_rate: + audio_data = upsample_audio(audio_data, low_sample_rate, high_sample_rate, num_channels) + + # Save as WAV file + save_audio_as_wav(path_wav_output, sample_rate, audio_data) + + +def determine_sample_rate(stem, low_sample_rate, high_sample_rate): + """ + Determine the sample rate based on the filename stem. + """ + if "LO" in stem: + return low_sample_rate + elif "HI" in stem: + return high_sample_rate + else: + raise RuntimeError("Unknown sample rate type in filename") + + +def determine_num_channels(stem): + """ + Determine the number of audio channels based on the filename stem. + """ + if stem[0] == "0": + return 2 # Stereo + elif stem[0] == "1": + return 1 # Mono + else: + raise RuntimeError("Unknown number of channels") + + +def upsample_audio(audio_data, low_sample_rate, high_sample_rate, num_channels): + """ + Upsample audio data from a lower sample rate to a higher sample rate using linear interpolation. + """ + xp = np.linspace(0, audio_data.shape[0], audio_data.shape[0]) + x = np.linspace(0, audio_data.shape[0], int(audio_data.shape[0] * (high_sample_rate / low_sample_rate))) + + if num_channels == 1: + return np.interp(x, xp, audio_data) + else: + fp0 = np.interp(x, xp, audio_data[:, 0]) + fp1 = np.interp(x, xp, audio_data[:, 1]) + return np.stack([fp0, fp1], axis=1) + + +def save_audio_as_wav(output_path, sample_rate, audio_data): + """ + Save audio data to a WAV file. + """ + write(filename=str(output_path), rate=sample_rate, data=audio_data) + + +def is_audio_file_name(name): + for x in audio_file_name_elements: + if x in name: + return True + return False def deal_audio(fn): + """ + Process all valid audio files in a directory. + """ data_folder = Path(fn) for path_raw_input in sorted(data_folder.iterdir()): print(path_raw_input, path_raw_input.suffix == "") - if (path_raw_input.is_file() and path_raw_input.suffix == "" and - ("MICLO" in path_raw_input.stem or "MICHI" in path_raw_input.stem or "audio" in path_raw_input.stem)): - - path_wav_output = path_raw_input.parent / (path_raw_input.stem + f"_{s_rate}.wav") - - # if path_raw_input.stem[4:6] == "LO": - # sample_rate = LOW_SAMPLE_RATE # Low frequency sampling - # raise NotImplementedError("Low sample rate does not work yet") - # elif path_raw_input.stem[4:6] == "HI": - # sample_rate = HIGH_SAMPLE_RATE # High frequency sampling - # else: - # raise RuntimeError("Unknown number of channels") - # - # if path_raw_input.stem[0] == "0": - # num_channels = 2 # Stereo - # elif path_raw_input.stem[0] == "1": - # num_channels = 1 # Mono - # else: - # raise RuntimeError("Unknown number of channels") - - sample_rate = HIGH_SAMPLE_RATE - num_channels = 2 - with path_raw_input.open("rb") as f: - raw_data = f.read() - - # 32-bit PCM uses numpy dtype int32 - audio_data = np.frombuffer(raw_data, dtype=np.int32) - - if num_channels == 2: - audio_data = audio_data.reshape(-1, 2) - - # Upsample the low sample rate to high sample rate by linear interpolation - if sample_rate == LOW_SAMPLE_RATE: - xp = np.linspace(0, audio_data.shape[0], audio_data.shape[0]) - x = np.linspace(0, audio_data.shape[0], audio_data.shape[0] * (HIGH_SAMPLE_RATE / LOW_SAMPLE_RATE)) - if num_channels == 1: - audio_data = np.interp(x, xp, audio_data) - else: - fp0 = np.interp(x, xp, audio_data[:, 0]) - fp1 = np.interp(x, xp, audio_data[:, 1]) - audio_data = np.stack([fp0, fp1], axis=1) - - print(audio_data.shape) - - # Save the audio data as a WAV file - write(filename=str(path_wav_output), rate=s_rate, data=audio_data) + if ( + path_raw_input.is_file() + and path_raw_input.suffix == "" + and is_audio_file_name(path_raw_input.stem) + ): + process_audio_file(path_raw_input, LOW_SAMPLE_RATE, HIGH_SAMPLE_RATE) + +def get_kth_latest(folder_path, k=1): + """ + Get the path of the k-th largest subfolder based on 'y' in the subfolder names 'x_y'. + + Args: + folder_path (str): Path to the main folder containing subfolders named 'x_y'. + k (int): The rank of the subfolder to retrieve (1-based). + + Returns: + str: Path of the k-th largest subfolder based on 'y'. + + Raises: + ValueError: If 'k' is invalid or there are not enough valid subfolders. + """ + # List to store subfolders with extracted y-values + subfolders_with_y = [] + + # Iterate over subfolders + for subfolder in os.listdir(folder_path): + subfolder_path = os.path.join(folder_path, subfolder) + if os.path.isdir(subfolder_path): + # Split subfolder name into x and y + try: + x, y = subfolder.split('_') + y = int(y) # Convert y to an integer + subfolders_with_y.append((y, subfolder_path)) + except (ValueError, IndexError): + # Skip subfolders that don't match the 'x_y' pattern + pass + + # Sort subfolders by y in descending order + subfolders_with_y.sort(reverse=True, key=lambda item: item[0]) + + # Validate k + if k < 1 or k > len(subfolders_with_y): + raise ValueError(f"Invalid value of k: {k}. Must be between 1 and {len(subfolders_with_y)}.") + + # Return the path of the k-th largest subfolder + return subfolders_with_y[k - 1][1] def main(): @@ -68,9 +155,9 @@ def main(): # parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') # parser.add_argument('--fn', required=True, help='Please enter the path to the file') # args = parser.parse_args() + midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/new_collection/tech_pilot_1/midge_data/" midge_id = 54 - midge_folder = os.path.join('/home/zonghuan/tudelft/projects/datasets/new_collection/tech_pilot_1/midge_data/', - str(midge_id), '65535_1730466472') + midge_folder = get_kth_latest(os.path.join(midge_data_parent, str(midge_id)), 1) deal_audio(fn=midge_folder) # add something at home @@ -78,4 +165,59 @@ def main(): if __name__ == '__main__': main() - +# import numpy as np +# from scipy.io.wavfile import write +# from pathlib import Path +# +# HIGH_SAMPLE_RATE = 8000 +# LOW_SAMPLE_RATE = 1250 +# +# +# def main(fn): +# data_folder = Path(fn) +# for path_raw_input in sorted(data_folder.iterdir()): +# if (path_raw_input.is_file() and path_raw_input.suffix == "" and +# ("MICLO" in path_raw_input.stem or "MICHI" in path_raw_input.stem)): +# +# print("Raw input file " + str(path_raw_input)) +# +# path_wav_output = path_raw_input.parent / (path_raw_input.stem + ".wav") +# +# if path_raw_input.stem[4:6] == "LO": +# sample_rate = LOW_SAMPLE_RATE # Low frequency sampling +# buffer_dtype = np.int16 # 16-bit PCM +# elif path_raw_input.stem[4:6] == "HI": +# sample_rate = HIGH_SAMPLE_RATE # High frequency sampling +# buffer_dtype = np.int32 # 32-bit PCM +# else: +# raise RuntimeError("Unknown number of channels") +# +# if path_raw_input.stem[0] == "0": +# num_channels = 2 # Stereo +# elif path_raw_input.stem[0] == "1": +# num_channels = 1 # Mono +# else: +# raise RuntimeError("Unknown number of channels") +# +# with path_raw_input.open("rb") as f: +# raw_data = f.read() +# +# audio_data = np.frombuffer(raw_data, dtype=buffer_dtype) +# +# if num_channels == 2: +# audio_data = audio_data.reshape(-1, 2) +# +# # Save the data in a WAV file +# write(filename=str(path_wav_output), rate=sample_rate, data=audio_data) +# +# +# if __name__ == '__main__': +# """ +# Takes as input a folder with RAW audio files from the midge and saves them as .wav files. +# """ +# import argparse +# +# parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') +# parser.add_argument('--fn', required=True, help='Please enter the path to the file') +# args = parser.parse_args() +# main(fn=args.fn) diff --git a/BadgeFramework/imu_parser_V0.py b/BadgeFramework/imu_parser_V0.py index 2bfdfd4..7ed11fd 100644 --- a/BadgeFramework/imu_parser_V0.py +++ b/BadgeFramework/imu_parser_V0.py @@ -4,16 +4,63 @@ import numpy as np import pandas as pd import seaborn as sns +import argparse +from audio_parser_V0 import get_kth_latest +import os + +def get_sensor_paths(folder_path: str) -> dict: + """ + Returns 5 lists of file names that end with specific suffixes. + + Args: + folder_path (str): Path to the folder. + + Returns: + tuple: Five lists of file names matching the suffixes: + - "_accel" + - "_gyr" + - "mag" + - "_rotation" + - "_proximity" + """ + # Initialize lists for each suffix + files = { + 'accel': [], 'gyr': [], 'mag': [], 'rotation': [], 'proximity': [] + } + + # Iterate through all files in the folder + for file_name in os.listdir(folder_path): + if os.path.isfile(os.path.join(folder_path, file_name)): + if file_name.endswith("_accel"): + files['accel'].append(file_name) + elif file_name.endswith("_gyr"): + files['gyr'].append(file_name) + elif file_name.endswith("mag"): + files['mag'].append(file_name) + elif file_name.endswith("_rotation"): + files['rotation'].append(file_name) + elif file_name.endswith("_proximity"): + files['proximity'].append(file_name) + + check_multiple_sensor_files(files) + return files + +def check_multiple_sensor_files(files): + for file_list in files.values(): + if len(file_list) == 0: + print("Warning: No sensor files found!") + if len(file_list) > 1: + print('Warning: Multiple sensor files detected!') class IMUParser(object): - - def __init__(self,filename): - self.filename = filename - self.path_accel = filename+'ACC_0' - self.path_gyro = filename+'GYR_0' - self.path_mag = filename+'MAG_0' - self.path_rotation = filename+'ROT_0' - self.path_scan = filename+'SCAN_0' + def __init__(self, record_path): + sensor_files = get_sensor_paths(record_path) + self.record_path = record_path + self.path_accel = os.path.join(record_path, sensor_files['accel'][0]) + self.path_gyro = os.path.join(record_path, sensor_files['gyr'][0]) + self.path_mag = os.path.join(record_path, sensor_files['mag'][0]) + self.path_rotation = os.path.join(record_path, sensor_files['rotation'][0]) + self.path_scan = os.path.join(record_path, sensor_files['proximity'][0]) def parse_generic(self,sensorname): data = [] @@ -116,17 +163,17 @@ def parse_rot(self): def plot_and_save(self,a,g,m): if a: - fname = self.filename + '_accel.png' + fname = self.record_path + '_accel.png' ax = self.accel_df.plot(x='time') fig = ax.get_figure() fig.savefig(fname) if g: - fname = self.filename + '_gyro.png' + fname = self.record_path + '_gyro.png' ax = self.gyro_df.plot(x='time') fig = ax.get_figure() fig.savefig(fname) if m: - fname = self.filename + '_mag.png' + fname = self.record_path + '_mag.png' ax = self.mag_df.plot(x='time') fig = ax.get_figure() fig.savefig(fname) @@ -158,7 +205,7 @@ def str2bool(v): else: raise argparse.ArgumentTypeError('Boolean value expected.') -def main(fn,acc,mag,gyr,rot,plot,scan): +def parse_imu(fn,acc,mag,gyr,rot,plot,scan): parser = IMUParser(fn) if acc: parser.parse_accel() @@ -174,22 +221,25 @@ def main(fn,acc,mag,gyr,rot,plot,scan): if plot: parser.plot_and_save(acc,mag,gyr) -if __name__ == '__main__': - import argparse +def get_default_file_path(): + midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/new_collection/tech_pilot_1/midge_data/" + midge_id = 54 + return get_kth_latest(os.path.join(midge_data_parent, str(midge_id)), 1) + +def main(): parser = argparse.ArgumentParser(description='Parser for the IMU data obtained from Minge Midges\ - (Acceleration, Gyroscope, Magnetometer, Rotation)') - parser.add_argument('--fn', required=True,help='Please enter the path to the file') - parser.add_argument('--acc', default=True,type=str2bool ,help='Check to parse and save acceleration data') - parser.add_argument('--mag', default=True,type=str2bool ,help='Check to parse and save magnetometer data') - parser.add_argument('--gyr', default=True,type=str2bool,help='Check to parse and save gyroscope data') - parser.add_argument('--scan', default=True,type=str2bool,help='Check to parse and save scan data') - parser.add_argument('--rot', default=True,type=str2bool ,help='Check to parse and save rotation data') - parser.add_argument('--plot', default=True,type=str2bool ,help='Check to plot the parsed data') + (Acceleration, Gyroscope, Magnetometer, Rotation)') + parser.add_argument('--fn', default=get_default_file_path(), help='Please enter the path to the file') + parser.add_argument('--acc', default=True, type=str2bool, help='Check to parse and save acceleration data') + parser.add_argument('--mag', default=True, type=str2bool, help='Check to parse and save magnetometer data') + parser.add_argument('--gyr', default=True, type=str2bool, help='Check to parse and save gyroscope data') + parser.add_argument('--scan', default=True, type=str2bool, help='Check to parse and save scan data') + parser.add_argument('--rot', default=True, type=str2bool, help='Check to parse and save rotation data') + parser.add_argument('--plot', default=True, type=str2bool, help='Check to plot the parsed data') args = parser.parse_args() - main(fn=args.fn, acc=args.acc, mag=args.mag, gyr=args.gyr, rot=args.rot, plot=args.plot, scan=args.scan) - - - - - + parse_imu(fn=args.fn, acc=args.acc, mag=args.mag, gyr=args.gyr, rot=args.rot, plot=args.plot, scan=args.scan) +if __name__ == '__main__': + main() + # Example command + # python ./imu_parser_V0.py --fn ../midge_0_files/ --scan TRUE --acc TRUE --mag TRUE --rot TRUE --gyr TRUE --rot TRUE --plot True From 780f15f958922edccd0e72220477d5537bb2e7a3 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 9 Dec 2024 15:36:15 +0100 Subject: [PATCH 13/46] Try to parse imu files and check timestamp --- BadgeFramework/imu_parser_V0.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/BadgeFramework/imu_parser_V0.py b/BadgeFramework/imu_parser_V0.py index 7ed11fd..97ea81f 100644 --- a/BadgeFramework/imu_parser_V0.py +++ b/BadgeFramework/imu_parser_V0.py @@ -76,9 +76,11 @@ def parse_generic(self,sensorname): x,y,z = struct.unpack(' thres] + timestamp_good = [x for x in timestamps if x < 1e13] + good_diff = [timestamp_good[x + 1] - timestamp_good[x] for x in range(len(timestamp_good) - 2)] + d = [x for x in good_diff if x > 20 or x < 10] + c = 9 + def parse_scanner(self): data = [] timestamps = [] From bf577bbe1067f8365577d903417289d68e49cc69 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 9 Dec 2024 18:34:54 +0100 Subject: [PATCH 14/46] Add some matlab scripts for better visualization --- BadgeFramework/imu_parser_V0.py | 10 +-- BadgeFramework/matlab/IMUParser.m | 89 +++++++++++++++++++ BadgeFramework/matlab/get_kth_latest.m | 57 ++++++++++++ BadgeFramework/matlab/get_sensor_paths.m | 40 +++++++++ BadgeFramework/matlab/main.m | 21 +++++ BadgeFramework/matlab/parse_generic.m | 32 +++++++ BadgeFramework/matlab/plot_xyz.m | 24 +++++ .../matlab/remove_large_time_rows.m | 21 +++++ 8 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 BadgeFramework/matlab/IMUParser.m create mode 100644 BadgeFramework/matlab/get_kth_latest.m create mode 100644 BadgeFramework/matlab/get_sensor_paths.m create mode 100644 BadgeFramework/matlab/main.m create mode 100644 BadgeFramework/matlab/parse_generic.m create mode 100644 BadgeFramework/matlab/plot_xyz.m create mode 100644 BadgeFramework/matlab/remove_large_time_rows.m diff --git a/BadgeFramework/imu_parser_V0.py b/BadgeFramework/imu_parser_V0.py index 97ea81f..5c286f0 100644 --- a/BadgeFramework/imu_parser_V0.py +++ b/BadgeFramework/imu_parser_V0.py @@ -83,8 +83,8 @@ def parse_generic(self,sensorname): data_xyz = np.asarray(data) timestamps = np.asarray(timestamps) - timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] - df = pd.DataFrame(timestamps_dt, columns=['time']) + # timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] + df = pd.DataFrame(timestamps, columns=['time']) df['X'] = data_xyz[:,0] df['Y'] = data_xyz[:,1] df['Z'] = data_xyz[:,2] @@ -164,8 +164,8 @@ def parse_rot(self): break rotation_xyz = np.asarray(rotation) timestamps = np.asarray(timestamps) - timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] - df = pd.DataFrame(timestamps_dt, columns=['time']) + # timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] + df = pd.DataFrame(timestamps, columns=['time']) df['a'] = rotation_xyz[:,0] df['b'] = rotation_xyz[:,1] df['c'] = rotation_xyz[:,2] @@ -244,7 +244,7 @@ def main(): parser.add_argument('--acc', default=True, type=str2bool, help='Check to parse and save acceleration data') parser.add_argument('--mag', default=True, type=str2bool, help='Check to parse and save magnetometer data') parser.add_argument('--gyr', default=True, type=str2bool, help='Check to parse and save gyroscope data') - parser.add_argument('--scan', default=True, type=str2bool, help='Check to parse and save scan data') + parser.add_argument('--scan', default=False, type=str2bool, help='Check to parse and save scan data') parser.add_argument('--rot', default=True, type=str2bool, help='Check to parse and save rotation data') parser.add_argument('--plot', default=True, type=str2bool, help='Check to plot the parsed data') args = parser.parse_args() diff --git a/BadgeFramework/matlab/IMUParser.m b/BadgeFramework/matlab/IMUParser.m new file mode 100644 index 0000000..bb0507a --- /dev/null +++ b/BadgeFramework/matlab/IMUParser.m @@ -0,0 +1,89 @@ +classdef IMUParser + properties + path_accel + path_gyro + path_mag + path_rotation + path_scan + + accel_df + gyro_df + mag_df + rot_df + scan_df + end + + methods + function obj = IMUParser(record_path) + sensor_files = get_sensor_paths(record_path); + obj.path_accel = sensor_files.accel{1}; + obj.path_gyro = sensor_files.gyr{1}; + obj.path_mag = sensor_files.mag{1}; + obj.path_rotation = sensor_files.rotation{1}; + obj.path_scan = sensor_files.proximity{1}; + end + + function obj = parse_accel(obj) + obj.accel_df = parse_generic(obj.path_accel); + end + + function obj = parse_gyro(obj) + obj.gyro_df = parse_generic(obj.path_gyro); + end + + function obj = parse_mag(obj) + obj.mag_df = parse_generic(obj.path_mag); + end + + function obj = parse_rot(obj) + fid = fopen(obj.path_rotation, 'rb'); + raw_data = fread(fid); + fclose(fid); + + block_size = 24; + + % Define block size and calculate the number of blocks + num_blocks = floor(length(raw_data) / block_size); + + % Extract all blocks in one go + blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); + + % Extract timestamps + timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block + timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); + + % Extract data dimensions (4 sets of 4-byte floats) + data_bytes = blocks(9:24, :); % Bytes 9 to 24 for each block + data_flat = typecast(uint8(data_bytes(:)), 'single'); + data = reshape(data_flat, 4, num_blocks)'; + + % Convert timestamps to datetime + timestamps_dt = datetime(timestamps / 1000, 'ConvertFrom', 'posixtime'); + + % Create a table + obj.rot_df = table(timestamps, data(:,1), data(:,2), data(:,3), ... + data(:,4), 'VariableNames', {'time', 'a', 'b', 'c', 'd'}); + + obj.rot_df = remove_large_time_rows(obj.rot_df); + end + + function save_dataframes(obj, acc, gyr, mag, rot) + if acc && ~isempty(obj.accel_df) + writetable(obj.accel_df, [obj.path_accel, '.csv']); + save([obj.path_accel, '.mat'], 'obj.accel_df'); + end + if gyr && ~isempty(obj.gyro_df) + writetable(obj.gyro_df, [obj.path_gyro, '.csv']); + save([obj.path_gyro, '.mat'], 'obj.gyro_df'); + end + if mag && ~isempty(obj.mag_df) + writetable(obj.mag_df, [obj.path_mag, '.csv']); + save([obj.path_mag, '.mat'], 'obj.mag_df'); + end + if rot && ~isempty(obj.rot_df) + writetable(obj.rot_df, [obj.path_rotation, '.csv']); + save([obj.path_rotation, '.mat'], 'obj.rot_df'); + end + end + end +end diff --git a/BadgeFramework/matlab/get_kth_latest.m b/BadgeFramework/matlab/get_kth_latest.m new file mode 100644 index 0000000..873a4cb --- /dev/null +++ b/BadgeFramework/matlab/get_kth_latest.m @@ -0,0 +1,57 @@ +function subfolder_path = get_kth_latest(folder_path, k) + % GET_KTH_LATEST Get the path of the k-th largest subfolder based on 'y' in the subfolder names 'x_y'. + % + % Args: + % folder_path (char): Path to the main folder containing subfolders named 'x_y'. + % k (int): The rank of the subfolder to retrieve (1-based). + % + % Returns: + % subfolder_path (char): Path of the k-th largest subfolder based on 'y'. + % + % Raises: + % Error if 'k' is invalid or there are not enough valid subfolders. + + % Get a list of all items in the folder + items = dir(folder_path); + + % Initialize a cell array to store subfolders with extracted y-values + subfolders_with_y = {}; + + % Iterate over all items + for i = 1:length(items) + if items(i).isdir && ~strcmp(items(i).name, '.') && ~strcmp(items(i).name, '..') + subfolder_name = items(i).name; + subfolder_path = fullfile(folder_path, subfolder_name); + + % Split the subfolder name into 'x' and 'y' + tokens = split(subfolder_name, '_'); + if length(tokens) == 2 + try + y = str2double(tokens{2}); + if ~isnan(y) + subfolders_with_y{end+1, 1} = y; % Store the y-value + subfolders_with_y{end, 2} = subfolder_path; % Store the subfolder path + end + catch + % Skip subfolders that don't match the 'x_y' pattern + end + end + end + end + + % Check if there are valid subfolders + if isempty(subfolders_with_y) + error('No valid subfolders found in the specified folder.'); + end + + % Sort the subfolders by y in descending order + subfolders_with_y = sortrows(subfolders_with_y, -1); + + % Validate k + if k < 1 || k > size(subfolders_with_y, 1) + error('Invalid value of k: %d. Must be between 1 and %d.', k, size(subfolders_with_y, 1)); + end + + % Return the path of the k-th largest subfolder + subfolder_path = subfolders_with_y{k, 2}; +end \ No newline at end of file diff --git a/BadgeFramework/matlab/get_sensor_paths.m b/BadgeFramework/matlab/get_sensor_paths.m new file mode 100644 index 0000000..4a30aba --- /dev/null +++ b/BadgeFramework/matlab/get_sensor_paths.m @@ -0,0 +1,40 @@ +function sensor_files = get_sensor_paths(folder_path) + % Returns a struct containing lists of file names matching specific suffixes. + sensor_files.accel = {}; + sensor_files.gyr = {}; + sensor_files.mag = {}; + sensor_files.rotation = {}; + sensor_files.proximity = {}; + + files = dir(folder_path); + for i = 1:length(files) + if ~files(i).isdir + file_name = files(i).name; + if endsWith(file_name, '_accel') + sensor_files.accel{end+1} = fullfile(folder_path, file_name); + elseif endsWith(file_name, '_gyr') + sensor_files.gyr{end+1} = fullfile(folder_path, file_name); + elseif endsWith(file_name, 'mag') + sensor_files.mag{end+1} = fullfile(folder_path, file_name); + elseif endsWith(file_name, '_rotation') + sensor_files.rotation{end+1} = fullfile(folder_path, file_name); + elseif endsWith(file_name, '_proximity') + sensor_files.proximity{end+1} = fullfile(folder_path, file_name); + end + end + end + + check_multiple_sensor_files(sensor_files); +end + +function check_multiple_sensor_files(files) + fields = fieldnames(files); + for i = 1:numel(fields) + file_list = files.(fields{i}); + if isempty(file_list) + warning('No %s sensor files found!', fields{i}); + elseif length(file_list) > 1 + warning('Multiple %s sensor files detected!', fields{i}); + end + end +end diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m new file mode 100644 index 0000000..4e31cd5 --- /dev/null +++ b/BadgeFramework/matlab/main.m @@ -0,0 +1,21 @@ +% Main script to parse IMU data +midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/" + ... + "new_collection/tech_pilot_1/midge_data/"; +midge_id = 54; +midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 1); + +% Create IMUParser object +parser = IMUParser(midge_folder); + +% Parse data +parser = parser.parse_accel(); +parser = parser.parse_gyro(); +parser = parser.parse_mag(); +parser = parser.parse_rot(); + +plot_xyz(parser.accel_df); + +% Save parsed data +parser.save_dataframes(true, true, true, true); + +disp('Parsing and saving completed.'); diff --git a/BadgeFramework/matlab/parse_generic.m b/BadgeFramework/matlab/parse_generic.m new file mode 100644 index 0000000..593194a --- /dev/null +++ b/BadgeFramework/matlab/parse_generic.m @@ -0,0 +1,32 @@ +function df = parse_generic(file_path) + + fid = fopen(file_path, 'rb'); + raw_data = fread(fid); + fclose(fid); + + block_size = 24; + + % Define block size and calculate the number of blocks + num_blocks = floor(length(raw_data) / block_size); + + % Extract all blocks in one go + blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); + + % Extract timestamps + timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block + timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); + + % Extract data dimensions (3 sets of 4-byte floats) + data_bytes = blocks(9:20, :); % Bytes 9 to 20 for each block + data_flat = typecast(uint8(data_bytes(:)), 'single'); + data = reshape(data_flat, 3, num_blocks)'; + + % Convert timestamps to datetime + timestamps_dt = datetime(timestamps / 1000, 'ConvertFrom', 'posixtime'); + + % Create a table + df = table(timestamps, data(:,1), data(:,2), data(:,3), ... + 'VariableNames', {'time', 'X', 'Y', 'Z'}); + + df = remove_large_time_rows(df); +end diff --git a/BadgeFramework/matlab/plot_xyz.m b/BadgeFramework/matlab/plot_xyz.m new file mode 100644 index 0000000..095f5df --- /dev/null +++ b/BadgeFramework/matlab/plot_xyz.m @@ -0,0 +1,24 @@ +function x = plot_xyz(T) + % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' + + % Plot the data + figure; + plot(T.time, T.X, '-r', 'DisplayName', 'X'); + hold on; + plot(T.time, T.Y, '-g', 'DisplayName', 'Y'); + plot(T.time, T.Z, '-b', 'DisplayName', 'Z'); + hold off; + + % Add labels and title + xlabel('Time'); + ylabel('Values'); + title('X, Y, and Z over Time'); + + % Add legend + legend('show'); + + % Optional: Improve grid visibility + grid on; + + x = 0; +end \ No newline at end of file diff --git a/BadgeFramework/matlab/remove_large_time_rows.m b/BadgeFramework/matlab/remove_large_time_rows.m new file mode 100644 index 0000000..9137319 --- /dev/null +++ b/BadgeFramework/matlab/remove_large_time_rows.m @@ -0,0 +1,21 @@ +function filteredTable = remove_large_time_rows(inputTable) + % REMOVE_LARGE_TIME_ROWS Removes rows where the time in the first column is > 1e13. + % + % Args: + % inputTable (table): The input table where the first column contains time values. + % + % Returns: + % filteredTable (table): The table with rows removed where time > 1e13. + + % Define the threshold + timeThreshold = 1e13; + + % Get the time values from the first column + timeValues = inputTable{:, 1}; + + % Create a logical index for rows where time <= 1e13 + validRows = timeValues <= timeThreshold; + + % Filter the table using the valid rows + filteredTable = inputTable(validRows, :); +end From 8218d591aad6a365764296141ded7ec06969da1e Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 9 Dec 2024 18:44:01 +0100 Subject: [PATCH 15/46] Add some matlab scripts for better visualization --- BadgeFramework/matlab/IMUParser.m | 7 ++++--- BadgeFramework/matlab/main.m | 3 +++ BadgeFramework/matlab/parse_generic.m | 8 +++++--- BadgeFramework/matlab/plot_abcd.m | 25 +++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 BadgeFramework/matlab/plot_abcd.m diff --git a/BadgeFramework/matlab/IMUParser.m b/BadgeFramework/matlab/IMUParser.m index bb0507a..d6e1183 100644 --- a/BadgeFramework/matlab/IMUParser.m +++ b/BadgeFramework/matlab/IMUParser.m @@ -57,14 +57,15 @@ data_flat = typecast(uint8(data_bytes(:)), 'single'); data = reshape(data_flat, 4, num_blocks)'; - % Convert timestamps to datetime - timestamps_dt = datetime(timestamps / 1000, 'ConvertFrom', 'posixtime'); - % Create a table obj.rot_df = table(timestamps, data(:,1), data(:,2), data(:,3), ... data(:,4), 'VariableNames', {'time', 'a', 'b', 'c', 'd'}); + % Remove erroneous timestamps obj.rot_df = remove_large_time_rows(obj.rot_df); + + % Convert timestamps to datetime + obj.rot_df.time = datetime(obj.rot_df.time / 1000, 'ConvertFrom', 'posixtime'); end function save_dataframes(obj, acc, gyr, mag, rot) diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index 4e31cd5..f9935a7 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -14,6 +14,9 @@ parser = parser.parse_rot(); plot_xyz(parser.accel_df); +plot_xyz(parser.gyro_df); +plot_xyz(parser.mag_df); +plot_abcd(parser.rot_df); % Save parsed data parser.save_dataframes(true, true, true, true); diff --git a/BadgeFramework/matlab/parse_generic.m b/BadgeFramework/matlab/parse_generic.m index 593194a..81bba9f 100644 --- a/BadgeFramework/matlab/parse_generic.m +++ b/BadgeFramework/matlab/parse_generic.m @@ -21,12 +21,14 @@ data_flat = typecast(uint8(data_bytes(:)), 'single'); data = reshape(data_flat, 3, num_blocks)'; - % Convert timestamps to datetime - timestamps_dt = datetime(timestamps / 1000, 'ConvertFrom', 'posixtime'); % Create a table df = table(timestamps, data(:,1), data(:,2), data(:,3), ... 'VariableNames', {'time', 'X', 'Y', 'Z'}); - + + % Remove erroneous timestamps df = remove_large_time_rows(df); + + % Convert timestamps to datetime + df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); end diff --git a/BadgeFramework/matlab/plot_abcd.m b/BadgeFramework/matlab/plot_abcd.m new file mode 100644 index 0000000..a12b5ca --- /dev/null +++ b/BadgeFramework/matlab/plot_abcd.m @@ -0,0 +1,25 @@ +function x = plot_abcd(T) + % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' + + % Plot the data + figure; + plot(T.time, T.a, '-r', 'DisplayName', 'a'); + hold on; + plot(T.time, T.b, '-g', 'DisplayName', 'b'); + plot(T.time, T.c, '-b', 'DisplayName', 'c'); + plot(T.time, T.d, '-y', 'DisplayName', 'd'); + hold off; + + % Add labels and title + xlabel('Time'); + ylabel('Values'); + title('a, b, c, and d over Time'); + + % Add legend + legend('show'); + + % Optional: Improve grid visibility + grid on; + + x = 0; +end \ No newline at end of file From 266cdab9ac8c130c169a0a7e0d0f14dc450aa0f3 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 18 Dec 2024 11:59:34 +0100 Subject: [PATCH 16/46] make plots better; check for block size 24 or 32; code optimization --- BadgeFramework/matlab/IMUParser.m | 51 +++++++++------ BadgeFramework/matlab/main.m | 22 ++++--- BadgeFramework/matlab/parse_generic.m | 65 +++++++++---------- BadgeFramework/matlab/plot_abcd.m | 48 +++++++------- BadgeFramework/matlab/plot_table.m | 40 ++++++++++++ BadgeFramework/matlab/plot_xyz.m | 24 ------- .../matlab/remove_large_time_rows.m | 3 +- 7 files changed, 143 insertions(+), 110 deletions(-) create mode 100644 BadgeFramework/matlab/plot_table.m delete mode 100644 BadgeFramework/matlab/plot_xyz.m diff --git a/BadgeFramework/matlab/IMUParser.m b/BadgeFramework/matlab/IMUParser.m index d6e1183..3a6ef28 100644 --- a/BadgeFramework/matlab/IMUParser.m +++ b/BadgeFramework/matlab/IMUParser.m @@ -11,61 +11,74 @@ mag_df rot_df scan_df + + block_size end methods - function obj = IMUParser(record_path) + function obj = IMUParser(record_path, block_size) sensor_files = get_sensor_paths(record_path); obj.path_accel = sensor_files.accel{1}; obj.path_gyro = sensor_files.gyr{1}; obj.path_mag = sensor_files.mag{1}; obj.path_rotation = sensor_files.rotation{1}; obj.path_scan = sensor_files.proximity{1}; + obj.block_size = block_size; end function obj = parse_accel(obj) - obj.accel_df = parse_generic(obj.path_accel); + obj.accel_df = obj.parse_generic(obj.path_accel, 3); end function obj = parse_gyro(obj) - obj.gyro_df = parse_generic(obj.path_gyro); + obj.gyro_df = obj.parse_generic(obj.path_gyro, 3); end function obj = parse_mag(obj) - obj.mag_df = parse_generic(obj.path_mag); + obj.mag_df = obj.parse_generic(obj.path_mag, 3); end function obj = parse_rot(obj) - fid = fopen(obj.path_rotation, 'rb'); + obj.rot_df = obj.parse_generic(obj.path_rotation, 4); + end + + function df = parse_generic(obj, file_path, axis_num) + fid = fopen(file_path, 'rb'); raw_data = fread(fid); fclose(fid); - - block_size = 24; % Define block size and calculate the number of blocks - num_blocks = floor(length(raw_data) / block_size); + num_blocks = floor(length(raw_data) / obj.block_size); % Extract all blocks in one go - blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); + blocks = reshape(raw_data(1:num_blocks * obj.block_size), ... + obj.block_size, num_blocks); % Extract timestamps timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); - % Extract data dimensions (4 sets of 4-byte floats) - data_bytes = blocks(9:24, :); % Bytes 9 to 24 for each block - data_flat = typecast(uint8(data_bytes(:)), 'single'); - data = reshape(data_flat, 4, num_blocks)'; + % Extract data dimensions (3 sets of 4-byte floats) + end_ind = 8 + axis_num * 4; + data_bytes = blocks(9:end_ind, :); % Bytes 9 to 20 for each block + data = typecast(uint8(data_bytes(:)), 'single'); + data = reshape(data, axis_num, num_blocks)'; % Create a table - obj.rot_df = table(timestamps, data(:,1), data(:,2), data(:,3), ... - data(:,4), 'VariableNames', {'time', 'a', 'b', 'c', 'd'}); - + if axis_num == 3 + % accel, gyro, magnitude + df = table(timestamps, data(:,1), data(:,2), data(:,3), ... + 'VariableNames', {'time', 'x', 'y', 'z'}); + elseif axis_num == 4 + % rotation + df = table(timestamps, data(:,1), data(:,2), data(:,3), ... + data(:,4), 'VariableNames', {'time', 'x', 'y', 'z', 'w'}); + end % Remove erroneous timestamps - obj.rot_df = remove_large_time_rows(obj.rot_df); - + df = remove_large_time_rows(df); + % Convert timestamps to datetime - obj.rot_df.time = datetime(obj.rot_df.time / 1000, 'ConvertFrom', 'posixtime'); + df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); end function save_dataframes(obj, acc, gyr, mag, rot) diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index f9935a7..1c3a7f0 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -1,11 +1,13 @@ +clear variables; close all; + % Main script to parse IMU data midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/" + ... "new_collection/tech_pilot_1/midge_data/"; -midge_id = 54; -midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 1); +midge_id = 59; +midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 2); % Create IMUParser object -parser = IMUParser(midge_folder); +parser = IMUParser(midge_folder, 24); % Parse data parser = parser.parse_accel(); @@ -13,12 +15,16 @@ parser = parser.parse_mag(); parser = parser.parse_rot(); -plot_xyz(parser.accel_df); -plot_xyz(parser.gyro_df); -plot_xyz(parser.mag_df); -plot_abcd(parser.rot_df); +plot_table(parser.accel_df, 'accelerometer'); +plot_table(parser.gyro_df, 'gyro'); +plot_table(parser.mag_df, 'magnitude'); +plot_table(parser.rot_df, 'rotation'); % Save parsed data -parser.save_dataframes(true, true, true, true); +% parser.save_dataframes(true, true, true, true); disp('Parsing and saving completed.'); + +bs24 = [12, 19, 24, 32, 33, 34, 35, 37, 45, 48, 54, 57, 59]; +bs32 = [24, 25, 28, 29]; +problematic = [19, 34, 35, 59]; \ No newline at end of file diff --git a/BadgeFramework/matlab/parse_generic.m b/BadgeFramework/matlab/parse_generic.m index 81bba9f..b84acad 100644 --- a/BadgeFramework/matlab/parse_generic.m +++ b/BadgeFramework/matlab/parse_generic.m @@ -1,34 +1,33 @@ -function df = parse_generic(file_path) +% function df = parse_generic(file_path, block_size) +% +% fid = fopen(file_path, 'rb'); +% raw_data = fread(fid); +% fclose(fid); +% +% % Define block size and calculate the number of blocks +% num_blocks = floor(length(raw_data) / block_size); +% +% % Extract all blocks in one go +% blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); +% +% % Extract timestamps +% timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block +% timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); +% +% % Extract data dimensions (3 sets of 4-byte floats) +% data_bytes = blocks(9:20, :); % Bytes 9 to 20 for each block +% data_flat = typecast(uint8(data_bytes(:)), 'single'); +% data = reshape(data_flat, 3, num_blocks)'; +% +% +% % Create a table +% df = table(timestamps, data(:,1), data(:,2), data(:,3), ... +% 'VariableNames', {'time', 'X', 'Y', 'Z'}); +% +% % Remove erroneous timestamps +% df = remove_large_time_rows(df); +% +% % Convert timestamps to datetime +% df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); +% end - fid = fopen(file_path, 'rb'); - raw_data = fread(fid); - fclose(fid); - - block_size = 24; - - % Define block size and calculate the number of blocks - num_blocks = floor(length(raw_data) / block_size); - - % Extract all blocks in one go - blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); - - % Extract timestamps - timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block - timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); - - % Extract data dimensions (3 sets of 4-byte floats) - data_bytes = blocks(9:20, :); % Bytes 9 to 20 for each block - data_flat = typecast(uint8(data_bytes(:)), 'single'); - data = reshape(data_flat, 3, num_blocks)'; - - - % Create a table - df = table(timestamps, data(:,1), data(:,2), data(:,3), ... - 'VariableNames', {'time', 'X', 'Y', 'Z'}); - - % Remove erroneous timestamps - df = remove_large_time_rows(df); - - % Convert timestamps to datetime - df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); -end diff --git a/BadgeFramework/matlab/plot_abcd.m b/BadgeFramework/matlab/plot_abcd.m index a12b5ca..576aec8 100644 --- a/BadgeFramework/matlab/plot_abcd.m +++ b/BadgeFramework/matlab/plot_abcd.m @@ -1,25 +1,23 @@ -function x = plot_abcd(T) - % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' - - % Plot the data - figure; - plot(T.time, T.a, '-r', 'DisplayName', 'a'); - hold on; - plot(T.time, T.b, '-g', 'DisplayName', 'b'); - plot(T.time, T.c, '-b', 'DisplayName', 'c'); - plot(T.time, T.d, '-y', 'DisplayName', 'd'); - hold off; - - % Add labels and title - xlabel('Time'); - ylabel('Values'); - title('a, b, c, and d over Time'); - - % Add legend - legend('show'); - - % Optional: Improve grid visibility - grid on; - - x = 0; -end \ No newline at end of file +% function plot_abcd(T, plot_title) +% % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' +% +% % Plot the data +% figure; +% plot(T.time, T.a, '-r', 'DisplayName', 'a'); +% hold on; +% plot(T.time, T.b, '-g', 'DisplayName', 'b'); +% plot(T.time, T.c, '-b', 'DisplayName', 'c'); +% plot(T.time, T.d, '-y', 'DisplayName', 'd'); +% hold off; +% +% % Add labels and title +% xlabel('Time'); +% ylabel('Values'); +% title(plot_title); +% +% % Add legend +% legend('show'); +% +% % Optional: Improve grid visibility +% grid on; +% end \ No newline at end of file diff --git a/BadgeFramework/matlab/plot_table.m b/BadgeFramework/matlab/plot_table.m new file mode 100644 index 0000000..f3f8900 --- /dev/null +++ b/BadgeFramework/matlab/plot_table.m @@ -0,0 +1,40 @@ +function plot_table(T, plot_title) + % Assuming 'T' is a table where the first column is 'time' and the rest are data columns + assert (length(T.time) == length(unique(T.time))); + + % Extract time and data + time = T{:, 1}; % The first column is 'time' + data = T{:, 2:end}; % All other columns contain the data to be plotted + + % Generate labels dynamically based on the number of columns + num_series = size(data, 2); % Number of data columns + labels = 'xyzwuv'; % Possible labels + plot_labels = cell(1, num_series); + for i = 1:num_series + plot_labels{i} = labels(i); + end + + % Define a set of colors to cycle through + colors = {'r', 'g', 'b', 'y', 'm', 'c', 'k'}; + num_colors = length(colors); + + % Create the figure and plot each series + figure; + hold on; + for i = 1:num_series + color = colors{mod(i - 1, num_colors) + 1}; % Cycle through colors + plot(time, data(:, i), '-', 'Color', color, 'DisplayName', plot_labels{i}); + end + hold off; + + % Add labels and title + xlabel('Time'); + ylabel('Values'); + title(plot_title); + + % Add legend + legend('show'); + + % Improve grid visibility + grid on; +end \ No newline at end of file diff --git a/BadgeFramework/matlab/plot_xyz.m b/BadgeFramework/matlab/plot_xyz.m deleted file mode 100644 index 095f5df..0000000 --- a/BadgeFramework/matlab/plot_xyz.m +++ /dev/null @@ -1,24 +0,0 @@ -function x = plot_xyz(T) - % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' - - % Plot the data - figure; - plot(T.time, T.X, '-r', 'DisplayName', 'X'); - hold on; - plot(T.time, T.Y, '-g', 'DisplayName', 'Y'); - plot(T.time, T.Z, '-b', 'DisplayName', 'Z'); - hold off; - - % Add labels and title - xlabel('Time'); - ylabel('Values'); - title('X, Y, and Z over Time'); - - % Add legend - legend('show'); - - % Optional: Improve grid visibility - grid on; - - x = 0; -end \ No newline at end of file diff --git a/BadgeFramework/matlab/remove_large_time_rows.m b/BadgeFramework/matlab/remove_large_time_rows.m index 9137319..4bcf704 100644 --- a/BadgeFramework/matlab/remove_large_time_rows.m +++ b/BadgeFramework/matlab/remove_large_time_rows.m @@ -1,3 +1,4 @@ + function filteredTable = remove_large_time_rows(inputTable) % REMOVE_LARGE_TIME_ROWS Removes rows where the time in the first column is > 1e13. % @@ -18,4 +19,4 @@ % Filter the table using the valid rows filteredTable = inputTable(validRows, :); -end +end \ No newline at end of file From c367e63cf5417b517596e947409950ed91bb7f26 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 19 Dec 2024 12:50:41 +0100 Subject: [PATCH 17/46] remove redundant files --- BadgeFramework/matlab/parse_generic.m | 33 --------------------------- BadgeFramework/matlab/plot_abcd.m | 23 ------------------- 2 files changed, 56 deletions(-) delete mode 100644 BadgeFramework/matlab/parse_generic.m delete mode 100644 BadgeFramework/matlab/plot_abcd.m diff --git a/BadgeFramework/matlab/parse_generic.m b/BadgeFramework/matlab/parse_generic.m deleted file mode 100644 index b84acad..0000000 --- a/BadgeFramework/matlab/parse_generic.m +++ /dev/null @@ -1,33 +0,0 @@ -% function df = parse_generic(file_path, block_size) -% -% fid = fopen(file_path, 'rb'); -% raw_data = fread(fid); -% fclose(fid); -% -% % Define block size and calculate the number of blocks -% num_blocks = floor(length(raw_data) / block_size); -% -% % Extract all blocks in one go -% blocks = reshape(raw_data(1:num_blocks * block_size), block_size, num_blocks); -% -% % Extract timestamps -% timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block -% timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); -% -% % Extract data dimensions (3 sets of 4-byte floats) -% data_bytes = blocks(9:20, :); % Bytes 9 to 20 for each block -% data_flat = typecast(uint8(data_bytes(:)), 'single'); -% data = reshape(data_flat, 3, num_blocks)'; -% -% -% % Create a table -% df = table(timestamps, data(:,1), data(:,2), data(:,3), ... -% 'VariableNames', {'time', 'X', 'Y', 'Z'}); -% -% % Remove erroneous timestamps -% df = remove_large_time_rows(df); -% -% % Convert timestamps to datetime -% df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); -% end - diff --git a/BadgeFramework/matlab/plot_abcd.m b/BadgeFramework/matlab/plot_abcd.m deleted file mode 100644 index 576aec8..0000000 --- a/BadgeFramework/matlab/plot_abcd.m +++ /dev/null @@ -1,23 +0,0 @@ -% function plot_abcd(T, plot_title) -% % Assuming 'T' is your table with columns 'time', 'X', 'Y', and 'Z' -% -% % Plot the data -% figure; -% plot(T.time, T.a, '-r', 'DisplayName', 'a'); -% hold on; -% plot(T.time, T.b, '-g', 'DisplayName', 'b'); -% plot(T.time, T.c, '-b', 'DisplayName', 'c'); -% plot(T.time, T.d, '-y', 'DisplayName', 'd'); -% hold off; -% -% % Add labels and title -% xlabel('Time'); -% ylabel('Values'); -% title(plot_title); -% -% % Add legend -% legend('show'); -% -% % Optional: Improve grid visibility -% grid on; -% end \ No newline at end of file From cad6e29ffac164520fbcadcfc2d37157c15eda6a Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 19 Dec 2024 14:44:44 +0100 Subject: [PATCH 18/46] adjust windows path --- BadgeFramework/matlab/main.m | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index 1c3a7f0..76bc5c4 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -1,13 +1,25 @@ clear variables; close all; % Main script to parse IMU data -midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/" + ... - "new_collection/tech_pilot_1/midge_data/"; -midge_id = 59; -midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 2); +% midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/" + ... +% "new_collection/tech_pilot_1/midge_data/"; +midge_data_parent = "C:/Users/zongh/OneDrive - Delft University of Technology" + ... + "/tudelft/projects/dataset_collection/tech_pilot_1/midge_data/"; +midge_id = 12; +midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 1); + +bs24 = [12, 19, 24, 32, 33, 34, 35, 37, 45, 48, 54, 57, 59]; +bs32 = [7, 24, 25, 28, 29]; +problematic = [19, 34, 35, 59]; + +if any(midge_id==bs24) + block_size = 24; +elseif any(midge_id==bs32) + block_size = 32; +end % Create IMUParser object -parser = IMUParser(midge_folder, 24); +parser = IMUParser(midge_folder, block_size); % Parse data parser = parser.parse_accel(); @@ -17,14 +29,9 @@ plot_table(parser.accel_df, 'accelerometer'); plot_table(parser.gyro_df, 'gyro'); -plot_table(parser.mag_df, 'magnitude'); -plot_table(parser.rot_df, 'rotation'); +% plot_table(parser.mag_df, 'magnitude'); +% plot_table(parser.rot_df, 'rotation'); % Save parsed data % parser.save_dataframes(true, true, true, true); - disp('Parsing and saving completed.'); - -bs24 = [12, 19, 24, 32, 33, 34, 35, 37, 45, 48, 54, 57, 59]; -bs32 = [24, 25, 28, 29]; -problematic = [19, 34, 35, 59]; \ No newline at end of file From 24d10534d05aa61e0eaeac924b63371749c04104 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 19 Dec 2024 18:16:10 +0100 Subject: [PATCH 19/46] save data matlab --- .gitignore | 1 + BadgeFramework/matlab/IMUParser.m | 31 ++++++--- BadgeFramework/matlab/checkbox_plot_control.m | 47 +++++++++++++ BadgeFramework/matlab/main.m | 68 ++++++++++++------- BadgeFramework/matlab/plot_table.m | 10 ++- 5 files changed, 121 insertions(+), 36 deletions(-) create mode 100644 BadgeFramework/matlab/checkbox_plot_control.m diff --git a/.gitignore b/.gitignore index 10c7cee..e60c572 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ BadgeFramework/rotation/rpi_sync.sh .idea *.log notes.txt +**/matlab/local diff --git a/BadgeFramework/matlab/IMUParser.m b/BadgeFramework/matlab/IMUParser.m index 3a6ef28..ec5af10 100644 --- a/BadgeFramework/matlab/IMUParser.m +++ b/BadgeFramework/matlab/IMUParser.m @@ -5,6 +5,7 @@ path_mag path_rotation path_scan + path_save accel_df gyro_df @@ -12,17 +13,20 @@ rot_df scan_df + midge_id block_size end methods - function obj = IMUParser(record_path, block_size) + function obj = IMUParser(record_path, midge_id, block_size, local_path) sensor_files = get_sensor_paths(record_path); obj.path_accel = sensor_files.accel{1}; obj.path_gyro = sensor_files.gyr{1}; obj.path_mag = sensor_files.mag{1}; obj.path_rotation = sensor_files.rotation{1}; obj.path_scan = sensor_files.proximity{1}; + obj.path_save = local_path; + obj.midge_id = midge_id; obj.block_size = block_size; end @@ -75,7 +79,7 @@ data(:,4), 'VariableNames', {'time', 'x', 'y', 'z', 'w'}); end % Remove erroneous timestamps - df = remove_large_time_rows(df); + % df = remove_large_time_rows(df); % Convert timestamps to datetime df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); @@ -83,21 +87,28 @@ function save_dataframes(obj, acc, gyr, mag, rot) if acc && ~isempty(obj.accel_df) - writetable(obj.accel_df, [obj.path_accel, '.csv']); - save([obj.path_accel, '.mat'], 'obj.accel_df'); + obj.save_single_csv(obj.accel_df, "acc") end if gyr && ~isempty(obj.gyro_df) - writetable(obj.gyro_df, [obj.path_gyro, '.csv']); - save([obj.path_gyro, '.mat'], 'obj.gyro_df'); + obj.save_single_csv(obj.gyro_df, "gyr") end if mag && ~isempty(obj.mag_df) - writetable(obj.mag_df, [obj.path_mag, '.csv']); - save([obj.path_mag, '.mat'], 'obj.mag_df'); + obj.save_single_csv(obj.mag_df, "mag") end if rot && ~isempty(obj.rot_df) - writetable(obj.rot_df, [obj.path_rotation, '.csv']); - save([obj.path_rotation, '.mat'], 'obj.rot_df'); + obj.save_single_csv(obj.rot_df, "rot") end + mat_file = obj.path_save + obj.midge_id + "_data.mat"; + acc_data = obj.accel_df; + gyr_data = obj.gyro_df; + mag_data = obj.mag_df; + rot_data = obj.rot_df; + save(mat_file, 'acc_data', 'gyr_data', 'mag_data', 'rot_data'); + end + + function save_single_csv(obj, var, sensor_name) + csv_file = obj.path_save + "csv/" + obj.midge_id + "_" + sensor_name + ".csv"; + writetable(var, csv_file); end end end diff --git a/BadgeFramework/matlab/checkbox_plot_control.m b/BadgeFramework/matlab/checkbox_plot_control.m new file mode 100644 index 0000000..5984216 --- /dev/null +++ b/BadgeFramework/matlab/checkbox_plot_control.m @@ -0,0 +1,47 @@ +function checkbox_plot_control + % Sample data + time = 0:0.1:10; + data1 = sin(time); + data2 = cos(time); + data3 = tanh(time); + + % Create a figure + fig = figure('Name', 'Checkbox Plot Control', 'Position', [100, 100, 800, 600]); + + % Plot the data and store the plot handles + h1 = plot(time, data1, 'r-', 'DisplayName', 'sin(t)'); + hold on; + h2 = plot(time, data2, 'g-', 'DisplayName', 'cos(t)'); + h3 = plot(time, data3, 'b-', 'DisplayName', 'tanh(t)'); + hold off; + + % Add legend + legend('show'); + + % Add labels and title + xlabel('Time'); + ylabel('Values'); + title('Plot with Checkbox Controls'); + + % Create checkboxes to control the visibility of each plot + uicontrol('Style', 'checkbox', 'String', 'Show sin(t)', ... + 'Position', [650, 500, 120, 20], 'Value', 1, ... + 'Callback', @(src, event) toggleVisibility(src, h1)); + + uicontrol('Style', 'checkbox', 'String', 'Show cos(t)', ... + 'Position', [650, 470, 120, 20], 'Value', 1, ... + 'Callback', @(src, event) toggleVisibility(src, h2)); + + uicontrol('Style', 'checkbox', 'String', 'Show tanh(t)', ... + 'Position', [650, 440, 120, 20], 'Value', 1, ... + 'Callback', @(src, event) toggleVisibility(src, h3)); +end + +% Callback function to toggle the visibility of a plot +function toggleVisibility(src, plotHandle) + if src.Value == 1 + plotHandle.Visible = 'on'; + else + plotHandle.Visible = 'off'; + end +end \ No newline at end of file diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index 76bc5c4..4262d0e 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -1,37 +1,57 @@ clear variables; close all; % Main script to parse IMU data -% midge_data_parent = "/home/zonghuan/tudelft/projects/datasets/" + ... -% "new_collection/tech_pilot_1/midge_data/"; -midge_data_parent = "C:/Users/zongh/OneDrive - Delft University of Technology" + ... - "/tudelft/projects/dataset_collection/tech_pilot_1/midge_data/"; -midge_id = 12; -midge_folder = get_kth_latest(midge_data_parent + midge_id + '/', 1); +block_sizes = zeros(1, 100); bs24 = [12, 19, 24, 32, 33, 34, 35, 37, 45, 48, 54, 57, 59]; bs32 = [7, 24, 25, 28, 29]; problematic = [19, 34, 35, 59]; +block_sizes(bs24) = 24; +block_sizes(bs32) = 32; +good_midges = setdiff(union(bs24, bs32), problematic); +local_path = "./local/"; -if any(midge_id==bs24) - block_size = 24; -elseif any(midge_id==bs32) - block_size = 32; +%% save data to mat and csv +% for k=good_midges +% save_imu_data(block_sizes, k); +% end + +%% plot +midge_id = [7, 12, 25]; +% plot_table(parser.accel_df, "accelerometer"); +% plot_table(parser.gyro_df, "gyro"); +% plot_table(parser.mag_df, "magnitude"); +% plot_table(parser.rot_df, "rotation"); + +% Save parsed data + +%% functions +function data_folder = get_data_folder() + if ispc + data_folder = "C:/Users/zongh/OneDrive - Delft University of " + ... + "Technology/tudelft/projects/dataset_collection/tech_pilot_1/midge_data/"; + elseif isunix + data_folder = "/home/zonghuan/tudelft/projects/datasets/" + ... + "new_collection/tech_pilot_1/midge_data/"; + end end -% Create IMUParser object -parser = IMUParser(midge_folder, block_size); +function save_imu_data(block_sizes, midge_id) + midge_data_parent = get_data_folder(); + midge_folder = get_kth_latest(midge_data_parent + midge_id + "/", 1); -% Parse data -parser = parser.parse_accel(); -parser = parser.parse_gyro(); -parser = parser.parse_mag(); -parser = parser.parse_rot(); + block_size = block_sizes(midge_id); -plot_table(parser.accel_df, 'accelerometer'); -plot_table(parser.gyro_df, 'gyro'); -% plot_table(parser.mag_df, 'magnitude'); -% plot_table(parser.rot_df, 'rotation'); + % Create IMUParser object + parser = IMUParser(midge_folder, midge_id, block_size, local_path); + + % Parse data + parser = parser.parse_accel(); + parser = parser.parse_gyro(); + parser = parser.parse_mag(); + parser = parser.parse_rot(); -% Save parsed data -% parser.save_dataframes(true, true, true, true); -disp('Parsing and saving completed.'); + parser.save_dataframes(true, true, true, true); + disp("Parsing and saving completed " + midge_id); + +end \ No newline at end of file diff --git a/BadgeFramework/matlab/plot_table.m b/BadgeFramework/matlab/plot_table.m index f3f8900..0f576e1 100644 --- a/BadgeFramework/matlab/plot_table.m +++ b/BadgeFramework/matlab/plot_table.m @@ -1,4 +1,4 @@ -function plot_table(T, plot_title) +function plot_table(T, plot_title, fig_handle) % Assuming 'T' is a table where the first column is 'time' and the rest are data columns assert (length(T.time) == length(unique(T.time))); @@ -17,9 +17,15 @@ function plot_table(T, plot_title) % Define a set of colors to cycle through colors = {'r', 'g', 'b', 'y', 'm', 'c', 'k'}; num_colors = length(colors); + + % Check if a figure handle is provided + if nargin < 3 || isempty(fig_handle) + figure; % Create a new figure if no handle is provided + else + figure(fig_handle); % Use the provided figure handle + end % Create the figure and plot each series - figure; hold on; for i = 1:num_series color = colors{mod(i - 1, num_colors) + 1}; % Cycle through colors From 15c56a4a995d4bfe2b96acdf80d7b9ef0ca87d24 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 20 Dec 2024 17:47:29 +0100 Subject: [PATCH 20/46] plot cross midge --- BadgeFramework/matlab/checkbox_plot_control.m | 50 ++++++++++---- BadgeFramework/matlab/main.m | 28 +++++--- BadgeFramework/matlab/plot_multiple_midge.m | 69 +++++++++++++++++++ .../matlab/remove_large_time_rows.m | 8 ++- 4 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 BadgeFramework/matlab/plot_multiple_midge.m diff --git a/BadgeFramework/matlab/checkbox_plot_control.m b/BadgeFramework/matlab/checkbox_plot_control.m index 5984216..70df957 100644 --- a/BadgeFramework/matlab/checkbox_plot_control.m +++ b/BadgeFramework/matlab/checkbox_plot_control.m @@ -5,8 +5,11 @@ data2 = cos(time); data3 = tanh(time); - % Create a figure - fig = figure('Name', 'Checkbox Plot Control', 'Position', [100, 100, 800, 600]); + % Create a figure with a resize callback + fig = figure; + set(fig, 'Name', 'Checkbox Plot Control', 'Position', ... + [100, 100, 800, 600], 'ResizeFcn', @resizeCheckBoxes); + % Plot the data and store the plot handles h1 = plot(time, data1, 'r-', 'DisplayName', 'sin(t)'); @@ -24,17 +27,19 @@ title('Plot with Checkbox Controls'); % Create checkboxes to control the visibility of each plot - uicontrol('Style', 'checkbox', 'String', 'Show sin(t)', ... - 'Position', [650, 500, 120, 20], 'Value', 1, ... - 'Callback', @(src, event) toggleVisibility(src, h1)); + cb1 = uicontrol('Style', 'checkbox', 'String', 'Show sin(t)', ... + 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h1)); + cb2 = uicontrol('Style', 'checkbox', 'String', 'Show cos(t)', ... + 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h2)); + cb3 = uicontrol('Style', 'checkbox', 'String', 'Show tanh(t)', ... + 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h3)); - uicontrol('Style', 'checkbox', 'String', 'Show cos(t)', ... - 'Position', [650, 470, 120, 20], 'Value', 1, ... - 'Callback', @(src, event) toggleVisibility(src, h2)); + % Store the checkboxes in a struct for easy access during resizing + checkboxes = struct('cb1', cb1, 'cb2', cb2, 'cb3', cb3); + setappdata(fig, 'checkboxes', checkboxes); - uicontrol('Style', 'checkbox', 'String', 'Show tanh(t)', ... - 'Position', [650, 440, 120, 20], 'Value', 1, ... - 'Callback', @(src, event) toggleVisibility(src, h3)); + % Initial positioning of the checkboxes + resizeCheckBoxes(fig); end % Callback function to toggle the visibility of a plot @@ -44,4 +49,25 @@ function toggleVisibility(src, plotHandle) else plotHandle.Visible = 'off'; end -end \ No newline at end of file +end + +% Resize function to adjust the position of the checkboxes +function resizeCheckBoxes(fig) + % Get figure size + figPos = fig.Position; + figWidth = figPos(3); + figHeight = figPos(4); + + % Checkbox width and height + cbWidth = 120; + cbHeight = 20; + spacing = 10; % Spacing between checkboxes + + % Get the checkboxes from the figure's app data + checkboxes = getappdata(fig, 'checkboxes'); + + % Set positions for each checkbox (top-right corner) + set(checkboxes.cb1, 'Position', [figWidth - cbWidth - spacing, figHeight - cbHeight - spacing, cbWidth, cbHeight]); + set(checkboxes.cb2, 'Position', [figWidth - cbWidth - spacing, figHeight - 2 * (cbHeight + spacing), cbWidth, cbHeight]); + set(checkboxes.cb3, 'Position', [figWidth - cbWidth - spacing, figHeight - 3 * (cbHeight + spacing), cbWidth, cbHeight]); +end diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index 4262d0e..aaa67ab 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -13,19 +13,31 @@ %% save data to mat and csv % for k=good_midges -% save_imu_data(block_sizes, k); +% save_imu_data(block_sizes, k, local_path); % end %% plot -midge_id = [7, 12, 25]; -% plot_table(parser.accel_df, "accelerometer"); -% plot_table(parser.gyro_df, "gyro"); -% plot_table(parser.mag_df, "magnitude"); -% plot_table(parser.rot_df, "rotation"); +midges = [7, 12, 25, 48]; +data = cell(1, length(midges)); +for k=1:length(midges) + midge_id = midges(k); + data{k} = load(local_path + midge_id + "_data.mat"); + % remove large timestamps + data{k} = filter_midge_data(data{k}); + data{k}.midge_id = midge_id; +end -% Save parsed data +plot_options = logical([1, 1, 0, 0]); +plot_multiple_midge(data, plot_options, false); %% functions +function filteredData = filter_midge_data(midge) + filteredData.acc_data = remove_large_time_rows(midge.acc_data); + filteredData.gyr_data = remove_large_time_rows(midge.gyr_data); + filteredData.mag_data = remove_large_time_rows(midge.mag_data); + filteredData.rot_data = remove_large_time_rows(midge.rot_data); +end + function data_folder = get_data_folder() if ispc data_folder = "C:/Users/zongh/OneDrive - Delft University of " + ... @@ -36,7 +48,7 @@ end end -function save_imu_data(block_sizes, midge_id) +function save_imu_data(block_sizes, midge_id, local_path) midge_data_parent = get_data_folder(); midge_folder = get_kth_latest(midge_data_parent + midge_id + "/", 1); diff --git a/BadgeFramework/matlab/plot_multiple_midge.m b/BadgeFramework/matlab/plot_multiple_midge.m new file mode 100644 index 0000000..3b759fb --- /dev/null +++ b/BadgeFramework/matlab/plot_multiple_midge.m @@ -0,0 +1,69 @@ +function plot_multiple_midge(data, plot_options, use_mag) + % Function to plot acc, gyr, mag, and rot data for multiple badges. + % badge_data: a cell array where each cell contains sensor data tables for one badge. + % + % Example: badge_data{1}.acc_data, badge_data{1}.gyr_data, etc. + + % Define sensor types and titles + sensor_types = {'acc', 'gyr', 'mag', 'rot'}; + sensor_titles = {'Accelerometer', 'Gyroscope', 'Magnetometer', 'Rotation'}; + + % Define colors for each axis: x, y, z, w + % Red for x, Green for y, Blue for z, Magenta for w (if applicable) + axis_colors = {'r', 'g', 'b', 'm'}; + + % Number of badges + num_badges = length(data); + sensor_axes = 'xyzwuv'; + + % Loop through each sensor type + for s = 1:length(sensor_types) + if ~plot_options(s) + continue; + end + figure('Name', sensor_titles{s}, 'NumberTitle', 'off'); + + % Loop through each badge to create a subplot for each badge's data + for b = 1:num_badges + + % Get the current badge's sensor data + sensor_field = [sensor_types{s}, '_data']; % e.g., 'acc_data' + data_single = data{b}.(sensor_field); + + % Extract timestamps and values + time = data_single{:, 1}; % First column is the timestamp + values = data_single{:, 2:end}; % Remaining columns are x, y, z, (possibly w) + num_axes = size(values, 2); + + if use_mag + % Compute the magnitude of the sensor data + mag_values = sqrt(sum(values.^2, 2)); + end + + % Create subplot with vertical spacing + subplot(num_badges, 1, b); + hold on; + if use_mag + plot(time, mag_values, 'k-', 'DisplayName', 'Magnitude'); + else + for a = 1:num_axes + % axis = axes(a); + plot(time, values(:, a), 'Color', axis_colors{a}, ... + 'DisplayName', sensor_axes(a)); + end + end + hold off; + + % Add labels and legend + ylabel(['Badge ', num2str(data{b}.midge_id)]); + if b == 1 + title([sensor_titles{s}, ' Data']); + end + if b == num_badges + xlabel('Timestamp'); + end + legend('show'); + grid on; + end + end +end diff --git a/BadgeFramework/matlab/remove_large_time_rows.m b/BadgeFramework/matlab/remove_large_time_rows.m index 4bcf704..7c49c3f 100644 --- a/BadgeFramework/matlab/remove_large_time_rows.m +++ b/BadgeFramework/matlab/remove_large_time_rows.m @@ -10,12 +10,18 @@ % Define the threshold timeThreshold = 1e13; + % Get the time values from the first column timeValues = inputTable{:, 1}; % Create a logical index for rows where time <= 1e13 - validRows = timeValues <= timeThreshold; + try + validRows = timeValues <= timeThreshold; + catch + timeThreshold = datetime(1e13 / 1000, 'ConvertFrom', 'posixtime'); + validRows = timeValues <= timeThreshold; + end % Filter the table using the valid rows filteredTable = inputTable(validRows, :); From a554b3d950dddcc3ebfe48cb292cbd01a5dc42d3 Mon Sep 17 00:00:00 2001 From: Kevinlzh9802 Date: Sat, 21 Dec 2024 00:29:28 +0100 Subject: [PATCH 21/46] multiple plots by sensor and midge --- BadgeFramework/matlab/main.m | 6 +- BadgeFramework/matlab/plot_multiple_midge.m | 84 ++++++++++--------- .../{plot_table.m => plot_single_midge.m} | 8 +- BadgeFramework/matlab/plot_single_sensor.m | 26 ++++++ 4 files changed, 79 insertions(+), 45 deletions(-) rename BadgeFramework/matlab/{plot_table.m => plot_single_midge.m} (81%) create mode 100644 BadgeFramework/matlab/plot_single_sensor.m diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m index aaa67ab..c088b7b 100644 --- a/BadgeFramework/matlab/main.m +++ b/BadgeFramework/matlab/main.m @@ -27,8 +27,12 @@ data{k}.midge_id = midge_id; end -plot_options = logical([1, 1, 0, 0]); +plot_options = struct(); +plot_options.midge = [1, 0, 0, 0]; +plot_options.sensor = [1, 1, 1, 1]; +plot_options.display = "midge"; plot_multiple_midge(data, plot_options, false); +% plot_single_midge(data{1}.acc_data, '44rr'); %% functions function filteredData = filter_midge_data(midge) diff --git a/BadgeFramework/matlab/plot_multiple_midge.m b/BadgeFramework/matlab/plot_multiple_midge.m index 3b759fb..89b1bb3 100644 --- a/BadgeFramework/matlab/plot_multiple_midge.m +++ b/BadgeFramework/matlab/plot_multiple_midge.m @@ -5,61 +5,65 @@ function plot_multiple_midge(data, plot_options, use_mag) % Example: badge_data{1}.acc_data, badge_data{1}.gyr_data, etc. % Define sensor types and titles - sensor_types = {'acc', 'gyr', 'mag', 'rot'}; - sensor_titles = {'Accelerometer', 'Gyroscope', 'Magnetometer', 'Rotation'}; + midge_info = struct(); + midge_info.ids = {}; + for k=1:length(data) + midge_info.ids{k} = data{k}.midge_id; + midge_info.names{k} = "Midge " + data{k}.midge_id; + end + midge_info.plot_options = plot_options.midge; + midge_info.plot_num_all = length(data); - % Define colors for each axis: x, y, z, w - % Red for x, Green for y, Blue for z, Magenta for w (if applicable) - axis_colors = {'r', 'g', 'b', 'm'}; + sensor_info = struct(); + sensor_info.types = {'acc', 'gyr', 'mag', 'rot'}; + sensor_info.names = {'Accelerometer', 'Gyro', 'Magnitude', 'Rotation'}; + sensor_info.plot_options = plot_options.sensor; + sensor_info.plot_num_all = length(sensor_info.types); - % Number of badges - num_badges = length(data); - sensor_axes = 'xyzwuv'; + if plot_options.display == "midge" + s1 = midge_info; + s2 = sensor_info; + elseif plot_options.display == "sensor" + s1 = sensor_info; + s2 = midge_info; + else + error("Invalid display option"); + end + + subplot_ids = find(s2.plot_options == 1); % Loop through each sensor type - for s = 1:length(sensor_types) - if ~plot_options(s) + for k1 = 1:s1.plot_num_all + if ~s1.plot_options(k1) continue; end - figure('Name', sensor_titles{s}, 'NumberTitle', 'off'); + fig = figure('Name', s1.names{k1}, 'NumberTitle', 'off'); % Loop through each badge to create a subplot for each badge's data - for b = 1:num_badges - - % Get the current badge's sensor data - sensor_field = [sensor_types{s}, '_data']; % e.g., 'acc_data' - data_single = data{b}.(sensor_field); - - % Extract timestamps and values - time = data_single{:, 1}; % First column is the timestamp - values = data_single{:, 2:end}; % Remaining columns are x, y, z, (possibly w) - num_axes = size(values, 2); - - if use_mag - % Compute the magnitude of the sensor data - mag_values = sqrt(sum(values.^2, 2)); + for k2 = 1:s2.plot_num_all + if ~s2.plot_options(k2) + continue; end - % Create subplot with vertical spacing - subplot(num_badges, 1, b); - hold on; - if use_mag - plot(time, mag_values, 'k-', 'DisplayName', 'Magnitude'); + % Get the current midge's sensor data + if plot_options.display == "midge" + sensor_field = [s2.types{k2}, '_data']; + data_single = data{k1}.(sensor_field); + elseif plot_options.display == "sensor" + sensor_field = [s1.types{k1}, '_data']; + data_single = data{k2}.(sensor_field); else - for a = 1:num_axes - % axis = axes(a); - plot(time, values(:, a), 'Color', axis_colors{a}, ... - 'DisplayName', sensor_axes(a)); - end + error("Invalid display option"); end - hold off; + subplot(length(subplot_ids), 1, k2); + plot_single_sensor(data_single, fig, use_mag); % Add labels and legend - ylabel(['Badge ', num2str(data{b}.midge_id)]); - if b == 1 - title([sensor_titles{s}, ' Data']); + ylabel(s2.names(k2)); + if k2 == subplot_ids(1) + title(s1.names{k1}); end - if b == num_badges + if k2 == subplot_ids(end) xlabel('Timestamp'); end legend('show'); diff --git a/BadgeFramework/matlab/plot_table.m b/BadgeFramework/matlab/plot_single_midge.m similarity index 81% rename from BadgeFramework/matlab/plot_table.m rename to BadgeFramework/matlab/plot_single_midge.m index 0f576e1..7fa3d7f 100644 --- a/BadgeFramework/matlab/plot_table.m +++ b/BadgeFramework/matlab/plot_single_midge.m @@ -1,10 +1,10 @@ -function plot_table(T, plot_title, fig_handle) +function plot_single_midge(data, plot_title, fig_handle) % Assuming 'T' is a table where the first column is 'time' and the rest are data columns - assert (length(T.time) == length(unique(T.time))); + assert (length(data.time) == length(unique(data.time))); % Extract time and data - time = T{:, 1}; % The first column is 'time' - data = T{:, 2:end}; % All other columns contain the data to be plotted + time = data{:, 1}; % The first column is 'time' + data = data{:, 2:end}; % All other columns contain the data to be plotted % Generate labels dynamically based on the number of columns num_series = size(data, 2); % Number of data columns diff --git a/BadgeFramework/matlab/plot_single_sensor.m b/BadgeFramework/matlab/plot_single_sensor.m new file mode 100644 index 0000000..e04a4f9 --- /dev/null +++ b/BadgeFramework/matlab/plot_single_sensor.m @@ -0,0 +1,26 @@ +function plot_single_sensor(T, fig_handle, use_mag) + axis_colors = 'rgbymck'; + sensor_axes = 'xyzwuv'; + + time = T{:, 1}; % First column is the timestamp + values = T{:, 2:end}; % Remaining columns are x, y, z, (possibly w) + num_axes = size(values, 2); + assert (length(T.time) == length(unique(T.time))); + if use_mag + % Compute the magnitude of the sensor data + mag_values = sqrt(sum(values.^2, 2)); + end + + % Create subplot with vertical spacing + figure(fig_handle); + hold on; + if use_mag + plot(time, mag_values, 'k-', 'DisplayName', 'Magnitude'); + else + for a = 1:num_axes + plot(time, values(:, a), 'Color', axis_colors(a), ... + 'DisplayName', sensor_axes(a)); + end + end + hold off; +end \ No newline at end of file From 2b318685263518cf1b7242f08cc63f80d31e6836 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 27 Jan 2025 16:30:01 +0100 Subject: [PATCH 22/46] Remove matlab, bluepy, imu_parser_v0 --- BadgeFramework/bluepy/badge.py | 316 ---- BadgeFramework/bluepy/badge_connection.py | 44 - BadgeFramework/bluepy/badge_protocol_old.py | 1583 ----------------- BadgeFramework/bluepy/ble_badge_connection.py | 184 -- BadgeFramework/bluepy/hub_connection_V1.py | 132 -- BadgeFramework/bluepy/hub_utilities_V1.py | 155 -- BadgeFramework/bluepy/scan_all.py | 24 - BadgeFramework/hub_V0.py | 316 ---- BadgeFramework/matlab/IMUParser.m | 114 -- BadgeFramework/matlab/checkbox_plot_control.m | 73 - BadgeFramework/matlab/get_kth_latest.m | 57 - BadgeFramework/matlab/get_sensor_paths.m | 40 - BadgeFramework/matlab/main.m | 73 - BadgeFramework/matlab/plot_multiple_midge.m | 73 - BadgeFramework/matlab/plot_single_midge.m | 46 - BadgeFramework/matlab/plot_single_sensor.m | 26 - .../matlab/remove_large_time_rows.m | 28 - 17 files changed, 3284 deletions(-) delete mode 100644 BadgeFramework/bluepy/badge.py delete mode 100644 BadgeFramework/bluepy/badge_connection.py delete mode 100644 BadgeFramework/bluepy/badge_protocol_old.py delete mode 100644 BadgeFramework/bluepy/ble_badge_connection.py delete mode 100644 BadgeFramework/bluepy/hub_connection_V1.py delete mode 100644 BadgeFramework/bluepy/hub_utilities_V1.py delete mode 100644 BadgeFramework/bluepy/scan_all.py delete mode 100644 BadgeFramework/hub_V0.py delete mode 100644 BadgeFramework/matlab/IMUParser.m delete mode 100644 BadgeFramework/matlab/checkbox_plot_control.m delete mode 100644 BadgeFramework/matlab/get_kth_latest.m delete mode 100644 BadgeFramework/matlab/get_sensor_paths.m delete mode 100644 BadgeFramework/matlab/main.m delete mode 100644 BadgeFramework/matlab/plot_multiple_midge.m delete mode 100644 BadgeFramework/matlab/plot_single_midge.m delete mode 100644 BadgeFramework/matlab/plot_single_sensor.m delete mode 100644 BadgeFramework/matlab/remove_large_time_rows.m diff --git a/BadgeFramework/bluepy/badge.py b/BadgeFramework/bluepy/badge.py deleted file mode 100644 index cef7beb..0000000 --- a/BadgeFramework/bluepy/badge.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import division, absolute_import, print_function -import time -import logging -import sys -import struct -import queue -from typing import Optional, Final -from badge_protocol import Request as mRequest - -DEFAULT_SCAN_WINDOW: Final[int] = 250 -DEFAULT_SCAN_INTERVAL: Final[int] = 1000 - -DEFAULT_IMU_ACC_FSR: Final[int] = 4 # Valid ranges: 2, 4, 8, 16 -DEFAULT_IMU_GYR_FSR: Final[int] = 1000 # Valid ranges: 250, 500, 1000, 2000 -DEFAULT_IMU_DATARATE: Final[int] = 50 - -DEFAULT_MICROPHONE_MODE: Final[int] = 1 # Valid options: 0=Stereo, 1=Mono - -from badge_protocol import * - -logger = logging.getLogger(__name__) - -# -- Helper methods used often in badge communication -- - -# We generally define timestamp_seconds to be in number of seconds since UTC epoch -# and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. - -# Returns the current timestamp as two parts - seconds and milliseconds -def get_timestamps(): - return get_timestamps_from_time(time.time()) - - -# Returns the given time as two parts - seconds and milliseconds -def get_timestamps_from_time(t): - timestamp_seconds = int(t) - timestamp_fraction_of_second = t - timestamp_seconds - timestamp_ms = int(1000 * timestamp_fraction_of_second) - return (timestamp_seconds, timestamp_ms) - - -# Convert badge timestamp representation to python representation -def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): - return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) - - -# Represents an OpenBadge currently connected via the BadgeConnection 'connection'. -# The 'connection' should already be connected when it is used to initialize this class. -# Implements methods that allow for interaction with that badge. -class OpenBadge(object): - def __init__(self, connection): - self.connection = connection - self.status_response_queue = queue.Queue() - self.start_microphone_response_queue = queue.Queue() - self.start_scan_response_queue = queue.Queue() - self.start_imu_response_queue = queue.Queue() - self.free_sdc_space_response_queue = queue.Queue() - - # Helper function to send a BadgeMessage `command_message` to a device, expecting a response - # of class `response_type` that is a subclass of BadgeMessage, or None if no response is expected. - def send_command(self, command_message, response_type): - expected_response_length = response_type.length() if response_type else 0 - - serialized_command = command_message.serialize_message() - logger.debug( - "Sending: {}, Raw: {}".format( - command_message, serialized_command.hex() - ) - ) - serialized_response = self.connection.send( - serialized_command, response_len=expected_response_length - ) - - if expected_response_length > 0: - response = response_type.deserialize_message(serialized_response) - logger.info("Recieved response {}".format(response)) - return response - else: - logger.info("No response expected, transmission successful.") - return True - - def send_request(self, request_message: mRequest): - print(type(request_message)) - serialized_request = request_message.encode() - - # Adding length header: - serialized_request_len = struct.pack(" len(self.buf)): - raise Exception("Not enough bytes in Istream to read") - ret = self.buf[0:l] - self.buf = self.buf[l:] - # for i in ret: - # print("_Istream:",i) - return ret - - -class Timestamp: - def __init__(self): - self.reset() - - def __repr__(self): - return str(self.__dict__) - - def reset(self): - self.seconds = 0 - self.ms = 0 - pass - - def encode(self): - ostream = _Ostream() - self.encode_internal(ostream) - return ostream.buf - - def encode_internal(self, ostream): - self.encode_seconds(ostream) - self.encode_ms(ostream) - pass - - def encode_seconds(self, ostream): - ostream.write(struct.pack(' 0: - while True: - while(not self.rx_queue.empty()): - rx_message += self.rx_queue.get() - if(len(rx_message) == rx_bytes_expected): - return rx_message - - self.conn.waitForNotifications(5.0) - - - - # Implements BadgeConnection's send() spec. - def send(self, message, response_len=0): - if not self.is_connected(): - raise RuntimeError("BLEBadgeConnection not connected before send()!") - - rx_message = "" - rx_bytes_expected = response_len - - self.tx.write(message,withResponse=True) - - - if rx_bytes_expected > 0: - while True: - while(not self.rx_queue.empty()): - rx_message += self.rx_queue.get() - if(len(rx_message) == rx_bytes_expected): - return rx_message - - self.conn.waitForNotifications(5.0) - - diff --git a/BadgeFramework/bluepy/hub_connection_V1.py b/BadgeFramework/bluepy/hub_connection_V1.py deleted file mode 100644 index f0f3625..0000000 --- a/BadgeFramework/bluepy/hub_connection_V1.py +++ /dev/null @@ -1,132 +0,0 @@ -from badge import OpenBadge -from ble_badge_connection import BLEBadgeConnection -import sys -constant_group_number = 1 - -class Connection(): - def __init__(self,pid,address): - try: - self.connection = BLEBadgeConnection.get_connection_to_badge(address) - self.connection.connect() - self.badge = OpenBadge(self.connection) - self.badge_id = int(pid) - self.mac_address = address - self.group_number = int(constant_group_number) - except Exception as err: - #if (err): - # print (str(err)) - raise Exception("Could not connect to participant" + str(pid)) - - def set_id_at_start(self): - try: - self.badge.get_status(new_id=self.badge_id, new_group_number=self.group_number) - except Exception as err: - print (err) - sys.stdout.flush() - - def disconnect(self): - self.connection.disconnect() - - def handle_status_request(self): - try: - out = self.badge.get_status() - return out - except Exception as err: - print (str(err)) - sys.stdout.flush() - raise Exception("Could not get status for participant"+ str(self.badge_id)) - - def handle_start_microphone_request(self): - try: - out = self.badge.start_microphone() - return out - except: - raise Exception("Could not start mic for participant" + str(self.badge_id)) - - def handle_stop_microphone_request(self): - try: - out = self.badge.stop_microphone() - return out - except: - raise Exception("Could not stop mic for participant" + str(self.badge_id)) - - def handle_start_scan_request(self): - try: - out = self.badge.start_scan() - return out - except: - raise Exception("Could not start scan for participant" + str(self.badge_id)) - - def handle_stop_scan_request(self): - try: - out = self.badge.stop_scan() - return out - except: - raise Exception("Could not stop scan for participant" + str(self.badge_id)) - - def handle_start_imu_request(self): - try: - out = self.badge.start_imu() - return out - except: - raise Exception("Could not start IMU for participant" + str(self.badge_id)) - - def handle_stop_imu_request(self): - try: - out = self.badge.stop_imu() - return out - except: - raise Exception("Could not stop IMU for participant " + str(self.badge_id)) - - def handle_identify_request(self): - try: - out = self.badge.identify() - return out - except: - raise Exception("Could not identify for participant " + str(self.badge_id)) - - def handle_restart_request(self): - try: - out = self.badge.restart() - return out - except Exception as err: - print (str(err)) - print ("Please wait at least 10 seconds to connect back to the device.") - print ("Don't forget to start the recording for the restarted badge.") - #raise Exception("Could not restart for participant " + str(self.badge_id)) - - def handle_get_free_space(self): - try: - out = (self.badge.get_free_sdc_space()) - return out - except: - raise Exception("Could not get free space for participant " + str(self.badge_id)) - - def start_recording_all_sensors(self): - self.handle_status_request() - self.handle_start_scan_request() - self.handle_start_microphone_request() - self.handle_start_imu_request() - - def stop_recording_all_sensors(self): - self.handle_stop_scan_request() - self.handle_stop_microphone_request() - self.handle_stop_imu_request() - - def print_help(self): - print(" Available commands:") - print(" status ") - print(" start_all_sensors") - print(" stop_all_sensors") - print(" start_microphone") - print(" stop_microphone") - print(" start_scan") - print(" stop_scan") - print(" start_imu") - print(" stop_imu") - print(" identify") - print(" restart") - print(" get_free_space") - print(" help") - print(" All commands use current system time as transmitted time.") - sys.stdout.flush() \ No newline at end of file diff --git a/BadgeFramework/bluepy/hub_utilities_V1.py b/BadgeFramework/bluepy/hub_utilities_V1.py deleted file mode 100644 index 2b581d9..0000000 --- a/BadgeFramework/bluepy/hub_utilities_V1.py +++ /dev/null @@ -1,155 +0,0 @@ -from hub_connection_V1 import Connection -import signal,sys,tty,termios -import time -import logging - -# from hub_connection_V1 import Connection - - -def get_logger(name): - log_format_file = '%(asctime)s %(levelname)5s %(message)s' - log_format_console = '%(message)s' - logging.basicConfig(level=logging.DEBUG, - format=log_format_file, - filename="data_collection.log", - filemode='w') - console = logging.StreamHandler() - console.setLevel(logging.INFO) - console.setFormatter(logging.Formatter(log_format_console)) - logging.getLogger(name).addHandler(console) - return logging.getLogger(name) - - -logger = get_logger("hub_utilities") - - -def choose_function(connection:Connection, input): - chooser = { - "help": connection.print_help, - "status": connection.handle_status_request, - "start_all_sensors": connection.start_recording_all_sensors, - "stop_all_sensors": connection.stop_recording_all_sensors, - "start_microphone": connection.handle_start_microphone_request, - "stop_microphone": connection.handle_stop_microphone_request, - "start_scan": connection.handle_start_scan_request, - "stop_scan": connection.handle_stop_scan_request, - "start_imu": connection.handle_start_imu_request, - "stop_imu": connection.handle_stop_imu_request, - "identify": connection.handle_identify_request, - "restart": connection.handle_restart_request, - "get_free_space": connection.handle_get_free_space, - } - func = chooser.get(input, lambda: "Invalid command!") - try: - out = func() - return out - except Exception as error: - print(error) - return - -def start_recording_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', sensors are not started.') - continue - try: - cur_connection.set_id_at_start() - cur_connection.start_recording_all_sensors() - cur_connection.disconnect() - except Exception as error: - print(error) - cur_connection.disconnect() - -def stop_recording_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', sensors are not stopped.') - continue - try: - cur_connection.stop_recording_all_sensors() - cur_connection.disconnect() - except Exception as error: - print(str(error)) - cur_connection.disconnect() - -def synchronise_and_check_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', cannot synchronise.') - sys.stdout.flush() - continue - try: - out = cur_connection.handle_status_request() - if out.imu_status == 0: - print ('IMU is not recording for participant ' + str(current_participant)) - if out.microphone_status == 0: - print ('Mic is not recording for participant ' + str(current_participant)) - if out.scan_status == 0: - print ('Scan is not recording for participant ' + str(current_participant)) - if out.clock_status == 0: - print ('Cant synch for participant ' + str(current_participant)) - sys.stdout.flush() - cur_connection.disconnect() - except Exception as error: - print(error) - sys.stdout.flush() - cur_connection.disconnect() - -class timeout_input(object): - def __init__(self, poll_period=0.05): - self.poll_period = poll_period - - def _getch_nix(self): - from select import select - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - [i, _, _] = select([sys.stdin.fileno()], [], [], self.poll_period) - if i: - ch = sys.stdin.read(1) - else: - ch = '' - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch - - def input(self, prompt=None, timeout=None, - extend_timeout_with_input=True, require_enter_to_confirm=True): - prompt = prompt or '' - sys.stdout.write(prompt) - sys.stdout.flush() - input_chars = [] - start_time = time.time() - received_enter = False - while (time.time() - start_time) < timeout: - c = self._getch_nix() - if c in ('\n', '\r'): - received_enter = True - break - elif c: - input_chars.append(c) - sys.stdout.write(c) - sys.stdout.flush() - if extend_timeout_with_input: - start_time = time.time() - sys.stdout.write('\n') - sys.stdout.flush() - captured_string = ''.join(input_chars) - if require_enter_to_confirm: - return_string = captured_string if received_enter else '' - else: - return_string = captured_string - return return_string \ No newline at end of file diff --git a/BadgeFramework/bluepy/scan_all.py b/BadgeFramework/bluepy/scan_all.py deleted file mode 100644 index 40a8d4a..0000000 --- a/BadgeFramework/bluepy/scan_all.py +++ /dev/null @@ -1,24 +0,0 @@ -from bluepy.btle import Scanner, DefaultDelegate - -class ScanDelegate(DefaultDelegate): - def __init__(self): - DefaultDelegate.__init__(self) - - # def handleDiscovery(self, dev, isNewDev, isNewData): - # if isNewDev: - # print "Discovered device", dev.addr - # elif isNewData: - # print "Received new data from", dev.addr - -scanner = Scanner().withDelegate(ScanDelegate()) -devices = scanner.scan(10.0) -device_temp_name = 'HDBDG' -midges = [] - -for dev in devices: - for (adtype, desc, value) in dev.getScanData(): - if desc == 'Complete Local Name' and value == device_temp_name: - midges.append(dev) - -for midge in midges: - print "Device %s, RSSI=%d dB" % (midge.addr, midge.rssi) \ No newline at end of file diff --git a/BadgeFramework/hub_V0.py b/BadgeFramework/hub_V0.py deleted file mode 100644 index 1ce19a4..0000000 --- a/BadgeFramework/hub_V0.py +++ /dev/null @@ -1,316 +0,0 @@ -from __future__ import division, absolute_import, print_function -import logging -import sys -import threading - -from badge import OpenBadge -from ble_badge_connection import BLEBadgeConnection -#from bluepy import * -from bluepy import btle -from bluepy.btle import UUID, Peripheral, DefaultDelegate, AssignedNumbers ,Scanner -from bluepy.btle import BTLEException -import numpy as np -import pandas as pd -import time -import signal,tty,termios - -constant_group_number = 1 - -class Connection(): - def __init__(self,pid,address): - try: - self.connection = BLEBadgeConnection.get_connection_to_badge(address) - self.connection.connect() - self.badge = OpenBadge(self.connection) - self.badge_id = int(pid) - self.mac_address = address - self.group_number = int(constant_group_number) - print ("MY ID:" + str(self.badge_id)) - except: - raise Exception("Could not connect to participant" + str(pid)) - - def set_id_at_start(self): - try: - self.badge.get_status(new_id=self.badge_id, new_group_number=self.group_number) - except Exception as err: - print (err) - - def disconnect(self): - self.connection.disconnect() - - def handle_status_request(self): - try: - out = self.badge.get_status() - return out - except Exception as err: - print (str(err)) - raise Exception("Could not get status for participant") - - def handle_start_microphone_request(self): - try: - self.badge.start_microphone() - except: - raise Exception("Could not start mic for participant" + str(self.badge_id)) - - def handle_stop_microphone_request(self): - try: - self.badge.stop_microphone() - except: - raise Exception("Could not stop mic for participant" + str(self.badge_id)) - - def handle_start_scan_request(self): - try: - self.badge.start_scan() - except: - raise Exception("Could not start scan for participant" + str(self.badge_id)) - - def handle_stop_scan_request(self): - try: - self.badge.stop_scan() - except: - raise Exception("Could not stop scan for participant" + str(self.badge_id)) - - def handle_start_imu_request(self): - try: - self.badge.start_imu() - except: - raise Exception("Could not start IMU for participant" + str(self.badge_id)) - - def handle_stop_imu_request(self): - try: - self.badge.stop_imu() - except: - raise Exception("Could not stop IMU for participant " + str(self.badge_id)) - - def handle_identify_request(self): - try: - self.badge.identify() - except: - raise Exception("Could not identify for participant " + str(self.badge_id)) - - def handle_restart_request(self): - try: - self.badge.restart() - except: - raise Exception("Could not restart for participant " + str(self.badge_id)) - - def handle_get_free_space(self): - try: - print(self.badge.get_free_sdc_space()) - except: - raise Exception("Could not get free space for participant " + str(self.badge_id)) - - def start_recording_all_sensors(self): - self.handle_status_request() - self.handle_start_scan_request() - self.handle_start_microphone_request() - self.handle_start_imu_request() - - def stop_recording_all_sensors(self): - self.handle_stop_scan_request() - self.handle_stop_microphone_request() - self.handle_stop_imu_request() - - def print_help(self): - print("> Available commands:") - print("> status ") - print("> start_all_sensors") - print("> stop_all_sensors") - print("> start_microphone") - print("> stop_microphone") - print("> start_scan") - print("> stop_scan") - print("> start_imu") - print("> stop_imu") - print("> identify [led duration seconds | 'off']") - print("> restart") - print("> get_free_space") - print("> help") - print("> All commands use current system time as transmitted time.") - -################################################################################## -################################################################################## - -def choose_function(connection,input): - chooser = { - "help": connection.print_help, - "status": connection.handle_status_request, - "start_all_sensors": connection.start_recording_all_sensors, - "stop_all_sensors": connection.stop_recording_all_sensors, - "start_microphone": connection.handle_start_microphone_request, - "stop_microphone": connection.handle_stop_microphone_request, - "start_scan": connection.handle_start_scan_request, - "stop_scan": connection.handle_stop_scan_request, - "start_imu": connection.handle_start_imu_request, - "stop_imu": connection.handle_stop_imu_request, - "identify": connection.handle_identify_request, - "restart": connection.handle_restart_request, - "get_free_space": connection.handle_get_free_space, - } - func = chooser.get(input, lambda: "Invalid month") - out = func() - return out - - -def start_recording_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', sensors are not started.') - continue - try: - cur_connection.set_id_at_start() - cur_connection.start_recording_all_sensors() - cur_connection.disconnect() - except Exception as error: - print(error) - cur_connection.disconnect() - -def stop_recording_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', sensors are not stopped.') - continue - try: - cur_connection.stop_recording_all_sensors() - cur_connection.disconnect() - except Exception as error: - print(str(error)) - cur_connection.disconnect() - -def synchronise_and_check_all_devices(df): - for _, row in df.iterrows(): - current_participant = row['Participant Id'] - current_mac = row['Mac Address'] - try: - cur_connection=Connection(current_participant,current_mac) - except Exception as error: - print(str(error) + ', cannot synchronise.') - continue - try: - out = cur_connection.handle_status_request() - if out.imu_status == 0: - print ('IMU is not recording for participant ' + str(current_participant)) - if out.microphone_status == 0: - print ('Mic is not recording for participant ' + str(current_participant)) - if out.scan_status == 0: - print ('Scan is not recording for participant ' + str(current_participant)) - if out.clock_status == 0: - print ('Cant synch for participant ' + str(current_participant)) - cur_connection.disconnect() - except Exception as error: - print(error) - cur_connection.disconnect() - -class TimeoutInput(object): - def __init__(self, poll_period=0.05): - self.poll_period = poll_period - - def _getch_nix(self): - from select import select - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - [i, _, _] = select([sys.stdin.fileno()], [], [], self.poll_period) - if i: - ch = sys.stdin.read(1) - else: - ch = '' - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch - - def input(self, prompt=None, timeout=None, - extend_timeout_with_input=True, require_enter_to_confirm=True): - """timeout: float seconds or None (blocking)""" - prompt = prompt or '' - sys.stdout.write(prompt) - sys.stdout.flush() - input_chars = [] - start_time = time.time() - received_enter = False - while (time.time() - start_time) < timeout: - c = self._getch_nix() - if c in ('\n', '\r'): - received_enter = True - break - elif c: - input_chars.append(c) - sys.stdout.write(c) - sys.stdout.flush() - if extend_timeout_with_input: - start_time = time.time() - sys.stdout.write('\n') - sys.stdout.flush() - captured_string = ''.join(input_chars) - if require_enter_to_confirm: - return_string = captured_string if received_enter else '' - else: - return_string = captured_string - return return_string - -if __name__ == "__main__": - df = pd.read_csv('sample_mapping_file.csv') - while True: - print("> Type start to start data collection or stop to finish data collection.") - sys.stdout.write("> ") - command = sys.stdin.readline()[:-1] - if command == "start": - start_recording_all_devices(df) - while True: - ti = TimeoutInput(poll_period=0.05) - s = ti.input(prompt='>Type int if you would like to enter interactive shell.\n'+'>', timeout=10.0, - extend_timeout_with_input=False, require_enter_to_confirm=True) - if s == "int": - print("> Welcome to the interactive shell. Please type the id of the Midge you want to connect.") - print("> Type exit if you would like to stop recording for all devices.") - sys.stdout.write("> ") - command = sys.stdin.readline()[:-1] - if command == "exit": - print("> Stopping the recording of all devices.") - stop_recording_all_devices(df) - print("> Devices are stopped.") - break - command_args = command.split(" ") - current_mac_addr= (df.loc[df['Participant Id'] == int(command)]['Mac Address']).values[0] - try: - cur_connection = Connection(int(command),current_mac_addr) - except Exception as error: - print (str(error)) - continue - print ('> Connected to the badge. For available commands, please type help.') - print ('> ') - while True: - command = sys.stdin.readline()[:-1] - command_args = command.split(" ") - if command == "exit": - cur_connection.disconnect() - break - if command_args[0] in cur_connection.command_handlers: - try: - out = choose_function(cur_connection,command_args[0]) - print (out) - except Exception as error: - print (str(error)) - continue - else: - print("> Command not found!") - cur_connection.print_help() - else: - print('> Synchronisation is starting. Please wait till ends.') - synchronise_and_check_all_devices(df) - print('> Synchronisation is finished.') - elif command == "stop": - print("> Stopping data collection.") - quit(0) - else: - print("> Command not found, please type start or stop to start or stop data collection.") - diff --git a/BadgeFramework/matlab/IMUParser.m b/BadgeFramework/matlab/IMUParser.m deleted file mode 100644 index ec5af10..0000000 --- a/BadgeFramework/matlab/IMUParser.m +++ /dev/null @@ -1,114 +0,0 @@ -classdef IMUParser - properties - path_accel - path_gyro - path_mag - path_rotation - path_scan - path_save - - accel_df - gyro_df - mag_df - rot_df - scan_df - - midge_id - block_size - end - - methods - function obj = IMUParser(record_path, midge_id, block_size, local_path) - sensor_files = get_sensor_paths(record_path); - obj.path_accel = sensor_files.accel{1}; - obj.path_gyro = sensor_files.gyr{1}; - obj.path_mag = sensor_files.mag{1}; - obj.path_rotation = sensor_files.rotation{1}; - obj.path_scan = sensor_files.proximity{1}; - obj.path_save = local_path; - obj.midge_id = midge_id; - obj.block_size = block_size; - end - - function obj = parse_accel(obj) - obj.accel_df = obj.parse_generic(obj.path_accel, 3); - end - - function obj = parse_gyro(obj) - obj.gyro_df = obj.parse_generic(obj.path_gyro, 3); - end - - function obj = parse_mag(obj) - obj.mag_df = obj.parse_generic(obj.path_mag, 3); - end - - function obj = parse_rot(obj) - obj.rot_df = obj.parse_generic(obj.path_rotation, 4); - end - - function df = parse_generic(obj, file_path, axis_num) - fid = fopen(file_path, 'rb'); - raw_data = fread(fid); - fclose(fid); - - % Define block size and calculate the number of blocks - num_blocks = floor(length(raw_data) / obj.block_size); - - % Extract all blocks in one go - blocks = reshape(raw_data(1:num_blocks * obj.block_size), ... - obj.block_size, num_blocks); - - % Extract timestamps - timestamp_bytes = blocks(1:8, :); % First 8 bytes of each block - timestamps = double(typecast(uint8(timestamp_bytes(:)), 'uint64')); - - % Extract data dimensions (3 sets of 4-byte floats) - end_ind = 8 + axis_num * 4; - data_bytes = blocks(9:end_ind, :); % Bytes 9 to 20 for each block - data = typecast(uint8(data_bytes(:)), 'single'); - data = reshape(data, axis_num, num_blocks)'; - - % Create a table - if axis_num == 3 - % accel, gyro, magnitude - df = table(timestamps, data(:,1), data(:,2), data(:,3), ... - 'VariableNames', {'time', 'x', 'y', 'z'}); - elseif axis_num == 4 - % rotation - df = table(timestamps, data(:,1), data(:,2), data(:,3), ... - data(:,4), 'VariableNames', {'time', 'x', 'y', 'z', 'w'}); - end - % Remove erroneous timestamps - % df = remove_large_time_rows(df); - - % Convert timestamps to datetime - df.time = datetime(df.time / 1000, 'ConvertFrom', 'posixtime'); - end - - function save_dataframes(obj, acc, gyr, mag, rot) - if acc && ~isempty(obj.accel_df) - obj.save_single_csv(obj.accel_df, "acc") - end - if gyr && ~isempty(obj.gyro_df) - obj.save_single_csv(obj.gyro_df, "gyr") - end - if mag && ~isempty(obj.mag_df) - obj.save_single_csv(obj.mag_df, "mag") - end - if rot && ~isempty(obj.rot_df) - obj.save_single_csv(obj.rot_df, "rot") - end - mat_file = obj.path_save + obj.midge_id + "_data.mat"; - acc_data = obj.accel_df; - gyr_data = obj.gyro_df; - mag_data = obj.mag_df; - rot_data = obj.rot_df; - save(mat_file, 'acc_data', 'gyr_data', 'mag_data', 'rot_data'); - end - - function save_single_csv(obj, var, sensor_name) - csv_file = obj.path_save + "csv/" + obj.midge_id + "_" + sensor_name + ".csv"; - writetable(var, csv_file); - end - end -end diff --git a/BadgeFramework/matlab/checkbox_plot_control.m b/BadgeFramework/matlab/checkbox_plot_control.m deleted file mode 100644 index 70df957..0000000 --- a/BadgeFramework/matlab/checkbox_plot_control.m +++ /dev/null @@ -1,73 +0,0 @@ -function checkbox_plot_control - % Sample data - time = 0:0.1:10; - data1 = sin(time); - data2 = cos(time); - data3 = tanh(time); - - % Create a figure with a resize callback - fig = figure; - set(fig, 'Name', 'Checkbox Plot Control', 'Position', ... - [100, 100, 800, 600], 'ResizeFcn', @resizeCheckBoxes); - - - % Plot the data and store the plot handles - h1 = plot(time, data1, 'r-', 'DisplayName', 'sin(t)'); - hold on; - h2 = plot(time, data2, 'g-', 'DisplayName', 'cos(t)'); - h3 = plot(time, data3, 'b-', 'DisplayName', 'tanh(t)'); - hold off; - - % Add legend - legend('show'); - - % Add labels and title - xlabel('Time'); - ylabel('Values'); - title('Plot with Checkbox Controls'); - - % Create checkboxes to control the visibility of each plot - cb1 = uicontrol('Style', 'checkbox', 'String', 'Show sin(t)', ... - 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h1)); - cb2 = uicontrol('Style', 'checkbox', 'String', 'Show cos(t)', ... - 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h2)); - cb3 = uicontrol('Style', 'checkbox', 'String', 'Show tanh(t)', ... - 'Value', 1, 'Callback', @(src, event) toggleVisibility(src, h3)); - - % Store the checkboxes in a struct for easy access during resizing - checkboxes = struct('cb1', cb1, 'cb2', cb2, 'cb3', cb3); - setappdata(fig, 'checkboxes', checkboxes); - - % Initial positioning of the checkboxes - resizeCheckBoxes(fig); -end - -% Callback function to toggle the visibility of a plot -function toggleVisibility(src, plotHandle) - if src.Value == 1 - plotHandle.Visible = 'on'; - else - plotHandle.Visible = 'off'; - end -end - -% Resize function to adjust the position of the checkboxes -function resizeCheckBoxes(fig) - % Get figure size - figPos = fig.Position; - figWidth = figPos(3); - figHeight = figPos(4); - - % Checkbox width and height - cbWidth = 120; - cbHeight = 20; - spacing = 10; % Spacing between checkboxes - - % Get the checkboxes from the figure's app data - checkboxes = getappdata(fig, 'checkboxes'); - - % Set positions for each checkbox (top-right corner) - set(checkboxes.cb1, 'Position', [figWidth - cbWidth - spacing, figHeight - cbHeight - spacing, cbWidth, cbHeight]); - set(checkboxes.cb2, 'Position', [figWidth - cbWidth - spacing, figHeight - 2 * (cbHeight + spacing), cbWidth, cbHeight]); - set(checkboxes.cb3, 'Position', [figWidth - cbWidth - spacing, figHeight - 3 * (cbHeight + spacing), cbWidth, cbHeight]); -end diff --git a/BadgeFramework/matlab/get_kth_latest.m b/BadgeFramework/matlab/get_kth_latest.m deleted file mode 100644 index 873a4cb..0000000 --- a/BadgeFramework/matlab/get_kth_latest.m +++ /dev/null @@ -1,57 +0,0 @@ -function subfolder_path = get_kth_latest(folder_path, k) - % GET_KTH_LATEST Get the path of the k-th largest subfolder based on 'y' in the subfolder names 'x_y'. - % - % Args: - % folder_path (char): Path to the main folder containing subfolders named 'x_y'. - % k (int): The rank of the subfolder to retrieve (1-based). - % - % Returns: - % subfolder_path (char): Path of the k-th largest subfolder based on 'y'. - % - % Raises: - % Error if 'k' is invalid or there are not enough valid subfolders. - - % Get a list of all items in the folder - items = dir(folder_path); - - % Initialize a cell array to store subfolders with extracted y-values - subfolders_with_y = {}; - - % Iterate over all items - for i = 1:length(items) - if items(i).isdir && ~strcmp(items(i).name, '.') && ~strcmp(items(i).name, '..') - subfolder_name = items(i).name; - subfolder_path = fullfile(folder_path, subfolder_name); - - % Split the subfolder name into 'x' and 'y' - tokens = split(subfolder_name, '_'); - if length(tokens) == 2 - try - y = str2double(tokens{2}); - if ~isnan(y) - subfolders_with_y{end+1, 1} = y; % Store the y-value - subfolders_with_y{end, 2} = subfolder_path; % Store the subfolder path - end - catch - % Skip subfolders that don't match the 'x_y' pattern - end - end - end - end - - % Check if there are valid subfolders - if isempty(subfolders_with_y) - error('No valid subfolders found in the specified folder.'); - end - - % Sort the subfolders by y in descending order - subfolders_with_y = sortrows(subfolders_with_y, -1); - - % Validate k - if k < 1 || k > size(subfolders_with_y, 1) - error('Invalid value of k: %d. Must be between 1 and %d.', k, size(subfolders_with_y, 1)); - end - - % Return the path of the k-th largest subfolder - subfolder_path = subfolders_with_y{k, 2}; -end \ No newline at end of file diff --git a/BadgeFramework/matlab/get_sensor_paths.m b/BadgeFramework/matlab/get_sensor_paths.m deleted file mode 100644 index 4a30aba..0000000 --- a/BadgeFramework/matlab/get_sensor_paths.m +++ /dev/null @@ -1,40 +0,0 @@ -function sensor_files = get_sensor_paths(folder_path) - % Returns a struct containing lists of file names matching specific suffixes. - sensor_files.accel = {}; - sensor_files.gyr = {}; - sensor_files.mag = {}; - sensor_files.rotation = {}; - sensor_files.proximity = {}; - - files = dir(folder_path); - for i = 1:length(files) - if ~files(i).isdir - file_name = files(i).name; - if endsWith(file_name, '_accel') - sensor_files.accel{end+1} = fullfile(folder_path, file_name); - elseif endsWith(file_name, '_gyr') - sensor_files.gyr{end+1} = fullfile(folder_path, file_name); - elseif endsWith(file_name, 'mag') - sensor_files.mag{end+1} = fullfile(folder_path, file_name); - elseif endsWith(file_name, '_rotation') - sensor_files.rotation{end+1} = fullfile(folder_path, file_name); - elseif endsWith(file_name, '_proximity') - sensor_files.proximity{end+1} = fullfile(folder_path, file_name); - end - end - end - - check_multiple_sensor_files(sensor_files); -end - -function check_multiple_sensor_files(files) - fields = fieldnames(files); - for i = 1:numel(fields) - file_list = files.(fields{i}); - if isempty(file_list) - warning('No %s sensor files found!', fields{i}); - elseif length(file_list) > 1 - warning('Multiple %s sensor files detected!', fields{i}); - end - end -end diff --git a/BadgeFramework/matlab/main.m b/BadgeFramework/matlab/main.m deleted file mode 100644 index c088b7b..0000000 --- a/BadgeFramework/matlab/main.m +++ /dev/null @@ -1,73 +0,0 @@ -clear variables; close all; - -% Main script to parse IMU data - -block_sizes = zeros(1, 100); -bs24 = [12, 19, 24, 32, 33, 34, 35, 37, 45, 48, 54, 57, 59]; -bs32 = [7, 24, 25, 28, 29]; -problematic = [19, 34, 35, 59]; -block_sizes(bs24) = 24; -block_sizes(bs32) = 32; -good_midges = setdiff(union(bs24, bs32), problematic); -local_path = "./local/"; - -%% save data to mat and csv -% for k=good_midges -% save_imu_data(block_sizes, k, local_path); -% end - -%% plot -midges = [7, 12, 25, 48]; -data = cell(1, length(midges)); -for k=1:length(midges) - midge_id = midges(k); - data{k} = load(local_path + midge_id + "_data.mat"); - % remove large timestamps - data{k} = filter_midge_data(data{k}); - data{k}.midge_id = midge_id; -end - -plot_options = struct(); -plot_options.midge = [1, 0, 0, 0]; -plot_options.sensor = [1, 1, 1, 1]; -plot_options.display = "midge"; -plot_multiple_midge(data, plot_options, false); -% plot_single_midge(data{1}.acc_data, '44rr'); - -%% functions -function filteredData = filter_midge_data(midge) - filteredData.acc_data = remove_large_time_rows(midge.acc_data); - filteredData.gyr_data = remove_large_time_rows(midge.gyr_data); - filteredData.mag_data = remove_large_time_rows(midge.mag_data); - filteredData.rot_data = remove_large_time_rows(midge.rot_data); -end - -function data_folder = get_data_folder() - if ispc - data_folder = "C:/Users/zongh/OneDrive - Delft University of " + ... - "Technology/tudelft/projects/dataset_collection/tech_pilot_1/midge_data/"; - elseif isunix - data_folder = "/home/zonghuan/tudelft/projects/datasets/" + ... - "new_collection/tech_pilot_1/midge_data/"; - end -end - -function save_imu_data(block_sizes, midge_id, local_path) - midge_data_parent = get_data_folder(); - midge_folder = get_kth_latest(midge_data_parent + midge_id + "/", 1); - - block_size = block_sizes(midge_id); - - % Create IMUParser object - parser = IMUParser(midge_folder, midge_id, block_size, local_path); - - % Parse data - parser = parser.parse_accel(); - parser = parser.parse_gyro(); - parser = parser.parse_mag(); - parser = parser.parse_rot(); - - parser.save_dataframes(true, true, true, true); - disp("Parsing and saving completed " + midge_id); - -end \ No newline at end of file diff --git a/BadgeFramework/matlab/plot_multiple_midge.m b/BadgeFramework/matlab/plot_multiple_midge.m deleted file mode 100644 index 89b1bb3..0000000 --- a/BadgeFramework/matlab/plot_multiple_midge.m +++ /dev/null @@ -1,73 +0,0 @@ -function plot_multiple_midge(data, plot_options, use_mag) - % Function to plot acc, gyr, mag, and rot data for multiple badges. - % badge_data: a cell array where each cell contains sensor data tables for one badge. - % - % Example: badge_data{1}.acc_data, badge_data{1}.gyr_data, etc. - - % Define sensor types and titles - midge_info = struct(); - midge_info.ids = {}; - for k=1:length(data) - midge_info.ids{k} = data{k}.midge_id; - midge_info.names{k} = "Midge " + data{k}.midge_id; - end - midge_info.plot_options = plot_options.midge; - midge_info.plot_num_all = length(data); - - sensor_info = struct(); - sensor_info.types = {'acc', 'gyr', 'mag', 'rot'}; - sensor_info.names = {'Accelerometer', 'Gyro', 'Magnitude', 'Rotation'}; - sensor_info.plot_options = plot_options.sensor; - sensor_info.plot_num_all = length(sensor_info.types); - - - if plot_options.display == "midge" - s1 = midge_info; - s2 = sensor_info; - elseif plot_options.display == "sensor" - s1 = sensor_info; - s2 = midge_info; - else - error("Invalid display option"); - end - - subplot_ids = find(s2.plot_options == 1); - % Loop through each sensor type - for k1 = 1:s1.plot_num_all - if ~s1.plot_options(k1) - continue; - end - fig = figure('Name', s1.names{k1}, 'NumberTitle', 'off'); - - % Loop through each badge to create a subplot for each badge's data - for k2 = 1:s2.plot_num_all - if ~s2.plot_options(k2) - continue; - end - - % Get the current midge's sensor data - if plot_options.display == "midge" - sensor_field = [s2.types{k2}, '_data']; - data_single = data{k1}.(sensor_field); - elseif plot_options.display == "sensor" - sensor_field = [s1.types{k1}, '_data']; - data_single = data{k2}.(sensor_field); - else - error("Invalid display option"); - end - subplot(length(subplot_ids), 1, k2); - plot_single_sensor(data_single, fig, use_mag); - - % Add labels and legend - ylabel(s2.names(k2)); - if k2 == subplot_ids(1) - title(s1.names{k1}); - end - if k2 == subplot_ids(end) - xlabel('Timestamp'); - end - legend('show'); - grid on; - end - end -end diff --git a/BadgeFramework/matlab/plot_single_midge.m b/BadgeFramework/matlab/plot_single_midge.m deleted file mode 100644 index 7fa3d7f..0000000 --- a/BadgeFramework/matlab/plot_single_midge.m +++ /dev/null @@ -1,46 +0,0 @@ -function plot_single_midge(data, plot_title, fig_handle) - % Assuming 'T' is a table where the first column is 'time' and the rest are data columns - assert (length(data.time) == length(unique(data.time))); - - % Extract time and data - time = data{:, 1}; % The first column is 'time' - data = data{:, 2:end}; % All other columns contain the data to be plotted - - % Generate labels dynamically based on the number of columns - num_series = size(data, 2); % Number of data columns - labels = 'xyzwuv'; % Possible labels - plot_labels = cell(1, num_series); - for i = 1:num_series - plot_labels{i} = labels(i); - end - - % Define a set of colors to cycle through - colors = {'r', 'g', 'b', 'y', 'm', 'c', 'k'}; - num_colors = length(colors); - - % Check if a figure handle is provided - if nargin < 3 || isempty(fig_handle) - figure; % Create a new figure if no handle is provided - else - figure(fig_handle); % Use the provided figure handle - end - - % Create the figure and plot each series - hold on; - for i = 1:num_series - color = colors{mod(i - 1, num_colors) + 1}; % Cycle through colors - plot(time, data(:, i), '-', 'Color', color, 'DisplayName', plot_labels{i}); - end - hold off; - - % Add labels and title - xlabel('Time'); - ylabel('Values'); - title(plot_title); - - % Add legend - legend('show'); - - % Improve grid visibility - grid on; -end \ No newline at end of file diff --git a/BadgeFramework/matlab/plot_single_sensor.m b/BadgeFramework/matlab/plot_single_sensor.m deleted file mode 100644 index e04a4f9..0000000 --- a/BadgeFramework/matlab/plot_single_sensor.m +++ /dev/null @@ -1,26 +0,0 @@ -function plot_single_sensor(T, fig_handle, use_mag) - axis_colors = 'rgbymck'; - sensor_axes = 'xyzwuv'; - - time = T{:, 1}; % First column is the timestamp - values = T{:, 2:end}; % Remaining columns are x, y, z, (possibly w) - num_axes = size(values, 2); - assert (length(T.time) == length(unique(T.time))); - if use_mag - % Compute the magnitude of the sensor data - mag_values = sqrt(sum(values.^2, 2)); - end - - % Create subplot with vertical spacing - figure(fig_handle); - hold on; - if use_mag - plot(time, mag_values, 'k-', 'DisplayName', 'Magnitude'); - else - for a = 1:num_axes - plot(time, values(:, a), 'Color', axis_colors(a), ... - 'DisplayName', sensor_axes(a)); - end - end - hold off; -end \ No newline at end of file diff --git a/BadgeFramework/matlab/remove_large_time_rows.m b/BadgeFramework/matlab/remove_large_time_rows.m deleted file mode 100644 index 7c49c3f..0000000 --- a/BadgeFramework/matlab/remove_large_time_rows.m +++ /dev/null @@ -1,28 +0,0 @@ - -function filteredTable = remove_large_time_rows(inputTable) - % REMOVE_LARGE_TIME_ROWS Removes rows where the time in the first column is > 1e13. - % - % Args: - % inputTable (table): The input table where the first column contains time values. - % - % Returns: - % filteredTable (table): The table with rows removed where time > 1e13. - - % Define the threshold - timeThreshold = 1e13; - - - % Get the time values from the first column - timeValues = inputTable{:, 1}; - - % Create a logical index for rows where time <= 1e13 - try - validRows = timeValues <= timeThreshold; - catch - timeThreshold = datetime(1e13 / 1000, 'ConvertFrom', 'posixtime'); - validRows = timeValues <= timeThreshold; - end - - % Filter the table using the valid rows - filteredTable = inputTable(validRows, :); -end \ No newline at end of file From 87250c7dfd5d7d99403f22b9163153b3f39db1c3 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 27 Jan 2025 16:35:18 +0100 Subject: [PATCH 23/46] Delete bluepy_test --- BadgeFramework/bluepy_test.py | 126 ---------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 BadgeFramework/bluepy_test.py diff --git a/BadgeFramework/bluepy_test.py b/BadgeFramework/bluepy_test.py deleted file mode 100644 index 8ab0912..0000000 --- a/BadgeFramework/bluepy_test.py +++ /dev/null @@ -1,126 +0,0 @@ -import pandas as pandas -from bluepy import btle -import uuid -import logging -import struct -import queue -from bluepy.btle import Peripheral -from ble_badge_connection import Peripheral2 -from badge import OpenBadge -import time -import badge_protocol - -UART_SERVICE_UUID = uuid.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") -TX_CHAR_UUID = uuid.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") -RX_CHAR_UUID = uuid.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") - -logger = logging.getLogger(__name__) - - -class SimpleBadge: - def __init__(self, q: queue.Queue): - self.queue = q - - def receive(self, data): - for b in data: - self.queue.put(b) - - def await_data(self, conn, data_len): - rx_message = b"" - rx_bytes_expected = data_len - - if rx_bytes_expected > 0: - while True: - while not self.queue.empty(): - rx_message += self.queue.get().to_bytes(1, byteorder="big") - if len(rx_message) == rx_bytes_expected: - return rx_message - - conn.waitForNotifications(5.0) - - def send(self, conn, tx: btle.Characteristic, message, response_len=0): - rx_message = b"" - rx_bytes_expected = response_len - - tx.write(message, withResponse=True) - conn.waitForNotifications(5.0) - # if True: - # while True: - while not self.queue.empty(): - rx_message += self.queue.get().to_bytes(1, byteorder="big") - if len(rx_message) >= 18: - break - return rx_message - # if len(rx_message) == rx_bytes_expected: - # return rx_message - - -class SimpleDelegate2(btle.DefaultDelegate): - def __init__(self, badge: SimpleBadge): - btle.DefaultDelegate.__init__(self) - self.badge = badge - - def handleNotification(self, cHandle, data): - print(f'handle: {cHandle}, data:{data}') - self.badge.receive(data) - - -def main(): - df = pandas.read_csv("mappings_allBackup.csv") - # df = pd.read_csv("mappings_all.csv") - devices_id = [29] # test with 1 midge - df = df[df["Participant Id"].isin(devices_id)] - for _, row in df.iterrows(): - current_participant: int = row["Participant Id"] - current_mac: str = row["Mac Address"] - conn = Peripheral(current_mac, btle.ADDR_TYPE_RANDOM) - # self.conn = Peripheral2(self.ble_device, btle.ADDR_TYPE_RANDOM) - - # Find the UART service and its characteristics. - uart = conn.getServiceByUUID(UART_SERVICE_UUID) - rx = uart.getCharacteristics(RX_CHAR_UUID)[0] - tx = uart.getCharacteristics(TX_CHAR_UUID)[0] - - # Turn on notification of RX characteristics - logger.debug("Subscribing to RX characteristic changes...") - CONFIG_HANDLE = 0x0013 - # was wrong 0x000c - get_free_sdc_space = b'\x01\x00\x1e' - start_microphone = b'\x08\x00\x02\xc6\xa2\xf9ek\x02\x01' - stop_microphone = b'\x01\x00\x03' - val = struct.pack(" Date: Mon, 27 Jan 2025 16:59:22 +0100 Subject: [PATCH 24/46] Delete badge_interface.py and bleak_test.py --- BadgeFramework/badge_interface.py | 47 ------------------- BadgeFramework/bleak_test.py | 75 ------------------------------- 2 files changed, 122 deletions(-) delete mode 100644 BadgeFramework/badge_interface.py delete mode 100644 BadgeFramework/bleak_test.py diff --git a/BadgeFramework/badge_interface.py b/BadgeFramework/badge_interface.py deleted file mode 100644 index 8646eda..0000000 --- a/BadgeFramework/badge_interface.py +++ /dev/null @@ -1,47 +0,0 @@ -from badge import * -from ble_badge_connection import * - -class BadgeInterface(): - def __init__(self, address): - self.address = address - self.connection = None - self.badge = None - - def connect(self): - self.connection = BLEBadgeConnection.get_connection_to_badge(self.address) - self.connection.connect() - self.badge = OpenBadge(self.connection) - print("Connected!") - - def get_status(self): - return self.badge.get_status() - - def get_free_sdc_space(self): - return self.badge.get_free_sdc_space() - - def start_imu(self): - return self.badge.start_imu() - - def start_microphone(self, mode): - #print("MODE interface ",mode) - return self.badge.start_microphone(mode=mode) - - def stop_microphone(self): - return self.badge.stop_microphone() - - def stop_imu(self): - return self.badge.stop_imu() - - def start_scan(self): - return self.badge.start_scan() - - def stop_scan(self): - return self.badge.stop_scan() - - def get_imu_data(self): - return self.badge.get_imu_data() - - def sdc_errase_all(self): - return self.badge.sdc_errase_all() - - \ No newline at end of file diff --git a/BadgeFramework/bleak_test.py b/BadgeFramework/bleak_test.py deleted file mode 100644 index b205ae1..0000000 --- a/BadgeFramework/bleak_test.py +++ /dev/null @@ -1,75 +0,0 @@ -import asyncio -import logging -import utils -from bleak import BleakScanner -from badge import OpenBadge -from datetime import datetime - - -async def synchronize_device(open_badge: OpenBadge, logger: logging.Logger) -> None: - # TODO: pass in the universal time code here? - status = await open_badge.get_status() - logger.info(f"Status received for the following midge: {open_badge.id}.") - # TODO This is not actually the timestamp before, find how to get it. - logger.debug(f"Device timestamp before sync - seconds: {status.timestamp.seconds}, ms:{status.timestamp.ms}.") - if status.imu_status == 0: - logger.info(f"IMU is not recording for participant {open_badge.id}.") - if status.microphone_status == 0: - logger.info(f"Mic is not recording for participant {open_badge.id}.") - if status.scan_status == 0: - logger.info(f"Scan is not recording for participant {open_badge.id}.") - if status.clock_status == 0: - logger.info(f"Can't sync for participant {open_badge.id}.") - - -def remove_decimal(number): - # Convert to string, remove the decimal point, and convert back to integer - no_decimal = int(str(number).replace('.', '')) - return no_decimal - - -def midge_timestamp(number: float): - a = int(number * 1000) - return int(str(a)[3:]) - - -async def main(): - logger = utils.get_logger('bleak_logger') - # Find all devices - # using both BLEdevice and advertisement data here since it has more information. Also mutes the warning. - devices = await BleakScanner.discover(timeout=10.0, return_adv=True) - - # Filter out the devices that are not the midge - devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] - - # Print Id to see if it matches with any devices in the csv file. - for ble_device, adv_data in devices: - device_id = utils.get_device_id(ble_device) - print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - - for ble_device, adv_data in devices: - # device_id = utils.get_device_id(ble_device) - async with OpenBadge(ble_device) as open_badge: - # async with OpenBadge(int(device_id), ble_device.address) as open_badge: - out = await open_badge.get_status(t=midge_timestamp(datetime.now().timestamp())) - print(datetime.now().timestamp()) - start = await open_badge.start_microphone() - print(out) - print(start) - print(remove_decimal(datetime.now().timestamp())) - # space = await open_badge.get_free_sdc_space() - # print(space) - # out = await open_badge.get_status(t=40.3356) - # print(out) - # - # time.sleep(15) - # async with OpenBadge(ble_device) as open_badge: - # stop = await open_badge.stop_microphone() - # await synchronize_device(open_badge, logger) - # c = 9 - print('completed') - # Connect to the midge - - -if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file From ce60a9de58decc6ced729f9a64361ac45692daf9 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 5 Mar 2025 09:20:25 +0100 Subject: [PATCH 25/46] Modify bleak gui according to comments --- BadgeFramework/badge_gui_bleak.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index 4a28829..21d9100 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -4,11 +4,8 @@ import pandas as pd from badge import OpenBadge from datetime import datetime -# import numpy as np import sys import time -import ntplib -# from functools import partial SENSOR_ALL = 0 @@ -297,12 +294,11 @@ async def async_sensor_operation(self, badge_id, op_name, mode): return_message = await sensor_operation(t=datetime.now().timestamp()) else: return_message = await sensor_operation() - # print(return_message) return return_message def get_badge_address(self, badge_id: int) -> str: badge = self.badges[self.badges['Participant Id'] == badge_id] - address = badge['Mac Address'].to_numpy()[0] # There should be a more elegant way to do this + address = badge['Mac Address'].values[0] return address @staticmethod From 879462230321af09b696906f67d3c685d5f66afe Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 5 Mar 2025 09:37:09 +0100 Subject: [PATCH 26/46] Modify bleak gui according to comments --- BadgeFramework/badge_gui_bleak.py | 50 +------------------------------ 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index 21d9100..a4029d0 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -5,7 +5,6 @@ from badge import OpenBadge from datetime import datetime import sys -import time SENSOR_ALL = 0 @@ -53,9 +52,7 @@ def __init__(self, ): self.title("Badge Status Monitor") self.badges = pd.read_csv('mappings2.csv') - # self.badges = [] - # Create a frame to contain the badge area and the terminal area self.main_frame = ttk.Frame(self) self.main_frame.grid(row=0, column=0, sticky="nsew") # Use grid for main_frame @@ -63,36 +60,29 @@ def __init__(self, ): self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) - # Create a canvas to allow scrolling for the badge section self.canvas = tk.Canvas(self.main_frame, borderwidth=0) self.frame = ttk.Frame(self.canvas) self.scrollbar = ttk.Scrollbar(self.main_frame, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.scrollbar.set) - # Place the scrollbar and canvas in the window self.scrollbar.grid(row=0, column=0, sticky="ns") self.canvas.grid(row=0, column=1, sticky="nsew") - # Bind mouse scroll to canvas self.bind_mouse_scroll(self.canvas) # Make the badge section expand vertically self.main_frame.grid_rowconfigure(0, weight=1) - # Create a window within the canvas self.canvas_frame = self.canvas.create_window((0, 0), window=self.frame, anchor="nw") - # Configure the frame to resize with the canvas self.frame.bind("", self.on_frame_configure) - # Terminal on the right self.terminal_frame = ttk.Frame(self.main_frame) self.terminal_frame.grid(row=0, column=2, sticky="nsew") self.terminal_text = tk.Text(self.terminal_frame, wrap="word", state="normal", width=40, height=20) self.terminal_text.pack(fill="both", expand=True) - # Redirect stdout to terminal Text widget self.stdout_redirector = RedirectText(self.terminal_text) sys.stdout = self.stdout_redirector @@ -161,15 +151,12 @@ def __init__(self, ): sensor_label = ttk.Label(self.frame, text=sensor + ' status:') sensor_label.grid(row=row_status, column=2*s_idx, padx=5, pady=5) - # Create a label to display the last updated time and elapsed time timestamp_label = ttk.Label(self.frame, text="Last updated: N/A") timestamp_label.grid(row=row_status, column=0, padx=5, pady=5) - # Create a label to display the elapsed time in seconds elapsed_time_label = ttk.Label(self.frame, text="Elapsed: N/A") elapsed_time_label.grid(row=row_status, column=1, padx=5, pady=5) - # Store the canvas, light, sensor lights, timestamp label, and elapsed time label self.timestamp_labels[badge] = (timestamp_label, elapsed_time_label) self.sensor_lights[badge] = sensor_light_canvases @@ -198,7 +185,6 @@ def schedule_async_task(self, badge_id, sensor_idx, mode: bool): asyncio.create_task(self.async_task_sensors(badge_id, sensor_idx, mode)) async def async_task_all_badges(self, sensor_idx, mode: bool, use_all: bool): - # await self.async_task_sensors(badge_id=1, sensor_idx=sensor_idx, mode=mode) for row_id in self.badges.index: badge_id, use_flag = int(self.badges['Participant Id'][row_id]), bool(self.badges['Use'][row_id]) if use_flag or use_all: @@ -223,13 +209,11 @@ async def async_task_sensors(self, badge_id: int, sensor_idx: int, mode: bool): async def async_check_status(self, badge_id, mode=CHECK_NO_SYNC): # Call the async function to check the status - # statuses, timestamp = await self.check_status(badge_id) statuses, timestamp = await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='status') if statuses is None: return sensor_statuses = [getattr(statuses, s + '_status') for s in indicators_long] - # Get the canvas and light object for the badge timestamp_label, elapsed_time_label = self.timestamp_labels[badge_id] sensor_light_canvases = self.sensor_lights[badge_id] @@ -237,18 +221,14 @@ async def async_check_status(self, badge_id, mode=CHECK_NO_SYNC): sensor_color = "green" if sensor_statuses[sensor_idx] == 1 else "red" sensor_light_canvas.itemconfig(sensor_light, fill=sensor_color) - # Format the timestamp and update the timestamp label formatted_time = timestamp.strftime("%Y-%m-%d %H:%M:%S") timestamp_label.config(text=f"Last updated: {formatted_time}") - # Update the timestamps dictionary with the current time self.timestamps[badge_id] = timestamp - # Cancel the previous update task if it exists if badge_id in self.update_tasks: self.update_tasks[badge_id].cancel() - # Start a new task to update the elapsed time task = asyncio.create_task(self.update_elapsed_time(badge_id, elapsed_time_label, timestamp)) self.update_tasks[badge_id] = task @@ -264,14 +244,12 @@ def get_operation_name(mode_name: str, sensor_name: str) -> str: raise ValueError async def async_sensor(self, badge_id, sensor_name, mode: bool): - # badge_addr = self.get_badge_address(badge_id) mode_name = 'start' if mode == SENSOR_START else 'stop' op_name = self.get_operation_name(mode_name, sensor_name) badge_op_desc = f'Badge {badge_id} {op_name}' print(f"Executing: {badge_op_desc}...") try: - # await asyncio.wait_for(self.eternity(), timeout=1.0) response = await asyncio.wait_for(self.async_sensor_operation(badge_id, op_name, mode), timeout=SENSOR_TIMEOUT) print(f'Info: {badge_op_desc} successfully.') @@ -282,8 +260,7 @@ async def async_sensor(self, badge_id, sensor_name, mode: bool): print(e) response = None - # await asyncio.sleep(1) # Simulate delay - timestamp = datetime.now() # Get the current timestamp + timestamp = datetime.now() return response, timestamp async def async_sensor_operation(self, badge_id, op_name, mode): @@ -314,7 +291,6 @@ async def update_elapsed_time(badge_id, elapsed_time_label, last_update_time): pass # Task was cancelled def run(self): - # Start the tkinter mainloop self.mainloop() @@ -328,32 +304,8 @@ async def main_loop(): app.update_idletasks() app.update() - # Schedule the main loop in asyncio asyncio.run(main_loop()) -# def get_ntp_time(ntp_server='pool.ntp.org'): -# # Create an NTP client -# client = ntplib.NTPClient() -# -# try: -# # Query the NTP server -# response = client.request(ntp_server) -# -# # Convert the response to datetime -# ntp_time = datetime.fromtimestamp(response.tx_time, tz=timezone.utc) -# print("NTP time:", ntp_time.strftime('%Y-%m-%d %H:%M:%S %Z')) -# return ntp_time -# -# except Exception as e: -# print("Could not connect to NTP server:", e) -# return None - - -# Use a local NTP server (replace with your server IP if needed) -# ntp_server_ip = '192.168.1.100' # Example IP for a local NTP server -# ntp_time = get_ntp_time(ntp_server=ntp_server_ip) - - if __name__ == "__main__": run_tkinter_async() \ No newline at end of file From d05f4a538893f043c053e0fe6c90e38e049388df Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 5 Mar 2025 10:52:50 +0100 Subject: [PATCH 27/46] Modify bleak gui according to comments --- BadgeFramework/badge_gui_bleak.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index a4029d0..05d66a5 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -5,6 +5,7 @@ from badge import OpenBadge from datetime import datetime import sys +from typing import Optional, Dict, Tuple SENSOR_ALL = 0 @@ -31,11 +32,11 @@ class RedirectText: """Class to redirect stdout to a tkinter Text widget.""" - def __init__(self, text_widget): + def __init__(self, text_widget: tk.Text): self.text_widget = text_widget self.text_widget.config(state=tk.NORMAL) - def write(self, string): + def write(self, string: str): """Redirects the text to the Text widget.""" self.text_widget.insert(tk.END, string) self.text_widget.see(tk.END) # Auto-scroll to the bottom @@ -64,7 +65,6 @@ def __init__(self, ): self.frame = ttk.Frame(self.canvas) self.scrollbar = ttk.Scrollbar(self.main_frame, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.scrollbar.set) - self.scrollbar.grid(row=0, column=0, sticky="ns") self.canvas.grid(row=0, column=1, sticky="nsew") @@ -72,14 +72,11 @@ def __init__(self, ): # Make the badge section expand vertically self.main_frame.grid_rowconfigure(0, weight=1) - self.canvas_frame = self.canvas.create_window((0, 0), window=self.frame, anchor="nw") - self.frame.bind("", self.on_frame_configure) self.terminal_frame = ttk.Frame(self.main_frame) self.terminal_frame.grid(row=0, column=2, sticky="nsew") - self.terminal_text = tk.Text(self.terminal_frame, wrap="word", state="normal", width=40, height=20) self.terminal_text.pack(fill="both", expand=True) @@ -160,31 +157,31 @@ def __init__(self, ): self.timestamp_labels[badge] = (timestamp_label, elapsed_time_label) self.sensor_lights[badge] = sensor_light_canvases - def bind_mouse_scroll(self, widget): + def bind_mouse_scroll(self, widget: tk.Canvas): """Bind the mouse scroll event to the canvas.""" # Windows OS uses "", others use "" and "" widget.bind_all("", self.on_mouse_wheel) widget.bind_all("", self.on_mouse_wheel) widget.bind_all("", self.on_mouse_wheel) - def on_mouse_wheel(self, event): + def on_mouse_wheel(self, event: tk.Event): """Handle the mouse scroll event.""" if event.num == 4 or event.delta > 0: # Scroll up self.canvas.yview_scroll(-1, "units") elif event.num == 5 or event.delta < 0: # Scroll down self.canvas.yview_scroll(1, "units") - def on_frame_configure(self, event): + def on_frame_configure(self, event: tk.Event): """Reset the scroll region to encompass the inner frame.""" self.canvas.configure(scrollregion=self.canvas.bbox("all")) - def schedule_async_task(self, badge_id, sensor_idx, mode: bool): + def schedule_async_task(self, badge_id: int, sensor_idx: int, mode: bool): if badge_id == BADGES_ALL: asyncio.create_task(self.async_task_all_badges(sensor_idx, mode, use_all=USE_ALL)) else: asyncio.create_task(self.async_task_sensors(badge_id, sensor_idx, mode)) - async def async_task_all_badges(self, sensor_idx, mode: bool, use_all: bool): + async def async_task_all_badges(self, sensor_idx: int, mode: bool, use_all: bool): for row_id in self.badges.index: badge_id, use_flag = int(self.badges['Participant Id'][row_id]), bool(self.badges['Use'][row_id]) if use_flag or use_all: @@ -207,7 +204,7 @@ async def async_task_sensors(self, badge_id: int, sensor_idx: int, mode: bool): else: await self.async_check_status(badge_id, mode=CHECK_NO_SYNC) - async def async_check_status(self, badge_id, mode=CHECK_NO_SYNC): + async def async_check_status(self, badge_id: int, mode: bool=CHECK_NO_SYNC): # Call the async function to check the status statuses, timestamp = await self.async_sensor(badge_id=badge_id, mode=mode, sensor_name='status') if statuses is None: @@ -229,7 +226,7 @@ async def async_check_status(self, badge_id, mode=CHECK_NO_SYNC): if badge_id in self.update_tasks: self.update_tasks[badge_id].cancel() - task = asyncio.create_task(self.update_elapsed_time(badge_id, elapsed_time_label, timestamp)) + task = asyncio.create_task(self.update_elapsed_time(elapsed_time_label, timestamp)) self.update_tasks[badge_id] = task @staticmethod @@ -243,7 +240,7 @@ def get_operation_name(mode_name: str, sensor_name: str) -> str: else: raise ValueError - async def async_sensor(self, badge_id, sensor_name, mode: bool): + async def async_sensor(self, badge_id: int, sensor_name: str, mode: bool) -> Tuple[Optional[Dict], datetime]: mode_name = 'start' if mode == SENSOR_START else 'stop' op_name = self.get_operation_name(mode_name, sensor_name) badge_op_desc = f'Badge {badge_id} {op_name}' @@ -263,7 +260,7 @@ async def async_sensor(self, badge_id, sensor_name, mode: bool): timestamp = datetime.now() return response, timestamp - async def async_sensor_operation(self, badge_id, op_name, mode): + async def async_sensor_operation(self, badge_id: int, op_name: str, mode: int) -> Dict: badge_addr = self.get_badge_address(badge_id) async with OpenBadge(badge_id, badge_addr) as open_badge: sensor_operation = getattr(open_badge, op_name) @@ -279,7 +276,7 @@ def get_badge_address(self, badge_id: int) -> str: return address @staticmethod - async def update_elapsed_time(badge_id, elapsed_time_label, last_update_time): + async def update_elapsed_time(elapsed_time_label: ttk.Label, last_update_time: datetime): """Continuously update the elapsed time since the last status update.""" try: while True: From 769d36793a6d195680646629209d99e64d93b191 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 6 Mar 2025 10:12:26 +0100 Subject: [PATCH 28/46] test connection time --- BadgeFramework/test.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index 09ed996..2dc0765 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -4,12 +4,13 @@ import threading import time -from badge import * +import badge +from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic from ble_badge_connection import * -from bluepy import * -from bluepy import btle -from bluepy.btle import UUID, Peripheral, DefaultDelegate, AssignedNumbers ,Scanner -from bluepy.btle import BTLEException +# from bluepy import * +# from bluepy import btle +# from bluepy.btle import UUID, Peripheral, DefaultDelegate, AssignedNumbers ,Scanner +# from bluepy.btle import BTLEException def mic_test(badge, mode): @@ -147,8 +148,6 @@ def imu_test(badge): print(" imu disabled: FAIL ") - - def scan_test(badge): print("###################################") print(" sensor: scan ") @@ -179,7 +178,8 @@ def scan_test(badge): print(" scan disabled: PASS ") else: print(" scan disabled: FAIL ") - + + def errase_mem(badge): errased = badge.sdc_errase_all() # clean sd memory @@ -235,5 +235,9 @@ def main(): connection.disconnect() +def test_conn(): + pass + if __name__ == "__main__": - main() \ No newline at end of file + # main() + test_conn() From fd504a22a892f9806733180b8523a60291af90ed Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 6 Mar 2025 13:20:41 +0100 Subject: [PATCH 29/46] modify test codes --- BadgeFramework/test.py | 175 ++++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 70 deletions(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index 2dc0765..47bbcd0 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -3,19 +3,17 @@ import sys import threading import time +from datetime import datetime +import utils +from badge import OpenBadge +from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic, BleakScanner +import asyncio +# from ble_badge_connection import * -import badge -from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic -from ble_badge_connection import * -# from bluepy import * -# from bluepy import btle -# from bluepy.btle import UUID, Peripheral, DefaultDelegate, AssignedNumbers ,Scanner -# from bluepy.btle import BTLEException - -def mic_test(badge, mode): +async def mic_test(badge, mode): if (mode==0): - start_stereo = badge.start_microphone(t=None,mode=0) # start mic in stereo mode + start_stereo = await badge.start_microphone(t=None,mode=0) # start mic in stereo mode print(" sensor: mic, stereo mode ") # check if mic was started in stereo mode if (start_stereo.mode==0): @@ -34,18 +32,18 @@ def mic_test(badge, mode): time.sleep(10) # reecording time - mic = badge.get_status() + mic = await badge.get_status() # check if mic was enabled if (mic.microphone_status): print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") - badge.stop_microphone() # stop recording + await badge.stop_microphone() # stop recording time.sleep(0.5) - mic = badge.get_status() + mic = await badge.get_status() # check if mic was disabled with success if (~mic.microphone_status): print(" mic disabled: PASS ") @@ -56,7 +54,7 @@ def mic_test(badge, mode): print(" sensor: mic, mono mode ") #print("-----------------------------------") - start_mono = badge.start_microphone(t=None,mode=1) + start_mono = await badge.start_microphone(t=None,mode=1) if (start_mono.mode==1): print(" mic mode: PASS ") @@ -75,30 +73,30 @@ def mic_test(badge, mode): time.sleep(10) # reecording time - mic = badge.get_status() + mic = await badge.get_status() # check if mic was enabled if (mic.microphone_status): print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") - badge.stop_microphone() # stop recording + await badge.stop_microphone() # stop recording time.sleep(0.5) - mic = badge.get_status() + mic = await badge.get_status() # check if mic was disabled with success if (~mic.microphone_status): print(" mic disabled: PASS ") else: print(" mic disabled: PASS ") -def imu_test(badge): +async def imu_test(badge): print("###################################") print(" sensor: imu ") #print("#-----------------------------------#") - imu_start = badge.start_imu() # start imu + imu_start = await badge.start_imu() # start imu time.sleep(0.1) # safety wait #check if imu self test was done if (imu_start.self_test_done): @@ -106,9 +104,9 @@ def imu_test(badge): else: print(" imu self test: FAIL ") - imu = badge.get_status() # get imu status + imu = await badge.get_status() # get imu status time.sleep(0.1) # safety wait - imu_data = badge.get_imu_data() # get imu data + imu_data = await badge.get_imu_data() # get imu data #print(imu_data) @@ -137,10 +135,10 @@ def imu_test(badge): time.sleep(10) # imu enabled time - badge.stop_imu() + await badge.stop_imu() time.sleep(0.5) # safety wait - imu = badge.get_status() + imu = await badge.get_status() if (~imu.imu_status): print(" imu disabled: PASS ") @@ -148,15 +146,15 @@ def imu_test(badge): print(" imu disabled: FAIL ") -def scan_test(badge): +async def scan_test(badge): print("###################################") print(" sensor: scan ") #print("#-----------------------------------#") - badge.start_scan(window_ms = 250, interval_ms = 1000) ## start scan with default parameters + await badge.start_scan(window_ms = 250, interval_ms = 1000) ## start scan with default parameters time.sleep(10) # scan enabled time - scan = badge.get_status() + scan = await badge.get_status() # check if scan was enabled succesfully if (scan.scan_status): print(" scan enabled: PASS ") @@ -168,11 +166,11 @@ def scan_test(badge): else: print(" scan data: FAIL ") - badge.stop_scan() # stop scan + await badge.stop_scan() # stop scan time.sleep(0.5) # safety wait - scan = badge.get_status() # get scan status + scan = await badge.get_status() # get scan status # check if scan was disabled succesfully if (~scan.scan_status): print(" scan disabled: PASS ") @@ -180,8 +178,8 @@ def scan_test(badge): print(" scan disabled: FAIL ") -def errase_mem(badge): - errased = badge.sdc_errase_all() # clean sd memory +async def errase_mem(badge): + errased = await badge.sdc_errase_all() # clean sd memory if (errased.done_errase): print(" memory cleaned: PASS ") @@ -190,54 +188,91 @@ def errase_mem(badge): print(" memory cleaned: FAIL ") print("###################################") - def connect(self): - connection = BLEBadgeConnection.get_connection_to_badge(self.address) - connection.connect() - badge = OpenBadge(self.connection) - print("Connected!") - return badge + # def connect(self): + # connection = BLEBadgeConnection.get_connection_to_badge(self.address) + # connection.connect() + # badge = OpenBadge(self.connection) + # print("Connected!") + # return badge -def main(): - device_addr = sys.argv[1] +async def main(): + # device_addr = sys.argv[1] - connection = BLEBadgeConnection.get_connection_to_badge(device_addr) - connection.connect() - badge = OpenBadge(connection) - print(" connetced ") - print("###################################") - print(" Midge test ") + devices = await BleakScanner.discover(timeout=10.0, return_adv=True) - try: - mic_test(badge,0) - except: - print("mic error") + # Filter out the devices that are not the midge + devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] - try: - mic_test(badge,1) - except: - print("mic error") + if devices: + ble_device, adv_data = devices[0] + else: + print("No devices found") + return try: - scan_test(badge) - except: - print("scan error") - + async with OpenBadge(ble_device) as open_badge: + print(" connetced ") + print("###################################") + print(" Midge test ") + try: + await mic_test(open_badge, 0) + except: + print("mic error") + + try: + await mic_test(open_badge, 1) + except: + print("mic error") + + try: + await scan_test(open_badge) + except: + print("scan error") + + try: + await imu_test(open_badge) + except: + print("imu error") + + try: + await errase_mem(open_badge) + except: + print("errase memory error") + + except TimeoutError: + print("failed to connect to device") + + +async def communicate_with_device(ble_device): try: - imu_test(badge) - except: - print("imu error") - - try: - errase_mem(badge) - except: - print("errase memory error") - - connection.disconnect() + async with OpenBadge(ble_device) as open_badge: + # async with OpenBadge(int(device_id), ble_device.address) as open_badge: + out = await open_badge.get_status() + # start = await open_badge.start_microphone() + print(out) + except TimeoutError: + print("failed to connect to device") + +async def test_conn(): + logger = utils.get_logger('bleak_logger') + # Find all devices + # using both BLEdevice and advertisement data here since it has more information. Also mutes the warning. + devices = await BleakScanner.discover(timeout=10.0, return_adv=True) + + # Filter out the devices that are not the midge + devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] + + # Print Id to see if it matches with any devices in the csv file. + for ble_device, adv_data in devices: + device_id = utils.get_device_id(ble_device) + print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") + + tasks = [communicate_with_device(ble_device) for ble_device, adv_data in devices] + await asyncio.gather(*tasks) -def test_conn(): - pass + print(f'after connection: {datetime.now().timestamp()}') if __name__ == "__main__": - # main() - test_conn() + asyncio.run(main()) + # asyncio.run(test_conn()) From d45196e81c884ffa9f86c0b893113c1725ed58c7 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 6 Mar 2025 17:47:44 +0100 Subject: [PATCH 30/46] modify protocol --- BadgeFramework/badge_protocol.py | 2190 ++++++++++++++++++------------ BadgeFramework/test.py | 4 +- 2 files changed, 1311 insertions(+), 883 deletions(-) diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index 6c2978a..93b25c5 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -10,1148 +10,1576 @@ Request_identify_request_tag = 27 Request_restart_request_tag = 29 Request_free_sdc_space_request_tag = 30 +Request_sdc_errase_all_request_tag = 31 +Request_get_imu_data_request_tag = 33 Response_status_response_tag = 1 Response_start_microphone_response_tag = 2 Response_start_scan_response_tag = 3 Response_start_imu_response_tag = 4 Response_free_sdc_space_response_tag = 5 +Response_sdc_errase_all_response_tag = 32 +Response_get_imu_data_response_tag = 34 class _Ostream: - def __init__(self): - self.buf = b'' + def __init__(self): + self.buf = b'' - def write(self, data): - self.buf += data + def write(self, data): + self.buf += data class _Istream: - def __init__(self, buf): - self.buf = buf + def __init__(self, buf): + self.buf = buf - def read(self, l): - if(l > len(self.buf)): - raise Exception("Not enough bytes in Istream to read") - ret = self.buf[0:l] - self.buf = self.buf[l:] - return ret + def read(self, l): + if (l > len(self.buf)): + raise Exception("Not enough bytes in Istream to read") + ret = self.buf[0:l] + self.buf = self.buf[l:] + # for i in ret: + # print("_Istream:",i) + return ret class Timestamp: - def __init__(self): - self.reset() - def __repr__(self): - return str(self.__dict__) + def __init__(self): + self.reset() - def reset(self): - self.seconds = 0 - self.ms = 0 - pass + def __repr__(self): + return str(self.__dict__) - def encode(self): - ostream = _Ostream() - self.encode_internal(ostream) - return ostream.buf + def reset(self): + self.seconds = 0 + self.ms = 0 + pass - def encode_internal(self, ostream): - self.encode_seconds(ostream) - self.encode_ms(ostream) - pass + def encode(self): + ostream = _Ostream() + self.encode_internal(ostream) + return ostream.buf - def encode_seconds(self, ostream): - ostream.write(struct.pack(' Date: Fri, 7 Mar 2025 09:54:04 +0100 Subject: [PATCH 31/46] modify protocol --- BadgeFramework/badge_protocol.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index 93b25c5..bfac5c2 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -847,7 +847,6 @@ def decode_get_imu_data_request(self, istream): class StatusResponse: - def __init__(self): self.reset() @@ -924,26 +923,19 @@ def decode_internal(self, istream): pass def decode_clock_status(self, istream): - # print("decode_clock_status:", istream.buf) - # print("decode_clock_status:", istream.read(1)) - - self.clock_status = struct.unpack(' Date: Fri, 7 Mar 2025 11:05:24 +0100 Subject: [PATCH 32/46] modify test --- BadgeFramework/test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index f3379f2..d16d366 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -267,7 +267,6 @@ async def test_conn(): for ble_device, adv_data in devices: device_id = utils.get_device_id(ble_device) print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - tasks = [communicate_with_device(ble_device) for ble_device, adv_data in devices] await asyncio.gather(*tasks) From 8ac99dda3dea8690793475d0758543450bace84d Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 7 Mar 2025 11:30:43 +0100 Subject: [PATCH 33/46] modify test and protocol --- BadgeFramework/test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index d16d366..92c64ce 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -259,6 +259,8 @@ async def test_conn(): # Find all devices # using both BLEdevice and advertisement data here since it has more information. Also mutes the warning. devices = await BleakScanner.discover(timeout=10.0, return_adv=True) + # async with BleakClient('de:94:80:39:25:be') as client: + # print(client.is_connected) # Filter out the devices that are not the midge devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] @@ -267,8 +269,8 @@ async def test_conn(): for ble_device, adv_data in devices: device_id = utils.get_device_id(ble_device) print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - tasks = [communicate_with_device(ble_device) for ble_device, adv_data in devices] - await asyncio.gather(*tasks) + # tasks = [communicate_with_device(ble_device) for ble_device, adv_data in devices] + # await asyncio.gather(*tasks) print(f'after connection: {datetime.now().timestamp()}') From 469309e4ee79c48f276f1b698a51ddc477d55642 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Fri, 7 Mar 2025 18:39:08 +0100 Subject: [PATCH 34/46] modify badge protocol and test --- BadgeFramework/badge.py | 13 +++++ BadgeFramework/badge_protocol.py | 99 ++++++++++++++++---------------- BadgeFramework/test.py | 52 +++++++++-------- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index fb67690..9774e87 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -30,6 +30,8 @@ DECODE_START_SCAN_RESPONSE = 3 DECODE_START_IMU_REQUEST = 4 DECODE_FREE_SDC_SPACE_RESPONSE = 5 +DECODE_SDC_ERASE_ALL_RESPONSE = 32 +DECODE_GET_IMU_DATA_RESPONSE = 34 logger = logging.getLogger(__name__) @@ -341,6 +343,17 @@ async def stop_imu(self) -> None: self.deal_response(response_type=-1) return None + @request_handler_marker(action_desc='get imu data') + async def get_imu_data(self): + request = bp.Request() + request.type.which = bp.Request_get_imu_data_request_tag + request.type.get_imu_data_request = bp.GetIMUDataRequest() + request.type.get_imu_data_request.timestamp = bp.Timestamp() + + await self.request_response(request) + self.deal_response(response_type=DECODE_GET_IMU_DATA_RESPONSE) + return None + @request_handler_marker(action_desc='identify') async def identify(self, duration_seconds=10) -> bool: """Send a request to the badge to light an LED to identify its self. diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index bfac5c2..a00e867 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -34,13 +34,11 @@ class _Istream: def __init__(self, buf): self.buf = buf - def read(self, l): - if (l > len(self.buf)): + def read(self, length): + if length > len(self.buf): raise Exception("Not enough bytes in Istream to read") - ret = self.buf[0:l] - self.buf = self.buf[l:] - # for i in ret: - # print("_Istream:",i) + ret = self.buf[0: length] + self.buf = self.buf[length:] return ret @@ -86,10 +84,10 @@ def decode_internal(self, istream): pass def decode_seconds(self, istream): - self.seconds = struct.unpack(' Date: Tue, 11 Mar 2025 16:36:10 +0100 Subject: [PATCH 35/46] Pass most of test --- BadgeFramework/badge.py | 61 ++++++++++----- BadgeFramework/badge_protocol.py | 62 ++++++++------- BadgeFramework/test.py | 127 ++++++++++++++++--------------- 3 files changed, 136 insertions(+), 114 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 9774e87..15c333d 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -6,6 +6,7 @@ import logging import struct import ntplib +from collections import deque from datetime import datetime, timezone from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic @@ -25,14 +26,6 @@ CONNECTION_RETRY_TIMES = 15 DUPLICATE_TIME_INTERVAL = 2 -DECODE_STATUS_RESPONSE = 1 -DECODE_START_MICROPHONE_RESPONSE = 2 -DECODE_START_SCAN_RESPONSE = 3 -DECODE_START_IMU_REQUEST = 4 -DECODE_FREE_SDC_SPACE_RESPONSE = 5 -DECODE_SDC_ERASE_ALL_RESPONSE = 32 -DECODE_GET_IMU_DATA_RESPONSE = 34 - logger = logging.getLogger(__name__) @@ -120,6 +113,7 @@ def __init__(self, device: BLEDevice or int, mac_address: str = None): self.client = BleakClient(self.address, disconnected_callback=badge_disconnected) # self.rx_message = b'' self.rx_list = [] + self.message_buffer = deque() async def __aenter__(self): for _ in range(CONNECTION_RETRY_TIMES): @@ -199,12 +193,29 @@ def message_is_duplicated(message_list: list, new_message: dict) -> bool: def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray): """callback function for receiving message. Note that this must be used in combination with the 'receive' function to work properly.""" - if len(message) > 0: - new_message = {'time': time.time(), 'message': message} - if not self.message_is_duplicated(self.rx_list, new_message): - # print(f"RX changed {sender}: {message}" + str(time.time())) - # self.rx_message = message + self.message_buffer.extend(message) # Store received bytes sequentially + # print(f"Received message: {message}" + str(time.time())) + while len(self.message_buffer) >= 2: # Ensure at least two bytes are available to read length + # Peek the first two bytes to get the expected message length + length_bytes = bytes([self.message_buffer[0], self.message_buffer[1]]) + expected_length = struct.unpack('= expected_length + 2: + full_message = bytearray() + for _ in range(2 + expected_length): + full_message.append(self.message_buffer.popleft()) # Remove bytes from buffer + new_message = {'time': time.time(), 'message': full_message} self.rx_list.append(new_message) + print(f"Full message: {full_message}" + str(time.time())) + else: + break # Wait for more data if full message is not yet received + # if len(message) > 0: + # new_message = {'time': time.time(), 'message': message} + # if not self.message_is_duplicated(self.rx_list, new_message): + # print(f"RX changed {sender}: {message}" + str(time.time())) + # # self.rx_message = message + # self.rx_list.append(new_message) async def request_response(self, message: bp.Request, require_response: Optional[bool] = True): """request response from client""" @@ -223,7 +234,6 @@ def decode_response(response: bytearray or bytes): def deal_response(self, response_type): """deal response from client. Currently, this only involves decoding.""" - # print('rx list:', self.rx_list) if response_type < 0: # response_type < 0 means this response does not contain messages if len(self.rx_list) > 0: @@ -258,7 +268,7 @@ async def get_status(self, t=None, new_id: Optional[int] = None, new_group_numbe request.type.status_request.has_badge_assignement = True await self.request_response(request) - return self.deal_response(response_type=DECODE_STATUS_RESPONSE).type.status_response + return self.deal_response(response_type=bp.Response_status_response_tag).type.status_response async def set_id_at_start(self, badge_id, group_number): try: @@ -277,7 +287,8 @@ async def start_microphone(self, t=None, mode=DEFAULT_MICROPHONE_MODE) -> bp.Sta request.type.start_microphone_request.mode = mode await self.request_response(request) - return self.deal_response(response_type=DECODE_START_MICROPHONE_RESPONSE).type.start_microphone_response + return (self.deal_response(response_type=bp.Response_start_microphone_response_tag) + .type.start_microphone_response) @request_handler_marker(action_desc='stop microphone') async def stop_microphone(self) -> None: @@ -305,7 +316,7 @@ async def start_scan(self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DE request.type.start_scan_request.interval = interval_ms await self.request_response(request) - return self.deal_response(response_type=DECODE_START_SCAN_RESPONSE).type.start_scan_response + return self.deal_response(response_type=bp.Response_start_scan_response_tag).type.start_scan_response @request_handler_marker(action_desc='stop scan') async def stop_scan(self) -> None: @@ -330,7 +341,7 @@ async def start_imu(self, t=None, acc_fsr=DEFAULT_IMU_ACC_FSR, gyr_fsr=DEFAULT_I request.type.start_imu_request.datarate = datarate await self.request_response(request) - return self.deal_response(response_type=DECODE_START_IMU_REQUEST).type.start_imu_response + return self.deal_response(response_type=bp.Response_start_imu_response_tag).type.start_imu_response @request_handler_marker(action_desc='stop imu') async def stop_imu(self) -> None: @@ -351,8 +362,16 @@ async def get_imu_data(self): request.type.get_imu_data_request.timestamp = bp.Timestamp() await self.request_response(request) - self.deal_response(response_type=DECODE_GET_IMU_DATA_RESPONSE) - return None + return self.deal_response(response_type=bp.Response_get_imu_data_response_tag).type.get_imu_data_response + + @request_handler_marker(action_desc='sdc erase all') + async def sdc_erase_all(self): + request = bp.Request() + request.type.which = bp.Request_sdc_erase_all_request_tag + request.type.sdc_erase_all_request = bp.EraseAllRequest() + + await self.request_response(request) + return self.deal_response(response_type=bp.Response_sdc_erase_all_response_tag).type.sdc_erase_all_response @request_handler_marker(action_desc='identify') async def identify(self, duration_seconds=10) -> bool: @@ -387,7 +406,7 @@ async def get_free_sdc_space(self) -> bp.FreeSDCSpaceResponse: request.type.free_sdc_space_request = bp.FreeSDCSpaceRequest() await self.request_response(request) - return self.deal_response(response_type=DECODE_FREE_SDC_SPACE_RESPONSE).type.free_sdc_space_response + return self.deal_response(response_type=bp.Response_free_sdc_space_response_tag).type.free_sdc_space_response async def start_recording_all_sensors(self): await self.get_status() diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index a00e867..da87e8f 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -10,7 +10,7 @@ Request_identify_request_tag = 27 Request_restart_request_tag = 29 Request_free_sdc_space_request_tag = 30 -Request_sdc_errase_all_request_tag = 31 +Request_sdc_erase_all_request_tag = 31 Request_get_imu_data_request_tag = 33 Response_status_response_tag = 1 @@ -18,7 +18,7 @@ Response_start_scan_response_tag = 3 Response_start_imu_response_tag = 4 Response_free_sdc_space_response_tag = 5 -Response_sdc_errase_all_response_tag = 32 +Response_sdc_erase_all_response_tag = 32 Response_get_imu_data_response_tag = 34 @@ -604,7 +604,7 @@ def decode_internal(self, istream): pass -class ErraseAllRequest: +class EraseAllRequest: def __init__(self): self.reset() @@ -716,7 +716,7 @@ def reset(self): self.identify_request = None self.restart_request = None self.free_sdc_space_request = None - self.sdc_errase_all_request = None + self.sdc_erase_all_request = None self.get_imu_data_request = None pass @@ -733,7 +733,7 @@ def encode_internal(self, ostream): 27: self.encode_identify_request, 29: self.encode_restart_request, 30: self.encode_free_sdc_space_request, - 31: self.encode_sdc_errase_all_request, + 31: self.encode_sdc_erase_all_request, 33: self.encode_get_imu_data_request, } options[self.which](ostream) @@ -769,8 +769,8 @@ def encode_restart_request(self, ostream): def encode_free_sdc_space_request(self, ostream): self.free_sdc_space_request.encode_internal(ostream) - def encode_sdc_errase_all_request(self, ostream): - self.sdc_errase_all_request.encode_internal(ostream) + def encode_sdc_erase_all_request(self, ostream): + self.sdc_erase_all_request.encode_internal(ostream) def encode_get_imu_data_request(self, ostream): self.get_imu_data_request.encode_internal(ostream) @@ -789,7 +789,7 @@ def decode_internal(self, istream): 27: self.decode_identify_request, 29: self.decode_restart_request, 30: self.decode_free_sdc_space_request, - 31: self.decode_sdc_errase_all_request, + 31: self.decode_sdc_erase_all_request, 33: self.decode_get_imu_data_request, } options[self.which](istream) @@ -835,12 +835,12 @@ def decode_free_sdc_space_request(self, istream): self.free_sdc_space_request = FreeSDCSpaceRequest() self.free_sdc_space_request.decode_internal(istream) - def decode_sdc_errase_all_request(self, istream): - self.sdc_errase_all_request = ErraseAllRequest() - self.sdc_errase_all_request.decode_internal(istream) + def decode_sdc_erase_all_request(self, istream): + self.sdc_erase_all_request = EraseAllRequest() + self.sdc_erase_all_request.decode_internal(istream) def decode_get_imu_data_request(self, istream): - self.get_imu_data_request = ErraseAllRequest() + self.get_imu_data_request = EraseAllRequest() self.get_imu_data_request.decode_internal(istream) @@ -1003,8 +1003,10 @@ def decode(cls, buf): return obj def decode_internal(self, istream): + print('decoding: ', istream.buf) self.reset() self.decode_timestamp(istream) + # TODO: After decoding timestamp, should remove 6 bytes and start from there self.decode_mode(istream) self.decode_switch_pos(istream) self.decode_gain_l(istream) @@ -1152,23 +1154,19 @@ def decode_timestamp(self, istream): self.timestamp.decode_internal(istream) def decode_self_test_done(self, istream): - print(istream.buf) self.self_test_done = struct.unpack(' Date: Fri, 14 Mar 2025 16:12:38 +0100 Subject: [PATCH 36/46] Modify audio parser and check decode parameters --- BadgeFramework/audio_parser_V0.py | 112 ++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index 5cc6dd7..089e7a2 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -1,3 +1,6 @@ +import argparse +import wave + import numpy as np from scipy.io.wavfile import write from pathlib import Path @@ -5,7 +8,8 @@ HIGH_SAMPLE_RATE = 8000 LOW_SAMPLE_RATE = 1250 -def main(fn): + +def parse_folder(fn): data_folder = Path(fn) for path_raw_input in sorted(data_folder.iterdir()): if (path_raw_input.is_file() and path_raw_input.suffix == "" and @@ -43,13 +47,109 @@ def main(fn): write(filename=str(path_wav_output), rate=sample_rate, data=audio_data) +def parse_simple(path_raw_input: Path, path_wav_output: Path): + sample_rate = 20000 # Low frequency sampling + buffer_dtype = np.int16 # 16-bit PCM + num_channels = 1 # Mono + + with path_raw_input.open("rb") as f: + raw_data = f.read() + + audio_data = np.frombuffer(raw_data, dtype=buffer_dtype) + if num_channels == 2: + audio_data = audio_data.reshape(-1, 2) + + # Save the data in a WAV file + write(filename=str(path_wav_output), rate=sample_rate, data=audio_data) + + +def get_wav_info(file_path: Path): + """Extracts metadata and raw audio data from a WAV file.""" + try: + with wave.open(str(file_path), 'rb') as wav_file: + params = wav_file.getparams() + frames = wav_file.readframes(params.nframes) + dtype = np.int16 if params.sampwidth == 2 else np.int32 + audio_data = np.frombuffer(frames, dtype=dtype) + if params.nchannels == 2: + audio_data = audio_data.reshape(-1, 2) + + info = { + "Channels": params.nchannels, + "Sample Width (bytes)": params.sampwidth, + "Frame Rate (Hz)": params.framerate, + "Number of Frames": params.nframes, + "Compression Type": params.comptype, + "Compression Name": params.compname, + "Duration (s)": params.nframes / params.framerate, + "Audio Data": audio_data + } + return info + except wave.Error as e: + print(f"Error reading {file_path}: {e}") + return None + + +def compare_wav_files(file1, file2): + """Compares two WAV files and prints their differences, including audio data analysis.""" + info1 = get_wav_info(file1) + info2 = get_wav_info(file2) + + if not info1 or not info2: + print("One or both files could not be read correctly.") + return + + print("Comparison of WAV Files:") + print(f"{'Attribute':<25}{'File 1':<20}{'File 2'}") + print("-" * 65) + for key in info1: + if key != "Audio Data": + value1 = info1[key] + value2 = info2[key] + match = "✔" if value1 == value2 else "✘" + print(f"{key:<25}{str(value1):<20}{str(value2)} {match}") + + # Compare actual audio data + audio_data1 = info1["Audio Data"] + audio_data2 = info2["Audio Data"] + + min_length = min(len(audio_data1), len(audio_data2)) + if min_length == 0: + print("Cannot compare empty audio data.") + return + + audio_data1 = audio_data1[:min_length] + audio_data2 = audio_data2[:min_length] + + diff = audio_data1 - audio_data2 + max_diff = np.max(np.abs(diff)) + norm_diff = np.linalg.norm(diff) + + print("\nAudio Data Comparison:") + print(f"Max Difference: {max_diff}") + print(f"Norm of Difference: {norm_diff}") + + if __name__ == '__main__': """ Takes as input a folder with RAW audio files from the midge and saves them as .wav files. """ - import argparse - parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') - parser.add_argument('--fn', required=True,help='Please enter the path to the file') - args = parser.parse_args() - main(fn=args.fn) + # import argparse + # parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') + # parser.add_argument('--fn', required=True,help='Please enter the path to the file') + # args = parser.parse_args() + # parse_folder(fn=args.fn) + + # 1587563550840_audio_2, 1587564263795_audio_2, 1587564858250_audio_2 + old_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" + "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") + output_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" + "dataset_collection\\tech_pilot_1\\audio_test") + + old_wav_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" + "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250.wav") + + new_wav_path = output_path / (old_path.stem + "_py.wav") + parse_simple(old_path, new_wav_path) + compare_wav_files(old_wav_path, new_wav_path) From ab1de26e5b09532a323c017da22912a1a4b976b7 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 10 Apr 2025 14:38:40 +0200 Subject: [PATCH 37/46] Modify audio parser and check decode parameters, modify badge_protocol --- BadgeFramework/audio_parser_V0.py | 12 ++- BadgeFramework/badge_protocol.py | 160 ++++++++++++++++-------------- BadgeFramework/test.py | 16 +-- 3 files changed, 99 insertions(+), 89 deletions(-) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index 089e7a2..8230d09 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -141,15 +141,17 @@ def compare_wav_files(file1, file2): # parse_folder(fn=args.fn) # 1587563550840_audio_2, 1587564263795_audio_2, 1587564858250_audio_2 - old_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" - "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") + stephanie_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" + "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") + pilot1_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" + "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") output_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" "dataset_collection\\tech_pilot_1\\audio_test") old_wav_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250.wav") - new_wav_path = output_path / (old_path.stem + "_py.wav") + new_wav_path = output_path / (pilot1_path.stem + "_py.wav") - parse_simple(old_path, new_wav_path) - compare_wav_files(old_wav_path, new_wav_path) + parse_simple(pilot1_path, new_wav_path) + # compare_wav_files(old_wav_path, new_wav_path) diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index da87e8f..b7f1af5 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -10,7 +10,7 @@ Request_identify_request_tag = 27 Request_restart_request_tag = 29 Request_free_sdc_space_request_tag = 30 -Request_sdc_erase_all_request_tag = 31 +Request_sdc_errase_all_request_tag = 31 Request_get_imu_data_request_tag = 33 Response_status_response_tag = 1 @@ -18,7 +18,7 @@ Response_start_scan_response_tag = 3 Response_start_imu_response_tag = 4 Response_free_sdc_space_response_tag = 5 -Response_sdc_erase_all_response_tag = 32 +Response_sdc_errase_all_response_tag = 32 Response_get_imu_data_response_tag = 34 @@ -34,11 +34,13 @@ class _Istream: def __init__(self, buf): self.buf = buf - def read(self, length): - if length > len(self.buf): + def read(self, l): + if (l > len(self.buf)): raise Exception("Not enough bytes in Istream to read") - ret = self.buf[0: length] - self.buf = self.buf[length:] + ret = self.buf[0:l] + self.buf = self.buf[l:] + # for i in ret: + # print("_Istream:",i) return ret @@ -84,10 +86,10 @@ def decode_internal(self, istream): pass def decode_seconds(self, istream): - self.seconds = struct.unpack(' Date: Thu, 10 Apr 2025 14:47:30 +0200 Subject: [PATCH 38/46] modify badge protocol in vscode --- BadgeFramework/badge_protocol.py | 2553 +++++++++++++++--------------- 1 file changed, 1274 insertions(+), 1279 deletions(-) diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index b7f1af5..aa61d33 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -21,1564 +21,1559 @@ Response_sdc_errase_all_response_tag = 32 Response_get_imu_data_response_tag = 34 - class _Ostream: - def __init__(self): - self.buf = b'' - - def write(self, data): - self.buf += data - + def __init__(self): + self.buf = b'' + def write(self, data): + self.buf += data class _Istream: - def __init__(self, buf): - self.buf = buf - - def read(self, l): - if (l > len(self.buf)): - raise Exception("Not enough bytes in Istream to read") - ret = self.buf[0:l] - self.buf = self.buf[l:] - # for i in ret: - # print("_Istream:",i) - return ret - + def __init__(self, buf): + self.buf = buf + def read(self, l): + if(l > len(self.buf)): + raise Exception("Not enough bytes in Istream to read") + ret = self.buf[0:l] + self.buf = self.buf[l:] + #for i in ret: + # print("_Istream:",i) + return ret class Timestamp: - def __init__(self): - self.reset() + def __init__(self): + self.reset() + + def __repr__(self): + return str(self.__dict__) - def __repr__(self): - return str(self.__dict__) + def reset(self): + self.seconds = 0 + self.ms = 0 + pass - def reset(self): - self.seconds = 0 - self.ms = 0 - pass + def encode(self): + ostream = _Ostream() + self.encode_internal(ostream) + return ostream.buf - def encode(self): - ostream = _Ostream() - self.encode_internal(ostream) - return ostream.buf + def encode_internal(self, ostream): + self.encode_seconds(ostream) + self.encode_ms(ostream) + pass - def encode_internal(self, ostream): - self.encode_seconds(ostream) - self.encode_ms(ostream) - pass + def encode_seconds(self, ostream): + ostream.write(struct.pack(' Date: Thu, 10 Apr 2025 16:55:35 +0200 Subject: [PATCH 39/46] roll back audio_parser_V0.py, imu_parser_V0.py, hub_V1.py, modify badge.py and badge_protocol.py, test.py --- BadgeFramework/audio_parser_V0.py | 115 +-------------- BadgeFramework/badge.py | 29 +--- BadgeFramework/badge_protocol.py | 68 ++++----- BadgeFramework/hub_V1.py | 230 +++--------------------------- BadgeFramework/imu_parser_V0.py | 18 +-- BadgeFramework/test.py | 44 +++--- 6 files changed, 87 insertions(+), 417 deletions(-) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index 8230d09..283d878 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -1,6 +1,3 @@ -import argparse -import wave - import numpy as np from scipy.io.wavfile import write from pathlib import Path @@ -8,8 +5,7 @@ HIGH_SAMPLE_RATE = 8000 LOW_SAMPLE_RATE = 1250 - -def parse_folder(fn): +def main(fn): data_folder = Path(fn) for path_raw_input in sorted(data_folder.iterdir()): if (path_raw_input.is_file() and path_raw_input.suffix == "" and @@ -47,111 +43,12 @@ def parse_folder(fn): write(filename=str(path_wav_output), rate=sample_rate, data=audio_data) -def parse_simple(path_raw_input: Path, path_wav_output: Path): - sample_rate = 20000 # Low frequency sampling - buffer_dtype = np.int16 # 16-bit PCM - num_channels = 1 # Mono - - with path_raw_input.open("rb") as f: - raw_data = f.read() - - audio_data = np.frombuffer(raw_data, dtype=buffer_dtype) - if num_channels == 2: - audio_data = audio_data.reshape(-1, 2) - - # Save the data in a WAV file - write(filename=str(path_wav_output), rate=sample_rate, data=audio_data) - - -def get_wav_info(file_path: Path): - """Extracts metadata and raw audio data from a WAV file.""" - try: - with wave.open(str(file_path), 'rb') as wav_file: - params = wav_file.getparams() - frames = wav_file.readframes(params.nframes) - dtype = np.int16 if params.sampwidth == 2 else np.int32 - audio_data = np.frombuffer(frames, dtype=dtype) - if params.nchannels == 2: - audio_data = audio_data.reshape(-1, 2) - - info = { - "Channels": params.nchannels, - "Sample Width (bytes)": params.sampwidth, - "Frame Rate (Hz)": params.framerate, - "Number of Frames": params.nframes, - "Compression Type": params.comptype, - "Compression Name": params.compname, - "Duration (s)": params.nframes / params.framerate, - "Audio Data": audio_data - } - return info - except wave.Error as e: - print(f"Error reading {file_path}: {e}") - return None - - -def compare_wav_files(file1, file2): - """Compares two WAV files and prints their differences, including audio data analysis.""" - info1 = get_wav_info(file1) - info2 = get_wav_info(file2) - - if not info1 or not info2: - print("One or both files could not be read correctly.") - return - - print("Comparison of WAV Files:") - print(f"{'Attribute':<25}{'File 1':<20}{'File 2'}") - print("-" * 65) - for key in info1: - if key != "Audio Data": - value1 = info1[key] - value2 = info2[key] - match = "✔" if value1 == value2 else "✘" - print(f"{key:<25}{str(value1):<20}{str(value2)} {match}") - - # Compare actual audio data - audio_data1 = info1["Audio Data"] - audio_data2 = info2["Audio Data"] - - min_length = min(len(audio_data1), len(audio_data2)) - if min_length == 0: - print("Cannot compare empty audio data.") - return - - audio_data1 = audio_data1[:min_length] - audio_data2 = audio_data2[:min_length] - - diff = audio_data1 - audio_data2 - max_diff = np.max(np.abs(diff)) - norm_diff = np.linalg.norm(diff) - - print("\nAudio Data Comparison:") - print(f"Max Difference: {max_diff}") - print(f"Norm of Difference: {norm_diff}") - - if __name__ == '__main__': """ Takes as input a folder with RAW audio files from the midge and saves them as .wav files. """ - # import argparse - # parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') - # parser.add_argument('--fn', required=True,help='Please enter the path to the file') - # args = parser.parse_args() - # parse_folder(fn=args.fn) - - # 1587563550840_audio_2, 1587564263795_audio_2, 1587564858250_audio_2 - stephanie_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" - "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") - pilot1_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" - "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250_audio_2") - output_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" - "dataset_collection\\tech_pilot_1\\audio_test") - - old_wav_path = Path("C:\\Users\\zongh\\OneDrive - Delft University of Technology\\tudelft\\projects\\" - "dataset_collection\\sync-experiments\\artefacts\\av_data\\midge\\15\\1587564858250.wav") - - new_wav_path = output_path / (pilot1_path.stem + "_py.wav") - - parse_simple(pilot1_path, new_wav_path) - # compare_wav_files(old_wav_path, new_wav_path) + import argparse + parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') + parser.add_argument('--fn', required=True,help='Please enter the path to the file') + args = parser.parse_args() + main(fn=args.fn) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 15c333d..d28d18b 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -367,11 +367,11 @@ async def get_imu_data(self): @request_handler_marker(action_desc='sdc erase all') async def sdc_erase_all(self): request = bp.Request() - request.type.which = bp.Request_sdc_erase_all_request_tag - request.type.sdc_erase_all_request = bp.EraseAllRequest() + request.type.which = bp.Request_sdc_errase_all_request_tag + request.type.sdc_errase_all_request = bp.ErraseAllRequest() await self.request_response(request) - return self.deal_response(response_type=bp.Response_sdc_erase_all_response_tag).type.sdc_erase_all_response + return self.deal_response(response_type=bp.Response_sdc_errase_all_response_tag).type.sdc_errase_all_response @request_handler_marker(action_desc='identify') async def identify(self, duration_seconds=10) -> bool: @@ -437,26 +437,3 @@ def print_help(): print(" help") print(" All commands use current system time as transmitted time.") sys.stdout.flush() - - -def display_current_time(): - try: - while True: - # Get current time with millisecond accuracy - current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] - # current_time = time.time() - print(f"\r{current_time}", end='') # Use carriage return to overwrite the line - time.sleep(0.001) # Sleep for 1 millisecond - except KeyboardInterrupt: - print("\nStopped.") - - -def main(): - display_current_time() -# c = 0 -# # ntp_server_ip = "127.0.0.1" -# # get_ntp_time(ntp_server_ip) - - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/BadgeFramework/badge_protocol.py b/BadgeFramework/badge_protocol.py index aa61d33..8dda673 100644 --- a/BadgeFramework/badge_protocol.py +++ b/BadgeFramework/badge_protocol.py @@ -936,29 +936,29 @@ def decode_internal(self, istream): def decode_clock_status(self, istream): #print("decode_clock_status:", istream.buf) #print("decode_clock_status:", istream.read(1)) - self.clock_status= struct.unpack(' ") - sys.stdout.flush() - command = sys.stdin.readline()[:-1] - if command == "exit": - break - - # Check valid input id - try: - midge_id = int(command) - except ValueError: - logger.warning('Invalid id! Only integers are allowed.') - continue - - try: - current_mac_addr = df.loc[df["Participant Id"] == midge_id]["Mac Address"] - current_mac_addr = current_mac_addr.values[0] - except Exception as e: - logger.info('Mac address for the midge ' + str(midge_id) + ' is not found.') - continue - - # try connection - try: - # TODO: uncomment the connection function - # cur_connection = Connection(int(command), current_mac_addr) - logger.info('placeholder for connection') - except Exception as error: - logger.warning("While connecting to midge " + str(command) - + ", following error occurred:" + str(error)) - sys.stdout.flush() - continue - - single_sensor_cmd(logger, midge_id) - - -def single_sensor_cmd(logger, midge_id): - while True: - logger.info("Connected to the midge " + str(midge_id) + "." - + " For available commands, please type help.") - sys.stdout.flush() - # sys.stdout.write("Now connected to the midge\n > ") - command = sys.stdin.readline()[:-1] - command_args = command.split(" ") - if command == "exit": - # TODO: uncomment the disconnect function - # cur_connection.disconnect() - logger.info("Disconnected from the midge.") - break - elif command == "help": - # TODO: uncomment the print help function - # cur_connection.print_help() - pass - else: - handle_function_choice(logger) - +from hub_utilities_V1 import * -def handle_function_choice(logger): - try: - # TODO: uncomment the choose function - # out = choose_function(cur_connection, command_args[0]) - # if out is not None: - # logger.info("Midge returned following status: " + str(out)) - sys.stdout.flush() - except Exception as error: - logger.warning(str(error)) - sys.stdout.flush() - - -def synchronization_cmd(logger): - logger.info("Synchronization is starting. Please wait till it ends") - # TODO: uncomment the synchronization function - # synchronise_and_check_all_devices(df) - logger.info("Synchronization is finished.") - sys.stdout.flush() - - -async def command_line(): - df = pd.read_csv("mappings2.csv") - logger = get_logger("hub_main") - recording_started = False - while True: - logger.info("Interactive shell of midge hub. Type 'help' for options.") - sys.stdout.write("> ") - sys.stdout.flush() - command = sys.stdin.readline()[:-1] - if command == "start": - if not recording_started: - recording_started = True - await start_recording_cmd(logger, df) - else: - logger.warning("Recording already started.") - elif command == "stop": - if recording_started: - await stop_recording_cmd(logger, df) - recording_started = False - else: - logger.warning("Cannot stop. Recording not started.") - elif command == "sync": - if recording_started: - synchronization_cmd(logger) - else: - logger.warning("Cannot synchronize. Recording not started.") - elif command == "int": - interactive_cmd(logger, df) - elif command == "help": - # TODO: add help - logger.info("help placeholder") - elif command == "exit": - break - else: - logger.warning('Command not recognized.') - logger.info("Interactive session is finished.") - - -def main(): - df = pd.read_csv("mappings2.csv") - logger = get_logger("hub_main") +if __name__ == "__main__": + df = pd.read_csv('sample_mapping_file.csv') while True: print("Type start to start data collection or stop to finish data collection.") sys.stdout.write("> ") sys.stdout.flush() command = sys.stdin.readline()[:-1] if command == "start": - logger.info("Connecting to the midges for starting the recordings.") - # TODO: uncomment the recording function - # start_recording_all_devices(df) - logger.info("Loop for starting the devices is finished.") + start_recording_all_devices(df) while True: ti = timeout_input(poll_period=0.05) - s = ti.input( - prompt="Type int if you would like to enter interactive shell.\n" - + ">", - timeout=10.0, - extend_timeout_with_input=False, - require_enter_to_confirm=True, - ) + s = ti.input(prompt='Type int if you would like to enter interactive shell.\n'+'>', timeout=10.0, + extend_timeout_with_input=False, require_enter_to_confirm=True) if s == "int": print("Welcome to the interactive shell. Please type the id of the Midge you want to connect.") print("Type exit if you would like to stop recording for all devices.") @@ -184,25 +24,14 @@ def main(): if command == "exit": print("Stopping the recording of all devices.") sys.stdout.flush() - # TODO: uncomment the stop recording function - # stop_recording_all_devices(df) - logger.info("Devices are stopped.") + stop_recording_all_devices(df) + print("Devices are stopped.") sys.stdout.flush() break command_args = command.split(" ") current_mac_addr= (df.loc[df['Participant Id'] == int(command)]['Mac Address']).values[0] try: - current_mac_addr = ( - df.loc[df["Participant Id"] == int(command)]["Mac Address"] - ).values[0] - except Exception: - logger.info('Mac address for the midge ' + str(command) - + ' is not found.') - continue - try: - # TODO: uncomment the connection function - # cur_connection = Connection(int(command), current_mac_addr) - logger.info('placeholder for connection') + cur_connection = Connection(int(command),current_mac_addr) except Exception as error: print (str(error)) sys.stdout.flush() @@ -214,29 +43,23 @@ def main(): command = sys.stdin.readline()[:-1] command_args = command.split(" ") if command == "exit": - # TODO: uncomment the disconnect function - # cur_connection.disconnect() - logger.info("Disconnected from the midge.") + cur_connection.disconnect() break try: - # TODO: uncomment the choose function - # out = choose_function(cur_connection, command_args[0]) - # if out is not None: - # logger.info("Midge returned following" - # + " status: " + str(out)) - sys.stdout.flush() + out = choose_function(cur_connection,command_args[0]) + if out != None: + print (out) + sys.stdout.flush() except Exception as error: print (str(error)) print(" Command not found!") sys.stdout.flush() - # TODO: uncomment the print help function - # cur_connection.print_help() + cur_connection.print_help() continue else: - logger.info("Synchronisation is starting. Please wait till it ends") - # TODO: uncomment the synchronization function - # synchronise_and_check_all_devices(df) - logger.info("Synchronisation is finished.") + print('Synchronisation is starting. Please wait till it ends.') + synchronise_and_check_all_devices(df) + print('Synchronisation is finished.') sys.stdout.flush() elif command == "stop": print("Stopping data collection.") @@ -245,20 +68,3 @@ def main(): else: print("Command not found, please type start or stop to start or stop data collection.") sys.stdout.flush() - - -async def main_v2(): - # print(os.getcwd()) - df = pd.read_csv('mappings2.csv') - # await synchronise_and_check_all_devices(df) - await start_recording_all_devices(df) - time.sleep(10) - await stop_recording_all_devices(df) - - -if __name__ == "__main__": - os.chdir('/home/zonghuan/tudelft/projects/spcl_app/BadgeFramework') - asyncio.run(main_v2()) - # main() - # df = pd.read_csv('mappings2.csv') - # asyncio.run(command_line()) diff --git a/BadgeFramework/imu_parser_V0.py b/BadgeFramework/imu_parser_V0.py index 57a2ac8..e03a8b5 100644 --- a/BadgeFramework/imu_parser_V0.py +++ b/BadgeFramework/imu_parser_V0.py @@ -34,22 +34,13 @@ def parse_generic(self,sensorname): break data_xyz = np.asarray(data) timestamps = np.asarray(timestamps) - # timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] - df = pd.DataFrame(timestamps, columns=['time']) + timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] + df = pd.DataFrame(timestamps_dt, columns=['time']) df['X'] = data_xyz[:,0] df['Y'] = data_xyz[:,1] df['Z'] = data_xyz[:,2] return df - @staticmethod - def check_ts(nums, thres=1e13): - timestamps = [x[0] for x in nums] - results = [(i, f"{num:.0f}") for i, num in enumerate(timestamps) if num > thres] - timestamp_good = [x for x in timestamps if x < 1e13] - good_diff = [timestamp_good[x + 1] - timestamp_good[x] for x in range(len(timestamp_good) - 2)] - d = [x for x in good_diff if x > 20 or x < 10] - c = 9 - def parse_scanner(self): data = [] timestamps = [] @@ -115,8 +106,8 @@ def parse_rot(self): break rotation_xyz = np.asarray(rotation) timestamps = np.asarray(timestamps) - # timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] - df = pd.DataFrame(timestamps, columns=['time']) + timestamps_dt = [dt.fromtimestamp(float(x)/1000) for x in timestamps] + df = pd.DataFrame(timestamps_dt, columns=['time']) df['a'] = rotation_xyz[:,0] df['b'] = rotation_xyz[:,1] df['c'] = rotation_xyz[:,2] @@ -203,4 +194,3 @@ def main(fn,acc,mag,gyr,rot,plot,scan): - diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index 098a417..eadbf24 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -180,7 +180,7 @@ async def scan_test(badge): async def erase_mem(badge): erased = await badge.sdc_erase_all() # clean sd memory - if erased.done_erase: + if erased.done_errase: print(" memory cleaned: PASS ") print("###################################") else: @@ -221,27 +221,27 @@ async def main(): except: print("mic error") - # async with OpenBadge(ble_device) as open_badge: - # try: - # await mic_test(open_badge, 1) - # except: - # print("mic error") - # - # async with OpenBadge(ble_device) as open_badge: - # try: - # await scan_test(open_badge) - # except: - # print("scan error") - # - # async with OpenBadge(ble_device) as open_badge: - # try: - # await imu_test(open_badge) - # except: - # print("imu error") - # try: - # await erase_mem(open_badge) - # except: - # print("erase memory error") + async with OpenBadge(ble_device) as open_badge: + try: + await mic_test(open_badge, 1) + except: + print("mic error") + + async with OpenBadge(ble_device) as open_badge: + try: + await scan_test(open_badge) + except: + print("scan error") + + async with OpenBadge(ble_device) as open_badge: + try: + await imu_test(open_badge) + except: + print("imu error") + try: + await erase_mem(open_badge) + except: + print("erase memory error") except TimeoutError: print("failed to connect to device") From 47048f0690b1c524ed02071e9ee36d2aa3f51e73 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 10 Apr 2025 17:43:08 +0200 Subject: [PATCH 40/46] modify test --- BadgeFramework/audio_parser_V0.py | 2 +- BadgeFramework/imu_parser_V0.py | 1 - BadgeFramework/test.py | 205 ++++++++++++------------------ 3 files changed, 80 insertions(+), 128 deletions(-) diff --git a/BadgeFramework/audio_parser_V0.py b/BadgeFramework/audio_parser_V0.py index 283d878..94164c5 100644 --- a/BadgeFramework/audio_parser_V0.py +++ b/BadgeFramework/audio_parser_V0.py @@ -51,4 +51,4 @@ def main(fn): parser = argparse.ArgumentParser(description='Parser for the audio data obtained from Mingle Midges') parser.add_argument('--fn', required=True,help='Please enter the path to the file') args = parser.parse_args() - main(fn=args.fn) + main(fn=args.fn) \ No newline at end of file diff --git a/BadgeFramework/imu_parser_V0.py b/BadgeFramework/imu_parser_V0.py index e03a8b5..daa4b68 100644 --- a/BadgeFramework/imu_parser_V0.py +++ b/BadgeFramework/imu_parser_V0.py @@ -193,4 +193,3 @@ def main(fn,acc,mag,gyr,rot,plot,scan): - diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index eadbf24..2b84f03 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -1,5 +1,7 @@ from __future__ import division, absolute_import, print_function import logging +import sys +import threading import time from datetime import datetime import utils @@ -7,34 +9,33 @@ from bleak import BleakScanner import asyncio -SLEEP_TIME = 10 async def mic_test(badge, mode): - if mode == 0: - start_stereo = await badge.start_microphone(t=None, mode=0) # start mic in stereo mode + if (mode==0): + start_stereo = badge.start_microphone(t=None,mode=0) # start mic in stereo mode print(" sensor: mic, stereo mode ") # check if mic was started in stereo mode - if start_stereo.mode == 0: + if (start_stereo.mode==0): print(" mic mode: PASS ") - if start_stereo.pdm_freq == 1000 or start_stereo.pdm_freq == 1032 or start_stereo.pdm_freq == 1067: + if (start_stereo.pdm_freq == 1000 or start_stereo.pdm_freq == 1032 or start_stereo.pdm_freq == 1067): print(" mic freq: PASS ") else: print(" mic freq: FAIL ") - if start_stereo.gain_l != 0 and start_stereo.gain_r != 0: + if (start_stereo.gain_l != 0 and start_stereo.gain_r != 0): print(" mic gain: PASS ") else: print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") - time.sleep(SLEEP_TIME) # reecording time + time.sleep(10) # reecording time mic = await badge.get_status() # check if mic was enabled - if mic.microphone_status: - print(" mic enabled: PASS ") + if (mic.microphone_status): + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") @@ -55,27 +56,27 @@ async def mic_test(badge, mode): start_mono = await badge.start_microphone(t=None, mode=1) - if start_mono.mode == 1: + if (start_mono.mode==1): print(" mic mode: PASS ") - if start_mono.pdm_freq == 1000 or start_mono.pdm_freq == 1032 or start_mono.pdm_freq == 1067: + if (start_mono.pdm_freq == 1000 or start_mono.pdm_freq == 1032 or start_mono.pdm_freq == 1067): print(" mic freq: PASS ") else: print(" mic freq: FAIL ") - if start_mono.gain_l != 0 and start_mono.gain_r != 0: + if (start_mono.gain_l != 0 and start_mono.gain_r != 0): print(" mic gain: PASS ") else: print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") - time.sleep(SLEEP_TIME) # reecording time + time.sleep(10) # reecording time mic = await badge.get_status() # check if mic was enabled - if mic.microphone_status: - print(" mic enabled: PASS ") + if (mic.microphone_status): + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") @@ -85,47 +86,46 @@ async def mic_test(badge, mode): mic = await badge.get_status() # check if mic was disabled with success - if ~mic.microphone_status: + if (~mic.microphone_status): print(" mic disabled: PASS ") else: print(" mic disabled: PASS ") - async def imu_test(badge): print("###################################") print(" sensor: imu ") #print("#-----------------------------------#") - imu_start = await badge.start_imu() # start imu - time.sleep(0.1) # safety wait - # check if imu self test was done - if imu_start.self_test_done: + imu_start = await badge.start_imu() # start imu + time.sleep(0.1) # safety wait + #check if imu self test was done + if (imu_start.self_test_done): print(" imu self test: PASS ") else: print(" imu self test: FAIL ") - imu = await badge.get_status() # get imu status - time.sleep(0.1) # safety wait - imu_data = await badge.get_imu_data() # get imu data + imu = await badge.get_status() # get imu status + time.sleep(0.1) # safety wait + imu_data = await badge.get_imu_data() # get imu data #print(imu_data) # check if imu sensors are sending data if (imu.imu_status): print(" imu enabled: PASS ") - if ~(imu_data.acc_x == 0 and imu_data.acc_y == 0 and imu_data.acc_z == 0): + if (~(imu_data.acc_x == 0 and imu_data.acc_y == 0 and imu_data.acc_z == 0)): print(" imu acc: PASS ") else: print(" imu acc: FAIL ") - if ~(imu_data.mag_x == 0 and imu_data.mag_y == 0 and imu_data.mag_z == 0): + if (~(imu_data.mag_x == 0 and imu_data.mag_y == 0 and imu_data.mag_z == 0)): print(" imu mag: PASS ") else: print(" imu mag: FAIL ") - if ~(imu_data.gyr_x == 0 and imu_data.gyr_y == 0 and imu_data.gyr_z == 0): + if (~(imu_data.gyr_x == 0 and imu_data.gyr_y == 0 and imu_data.gyr_z == 0)): print(" imu gyr: PASS ") else: print(" imu gyr: FAIL ") - if ~(imu_data.rot_x == 0 and imu_data.rot_y == 0 and imu_data.rot_z == 0): + if (~(imu_data.rot_x == 0 and imu_data.rot_y == 0 and imu_data.rot_z == 0)): print(" imu rot: PASS ") else: print(" imu rot: FAIL ") @@ -133,29 +133,32 @@ async def imu_test(badge): else: print(" imu enabled: FAIL ") - time.sleep(SLEEP_TIME) # imu enabled time + + time.sleep(10) # imu enabled time await badge.stop_imu() - time.sleep(0.5) # safety wait + time.sleep(0.5) # safety wait imu = await badge.get_status() - if ~imu.imu_status: + if (~imu.imu_status): print(" imu disabled: PASS ") else: print(" imu disabled: FAIL ") + + async def scan_test(badge): print("###################################") print(" sensor: scan ") #print("#-----------------------------------#") await badge.start_scan(window_ms = 250, interval_ms = 1000) ## start scan with default parameters - time.sleep(SLEEP_TIME) # scan enabled time + time.sleep(10) # scan enabled time scan = await badge.get_status() # check if scan was enabled succesfully - if scan.scan_status: + if (scan.scan_status): print(" scan enabled: PASS ") else: print(" scan enabled: FAIL ") @@ -165,22 +168,21 @@ async def scan_test(badge): else: print(" scan data: FAIL ") - await badge.stop_scan() # stop scan + await badge.stop_scan() # stop scan - time.sleep(0.5) # safety wait + time.sleep(0.5) # safety wait - scan = await badge.get_status() # get scan status + scan =await badge.get_status() # get scan status # check if scan was disabled succesfully - if ~scan.scan_status: + if (~scan.scan_status): print(" scan disabled: PASS ") else: print(" scan disabled: FAIL ") + +async def errase_mem(badge): + errased = await badge.sdc_errase_all() # clean sd memory - -async def erase_mem(badge): - erased = await badge.sdc_erase_all() # clean sd memory - - if erased.done_errase: + if (errased.done_errase): print(" memory cleaned: PASS ") print("###################################") else: @@ -196,91 +198,42 @@ async def erase_mem(badge): async def main(): - # device_addr = sys.argv[1] - - devices = await BleakScanner.discover(timeout=10.0, return_adv=True) - - # Filter out the devices that are not the midge - devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] - - if devices: - ble_device, adv_data = devices[0] - else: - print("No devices found") - return + device_addr = sys.argv[1] # In Bleak, my experience is that it is rather prone to disconnection when executing many commands in one # context manager. Separate each command with context managers increase the rate of success. - try: - print(" connected ") + + + async with OpenBadge(0, device_addr) as badge: + print(" connetced ") print("###################################") - async with OpenBadge(ble_device) as open_badge: - print(" Midge test ") - try: - await mic_test(open_badge, 0) - except: - print("mic error") - - async with OpenBadge(ble_device) as open_badge: - try: - await mic_test(open_badge, 1) - except: - print("mic error") - - async with OpenBadge(ble_device) as open_badge: - try: - await scan_test(open_badge) - except: - print("scan error") - - async with OpenBadge(ble_device) as open_badge: - try: - await imu_test(open_badge) - except: - print("imu error") - try: - await erase_mem(open_badge) - except: - print("erase memory error") - - except TimeoutError: - print("failed to connect to device") - - except: - print("test failed") - - -async def communicate_with_device(ble_device): - try: - async with OpenBadge(ble_device) as open_badge: - # async with OpenBadge(int(device_id), ble_device.address) as open_badge: - out = await open_badge.get_status() - # start = await open_badge.start_microphone() - print(out) - except TimeoutError: - print("failed to connect to device") - - -async def test_conn(): - logger = utils.get_logger('bleak_logger') - # Find all devices - # using both BLEdevice and advertisement data here since it has more information. Also mutes the warning. - devices = await BleakScanner.discover(timeout=10.0, return_adv=True) - # async with BleakClient('de:94:80:39:25:be') as client: - # print(client.is_connected) - - # Filter out the devices that are not the midge - devices = [d for d in devices.values() if utils.is_spcl_midge(d[0])] - - # Print Id to see if it matches with any devices in the csv file. - for ble_device, adv_data in devices: - device_id = utils.get_device_id(ble_device) - print(f"RSSI: {adv_data.rssi}, Id: {device_id}, Address: {ble_device.address}") - tasks = [communicate_with_device(ble_device) for ble_device, adv_data in devices] - await asyncio.gather(*tasks) - - print(f'after connection: {datetime.now().timestamp()}') + print(" Midge test ") + + try: + await mic_test(badge,0) + except: + print("mic error") + + try: + await mic_test(badge,1) + except: + print("mic error") + + try: + await scan_test(badge) + except: + print("scan error") + + try: + await imu_test(badge) + except: + print("imu error") + + try: + await errase_mem(badge) + except: + print("errase memory error") + if __name__ == "__main__": - asyncio.run(main()) - # asyncio.run(test_conn()) + asyncio.run(main()) \ No newline at end of file From c056d9ce5e788b1fa76135477370876d0bd3400a Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Thu, 10 Apr 2025 17:50:34 +0200 Subject: [PATCH 41/46] modify test --- BadgeFramework/test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index 2b84f03..9e06215 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -13,7 +13,7 @@ async def mic_test(badge, mode): if (mode==0): - start_stereo = badge.start_microphone(t=None,mode=0) # start mic in stereo mode + start_stereo = await badge.start_microphone(t=None,mode=0) # start mic in stereo mode print(" sensor: mic, stereo mode ") # check if mic was started in stereo mode if (start_stereo.mode==0): @@ -28,14 +28,14 @@ async def mic_test(badge, mode): else: print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") time.sleep(10) # reecording time mic = await badge.get_status() # check if mic was enabled if (mic.microphone_status): - print(" mic enabled: PASS ") + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") @@ -69,14 +69,14 @@ async def mic_test(badge, mode): print(" mic gain: FAIL ") else: - print(" mic mode: FAIL ") + print(" mic mode: FAIL ") time.sleep(10) # reecording time mic = await badge.get_status() # check if mic was enabled if (mic.microphone_status): - print(" mic enabled: PASS ") + print(" mic enabled: PASS ") else: print(" mic enabled: FAIL ") From e5b2a24e7a75d9fa30e91d8d5e352d7525088880 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 28 Apr 2025 14:07:38 +0200 Subject: [PATCH 42/46] minimal modification of badge.py --- .gitignore | 2 + .vscode/launch.json | 20 +- BadgeFramework/badge.py | 669 +++++++++++++++++++--------------------- BadgeFramework/utils.py | 2 +- 4 files changed, 334 insertions(+), 359 deletions(-) diff --git a/.gitignore b/.gitignore index e60c572..2c46f12 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ _build/* __pycache/ .vscode/c_cpp_properties.json .vscode/settings.json +.vscode/launch.json +.vscode **/.DS_Store BadgeFramework/rotation/rpi_sync.sh .idea diff --git a/.vscode/launch.json b/.vscode/launch.json index bcbf2ab..86bd06b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,14 @@ { "version": "0.2.0", "configurations": [ + { + "name": "Python: test.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/BadgeFramework/test.py", + "console": "integratedTerminal", + "args": "de:94:80:39:25:be" + }, { "name": "Cortex Debug", "cwd": "${workspaceFolder}", @@ -13,16 +21,20 @@ "configFiles": [ "interface/cmsis-dap.cfg", "target/nrf52.cfg" - ], - "openOCDLaunchCommands": ["adapter speed 2000"], + ], + "openOCDLaunchCommands": [ + "adapter speed 2000" + ], "interface": "swd", "armToolchainPath": "", "svdFile": "${workspaceRoot}/nrf52832.svd", - "preLaunchCommands":["set remotetimeout 60"], + "preLaunchCommands": [ + "set remotetimeout 60" + ], "rttConfig": { "enabled": true, "address": "auto", - "clearSearch": false, // OpenOCD users may have to un-comment this + "clearSearch": false, // OpenOCD users may have to un-comment this "decoders": [ { "port": 0, diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index d28d18b..fdfe68a 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -1,91 +1,68 @@ from __future__ import division, absolute_import, print_function - -import sys -import functools import time import logging +import sys import struct -import ntplib -from collections import deque -from datetime import datetime, timezone - -from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic -from typing import Optional, Final -import badge_protocol as bp +import queue as Queue import utils +from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic -DEFAULT_SCAN_WINDOW: Final[int] = 250 -DEFAULT_SCAN_INTERVAL: Final[int] = 1000 +CONNECTION_RETRY_TIMES = 15 +DEFAULT_SCAN_WINDOW = 250 +DEFAULT_SCAN_INTERVAL = 1000 -DEFAULT_IMU_ACC_FSR: Final[int] = 4 # Valid ranges: 2, 4, 8, 16 -DEFAULT_IMU_GYR_FSR: Final[int] = 1000 # Valid ranges: 250, 500, 1000, 2000 -DEFAULT_IMU_DATARATE: Final[int] = 50 +DEFAULT_IMU_ACC_FSR = 4 # Valid ranges: 2, 4, 8, 16 +DEFAULT_IMU_GYR_FSR = 1000 # Valid ranges: 250, 500, 1000, 2000 +DEFAULT_IMU_DATARATE = 50 -DEFAULT_MICROPHONE_MODE: Final[int] = 1 # Valid options: 0=Stereo, 1=Mono +DEFAULT_MICROPHONE_MODE = 0 #Valid options: 0=Stereo, 1=Mono -CONNECTION_RETRY_TIMES = 15 -DUPLICATE_TIME_INTERVAL = 2 +from badge_protocol import * logger = logging.getLogger(__name__) +# logging.basicConfig( +# level=logging.DEBUG, # or logging.INFO for less verbosity +# format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" +# ) -# -- Helper methods used often in badge communication -- +# -- Helper methods used often in badge communication -- # We generally define timestamp_seconds to be in number of seconds since UTC epoch # and timestamp_miliseconds to be the miliseconds portion of that UTC timestamp. +# Returns the current timestamp as two parts - seconds and milliseconds +def get_timestamps(): + return get_timestamps_from_time(time.time()) -def get_timestamps_from_time(t=None) -> (int, int): - """Returns the given time as two parts - seconds and milliseconds""" - if t is None: - t = time.time() +# Returns the given time as two parts - seconds and milliseconds +def get_timestamps_from_time(t): timestamp_seconds = int(t) timestamp_fraction_of_second = t - timestamp_seconds timestamp_ms = int(1000 * timestamp_fraction_of_second) - return timestamp_seconds, timestamp_ms + return (timestamp_seconds, timestamp_ms) # Convert badge timestamp representation to python representation -# def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): -# return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) - - -def bp_timestamp_from_time(t=None) -> bp.Timestamp: - ts = bp.Timestamp() - ts.seconds, ts.ms = get_timestamps_from_time(t) - return ts - +def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): + return float(timestamp_seconds) + (float(timestamp_miliseconds) / 1000.0) -def badge_disconnected(b: BleakClient) -> None: - """disconnection callback""" - print(f"Warning: disconnected badge") - -def request_handler(device_id, action_desc): - def request_handler_decorator(func): - @functools.wraps(func) - async def wrapper(*args, **kwargs): - try: - value = await func(*args, **kwargs) - return value - except Exception as err: - error_desc = "Could not {} for participant {}, error: {}" - raise Exception(error_desc.format(action_desc, str(device_id), str(err))) - return wrapper - return request_handler_decorator - - -def request_handler_marker(action_desc): - def wrapper(func): - func._handler = True # Mark the function to be repeated - func._action_desc = action_desc - return func - return wrapper - - -class OpenBadgeMeta: +# Represents an OpenBadge currently connected via the BadgeConnection 'connection'. +# The 'connection' should already be connected when it is used to initialize this class. +# Implements methods that allow for interaction with that badge. +class OpenBadge(object): def __init__(self, device: BLEDevice or int, mac_address: str = None): - # self.device = device + # self.connection = connection + self.status_response_queue = Queue.Queue() + self.start_microphone_response_queue = Queue.Queue() + self.start_scan_response_queue = Queue.Queue() + self.start_imu_response_queue = Queue.Queue() + self.free_sdc_space_response_queue = Queue.Queue() + self.sdc_errase_all_response_queue = Queue.Queue() + self.get_imu_data_response_queue = Queue.Queue() + self.rx_queue = Queue.Queue() + if isinstance(device, BLEDevice): self.device_id = utils.get_device_id(device) self.address = device.address @@ -94,26 +71,7 @@ def __init__(self, device: BLEDevice or int, mac_address: str = None): self.address = mac_address else: raise TypeError - self._decorate_methods() - - def _decorate_methods(self): - # Automatically decorate methods marked with @repeat_method - for attr_name in dir(self): - attr = getattr(self, attr_name, None) - if callable(attr) and getattr(attr, '_handler', False): - action_desc = getattr(attr, '_action_desc', '[unknown operation]') - decorated = request_handler(self.device_id, action_desc)(attr) - setattr(self, attr_name, decorated) - - -class OpenBadge(OpenBadgeMeta): - """Represents an OpenBadge and implements methods that allow for interaction with that badge.""" - def __init__(self, device: BLEDevice or int, mac_address: str = None): - super().__init__(device, mac_address) - self.client = BleakClient(self.address, disconnected_callback=badge_disconnected) - # self.rx_message = b'' - self.rx_list = [] - self.message_buffer = deque() + self.client = BleakClient(self.address, disconnected_callback=self.badge_disconnected) async def __aenter__(self): for _ in range(CONNECTION_RETRY_TIMES): @@ -128,9 +86,6 @@ async def __aenter__(self): async def __aexit__(self, exc_type, exc_val, exc_tb): await self.client.disconnect() - # Helper function to send a BadgeMessage `command_message` to a device, expecting a response - # of class `response_type` that is a subclass of BadgeMessage, or None if no response is expected. - @property def is_connected(self) -> bool: return self.client.is_connected @@ -138,302 +93,308 @@ def is_connected(self) -> bool: def badge_disconnected(self, b: BleakClient) -> None: """disconnection callback""" print(f"Warning: disconnected badge") + + def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray): + logger.debug("Recieved {}".format(message.hex())) + for b in message: + self.rx_queue.put(b) + - @staticmethod - def add_serialized_header(request_message: bp.Request) -> bytes or bytearray: - """add serialized header to message""" + # Helper function to send a BadgeMessage `command_message` to a device, expecting a response + # of class `response_type` that is a subclass of BadgeMessage, or None if no response is expected. + async def send_command(self, command_message, response_type): + serialized_command = command_message.serialize_message() + logger.debug( + "Sending: {}, Raw: {}".format( + command_message, serialized_command.hex() + ) + ) + await self.client.write_gatt_char(utils.TX_CHAR_UUID, serialized_command, response=True) + + + async def send_request(self, request_message): serialized_request = request_message.encode() # Adding length header: serialized_request_len = struct.pack(" None: - """send message to client""" - trial_times = 0 - while trial_times < 5: - if client.is_connected: - await client.write_gatt_char(utils.TX_CHAR_UUID, message, response=True) - return - else: - await self.__aenter__() - trial_times += 1 - raise TimeoutError - - # @staticmethod - async def receive(self, client: BleakClient) -> bytes or bytearray: - """receive message from client""" - response_rx = b'' - for _ in range(2): - # if len(response_rx) > 0: - # break - if client.is_connected: - response_rx = await client.read_gatt_char(utils.RX_CHAR_UUID) - else: - await self.__aenter__() - time.sleep(1) - return response_rx - - @staticmethod - def message_is_duplicated(message_list: list, new_message: dict) -> bool: - """check if message is duplicated with existing message list""" - if len(message_list) == 0: - return False - last_message = message_list[-1] - time_duplicated = abs(last_message['time'] - new_message['time']) < DUPLICATE_TIME_INTERVAL - message_duplicated = last_message['message'] == new_message['message'] - return time_duplicated and message_duplicated - - def received_callback(self, sender: BleakGATTCharacteristic, message: bytearray): - """callback function for receiving message. Note that this must be used in combination with the - 'receive' function to work properly.""" - self.message_buffer.extend(message) # Store received bytes sequentially - # print(f"Received message: {message}" + str(time.time())) - while len(self.message_buffer) >= 2: # Ensure at least two bytes are available to read length - # Peek the first two bytes to get the expected message length - length_bytes = bytes([self.message_buffer[0], self.message_buffer[1]]) - expected_length = struct.unpack('= expected_length + 2: - full_message = bytearray() - for _ in range(2 + expected_length): - full_message.append(self.message_buffer.popleft()) # Remove bytes from buffer - new_message = {'time': time.time(), 'message': full_message} - self.rx_list.append(new_message) - print(f"Full message: {full_message}" + str(time.time())) - else: - break # Wait for more data if full message is not yet received - # if len(message) > 0: - # new_message = {'time': time.time(), 'message': message} - # if not self.message_is_duplicated(self.rx_list, new_message): - # print(f"RX changed {sender}: {message}" + str(time.time())) - # # self.rx_message = message - # self.rx_list.append(new_message) - - async def request_response(self, message: bp.Request, require_response: Optional[bool] = True): - """request response from client""" - serialized_request = self.add_serialized_header(message) - # logger.debug("Sending: {}, Raw: {}".format(message, serialized_request.hex())) - await self.send(self.client, serialized_request) - response = await self.receive(self.client) - return response if require_response else None - - @staticmethod - def decode_response(response: bytearray or bytes): - """decode response from client. First two bytes represent the length.""" - response_len = struct.unpack(" 0: - self.rx_list.pop(0) - else: + serialized_request = serialized_request_len + serialized_request + + logger.debug( + "Sending: {}, Raw: {}".format( + request_message, serialized_request.hex() + ) + ) + await self.client.write_gatt_char(utils.TX_CHAR_UUID, serialized_request, response=True) + # self.connection.send(serialized_request, response_len=0) + + async def await_data(self, data_len): + if not self.is_connected: + raise RuntimeError("BLEBadgeConnection not connected before await_data()!") + rx_message = bytearray() + rx_bytes_expected = data_len + + if rx_bytes_expected > 0: while True: - try: - serialized_response = self.rx_list.pop(0)['message'] - if self.check_response_type(response_type, serialized_response): - return self.decode_response(serialized_response) - except IndexError: - print('Requested response type not found in list!') - - @staticmethod - def check_response_type(response_type: int, response: bytes or bytearray): - return response[2] == response_type - - @request_handler_marker(action_desc='get status') - async def get_status(self, t=None, new_id: Optional[int] = None, new_group_number: Optional[int] = None)\ - -> bp.StatusResponse: - """Sends a status request to this Badge. Optional fields new_id and new_group number will set - the badge's id and group number. They must be sent together. Returns a StatusResponse() - representing badge's response.""" - request = bp.Request() - request.type.which = bp.Request_status_request_tag - request.type.status_request = bp.StatusRequest() - request.type.status_request.timestamp = bp_timestamp_from_time(t) + while(not self.rx_queue.empty()): + rx_message.append(self.rx_queue.get()) + if(len(rx_message) == rx_bytes_expected): + return rx_message + + response_rx = await self.client.read_gatt_char(utils.RX_CHAR_UUID) + + async def receive_response(self): + response_len = struct.unpack(" bp.StartMicrophoneResponse: - """Sends a request to the badge to start recording microphone data. Returns a StartRecordResponse() - representing the badges' response.""" - request = bp.Request() - request.type.which = bp.Request_start_microphone_request_tag - request.type.start_microphone_request = bp.StartMicrophoneRequest() - request.type.start_microphone_request.timestamp = bp_timestamp_from_time(t) + await self.send_request(request) + + # Clear the queue before receiving + with self.status_response_queue.mutex: + self.status_response_queue.queue.clear() + + while self.status_response_queue.empty(): + await self.receive_response() + + return self.status_response_queue.get() + + # Sends a request to the badge to start recording microphone data. + # Returns a StartRecordResponse() representing the badges response. + async def start_microphone(self, t=None, mode=DEFAULT_MICROPHONE_MODE): + if t is None: + (timestamp_seconds, timestamp_ms) = get_timestamps() + else: + (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) + #print("MODE ",mode) + request = Request() + request.type.which = Request_start_microphone_request_tag + request.type.start_microphone_request = StartMicrophoneRequest() + request.type.start_microphone_request.timestamp = Timestamp() + request.type.start_microphone_request.timestamp.seconds = timestamp_seconds + request.type.start_microphone_request.timestamp.ms = timestamp_ms request.type.start_microphone_request.mode = mode - await self.request_response(request) - return (self.deal_response(response_type=bp.Response_start_microphone_response_tag) - .type.start_microphone_response) - - @request_handler_marker(action_desc='stop microphone') - async def stop_microphone(self) -> None: - """Sends a request to the badge to stop recording.""" - request = bp.Request() - request.type.which = bp.Request_stop_microphone_request_tag - request.type.stop_microphone_request = bp.StopMicrophoneRequest() - - await self.request_response(request, require_response=False) - self.deal_response(response_type=-1) - return None - - @request_handler_marker(action_desc='start scan') - async def start_scan(self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DEFAULT_SCAN_INTERVAL)\ - -> bp.StartScanResponse: - """Sends a request to the badge to start performing scans and collecting scan data. - window_miliseconds and interval_miliseconds controls radio duty cycle during scanning (0 for firmware default) - radio is active for [window_miliseconds] every [interval_miliseconds] - Returns a StartScanningResponse() representing the badge's response.""" - request = bp.Request() - request.type.which = bp.Request_start_scan_request_tag - request.type.start_scan_request = bp.StartScanRequest() - request.type.start_scan_request.timestamp = bp_timestamp_from_time(t) + await self.send_request(request) + + with self.start_microphone_response_queue.mutex: + self.start_microphone_response_queue.queue.clear() + + while self.start_microphone_response_queue.empty(): + await self.receive_response() + + return self.start_microphone_response_queue.get() + + # Sends a request to the badge to stop recording. + # Returns True if request was successfuly sent. + async def stop_microphone(self): + + request = Request() + request.type.which = Request_stop_microphone_request_tag + request.type.stop_microphone_request = StopMicrophoneRequest() + + await self.send_request(request) + + # Sends a request to the badge to start performing scans and collecting scan data. + # window_miliseconds and interval_miliseconds controls radio duty cycle during scanning (0 for firmware default) + # radio is active for [window_miliseconds] every [interval_miliseconds] + # Returns a StartScanningResponse() representing the badge's response. + async def start_scan( + self, t=None, window_ms=DEFAULT_SCAN_WINDOW, interval_ms=DEFAULT_SCAN_INTERVAL + ): + if t is None: + (timestamp_seconds, timestamp_ms) = get_timestamps() + else: + (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) + + request = Request() + request.type.which = Request_start_scan_request_tag + request.type.start_scan_request = StartScanRequest() + request.type.start_scan_request.timestamp = Timestamp() + request.type.start_scan_request.timestamp.seconds = timestamp_seconds + request.type.start_scan_request.timestamp.ms = timestamp_ms request.type.start_scan_request.window = window_ms request.type.start_scan_request.interval = interval_ms - await self.request_response(request) - return self.deal_response(response_type=bp.Response_start_scan_response_tag).type.start_scan_response - - @request_handler_marker(action_desc='stop scan') - async def stop_scan(self) -> None: - """Sends a request to the badge to stop scanning.""" - request = bp.Request() - request.type.which = bp.Request_stop_scan_request_tag - request.type.stop_scan_request = bp.StopScanRequest() - - await self.request_response(request) - return self.deal_response(response_type=-1) - - @request_handler_marker(action_desc='start imu') - async def start_imu(self, t=None, acc_fsr=DEFAULT_IMU_ACC_FSR, gyr_fsr=DEFAULT_IMU_GYR_FSR, - datarate=DEFAULT_IMU_DATARATE) -> bp.StartImuResponse: - """Sends a request to the badge to start IMU. Returns the response object.""" - request = bp.Request() - request.type.which = bp.Request_start_imu_request_tag - request.type.start_imu_request = bp.StartImuRequest() - request.type.start_imu_request.timestamp = bp_timestamp_from_time(t) + await self.send_request(request) + + # Clear the queue before receiving + with self.start_scan_response_queue.mutex: + self.start_scan_response_queue.queue.clear() + + while self.start_scan_response_queue.empty(): + await self.receive_response() + + return self.start_scan_response_queue.get() + + # Sends a request to the badge to stop scanning. + # Returns True if request was successfuly sent. + async def stop_scan(self): + + request = Request() + request.type.which = Request_stop_scan_request_tag + request.type.stop_scan_request = StopScanRequest() + + await self.send_request(request) + + async def start_imu( + self, + t=None, + acc_fsr=DEFAULT_IMU_ACC_FSR, + gyr_fsr=DEFAULT_IMU_GYR_FSR, + datarate=DEFAULT_IMU_DATARATE, + ): + if t is None: + (timestamp_seconds, timestamp_ms) = get_timestamps() + else: + (timestamp_seconds, timestamp_ms) = get_timestamps_from_time(t) + + request = Request() + request.type.which = Request_start_imu_request_tag + request.type.start_imu_request = StartImuRequest() + request.type.start_imu_request.timestamp = Timestamp() + request.type.start_imu_request.timestamp.seconds = timestamp_seconds + request.type.start_imu_request.timestamp.ms = timestamp_ms request.type.start_imu_request.acc_fsr = acc_fsr request.type.start_imu_request.gyr_fsr = gyr_fsr request.type.start_imu_request.datarate = datarate - await self.request_response(request) - return self.deal_response(response_type=bp.Response_start_imu_response_tag).type.start_imu_response + await self.send_request(request) - @request_handler_marker(action_desc='stop imu') - async def stop_imu(self) -> None: - """Sends a request to the badge to stop IMU.""" - request = bp.Request() - request.type.which = bp.Request_stop_imu_request_tag - request.type.stop_imu_request = bp.StopImuRequest() + # Clear the queue before receiving + with self.start_imu_response_queue.mutex: + self.start_imu_response_queue.queue.clear() - await self.request_response(request) - self.deal_response(response_type=-1) - return None + while self.start_imu_response_queue.empty(): + await self.receive_response() - @request_handler_marker(action_desc='get imu data') - async def get_imu_data(self): - request = bp.Request() - request.type.which = bp.Request_get_imu_data_request_tag - request.type.get_imu_data_request = bp.GetIMUDataRequest() - request.type.get_imu_data_request.timestamp = bp.Timestamp() - - await self.request_response(request) - return self.deal_response(response_type=bp.Response_get_imu_data_response_tag).type.get_imu_data_response - - @request_handler_marker(action_desc='sdc erase all') - async def sdc_erase_all(self): - request = bp.Request() - request.type.which = bp.Request_sdc_errase_all_request_tag - request.type.sdc_errase_all_request = bp.ErraseAllRequest() - - await self.request_response(request) - return self.deal_response(response_type=bp.Response_sdc_errase_all_response_tag).type.sdc_errase_all_response - - @request_handler_marker(action_desc='identify') - async def identify(self, duration_seconds=10) -> bool: - """Send a request to the badge to light an LED to identify its self. - If duration_seconds == 0, badge will turn off LED if currently lit. - Returns True if request was successfully sent.""" - request = bp.Request() - request.type.which = bp.Request_identify_request_tag - request.type.identify_request = bp.IdentifyRequest() + return self.start_imu_response_queue.get() + + async def stop_imu(self): + + request = Request() + request.type.which = Request_stop_imu_request_tag + request.type.stop_imu_request = StopImuRequest() + + await self.send_request(request) + + # Send a request to the badge to light an led to identify its self. + # If duration_seconds == 0, badge will turn off LED if currently lit. + # Returns True if request was successfuly sent. + async def identify(self, duration_seconds=10): + + request = Request() + request.type.which = Request_identify_request_tag + request.type.identify_request = IdentifyRequest() request.type.identify_request.timeout = duration_seconds - await self.request_response(request) - self.deal_response(response_type=-1) + await self.send_request(request) + return True - @request_handler_marker(action_desc='restart') - async def restart(self) -> bool: - """Sends a request to the badge to restart the badge. Returns True if request was successfully sent.""" - request = bp.Request() - request.type.which = bp.Request_restart_request_tag - request.type.restart_request = bp.RestartRequest() + async def restart(self): + + request = Request() + request.type.which = Request_restart_request_tag + request.type.restart_request = RestartRequest() + + await self.send_request(request) - await self.request_response(request) - self.deal_response(response_type=-1) return True - @request_handler_marker(action_desc='get free sdc space') - async def get_free_sdc_space(self) -> bp.FreeSDCSpaceResponse: - """Sends a request to the badge to get a free sdc space. Returns the response object.""" - request = bp.Request() - request.type.which = bp.Request_free_sdc_space_request_tag - request.type.free_sdc_space_request = bp.FreeSDCSpaceRequest() - - await self.request_response(request) - return self.deal_response(response_type=bp.Response_free_sdc_space_response_tag).type.free_sdc_space_response - - async def start_recording_all_sensors(self): - await self.get_status() - await self.start_scan() - await self.start_microphone() - await self.start_imu() - - async def stop_recording_all_sensors(self): - await self.stop_scan() - await self.stop_microphone() - await self.stop_imu() - - @staticmethod - def print_help(): - print(" Available commands:") - print(" status ") - print(" start_all_sensors") - print(" stop_all_sensors") - print(" start_microphone") - print(" stop_microphone") - print(" start_scan") - print(" stop_scan") - print(" start_imu") - print(" stop_imu") - print(" identify") - print(" restart") - print(" get_free_space") - print(" help") - print(" All commands use current system time as transmitted time.") - sys.stdout.flush() + async def get_free_sdc_space(self): + + request = Request() + request.type.which = Request_free_sdc_space_request_tag + request.type.free_sdc_space_request = FreeSDCSpaceRequest() + + await self.send_request(request) + + # Clear the queue before receiving + with self.free_sdc_space_response_queue.mutex: + self.free_sdc_space_response_queue.queue.clear() + + while self.free_sdc_space_response_queue.empty(): + await self.receive_response() + + return self.free_sdc_space_response_queue.get() + + + async def sdc_errase_all(self): + + request = Request() + request.type.which = Request_sdc_errase_all_request_tag + request.type.sdc_errase_all_request = ErraseAllRequest() + + await self.send_request(request) + + # Clear the queue before receiving + with self.sdc_errase_all_response_queue.mutex: + self.sdc_errase_all_response_queue.queue.clear() + + while self.sdc_errase_all_response_queue.empty(): + await self.receive_response() + + return self.sdc_errase_all_response_queue.get() + + async def get_imu_data(self): + + request = Request() + request.type.which = Request_get_imu_data_request_tag + request.type.get_imu_data_request = GetIMUDataRequest() + request.type.get_imu_data_request.timestamp = Timestamp() + + await self.send_request(request) + + # Clear the queue before receiving + with self.get_imu_data_response_queue.mutex: + self.get_imu_data_response_queue.queue.clear() + + while self.get_imu_data_response_queue.empty(): + await self.receive_response() + + return self.get_imu_data_response_queue.get() \ No newline at end of file diff --git a/BadgeFramework/utils.py b/BadgeFramework/utils.py index 4aeaabb..300ab83 100644 --- a/BadgeFramework/utils.py +++ b/BadgeFramework/utils.py @@ -8,7 +8,7 @@ UART_SERVICE_UUID = uuid.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E") TX_CHAR_UUID = uuid.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E") RX_CHAR_UUID = uuid.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E") -df_badge = pandas.read_csv("mappings2.csv") +df_badge = pandas.read_csv("BadgeFramework/mappings2.csv") def get_logger(name): From d732e7bc1cc4e84b0e61fb89fe03bbbbad060274 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 28 Apr 2025 14:15:25 +0200 Subject: [PATCH 43/46] minimal changes in test.py --- BadgeFramework/badge.py | 3 ++- BadgeFramework/test.py | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index fdfe68a..5c29357 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -6,6 +6,7 @@ import queue as Queue import utils from bleak import BleakClient, BLEDevice, BleakGATTCharacteristic +from typing import Union CONNECTION_RETRY_TIMES = 15 DEFAULT_SCAN_WINDOW = 250 @@ -52,7 +53,7 @@ def timestamps_to_time(timestamp_seconds, timestamp_miliseconds): # The 'connection' should already be connected when it is used to initialize this class. # Implements methods that allow for interaction with that badge. class OpenBadge(object): - def __init__(self, device: BLEDevice or int, mac_address: str = None): + def __init__(self, device: Union[BLEDevice, int], mac_address: str = None): # self.connection = connection self.status_response_queue = Queue.Queue() self.start_microphone_response_queue = Queue.Queue() diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index 9e06215..b66c939 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -189,12 +189,12 @@ async def errase_mem(badge): print(" memory cleaned: FAIL ") print("###################################") - # def connect(self): - # connection = BLEBadgeConnection.get_connection_to_badge(self.address) - # connection.connect() - # badge = OpenBadge(self.connection) - # print("Connected!") - # return badge + def connect(self): + connection = BLEBadgeConnection.get_connection_to_badge(self.address) + connection.connect() + badge = OpenBadge(self.connection) + print("Connected!") + return badge async def main(): From 1c4d1cd664fbbae3a8f0e6e7faa9b03a9e0c5b58 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Mon, 28 Apr 2025 14:59:29 +0200 Subject: [PATCH 44/46] python 2to3 badge_gui --- .gitignore | 4 ++-- .vscode/launch.json | 8 ++++++++ BadgeFramework/badge_gui.py | 26 +++++++++++++------------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 2c46f12..7309605 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,10 @@ __pycache/ .vscode/c_cpp_properties.json .vscode/settings.json .vscode/launch.json -.vscode +.vscode/ **/.DS_Store BadgeFramework/rotation/rpi_sync.sh -.idea +.idea/ *.log notes.txt **/matlab/local diff --git a/.vscode/launch.json b/.vscode/launch.json index 86bd06b..59f5855 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,14 @@ "console": "integratedTerminal", "args": "de:94:80:39:25:be" }, + { + "name": "Python: badge_gui.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/BadgeFramework/badge_gui.py", + "console": "integratedTerminal", + // "args": "de:94:80:39:25:be" + }, { "name": "Cortex Debug", "cwd": "${workspaceFolder}", diff --git a/BadgeFramework/badge_gui.py b/BadgeFramework/badge_gui.py index 391cecb..0ea3e2b 100644 --- a/BadgeFramework/badge_gui.py +++ b/BadgeFramework/badge_gui.py @@ -1,7 +1,7 @@ -import Tkinter as tk -import ttk -from Tkinter import Label, Button, Canvas -import tkFont +import tkinter as tk +import tkinter.ttk +from tkinter import Label, Button, Canvas +import tkinter.font import random import time import csv @@ -43,7 +43,7 @@ def initUI(self): # Device info self.badgeId = Label(self, text="Badge ID: {}".format(self.name)) self.badgeId.grid(row=0, column=0, padx=10, pady=5) - self.midge = Label(self, text=self.address, font=tkFont.Font(size=12)) + self.midge = Label(self, text=self.address, font=tkinter.font.Font(size=12)) self.midge.grid(row=1, column=0, padx=10, pady=5) self.battery = Label(self, text='Battery: {}%'.format(self.badge_status.battery_level), relief="solid") self.battery.grid(row=2, column=0, padx=10, pady=5) @@ -207,7 +207,7 @@ def __init__(self, parent, imu_status): def initUI(self): info = ["acc_fsr (g): 16", "gyr_fsr (dps): 2000", "datarate: 50"] - title = tk.Label(self.top_frame, text='IMU Data', font=tkFont.Font(size=10, weight="bold")) + title = tk.Label(self.top_frame, text='IMU Data', font=tkinter.font.Font(size=10, weight="bold")) title.grid(row=0, column=0, columnspan=2, sticky='w') # Create information header for i, text in enumerate(info): @@ -263,7 +263,7 @@ def initUI(self): top_frame.grid(row=0, column=0, sticky='nsew') table.grid(row=1, column=0, sticky='nsew') - title = tk.Label(top_frame, text='Microphones Data', font=tkFont.Font(size=10, weight="bold")) + title = tk.Label(top_frame, text='Microphones Data', font=tkinter.font.Font(size=10, weight="bold")) title.grid(row=0, column=0, columnspan=2, sticky='w') # Loop over the dictionary and create labels for i, (key, value) in enumerate(self.info.items()): @@ -296,7 +296,7 @@ def initUI(self): top_frame.grid(row=0, column=0, sticky='nsew') table.grid(row=1, column=0, sticky='nsew') - title = tk.Label(top_frame, text='Scanner Data', font=tkFont.Font(size=10, weight="bold")) + title = tk.Label(top_frame, text='Scanner Data', font=tkinter.font.Font(size=10, weight="bold")) title.grid(row=0, column=0, columnspan=2, sticky='w') # Loop over the dictionary and create labels for i, (key, value) in enumerate(self.info.items()): @@ -304,7 +304,7 @@ def initUI(self): label = tk.Label(top_frame, text=label_text) label.grid(row=i+1, column=0, columnspan=2, sticky='w') - table_title = tk.Label(table, text='RSSI', font=tkFont.Font( weight="bold"), relief=tk.RIDGE, width=45) + table_title = tk.Label(table, text='RSSI', font=tkinter.font.Font( weight="bold"), relief=tk.RIDGE, width=45) table_title.grid(row=0, column=0, sticky="nsew") for i, e in enumerate(self.data): label = tk.Label(table, text=str(e), relief=tk.RIDGE, width=45) @@ -366,11 +366,11 @@ def initUI(self): right_down.grid(row=1, column=1, sticky='nsew') # header - self.badgeId = Label(left_top, text="Badge Id: {}".format(self.name), font=tkFont.Font(size=10, )) + self.badgeId = Label(left_top, text="Badge Id: {}".format(self.name), font=tkinter.font.Font(size=10, )) self.badgeId.grid(row=0, column=0, columnspan=2, sticky='w') - self.midge = Label(left_top, text="Battery: {}%".format(self.status.battery_level), font=tkFont.Font(size=10)) + self.midge = Label(left_top, text="Battery: {}%".format(self.status.battery_level), font=tkinter.font.Font(size=10)) self.midge.grid(row=1, column=0, columnspan=2, sticky='w') - self.battery = Label(left_top, text="Available memory: {}MB".format(self.free_space), font=tkFont.Font(size=10)) + self.battery = Label(left_top, text="Available memory: {}MB".format(self.free_space), font=tkinter.font.Font(size=10)) self.battery.grid(row=2, column=0, columnspan=2, sticky='w') self.reset_battery = Button(left_top, text='Erase', command=self.badge.sdc_errase_all) self.reset_battery.grid(row=2, column=1, columnspan=2, sticky='w') @@ -463,7 +463,7 @@ def __init__(self): tk.Tk.__init__(self) self.title("Lista de dispositivos") self.geometry("1100x700") - self.container_frame = ttk.Frame(self) + self.container_frame = tkinter.ttk.Frame(self) self.container_frame.pack(expand=True, fill="both") self.custom_components = [] self.badges = get_badges() From 48e133d5d151934d5a37375ef76f6fe1975540e9 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Tue, 29 Apr 2025 14:23:34 +0200 Subject: [PATCH 45/46] restore badge_interface --- BadgeFramework/badge_gui.py | 179 +++++++++++++++++++++--------- BadgeFramework/badge_interface.py | 73 ++++++++++++ BadgeFramework/mappings2.csv | 2 +- 3 files changed, 202 insertions(+), 52 deletions(-) create mode 100644 BadgeFramework/badge_interface.py diff --git a/BadgeFramework/badge_gui.py b/BadgeFramework/badge_gui.py index 0ea3e2b..ce56c6b 100644 --- a/BadgeFramework/badge_gui.py +++ b/BadgeFramework/badge_gui.py @@ -8,6 +8,9 @@ from badge_interface import * from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from matplotlib.figure import Figure +import asyncio +from asyncio import get_event_loop_policy +from dataclasses import dataclass DEFAULT_RADIUS = 20 GET_STATUS_MAIN = True @@ -26,14 +29,23 @@ def __init__(self, parent, radius=DEFAULT_RADIUS, status=0): self.color = calculate_color(self.status) self.create_oval(0, 0, 2 * radius, 2 * radius, fill=self.color) # Draw a circular shape + def update_status(self, new_status): + self.status = new_status + self.color = calculate_color(self.status) + self.create_oval(0, 0, 2 * self.radius, 2 * self.radius, fill=self.color) + class CustomComponent(tk.Frame): - def __init__(self, parent, name, badge, address): + def __init__(self, parent, name, badge, address, gui_instance): tk.Frame.__init__(self, parent, pady=10) + self.parent = parent + self.gui = gui_instance # Store reference to main GUI + self.loop = self.gui.loop # Get loop from GUI instance self.name = name self.badge = badge self.address = address - self.badge_status = badge.get_status() - self.free_space = badge.get_free_sdc_space().free_space + # Initialize with default values + self.badge_status = MockStatusResponse() # Use the mock status as default + self.free_space = 0 self.new_window = {} self.scan_info = {'window': '', 'interval': ''} self.mic_info = {'mode': '', 'gain_r': '', 'gain_l': '', 'switch_pos': '', 'pdm_freq': ''} @@ -45,45 +57,50 @@ def initUI(self): self.badgeId.grid(row=0, column=0, padx=10, pady=5) self.midge = Label(self, text=self.address, font=tkinter.font.Font(size=12)) self.midge.grid(row=1, column=0, padx=10, pady=5) - self.battery = Label(self, text='Battery: {}%'.format(self.badge_status.battery_level), relief="solid") + self.battery = Label(self, text='Battery: Connecting...', relief="solid") self.battery.grid(row=2, column=0, padx=10, pady=5) # Status self.statusLabel = Label(self, text="Status") self.statusLabel.grid(row=0, column=1, padx=10, pady=5) - self.statusCircle = Circle(self, status=self.badge_status.clock_status) + self.statusCircle = Circle(self, status=False) # Start with inactive status self.statusCircle.grid(row=1, column=1, padx=10, pady=5) # IMU self.IMULabel = Label(self, text="IMU") self.IMULabel.grid(row=0, column=2, columnspan=2, padx=10, pady=5) - self.IMUCircle = Circle(self, status=self.badge_status.imu_status) + self.IMUCircle = Circle(self, status=False) # Start inactive self.IMUCircle.grid(row=1, column=2, columnspan=2, padx=10, pady=5) - self.IMUStartButton = Button(self, text="Start", command=self.start_imu) + self.IMUStartButton = Button(self, text="Start", command=lambda: self.button_click_handler(self.start_imu)) self.IMUStartButton.grid(row=2, column=2, padx=10, pady=5) - self.IMUStopButton = Button(self, text="Stop", command=self.stop_imu) + self.IMUStopButton = Button(self, text="Stop", command=lambda: self.button_click_handler(self.stop_imu)) self.IMUStopButton.grid(row=2, column=3, padx=10, pady=5) # Mic self.MicLabel = Label(self, text="Microphones") self.MicLabel.grid(row=0, column=4, columnspan=2, padx=10, pady=5) - self.MicCircle = Circle(self, status=self.badge_status.microphone_status) + self.MicCircle = Circle(self, status=False) # Start inactive self.MicCircle.grid(row=1, column=4, columnspan=2, padx=10, pady=5) - self.MicStartMonoButton = Button(self, text="Start mono", command=lambda: self.start_microphone(t=None, mode=1)) + self.MicStartMonoButton = Button(self, text="Start mono", + command=lambda: self.button_click_handler(self.start_microphone, None, 1)) self.MicStartMonoButton.grid(row=2, column=4, padx=10, pady=5) - self.MicStartStereoButton = Button(self, text="Start stereo", command=lambda: self.start_microphone(t=None, mode=0)) + self.MicStartStereoButton = Button(self, text="Start stereo", + command=lambda: self.button_click_handler(self.start_microphone, None, 0)) self.MicStartStereoButton.grid(row=2, column=5, padx=10, pady=5) - self.MicStopButton = Button(self, text="Stop", command=self.badge.stop_microphone) + self.MicStopButton = Button(self, text="Stop", + command=lambda: self.button_click_handler(self.stop_microphone)) self.MicStopButton.grid(row=3, column=4, columnspan=2, padx=10, pady=5, sticky="nsew") # Scan self.ScanLabel = Label(self, text="Scanner") self.ScanLabel.grid(row=0, column=6, columnspan=2, padx=10, pady=5) - self.ScanCircle = Circle(self, status=self.badge_status.scan_status) + self.ScanCircle = Circle(self, status=False) # Start inactive self.ScanCircle.grid(row=1, column=6, columnspan=2, padx=10, pady=5) - self.ScanStartButton = Button(self, text="Start", command=self.start_scan) + self.ScanStartButton = Button(self, text="Start", + command=lambda: self.button_click_handler(self.start_scan)) self.ScanStartButton.grid(row=2, column=6, padx=10, pady=5) - self.ScanStopButton = Button(self, text="Stop", command=self.stop_scan) + self.ScanStopButton = Button(self, text="Stop", + command=lambda: self.button_click_handler(self.stop_scan)) self.ScanStopButton.grid(row=2, column=7, padx=10, pady=5) self.bind("", self.onMouseClick) # Handle mouse click @@ -93,24 +110,25 @@ def initUI(self): self.bind("", lambda event: self.config(cursor="")) def onMouseClick(self, event): + pass # Handle mouse click event - self.new_window = NewWindow(self.badge, self.free_space, name=self.name, scan_info=self.scan_info, mic_info=self.mic_info) - self.new_window.mainloop() + # self.new_window = NewWindow(self.badge, self.free_space, name=self.name, scan_info=self.scan_info, mic_info=self.mic_info) + # self.new_window.mainloop() - def start_imu(self): + async def start_imu(self): if self.badge_status.imu_status == 0: - self.badge.start_imu() + await self.badge.start_imu() else: print("IMU already started") - def stop_imu(self): - self.badge.stop_imu() + async def stop_imu(self): + await self.badge.stop_imu() - def start_microphone(self, t, mode): + async def start_microphone(self, t, mode): if self.badge_status.microphone_status == 1: print("Microphone already started") return - start_mic_response = self.badge.start_microphone(mode) + start_mic_response = await self.badge.start_microphone(mode) if (start_mic_response.switch_pos == 2): switch_pos = "HIGH" elif (start_mic_response.switch_pos == 1): switch_pos = "LOW" @@ -130,14 +148,14 @@ def start_microphone(self, t, mode): except: pass - def stop_microphone(self): - self.badge.stop_microphone() + async def stop_microphone(self): + await self.badge.stop_microphone() - def start_scan(self): + async def start_scan(self): if self.badge_status.scan_status == 1: print("Scanner already started") return - start_scan_response = self.badge.start_scan() + start_scan_response = await self.badge.start_scan() self.scan_info = {'window': start_scan_response.window, 'interval': start_scan_response.interval} try: @@ -146,9 +164,48 @@ def start_scan(self): except: pass - def stop_scan(self): - self.badge.stop_scan() + async def stop_scan(self): + await self.badge.stop_scan() + async def async_init(self): + try: + if self.badge: + self.badge_status = await self.badge.get_status() + self.free_space = (await self.badge.get_free_sdc_space()).free_space + else: + # Use mock data if no badge + self.badge_status = MockStatusResponse() + self.free_space = 0 + except Exception as e: + print(f"Error in async_init: {e}") + # Keep using mock data on error + self.badge_status = MockStatusResponse() + self.free_space = 0 + finally: + # Always update UI + self.update_ui_with_status() + + def update_ui_with_status(self): + """Update all UI elements with actual values from badge_status""" + try: + self.battery.config(text='Battery: {}%'.format(self.badge_status.battery_level)) + self.statusCircle.update_status(self.badge_status.clock_status) + self.IMUCircle.update_status(self.badge_status.imu_status) + self.MicCircle.update_status(self.badge_status.microphone_status) + self.ScanCircle.update_status(getattr(self.badge_status, 'scan_status', False)) + except Exception as e: + print(f"Error updating UI: {e}") + + def button_click_handler(self, async_func, *args): + """Wrapper to run async functions from button clicks""" + try: + if not self.badge: + print("No device connected") + return + self.loop.create_task(async_func(*args)) + except Exception as e: + print(f"Error in button handler: {e}") + class MatplolibFrame(tk.Frame): def __init__(self, parent, time, x, y, z): tk.Frame.__init__(self, parent, relief=tk.RIDGE) @@ -399,21 +456,21 @@ def initUI(self): self.MicConfig = MicConfig(right_top, data=self.mic_data.queue, info=self.mic_info) self.MicConfig.grid(row=4, column=2, columnspan=2, padx=10, pady=5) - def update_data(self): + async def update_data(self): self.time = self.time + 1 try: - status = self.badge.get_status() + status = await self.badge.get_status() time.sleep(0.1) - imu_status = self.badge.get_imu_data() + imu_status = await self.badge.get_imu_data() time.sleep(0.1) if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) except: print('Error with request') self.badge.connect() - status = self.badge.get_status() + status = await self.badge.get_status() time.sleep(0.1) - imu_status = self.badge.get_imu_data() + imu_status = await self.badge.get_imu_data() time.sleep(0.1) if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) @@ -441,15 +498,23 @@ def update_data(self): self.IMUConfig.time.enqueue(self.time) self.IMUConfig.initUI() # update circles - self.MicCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.microphone_status)) - self.IMUCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.imu_status)) - self.ScanCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.scan_status)) + self.MicCircle.update_status(status.microphone_status) + self.IMUCircle.update_status(status.imu_status) + self.ScanCircle.update_status(status.scan_status) self.after(5500, self.update_data) +@dataclass +class MockStatusResponse: + battery_level: int = 0 + clock_status: bool = False + imu_status: bool = False + microphone_status: bool = False + scan_status: bool = False + def get_badges(): badges = [] - with open('sample_mapping_file.csv', 'r') as f: + with open('BadgeFramework/mappings2.csv', 'r') as f: reader = csv.reader(f) next(reader) for row in reader: @@ -468,33 +533,45 @@ def __init__(self): self.custom_components = [] self.badges = get_badges() + # Set up asyncio loop + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + for badge in self.badges: - custom_component = CustomComponent(self.container_frame, name=badge['name'], badge=badge['badge'], address=badge['address']) + custom_component = CustomComponent(self.container_frame, name=badge['name'], badge=badge['badge'], address=badge['address'], gui_instance=self) custom_component.pack() # Draw a border between custom components border = tk.Frame(self.container_frame, bg="gray", height=1) border.pack(fill="x") self.custom_components.append(custom_component) + # self.loop.create_task(custom_component.async_init()) - self.after(1000, self.update_data) + # Schedule asyncio loop to run regularly + self.after(100, self.run_asyncio_loop) + + def run_asyncio_loop(self): + """Run a single iteration of the asyncio loop""" + self.loop.call_soon(self.loop.stop) + self.loop.run_forever() + self.after(100, self.run_asyncio_loop) - def update_data(self): + async def update_data(self): for badge, custom_component in zip(self.badges, self.custom_components): try: - badge_status = badge['badge'].get_status() + badge_status = await badge['badge'].get_status() except: print('Error with request') - try: - badge['badge'].connect() - except: - badge['badge'].connect() - badge_status = badge['badge'].get_status() + # try: + # badge['badge'].connect() + # except: + # badge['badge'].connect() + # badge_status = badge['badge'].get_status() custom_component.badge_status = badge_status custom_component.battery['text'] = 'Battery: {}%'.format(badge_status.battery_level) - custom_component.statusCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.clock_status)) - custom_component.MicCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.microphone_status)) - custom_component.IMUCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.imu_status)) - custom_component.ScanCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.scan_status)) + custom_component.statusCircle.update_status(badge_status.clock_status) + custom_component.MicCircle.update_status(badge_status.microphone_status) + custom_component.IMUCircle.update_status(badge_status.imu_status) + custom_component.ScanCircle.update_status(badge_status.scan_status) if (badge_status.microphone_status == 0): custom_component.MicStartMonoButton['state'] = 'normal' custom_component.MicStartStereoButton['state'] = 'normal' diff --git a/BadgeFramework/badge_interface.py b/BadgeFramework/badge_interface.py new file mode 100644 index 0000000..ef43700 --- /dev/null +++ b/BadgeFramework/badge_interface.py @@ -0,0 +1,73 @@ +from badge import * +# from ble_badge_connection import * +from dataclasses import dataclass + +# Add a mock status class +@dataclass +class MockStatusResponse: + battery_level: int = 100 + clock_status: bool = False + imu_status: bool = False + microphone_status: bool = False + +class BadgeInterface(): + def __init__(self, address): + self.address = address + self.connection = None + self.badge = None + # Add a mock status that can be updated + self._mock_status = MockStatusResponse() + + def connect(self): + # self.connection = BLEBadgeConnection.get_connection_to_badge(self.address) + # self.connection.connect() + # self.badge = OpenBadge(self.connection) + pass + # print("Connected!") + + async def get_status(self): + try: + async with OpenBadge(0, self.address) as badge: + return await badge.get_status() + except Exception as e: + print(f"Using mock status due to: {e}") + return self._mock_status + + async def get_free_sdc_space(self): + async with OpenBadge(0, self.address) as badge: + return await badge.get_free_sdc_space() + + async def start_imu(self): + async with OpenBadge(0, self.address) as badge: + return await badge.start_imu() + + async def start_microphone(self, mode): + #print("MODE interface ",mode) + async with OpenBadge(0, self.address) as badge: + return await badge.start_microphone(mode=mode) + + async def stop_microphone(self): + async with OpenBadge(0, self.address) as badge: + return await badge.stop_microphone() + + async def stop_imu(self): + async with OpenBadge(0, self.address) as badge: + return await badge.stop_imu() + + async def start_scan(self): + async with OpenBadge(0, self.address) as badge: + return await badge.start_scan() + + async def stop_scan(self): + async with OpenBadge(0, self.address) as badge: + return await badge.stop_scan() + + async def get_imu_data(self): + async with OpenBadge(0, self.address) as badge: + return await badge.get_imu_data() + + async def sdc_errase_all(self): + async with OpenBadge(0, self.address) as badge: + return await badge.sdc_errase_all() + + \ No newline at end of file diff --git a/BadgeFramework/mappings2.csv b/BadgeFramework/mappings2.csv index 0c87530..debd935 100644 --- a/BadgeFramework/mappings2.csv +++ b/BadgeFramework/mappings2.csv @@ -1,5 +1,5 @@ ,Participant Id,Mac Address,Data Frame Length,Use -0,1,d5:46:26:05:1f:2f,32.0,0 +0,1,de:94:80:39:25:be,32.0,0 1,2,c3:4c:d5:ba:b1:44,32.0,0 2,3,e5:93:96:ca:ee:c4,,0 3,4,f9:39:24:a9:04:f1,,0 From 885f6d0a34a92224fd3ba7398abe9b97c5c1dfb2 Mon Sep 17 00:00:00 2001 From: Zonghuan Li Date: Wed, 30 Apr 2025 19:04:00 +0200 Subject: [PATCH 46/46] partially fix old GUI --- .vscode/launch.json | 8 + BadgeFramework/badge.py | 2 +- BadgeFramework/badge_gui.py | 240 +++++++++----- BadgeFramework/badge_gui_bleak.py | 2 +- BadgeFramework/badge_gui_old.py | 525 ++++++++++++++++++++++++++++++ BadgeFramework/test.py | 30 +- 6 files changed, 718 insertions(+), 89 deletions(-) create mode 100644 BadgeFramework/badge_gui_old.py diff --git a/.vscode/launch.json b/.vscode/launch.json index 59f5855..a402ce9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,14 @@ "console": "integratedTerminal", // "args": "de:94:80:39:25:be" }, + { + "name": "Python: badge_gui_bleak.py", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/BadgeFramework/badge_gui_bleak.py", + "console": "integratedTerminal", + // "args": "de:94:80:39:25:be" + }, { "name": "Cortex Debug", "cwd": "${workspaceFolder}", diff --git a/BadgeFramework/badge.py b/BadgeFramework/badge.py index 5c29357..e0776f8 100644 --- a/BadgeFramework/badge.py +++ b/BadgeFramework/badge.py @@ -81,7 +81,7 @@ async def __aenter__(self): await self.client.start_notify(utils.RX_CHAR_UUID, self.received_callback) return self except Exception as e: - pass + print(e) raise TimeoutError(f'Failed to connect to device after {CONNECTION_RETRY_TIMES} attempts.') async def __aexit__(self, exc_type, exc_val, exc_tb): diff --git a/BadgeFramework/badge_gui.py b/BadgeFramework/badge_gui.py index ce56c6b..531536f 100644 --- a/BadgeFramework/badge_gui.py +++ b/BadgeFramework/badge_gui.py @@ -63,13 +63,13 @@ def initUI(self): # Status self.statusLabel = Label(self, text="Status") self.statusLabel.grid(row=0, column=1, padx=10, pady=5) - self.statusCircle = Circle(self, status=False) # Start with inactive status + self.statusCircle = Circle(self, status=self.badge_status.clock_status) self.statusCircle.grid(row=1, column=1, padx=10, pady=5) # IMU self.IMULabel = Label(self, text="IMU") self.IMULabel.grid(row=0, column=2, columnspan=2, padx=10, pady=5) - self.IMUCircle = Circle(self, status=False) # Start inactive + self.IMUCircle = Circle(self, status=self.badge_status.imu_status) self.IMUCircle.grid(row=1, column=2, columnspan=2, padx=10, pady=5) self.IMUStartButton = Button(self, text="Start", command=lambda: self.button_click_handler(self.start_imu)) self.IMUStartButton.grid(row=2, column=2, padx=10, pady=5) @@ -110,10 +110,9 @@ def initUI(self): self.bind("", lambda event: self.config(cursor="")) def onMouseClick(self, event): - pass # Handle mouse click event - # self.new_window = NewWindow(self.badge, self.free_space, name=self.name, scan_info=self.scan_info, mic_info=self.mic_info) - # self.new_window.mainloop() + self.new_window = NewWindow(self.badge, self.free_space, name=self.name, scan_info=self.scan_info, mic_info=self.mic_info) + self.new_window.mainloop() async def start_imu(self): if self.badge_status.imu_status == 0: @@ -125,11 +124,12 @@ async def stop_imu(self): await self.badge.stop_imu() async def start_microphone(self, t, mode): + print('starting microphone for badge: {}'.format(self.name)) if self.badge_status.microphone_status == 1: print("Microphone already started") return start_mic_response = await self.badge.start_microphone(mode) - + if (start_mic_response.switch_pos == 2): switch_pos = "HIGH" elif (start_mic_response.switch_pos == 1): switch_pos = "LOW" else: switch_pos = "OFF" @@ -145,8 +145,8 @@ async def start_microphone(self, t, mode): try: self.new_window.MicConfig.info = self.mic_info self.new_window.initUI() - except: - pass + except Exception as e: + print('error in updating microphone config: {}'.format(e)) async def stop_microphone(self): await self.badge.stop_microphone() @@ -390,6 +390,37 @@ def __repr__(self): return str(self.queue) +@dataclass +class MockStatusResponse: + battery_level: int = 0 + clock_status: bool = False + imu_status: bool = False + microphone_status: bool = False + scan_status: bool = False + pdm_data: list = None + scan_data: list = None + + def __post_init__(self): + if self.pdm_data is None: + self.pdm_data = [] + if self.scan_data is None: + self.scan_data = [] + +@dataclass +class MockIMUResponse: + gyr_x: float = 0.0 + gyr_y: float = 0.0 + gyr_z: float = 0.0 + rot_x: float = 0.0 + rot_y: float = 0.0 + rot_z: float = 0.0 + acc_x: float = 0.0 + acc_y: float = 0.0 + acc_z: float = 0.0 + mag_x: float = 0.0 + mag_y: float = 0.0 + mag_z: float = 0.0 + class NewWindow(tk.Toplevel): def __init__(self, badge, free_space, name, scan_info, mic_info): GET_STATUS_MAIN = False @@ -400,16 +431,69 @@ def __init__(self, badge, free_space, name, scan_info, mic_info): self.badge = badge self.mic_data = LimitedQueue(10) self.scan_data = LimitedQueue(7) - self.status = self.badge.get_status() + self.status = MockStatusResponse() # Initialize with mock status self.mic_data.enqueue(self.status.pdm_data) self.scan_data.enqueue(self.status.scan_data) self.free_space = free_space - self.imu_status = self.badge.get_imu_data() + self.imu_status = MockIMUResponse() # Initialize with mock IMU status self.time = 0 self.scan_info = scan_info self.mic_info = mic_info self.initUI() - self.after(1000, self.update_data) + self.loop = asyncio.get_event_loop() + self.loop.create_task(self.async_init()) + self.schedule_update() + + def schedule_update(self): + """Schedule the next update""" + self.loop.create_task(self.update_data()) + self.after(5500, self.schedule_update) + + async def update_data(self): + self.time = self.time + 1 + try: + status = await self.badge.get_status() + imu_status = await self.badge.get_imu_data() + if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): + self.battery.config(text="Available memory: {} MB".format((await self.badge.get_free_sdc_space()).free_space)) + except: + print('Error with request') + try: + await self.badge.connect() + status = await self.badge.get_status() + imu_status = await self.badge.get_imu_data() + if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): + self.battery.config(text="Available memory: {} MB".format((await self.badge.get_free_sdc_space()).free_space)) + except: + print('Failed to reconnect') + return + + if status.microphone_status == 0: + self.MicConfig.data = [] + self.MicConfig.initUI() + else: + pdm_data = status.pdm_data + # update mic + self.mic_data.enqueue(pdm_data) + self.MicConfig.data = self.mic_data.queue + self.MicConfig.initUI() + if status.scan_status == 0: + self.ScanConfig.data = [] + self.ScanConfig.initUI() + else: + scan_data = status.scan_data + # update scan + self.scan_data.enqueue(scan_data) + self.ScanConfig.data = self.scan_data.queue + self.ScanConfig.initUI() + #update imu + self.IMUConfig.imu_status = imu_status + self.IMUConfig.time.enqueue(self.time) + self.IMUConfig.initUI() + # update circles + self.MicCircle.update_status(status.microphone_status) + self.IMUCircle.update_status(status.imu_status) + self.ScanCircle.update_status(status.scan_status) def initUI(self): # Create the layout frames @@ -456,61 +540,38 @@ def initUI(self): self.MicConfig = MicConfig(right_top, data=self.mic_data.queue, info=self.mic_info) self.MicConfig.grid(row=4, column=2, columnspan=2, padx=10, pady=5) - async def update_data(self): - self.time = self.time + 1 + async def async_init(self): try: - status = await self.badge.get_status() - time.sleep(0.1) - imu_status = await self.badge.get_imu_data() - time.sleep(0.1) - if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): - self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) - except: - print('Error with request') - self.badge.connect() - status = await self.badge.get_status() - time.sleep(0.1) - imu_status = await self.badge.get_imu_data() - time.sleep(0.1) - if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): - self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) - - if status.microphone_status == 0: - self.MicConfig.data = [] - self.MicConfig.initUI() - else: - pdm_data = status.pdm_data - # update mic - self.mic_data.enqueue(pdm_data) - self.MicConfig.data = self.mic_data.queue - self.MicConfig.initUI() - if status.scan_status == 0: - self.ScanConfig.data = [] - self.ScanConfig.initUI() - else: - scan_data = status.scan_data - # update scan - self.scan_data.enqueue(scan_data) - self.ScanConfig.data = self.scan_data.queue - self.ScanConfig.initUI() - #update imu - self.IMUConfig.imu_status = imu_status - self.IMUConfig.time.enqueue(self.time) - self.IMUConfig.initUI() - # update circles - self.MicCircle.update_status(status.microphone_status) - self.IMUCircle.update_status(status.imu_status) - self.ScanCircle.update_status(status.scan_status) - - self.after(5500, self.update_data) + if self.badge: + self.status = await self.badge.get_status() + self.free_space = (await self.badge.get_free_sdc_space()).free_space + self.imu_status = await self.badge.get_imu_data() + print(self.status) + else: + # Use mock data if no badge + print('badge detected, using mock data') + self.status = MockStatusResponse() + self.free_space = 0 + self.imu_status = MockIMUResponse() + except Exception as e: + print(f"Error in async_init: {e}") + # Keep using mock data on error + self.status = MockStatusResponse() + self.free_space = 0 + self.imu_status = MockIMUResponse() + finally: + # Always update UI + self.update_ui_with_status() -@dataclass -class MockStatusResponse: - battery_level: int = 0 - clock_status: bool = False - imu_status: bool = False - microphone_status: bool = False - scan_status: bool = False + def update_ui_with_status(self): + """Update all UI elements with actual values from status""" + try: + self.battery.config(text='Battery: {}%'.format(self.status.battery_level)) + self.IMUCircle.update_status(self.status.imu_status) + self.MicCircle.update_status(self.status.microphone_status) + self.ScanCircle.update_status(self.status.scan_status) + except Exception as e: + print(f"Error updating UI: {e}") def get_badges(): badges = [] @@ -518,9 +579,9 @@ def get_badges(): reader = csv.reader(f) next(reader) for row in reader: - badge = BadgeInterface(row[1]) + badge = BadgeInterface(row[2]) badge.connect() - badges.append({'badge': badge, 'name': row[0], 'address': row[1]}) + badges.append({'badge': badge, 'name': row[1], 'address': row[2]}) return badges class MainApp(tk.Tk): @@ -544,34 +605,38 @@ def __init__(self): border = tk.Frame(self.container_frame, bg="gray", height=1) border.pack(fill="x") self.custom_components.append(custom_component) - # self.loop.create_task(custom_component.async_init()) # Schedule asyncio loop to run regularly self.after(100, self.run_asyncio_loop) + self.schedule_update() - def run_asyncio_loop(self): - """Run a single iteration of the asyncio loop""" - self.loop.call_soon(self.loop.stop) - self.loop.run_forever() - self.after(100, self.run_asyncio_loop) + def schedule_update(self): + """Schedule the next update""" + self.loop.create_task(self.update_data()) + self.after(1000, self.schedule_update) async def update_data(self): for badge, custom_component in zip(self.badges, self.custom_components): - try: - badge_status = await badge['badge'].get_status() - except: - print('Error with request') - # try: - # badge['badge'].connect() - # except: - # badge['badge'].connect() - # badge_status = badge['badge'].get_status() + # TODO: fix update data + badge_status = custom_component.badge_status + # try: + # badge_status = await badge['badge'].get_status() + # except: + # print('Error with request') + # try: + # await badge['badge'].connect() + # badge_status = await badge['badge'].get_status() + # except: + # print('Failed to reconnect') + # continue + custom_component.badge_status = badge_status custom_component.battery['text'] = 'Battery: {}%'.format(badge_status.battery_level) custom_component.statusCircle.update_status(badge_status.clock_status) custom_component.MicCircle.update_status(badge_status.microphone_status) custom_component.IMUCircle.update_status(badge_status.imu_status) custom_component.ScanCircle.update_status(badge_status.scan_status) + if (badge_status.microphone_status == 0): custom_component.MicStartMonoButton['state'] = 'normal' custom_component.MicStartStereoButton['state'] = 'normal' @@ -580,23 +645,28 @@ async def update_data(self): custom_component.MicStartMonoButton['state'] = 'disabled' custom_component.MicStartStereoButton['state'] = 'disabled' custom_component.MicStopButton['state'] = 'normal' + if (badge_status.imu_status == 0): custom_component.IMUStartButton['state'] = 'normal' custom_component.IMUStopButton['state'] = 'disabled' else: custom_component.IMUStartButton['state'] = 'disabled' custom_component.IMUStopButton['state'] = 'normal' + if (badge_status.scan_status == 0): custom_component.ScanStartButton['state'] = 'normal' custom_component.ScanStopButton['state'] = 'disabled' else: custom_component.ScanStartButton['state'] = 'disabled' custom_component.ScanStopButton['state'] = 'normal' - - if (GET_STATUS_MAIN == True): - print('updating main') - self.after(1000, self.update_data) + + def run_asyncio_loop(self): + """Run a single iteration of the asyncio loop""" + self.loop.call_soon(self.loop.stop) + self.loop.run_forever() + self.after(100, self.run_asyncio_loop) if __name__ == '__main__': + print('starting app') app = MainApp() app.mainloop() diff --git a/BadgeFramework/badge_gui_bleak.py b/BadgeFramework/badge_gui_bleak.py index 05d66a5..173002f 100644 --- a/BadgeFramework/badge_gui_bleak.py +++ b/BadgeFramework/badge_gui_bleak.py @@ -52,7 +52,7 @@ def __init__(self, ): super().__init__() self.title("Badge Status Monitor") - self.badges = pd.read_csv('mappings2.csv') + self.badges = pd.read_csv('BadgeFramework/mappings2.csv') self.main_frame = ttk.Frame(self) self.main_frame.grid(row=0, column=0, sticky="nsew") # Use grid for main_frame diff --git a/BadgeFramework/badge_gui_old.py b/BadgeFramework/badge_gui_old.py new file mode 100644 index 0000000..48bd348 --- /dev/null +++ b/BadgeFramework/badge_gui_old.py @@ -0,0 +1,525 @@ +import Tkinter as tk +import ttk +from Tkinter import Label, Button, Canvas +import tkFont +import random +import time +import csv +from badge_interface import * +from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg +from matplotlib.figure import Figure + +DEFAULT_RADIUS = 20 +GET_STATUS_MAIN = True + +def calculate_color(status): + if status == 1: + return "green" + else: + return "red" + +class Circle(Canvas): + def __init__(self, parent, radius=DEFAULT_RADIUS, status=0): + Canvas.__init__(self, parent, width=2 * radius, height=2 * radius, bd=0, highlightthickness=0) + self.radius = radius + self.status = status + self.color = calculate_color(self.status) + self.create_oval(0, 0, 2 * radius, 2 * radius, fill=self.color) # Draw a circular shape + +class CustomComponent(tk.Frame): + def __init__(self, parent, name, badge, address): + tk.Frame.__init__(self, parent, pady=10) + self.name = name + self.badge = badge + self.address = address + self.badge_status = badge.get_status() + self.free_space = badge.get_free_sdc_space().free_space + self.new_window = {} + self.scan_info = {'window': '', 'interval': ''} + self.mic_info = {'mode': '', 'gain_r': '', 'gain_l': '', 'switch_pos': '', 'pdm_freq': ''} + self.initUI() + + def initUI(self): + # Device info + self.badgeId = Label(self, text="Badge ID: {}".format(self.name)) + self.badgeId.grid(row=0, column=0, padx=10, pady=5) + self.midge = Label(self, text=self.address, font=tkFont.Font(size=12)) + self.midge.grid(row=1, column=0, padx=10, pady=5) + self.battery = Label(self, text='Battery: {}%'.format(self.badge_status.battery_level), relief="solid") + self.battery.grid(row=2, column=0, padx=10, pady=5) + + # Status + self.statusLabel = Label(self, text="Status") + self.statusLabel.grid(row=0, column=1, padx=10, pady=5) + self.statusCircle = Circle(self, status=self.badge_status.clock_status) + self.statusCircle.grid(row=1, column=1, padx=10, pady=5) + + # IMU + self.IMULabel = Label(self, text="IMU") + self.IMULabel.grid(row=0, column=2, columnspan=2, padx=10, pady=5) + self.IMUCircle = Circle(self, status=self.badge_status.imu_status) + self.IMUCircle.grid(row=1, column=2, columnspan=2, padx=10, pady=5) + self.IMUStartButton = Button(self, text="Start", command=self.start_imu) + self.IMUStartButton.grid(row=2, column=2, padx=10, pady=5) + self.IMUStopButton = Button(self, text="Stop", command=self.stop_imu) + self.IMUStopButton.grid(row=2, column=3, padx=10, pady=5) + + # Mic + self.MicLabel = Label(self, text="Microphones") + self.MicLabel.grid(row=0, column=4, columnspan=2, padx=10, pady=5) + self.MicCircle = Circle(self, status=self.badge_status.microphone_status) + self.MicCircle.grid(row=1, column=4, columnspan=2, padx=10, pady=5) + self.MicStartMonoButton = Button(self, text="Start mono", command=lambda: self.start_microphone(t=None, mode=1)) + self.MicStartMonoButton.grid(row=2, column=4, padx=10, pady=5) + self.MicStartStereoButton = Button(self, text="Start stereo", command=lambda: self.start_microphone(t=None, mode=0)) + self.MicStartStereoButton.grid(row=2, column=5, padx=10, pady=5) + self.MicStopButton = Button(self, text="Stop", command=self.badge.stop_microphone) + self.MicStopButton.grid(row=3, column=4, columnspan=2, padx=10, pady=5, sticky="nsew") + + # Scan + self.ScanLabel = Label(self, text="Scanner") + self.ScanLabel.grid(row=0, column=6, columnspan=2, padx=10, pady=5) + self.ScanCircle = Circle(self, status=self.badge_status.scan_status) + self.ScanCircle.grid(row=1, column=6, columnspan=2, padx=10, pady=5) + self.ScanStartButton = Button(self, text="Start", command=self.start_scan) + self.ScanStartButton.grid(row=2, column=6, padx=10, pady=5) + self.ScanStopButton = Button(self, text="Stop", command=self.stop_scan) + self.ScanStopButton.grid(row=2, column=7, padx=10, pady=5) + + self.bind("", self.onMouseClick) # Handle mouse click + + # Change the cursor when hovering + self.bind("", lambda event: self.config(cursor="hand2")) + self.bind("", lambda event: self.config(cursor="")) + + def onMouseClick(self, event): + # Handle mouse click event + self.new_window = NewWindow(self.badge, self.free_space, name=self.name, scan_info=self.scan_info, mic_info=self.mic_info) + self.new_window.mainloop() + + def start_imu(self): + if self.badge_status.imu_status == 0: + self.badge.start_imu() + else: + print("IMU already started") + + def stop_imu(self): + self.badge.stop_imu() + + def start_microphone(self, t, mode): + if self.badge_status.microphone_status == 1: + print("Microphone already started") + return + start_mic_response = self.badge.start_microphone(mode) + + if (start_mic_response.switch_pos == 2): switch_pos = "HIGH" + elif (start_mic_response.switch_pos == 1): switch_pos = "LOW" + else: switch_pos = "OFF" + + if (start_mic_response.mode == 1): mic_mode = "mono" + else: mic_mode = "stereo" + + self.mic_info = {'mode': mic_mode, + 'gain_r (dBm)': start_mic_response.gain_r, + 'gain_l (dBm)': start_mic_response.gain_l, + 'switch_pos': switch_pos, + 'pdm_freq (kHz)': start_mic_response.pdm_freq} + try: + self.new_window.MicConfig.info = self.mic_info + self.new_window.initUI() + except: + pass + + def stop_microphone(self): + self.badge.stop_microphone() + + def start_scan(self): + if self.badge_status.scan_status == 1: + print("Scanner already started") + return + start_scan_response = self.badge.start_scan() + self.scan_info = {'window': start_scan_response.window, + 'interval': start_scan_response.interval} + try: + self.new_window.ScanConfig.info = self.scan_info + self.new_window.ScanConfig.initUI() + except: + pass + + def stop_scan(self): + self.badge.stop_scan() + +class MatplolibFrame(tk.Frame): + def __init__(self, parent, time, x, y, z): + tk.Frame.__init__(self, parent, relief=tk.RIDGE) + self.figure = Figure(figsize=(3, 1.5), dpi=100) + self.plot = self.figure.add_subplot(111) + self.plot.plot(time, x, label='x') + self.plot.plot(time, y, label='y') + self.plot.plot(time, z, label='z') + # Adding the legend + # Place the legend outside the plot + self.plot.legend(fontsize='x-small') + # Set x-axis limits if your data is not displaying correctly + self.plot.set_xlim([min(time), max(time)]) + + # Set x-axis labels + self.plot.set_xticks(time) + self.plot.set_xticklabels(time) + + self.canvas = FigureCanvasTkAgg(self.figure, self) + self.canvas.draw() + + self.canvas.get_tk_widget().grid(row=0, column=0) + +class IMUConfig(tk.Frame): + def __init__(self, parent, imu_status): + tk.Frame.__init__(self, parent, borderwidth=1, relief="flat") + label = Label(self, text="IMU Config") + self.imu_status = imu_status + self.time = LimitedQueue(4) + self.time.enqueue(0) + # gyr + self.gyr_x = LimitedQueue(4) + self.gyr_y = LimitedQueue(4) + self.gyr_z = LimitedQueue(4) + # rot + self.rot_x = LimitedQueue(4) + self.rot_y = LimitedQueue(4) + self.rot_z = LimitedQueue(4) + # acc + self.acc_x = LimitedQueue(4) + self.acc_y = LimitedQueue(4) + self.acc_z = LimitedQueue(4) + # mag + self.mag_x = LimitedQueue(4) + self.mag_y = LimitedQueue(4) + self.mag_z = LimitedQueue(4) + + # Create the layout frames + self.top_frame = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + self.table = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + self.top_frame.grid(row=0, column=0, sticky='nsew') + self.table.grid(row=1, column=0, sticky='nsew') + + self.initUI() + + def initUI(self): + info = ["acc_fsr (g): 16", "gyr_fsr (dps): 2000", "datarate: 50"] + + title = tk.Label(self.top_frame, text='IMU Data', font=tkFont.Font(size=10, weight="bold")) + title.grid(row=0, column=0, columnspan=2, sticky='w') + # Create information header + for i, text in enumerate(info): + label = tk.Label(self.top_frame, text=text, bg='lightgrey') + label.grid(row=i+1, column=0, columnspan=2, sticky='w') + # Create table + # gyr + self.gyr_x.enqueue(self.imu_status.gyr_x) + self.gyr_y.enqueue(self.imu_status.gyr_y) + self.gyr_z.enqueue(self.imu_status.gyr_z) + label = tk.Label(self.table, text="gyr (dps vs time)", relief=tk.RIDGE) + label.grid(row=0, column=0, sticky='nsew') + line_chart = MatplolibFrame(self.table, time=self.time.queue, x=self.gyr_x.queue, y=self.gyr_y.queue, z=self.gyr_z.queue) + line_chart.grid(row=1, column=0) + # rot + self.rot_x.enqueue(self.imu_status.rot_x) + self.rot_y.enqueue(self.imu_status.rot_y) + self.rot_z.enqueue(self.imu_status.rot_z) + label = tk.Label(self.table, text="rot (rad/s vs time)", relief=tk.RIDGE) + label.grid(row=0, column=1, sticky='nsew') + line_chart = MatplolibFrame(self.table, time=self.time.queue, x=self.rot_x.queue, y=self.rot_y.queue, z=self.rot_z.queue) + line_chart.grid(row=1, column=1) + # acc + self.acc_x.enqueue(self.imu_status.acc_x) + self.acc_y.enqueue(self.imu_status.acc_y) + self.acc_z.enqueue(self.imu_status.acc_z) + label = tk.Label(self.table, text="acc (g vs time)", relief=tk.RIDGE) + label.grid(row=2, column=0, sticky='nsew') + line_chart = MatplolibFrame(self.table, time=self.time.queue, x=self.acc_x.queue, y=self.acc_y.queue, z=self.acc_z.queue) + line_chart.grid(row=3, column=0) + # mag + self.mag_x.enqueue(self.imu_status.mag_x) + self.mag_y.enqueue(self.imu_status.mag_y) + self.mag_z.enqueue(self.imu_status.mag_z) + label = tk.Label(self.table, text="mag (uT vs time)", relief=tk.RIDGE) + label.grid(row=2, column=1, sticky='nsew') + line_chart = MatplolibFrame(self.table, time=self.time.queue, x=self.mag_x.queue, y=self.mag_y.queue, z=self.mag_z.queue) + line_chart.grid(row=3, column=1) + + self.config(bg="white", relief="solid") + +class MicConfig(tk.Frame): + def __init__(self, parent, data, info): + tk.Frame.__init__(self, parent, borderwidth=1, relief="flat") + self.data = data + self.info = info + self.initUI() + + def initUI(self): + # Create the layout frames + top_frame = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + table = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + top_frame.grid(row=0, column=0, sticky='nsew') + table.grid(row=1, column=0, sticky='nsew') + + title = tk.Label(top_frame, text='Microphones Data', font=tkFont.Font(size=10, weight="bold")) + title.grid(row=0, column=0, columnspan=2, sticky='w') + # Loop over the dictionary and create labels + for i, (key, value) in enumerate(self.info.items()): + label_text = "{}: {}".format(key, value) + label = tk.Label(top_frame, text=label_text) + label.grid(row=i+1, column=0, columnspan=2, sticky='w') + + for i, e in enumerate(self.data): + label = tk.Label(table, text=str(e), relief=tk.RIDGE, width=45) + label.grid(row=i, column=0, sticky="nsew") + + # Configure row and column weights to make the cells expand + for i in range(len(self.data)): + self.grid_rowconfigure(i, weight=1) + + self.config(bg="white", relief="solid") + +# Scan +class ScanConfig(tk.Frame): + def __init__(self, parent, data, info): + tk.Frame.__init__(self, parent, borderwidth=1, relief="flat") + self.data = data + self.info = info + self.initUI() + + def initUI(self): + # Create the layout frames + top_frame = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + table = tk.Frame(self, padx=5, pady=5, bg='lightgrey') + top_frame.grid(row=0, column=0, sticky='nsew') + table.grid(row=1, column=0, sticky='nsew') + + title = tk.Label(top_frame, text='Scanner Data', font=tkFont.Font(size=10, weight="bold")) + title.grid(row=0, column=0, columnspan=2, sticky='w') + # Loop over the dictionary and create labels + for i, (key, value) in enumerate(self.info.items()): + label_text = "{}: {}".format(key, value) + label = tk.Label(top_frame, text=label_text) + label.grid(row=i+1, column=0, columnspan=2, sticky='w') + + table_title = tk.Label(table, text='RSSI', font=tkFont.Font( weight="bold"), relief=tk.RIDGE, width=45) + table_title.grid(row=0, column=0, sticky="nsew") + for i, e in enumerate(self.data): + label = tk.Label(table, text=str(e), relief=tk.RIDGE, width=45) + label.grid(row=i+1, column=0, sticky="nsew") + + # Configure row and column weights to make the cells expand + for i in range(len(self.data)): + self.grid_rowconfigure(i, weight=1) + + self.config(bg="white", relief="solid") + +class LimitedQueue: + def __init__(self, max_size): + self.queue = [] + self.max_size = max_size + + def enqueue(self, item): + if len(self.queue) >= self.max_size: + self.queue.pop(0) + self.queue.append(item) + + def dequeue(self): + return self.queue.pop(0) if self.queue else None + + def __repr__(self): + return str(self.queue) + + +class NewWindow(tk.Toplevel): + def __init__(self, badge, free_space, name, scan_info, mic_info): + GET_STATUS_MAIN = False + tk.Toplevel.__init__(self) + self.name = name + self.title("New Window for badge: {}".format(name)) + self.geometry("1500x700") + self.badge = badge + self.mic_data = LimitedQueue(10) + self.scan_data = LimitedQueue(7) + self.status = self.badge.get_status() + self.mic_data.enqueue(self.status.pdm_data) + self.scan_data.enqueue(self.status.scan_data) + self.free_space = free_space + self.imu_status = self.badge.get_imu_data() + self.time = 0 + self.scan_info = scan_info + self.mic_info = mic_info + self.initUI() + self.after(1000, self.update_data) + + def initUI(self): + # Create the layout frames + left_top = tk.Frame(self, padx=5, pady=5) + right_top = tk.Frame(self, padx=5, pady=5) + left_down = tk.Frame(self, padx=5, pady=5) + right_down = tk.Frame(self, padx=5, pady=5) + left_top.grid(row=0, column=0, sticky='nsew') + right_top.grid(row=0, column=1, sticky='nsew') + left_down.grid(row=1, column=0, sticky='nsew') + right_down.grid(row=1, column=1, sticky='nsew') + + # header + self.badgeId = Label(left_top, text="Badge Id: {}".format(self.name), font=tkFont.Font(size=10, )) + self.badgeId.grid(row=0, column=0, columnspan=2, sticky='w') + self.midge = Label(left_top, text="Battery: {}%".format(self.status.battery_level), font=tkFont.Font(size=10)) + self.midge.grid(row=1, column=0, columnspan=2, sticky='w') + self.battery = Label(left_top, text="Available memory: {}MB".format(self.free_space), font=tkFont.Font(size=10)) + self.battery.grid(row=2, column=0, columnspan=2, sticky='w') + self.reset_battery = Button(left_top, text='Erase', command=self.badge.sdc_errase_all) + self.reset_battery.grid(row=2, column=1, columnspan=2, sticky='w') + + # IMU + self.IMULabel = Label(left_top, text="IMU") + self.IMULabel.grid(row=4, column=0, padx=10, pady=5) + self.IMUCircle = Circle(left_top, radius=10, status=self.status.imu_status) + self.IMUCircle.grid(row=4, column=1, padx=10, pady=5) + self.IMUConfig = IMUConfig(left_top, imu_status=self.imu_status) + self.IMUConfig.grid(row=5, column=0, columnspan=2, padx=10, pady=5) + + # Scan + self.ScanLabel = Label(right_top, text="Scanner") + self.ScanLabel.grid(row=1, column=2, padx=10, pady=5) + self.ScanCircle = Circle(right_top, radius=10, status=self.status.scan_status) + self.ScanCircle.grid(row=1, column=3, padx=10, pady=5) + self.ScanConfig = ScanConfig(right_top, data=self.scan_data.queue, info=self.scan_info) + self.ScanConfig.grid(row=2, column=2, columnspan=2, padx=10, pady=5) + + # Mic + self.MicLabel = Label(right_top, text="Microphones") + self.MicLabel.grid(row=3, column=2, padx=10, pady=5) + self.MicCircle = Circle(right_top, radius=10, status=self.status.microphone_status) + self.MicCircle.grid(row=3, column=3, padx=10, pady=5) + self.MicConfig = MicConfig(right_top, data=self.mic_data.queue, info=self.mic_info) + self.MicConfig.grid(row=4, column=2, columnspan=2, padx=10, pady=5) + + def update_data(self): + self.time = self.time + 1 + try: + status = self.badge.get_status() + time.sleep(0.1) + imu_status = self.badge.get_imu_data() + time.sleep(0.1) + if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): + self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) + except: + print('Error with request') + self.badge.connect() + status = self.badge.get_status() + time.sleep(0.1) + imu_status = self.badge.get_imu_data() + time.sleep(0.1) + if (status.microphone_status == 0 and status.scan_status == 0 and status.imu_status == 0): + self.battery.text="Available memory: {} MB".format(self.badge.get_free_sdc_space().free_space) + + if status.microphone_status == 0: + self.MicConfig.data = [] + self.MicConfig.initUI() + else: + pdm_data = status.pdm_data + # update mic + self.mic_data.enqueue(pdm_data) + self.MicConfig.data = self.mic_data.queue + self.MicConfig.initUI() + if status.scan_status == 0: + self.ScanConfig.data = [] + self.ScanConfig.initUI() + else: + scan_data = status.scan_data + # update scan + self.scan_data.enqueue(scan_data) + self.ScanConfig.data = self.scan_data.queue + self.ScanConfig.initUI() + #update imu + self.IMUConfig.imu_status = imu_status + self.IMUConfig.time.enqueue(self.time) + self.IMUConfig.initUI() + # update circles + self.MicCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.microphone_status)) + self.IMUCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.imu_status)) + self.ScanCircle.create_oval(0, 0, 2 * 10, 2 * 10, fill=calculate_color(status.scan_status)) + + self.after(5500, self.update_data) + +def get_badges(): + badges = [] + with open('sample_mapping_file.csv', 'r') as f: + reader = csv.reader(f) + next(reader) + for row in reader: + badge = BadgeInterface(row[1]) + badge.connect() + badges.append({'badge': badge, 'name': row[0], 'address': row[1]}) + return badges + +class MainApp(tk.Tk): + def __init__(self): + tk.Tk.__init__(self) + self.title("Lista de dispositivos") + self.geometry("1100x700") + self.container_frame = ttk.Frame(self) + self.container_frame.pack(expand=True, fill="both") + self.custom_components = [] + self.badges = get_badges() + + for badge in self.badges: + custom_component = CustomComponent(self.container_frame, name=badge['name'], badge=badge['badge'], address=badge['address']) + custom_component.pack() + # Draw a border between custom components + border = tk.Frame(self.container_frame, bg="gray", height=1) + border.pack(fill="x") + self.custom_components.append(custom_component) + + self.after(1000, self.update_data) + + def update_data(self): + for badge, custom_component in zip(self.badges, self.custom_components): + try: + badge_status = badge['badge'].get_status() + except: + print('Error with request') + try: + badge['badge'].connect() + except: + badge['badge'].connect() + badge_status = badge['badge'].get_status() + custom_component.badge_status = badge_status + custom_component.battery['text'] = 'Battery: {}%'.format(badge_status.battery_level) + custom_component.statusCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.clock_status)) + custom_component.MicCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.microphone_status)) + custom_component.IMUCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.imu_status)) + custom_component.ScanCircle.create_oval(0, 0, 2 * DEFAULT_RADIUS, 2 * DEFAULT_RADIUS, fill=calculate_color(badge_status.scan_status)) + if (badge_status.microphone_status == 0): + custom_component.MicStartMonoButton['state'] = 'normal' + custom_component.MicStartStereoButton['state'] = 'normal' + custom_component.MicStopButton['state'] = 'disabled' + else: + custom_component.MicStartMonoButton['state'] = 'disabled' + custom_component.MicStartStereoButton['state'] = 'disabled' + custom_component.MicStopButton['state'] = 'normal' + if (badge_status.imu_status == 0): + custom_component.IMUStartButton['state'] = 'normal' + custom_component.IMUStopButton['state'] = 'disabled' + else: + custom_component.IMUStartButton['state'] = 'disabled' + custom_component.IMUStopButton['state'] = 'normal' + if (badge_status.scan_status == 0): + custom_component.ScanStartButton['state'] = 'normal' + custom_component.ScanStopButton['state'] = 'disabled' + else: + custom_component.ScanStartButton['state'] = 'disabled' + custom_component.ScanStopButton['state'] = 'normal' + + if (GET_STATUS_MAIN == True): + print('updating main') + self.after(1000, self.update_data) + +if __name__ == '__main__': + app = MainApp() + app.mainloop() \ No newline at end of file diff --git a/BadgeFramework/test.py b/BadgeFramework/test.py index b66c939..db1cb2e 100644 --- a/BadgeFramework/test.py +++ b/BadgeFramework/test.py @@ -6,7 +6,7 @@ from datetime import datetime import utils from badge import OpenBadge -from bleak import BleakScanner +from bleak import BleakScanner, BleakClient import asyncio @@ -235,5 +235,31 @@ async def main(): print("errase memory error") +async def test_client(): + for i in range(10): + try: + # Measure scanning time + scan_start = time.time() + device = await BleakScanner.find_device_by_address("de:94:80:39:25:be", timeout=10) + scan_time = time.time() - scan_start + + if device is None: + print("Device not found during scan") + continue + + # Measure connection time + print(f"Found device in {scan_time:.2f} seconds") + connect_start = time.time() + async with BleakClient(device, timeout=10) as client: + connect_time = time.time() - connect_start + print(f"Connected in {connect_time:.2f} seconds!") + + # await client.start_notify(utils.RX_CHAR_UUID, callback=lambda sender, data: print(f"Received data: {data}"), timeout=10) + except Exception as e: + total_time = time.time() - scan_start + print(f"Error after {total_time:.2f} seconds: {str(e)}") + + if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + # asyncio.run(main()) + asyncio.run(test_client()) \ No newline at end of file