From 76e406ec95a970f5bf09ef4c3a4c67eebebaaa57 Mon Sep 17 00:00:00 2001 From: Vladimir Roncevic Date: Tue, 1 Sep 2026 09:00:18 +0200 Subject: [PATCH] [abcomm] Updated general docs --- README.md | 145 +++++++++++++++--------- docs/source/index.rst | 145 +++++++++++++++--------- scripts/ble/README.md | 73 ++++++++++++ scripts/ble/ble_listen.sh | 19 ++++ scripts/wifi/README.md | 35 ++++++ scripts/wifi/wifi_server.py | 220 ++++++++++++++++++++++++++++++++++++ 6 files changed, 531 insertions(+), 106 deletions(-) create mode 100644 scripts/ble/README.md create mode 100755 scripts/ble/ble_listen.sh create mode 100644 scripts/wifi/README.md create mode 100644 scripts/wifi/wifi_server.py diff --git a/README.md b/README.md index 5034a49..8a758a3 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ The application features a Cyberpunk-styled interface supporting dual-mode conne - [Run Unit Tests](#run-unit-tests) - [πŸ“¦ Dependencies & Permissions](#-dependencies--permissions) - [πŸ“ Project Architecture](#-project-architecture) -- [πŸ›  Usage Guide](#-usage-guide) +- [πŸ›  Usage & Hardware Emulation Guide](#-usage--hardware-emulation-guide) - [Bluetooth (BLE / RFCOMM) Mode](#bluetooth-ble--rfcomm-mode) - [Wi-Fi (TCP Socket) Mode](#wi-fi-tcp-socket-mode) - - [Testing with Python Mock Server](#testing-with-python-mock-server) + - [Testing Bluetooth with Linux Laptop](#testing-bluetooth-with-linux-laptop) + - [Testing Wi-Fi with Python Mock Server](#testing-wi-fi-with-python-mock-server) - [πŸ‘₯ Contributing](#-contributing) - [πŸ“„ License](#-license) @@ -43,6 +44,7 @@ The application features a Cyberpunk-styled interface supporting dual-mode conne * **Manual Sync & Device Reboot**: Dedicated **SYNC** button for manual state refreshing and **RESET** button with a confirmation dialog. * **Robust Disconnection Handling**: Immediate socket cleanup and automatic UI state reset to `OFF` when the device disconnects or powers down. * **Clean Architecture**: 100% Type-Safe (`ConnectionStatus`, `DeviceResponse`), Dependency Inversion (DIP), Open/Closed (OCP) response matchers, and Coroutine-based background I/O (`Dispatchers.IO`). +* **Hardware-Free Testing Scripts**: Ready-to-use scripts in `scripts/` to emulate both Bluetooth SPP and Wi-Fi TCP servers from a laptop without physical Pico hardware. --- @@ -106,59 +108,77 @@ The app declares and dynamically requests appropriate permissions: The codebase strictly follows the **Single Type per File** and **SOLID** principles, organized into domain packages: ```bash -app/src/main/java/com/abcomm/ -β”œβ”€β”€ protocol/ -β”‚ β”œβ”€β”€ MicrohilProtocolConstants.kt # Protocol frame delimiters and command keywords -β”‚ β”œβ”€β”€ CommandFormatter.kt # Contract for outbound command formatting -β”‚ β”œβ”€β”€ MicrohilCommandFormatter.kt # Implementation of CommandFormatter -β”‚ β”œβ”€β”€ FrameParser.kt # Stream framing contract (<...>) -β”‚ β”œβ”€β”€ MicrohilFrameParser.kt # Chunked stream frame extractor -β”‚ β”œβ”€β”€ DeviceResponse.kt # Sealed interface for typed device responses -β”‚ β”œβ”€β”€ ResponseParser.kt # Response parsing contract -β”‚ β”œβ”€β”€ ResponseMatcher.kt # Extensible response matcher interface (OCP) -β”‚ β”œβ”€β”€ MicrohilResponseParser.kt # ResponseParser delegating to matcher list -β”‚ └── matchers/ # Individual pattern matchers for each response -β”‚ β”œβ”€β”€ ChannelStateMatcher.kt -β”‚ β”œβ”€β”€ AllChannelsStateMatcher.kt -β”‚ β”œβ”€β”€ AllChannelsSnapshotMatcher.kt -β”‚ β”œβ”€β”€ MaskAppliedMatcher.kt -β”‚ β”œβ”€β”€ BoardIdMatcher.kt -β”‚ β”œβ”€β”€ FirmwareVersionMatcher.kt -β”‚ └── SystemResettingMatcher.kt +abcomm/ +β”œβ”€β”€ app/ +β”‚ └── src/ +β”‚ β”œβ”€β”€ main/java/com/abcomm/ +β”‚ β”‚ β”œβ”€β”€ protocol/ +β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilProtocolConstants.kt # Delimiters and command keywords +β”‚ β”‚ β”‚ β”œβ”€β”€ CommandFormatter.kt # Outbound formatting contract +β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilCommandFormatter.kt # Implementation of CommandFormatter +β”‚ β”‚ β”‚ β”œβ”€β”€ FrameParser.kt # Stream framing contract (<...>) +β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilFrameParser.kt # Chunked stream extractor +β”‚ β”‚ β”‚ β”œβ”€β”€ DeviceResponse.kt # Typed device response model +β”‚ β”‚ β”‚ β”œβ”€β”€ ResponseParser.kt # Response parser contract +β”‚ β”‚ β”‚ β”œβ”€β”€ ResponseMatcher.kt # Response matcher interface (OCP) +β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilResponseParser.kt # Parser delegating to matchers +β”‚ β”‚ β”‚ └── matchers/ # Individual pattern matchers +β”‚ β”‚ β”‚ β”œβ”€β”€ ChannelStateMatcher.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ AllChannelsStateMatcher.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ AllChannelsSnapshotMatcher.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ MaskAppliedMatcher.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ BoardIdMatcher.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ FirmwareVersionMatcher.kt +β”‚ β”‚ β”‚ └── SystemResettingMatcher.kt +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ communication/ +β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionMode.kt # Enum: BLE, WIFI +β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionTarget.kt # Sealed: Bluetooth, Wifi +β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionStatus.kt # Sealed: Disconnected, Connecting, Connected, Error +β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionController.kt # Lifecycle contract +β”‚ β”‚ β”‚ β”œβ”€β”€ CommandSender.kt # Dispatch contract +β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionObservable.kt # Observer contract +β”‚ β”‚ β”‚ β”œβ”€β”€ CommunicationProvider.kt # Composite provider contract +β”‚ β”‚ β”‚ β”œβ”€β”€ CommunicationProviderRegistry.kt # Provider registry contract +β”‚ β”‚ β”‚ β”œβ”€β”€ DefaultCommunicationProviderRegistry.kt +β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothService.kt # RFCOMM provider (Coroutines / Dispatchers.IO) +β”‚ β”‚ β”‚ └── WifiService.kt # TCP Socket provider (Coroutines / Dispatchers.IO) +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ settings/ +β”‚ β”‚ β”‚ β”œβ”€β”€ AppSettings.kt # Config data model & port boundaries +β”‚ β”‚ β”‚ β”œβ”€β”€ AppSettingsRepository.kt # Storage contract +β”‚ β”‚ β”‚ └── SharedPreferencesSettingsRepository.kt +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ ui/ +β”‚ β”‚ β”‚ β”œβ”€β”€ MainUiState.kt # Immutable UI State model +β”‚ β”‚ β”‚ β”œβ”€β”€ MainViewModel.kt # ViewModel state machine +β”‚ β”‚ β”‚ β”œβ”€β”€ MainViewModelFactory.kt # Dependency injection factory +β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothPermissionChecker.kt # Permission checker interface +β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothPermissionHelper.kt # SDK version-aware helper +β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothDeviceProvider.kt # Bluetooth adapter interface +β”‚ β”‚ β”‚ └── BluetoothDeviceManager.kt # Paired device manager +β”‚ β”‚ β”‚ +β”‚ β”‚ └── MainActivity.kt # Primary Android Activity view layer +β”‚ β”‚ +β”‚ └── test/java/com/abcomm/ # Complete MockK Unit Test Suite β”‚ -β”œβ”€β”€ communication/ -β”‚ β”œβ”€β”€ ConnectionMode.kt # Enum: BLE, WIFI -β”‚ β”œβ”€β”€ ConnectionTarget.kt # Sealed interface: Bluetooth(device), Wifi(host, port) -β”‚ β”œβ”€β”€ ConnectionStatus.kt # Sealed interface: Disconnected, Connecting, Connected, Error -β”‚ β”œβ”€β”€ ConnectionController.kt # Lifecycle management contract -β”‚ β”œβ”€β”€ CommandSender.kt # Command dispatch contract -β”‚ β”œβ”€β”€ ConnectionObservable.kt # Status and response observer contract -β”‚ β”œβ”€β”€ CommunicationProvider.kt # Composite provider interface -β”‚ β”œβ”€β”€ CommunicationProviderRegistry.kt # Dynamic provider resolution contract -β”‚ β”œβ”€β”€ DefaultCommunicationProviderRegistry.kt -β”‚ β”œβ”€β”€ BluetoothService.kt # RFCOMM Bluetooth provider (Coroutines / Dispatchers.IO) -β”‚ └── WifiService.kt # TCP Socket Wi-Fi provider (Coroutines / Dispatchers.IO) +β”œβ”€β”€ docs/ # Sphinx / ReadTheDocs Documentation +β”‚ └── source/ +β”‚ β”œβ”€β”€ conf.py +β”‚ └── index.rst β”‚ -β”œβ”€β”€ settings/ -β”‚ β”œβ”€β”€ AppSettings.kt # Configuration data model and port boundaries -β”‚ β”œβ”€β”€ AppSettingsRepository.kt # Storage abstraction contract -β”‚ └── SharedPreferencesSettingsRepository.kt -β”‚ -β”œβ”€β”€ ui/ -β”‚ β”œβ”€β”€ MainUiState.kt # Immutable UI State data model -β”‚ β”œβ”€β”€ MainViewModel.kt # State machine orchestrating UI & hardware -β”‚ β”œβ”€β”€ MainViewModelFactory.kt # Dependency injection factory -β”‚ β”œβ”€β”€ BluetoothPermissionChecker.kt # Permission checker interface -β”‚ β”œβ”€β”€ BluetoothPermissionHelper.kt # Android SDK version-aware permission helper -β”‚ β”œβ”€β”€ BluetoothDeviceProvider.kt # Bluetooth adapter abstraction interface -β”‚ └── BluetoothDeviceManager.kt # Paired device manager -β”‚ -└── MainActivity.kt # Primary Android Activity view layer +└── scripts/ # Hardware Emulation & Testing Scripts + β”œβ”€β”€ ble/ + β”‚ β”œβ”€β”€ ble_listen.sh # Linux RFCOMM SPP sniffer/server script + β”‚ └── README.md # Bluetooth test setup guide + └── wifi/ + β”œβ”€β”€ wifi_server.py # Python TCP microHIL mock server + └── README.md # Wi-Fi test setup guide ``` --- -## πŸ›  Usage Guide +## πŸ›  Usage & Hardware Emulation Guide ### Bluetooth (BLE / RFCOMM) Mode @@ -175,16 +195,35 @@ app/src/main/java/com/abcomm/ 3. Tap **CONNECT**. 4. Telemetry and relay buttons will update automatically upon connection. -### Testing with Python Mock Server +### Testing Bluetooth with Linux Laptop + +To test Bluetooth connectivity without physical Raspberry Pi Pico hardware, configure a Linux (Ubuntu) laptop as an RFCOMM server: + +```bash +# In Terminal A on Ubuntu: +chmod +x scripts/ble/ble_listen.sh +./scripts/ble/ble_listen.sh + +# In Terminal B (to monitor commands sent from phone): +sudo cat /dev/rfcomm10 +``` + +Refer to [`scripts/ble/README.md`](scripts/ble/README.md) for full Bluetooth pairing and compatibility instructions. -You can test Wi-Fi communication without physical hardware using the included mock server: +### Testing Wi-Fi with Python Mock Server + +To test Wi-Fi communication without physical hardware, run the Python mock server: ```bash # Run the mock server from the repository root -python3 wifi/wifi_server.py +python3 scripts/wifi/wifi_server.py --port 5000 ``` -The mock server binds to `0.0.0.0:5000` and emulates real microHIL firmware behavior (board ID, version, channel toggling, and snapshots). +1. The script will print the laptop's local IP address (e.g. `192.168.1.150`). +2. In the ABComm app, switch to **WIFI** mode, enter the printed IP and port `5000`, and tap **CONNECT**. +3. All button presses will update real-time terminal logs and reflect microHIL firmware behavior. + +Refer to [`scripts/wifi/README.md`](scripts/wifi/README.md) for further details. --- diff --git a/docs/source/index.rst b/docs/source/index.rst index 56dd7b9..b703b09 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -46,6 +46,7 @@ Table of Contents * **Manual Sync & Device Reboot**: Dedicated **SYNC** button for manual state refreshing and **RESET** button with a confirmation dialog. * **Robust Disconnection Handling**: Immediate socket cleanup and automatic UI state reset to ``OFF`` when the device disconnects or powers down. * **Clean Architecture**: 100% Type-Safe (``ConnectionStatus``, ``DeviceResponse``), Dependency Inversion (DIP), Open/Closed (OCP) response matchers, and Coroutine-based background I/O (``Dispatchers.IO``). +* **Hardware-Free Testing Scripts**: Ready-to-use scripts in ``scripts/`` to emulate both Bluetooth SPP and Wi-Fi TCP servers from a laptop without physical Pico hardware. πŸ“‘ microHIL Communication Protocol ================================== @@ -131,57 +132,75 @@ The codebase strictly follows the **Single Type per File** and **SOLID** princip .. code-block:: text - app/src/main/java/com/abcomm/ - β”œβ”€β”€ protocol/ - β”‚ β”œβ”€β”€ MicrohilProtocolConstants.kt # Protocol frame delimiters and command keywords - β”‚ β”œβ”€β”€ CommandFormatter.kt # Contract for outbound command formatting - β”‚ β”œβ”€β”€ MicrohilCommandFormatter.kt # Implementation of CommandFormatter - β”‚ β”œβ”€β”€ FrameParser.kt # Stream framing contract (<...>) - β”‚ β”œβ”€β”€ MicrohilFrameParser.kt # Chunked stream frame extractor - β”‚ β”œβ”€β”€ DeviceResponse.kt # Sealed interface for typed device responses - β”‚ β”œβ”€β”€ ResponseParser.kt # Response parsing contract - β”‚ β”œβ”€β”€ ResponseMatcher.kt # Extensible response matcher interface (OCP) - β”‚ β”œβ”€β”€ MicrohilResponseParser.kt # ResponseParser delegating to matcher list - β”‚ └── matchers/ # Individual pattern matchers for each response - β”‚ β”œβ”€β”€ ChannelStateMatcher.kt - β”‚ β”œβ”€β”€ AllChannelsStateMatcher.kt - β”‚ β”œβ”€β”€ AllChannelsSnapshotMatcher.kt - β”‚ β”œβ”€β”€ MaskAppliedMatcher.kt - β”‚ β”œβ”€β”€ BoardIdMatcher.kt - β”‚ β”œβ”€β”€ FirmwareVersionMatcher.kt - β”‚ └── SystemResettingMatcher.kt + abcomm/ + β”œβ”€β”€ app/ + β”‚ └── src/ + β”‚ β”œβ”€β”€ main/java/com/abcomm/ + β”‚ β”‚ β”œβ”€β”€ protocol/ + β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilProtocolConstants.kt # Delimiters and command keywords + β”‚ β”‚ β”‚ β”œβ”€β”€ CommandFormatter.kt # Outbound formatting contract + β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilCommandFormatter.kt # Implementation of CommandFormatter + β”‚ β”‚ β”‚ β”œβ”€β”€ FrameParser.kt # Stream framing contract (<...>) + β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilFrameParser.kt # Chunked stream extractor + β”‚ β”‚ β”‚ β”œβ”€β”€ DeviceResponse.kt # Typed device response model + β”‚ β”‚ β”‚ β”œβ”€β”€ ResponseParser.kt # Response parser contract + β”‚ β”‚ β”‚ β”œβ”€β”€ ResponseMatcher.kt # Response matcher interface (OCP) + β”‚ β”‚ β”‚ β”œβ”€β”€ MicrohilResponseParser.kt # Parser delegating to matchers + β”‚ β”‚ β”‚ └── matchers/ # Individual pattern matchers + β”‚ β”‚ β”‚ β”œβ”€β”€ ChannelStateMatcher.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ AllChannelsStateMatcher.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ AllChannelsSnapshotMatcher.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ MaskAppliedMatcher.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ BoardIdMatcher.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ FirmwareVersionMatcher.kt + β”‚ β”‚ β”‚ └── SystemResettingMatcher.kt + β”‚ β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€ communication/ + β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionMode.kt # Enum: BLE, WIFI + β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionTarget.kt # Sealed: Bluetooth, Wifi + β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionStatus.kt # Sealed: Disconnected, Connecting, Connected, Error + β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionController.kt # Lifecycle contract + β”‚ β”‚ β”‚ β”œβ”€β”€ CommandSender.kt # Dispatch contract + β”‚ β”‚ β”‚ β”œβ”€β”€ ConnectionObservable.kt # Observer contract + β”‚ β”‚ β”‚ β”œβ”€β”€ CommunicationProvider.kt # Composite provider contract + β”‚ β”‚ β”‚ β”œβ”€β”€ CommunicationProviderRegistry.kt # Provider registry contract + β”‚ β”‚ β”‚ β”œβ”€β”€ DefaultCommunicationProviderRegistry.kt + β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothService.kt # RFCOMM provider (Coroutines / Dispatchers.IO) + β”‚ β”‚ β”‚ └── WifiService.kt # TCP Socket provider (Coroutines / Dispatchers.IO) + β”‚ β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€ settings/ + β”‚ β”‚ β”‚ β”œβ”€β”€ AppSettings.kt # Config data model & port boundaries + β”‚ β”‚ β”‚ β”œβ”€β”€ AppSettingsRepository.kt # Storage contract + β”‚ β”‚ β”‚ └── SharedPreferencesSettingsRepository.kt + β”‚ β”‚ β”‚ + β”‚ β”‚ β”œβ”€β”€ ui/ + β”‚ β”‚ β”‚ β”œβ”€β”€ MainUiState.kt # Immutable UI State model + β”‚ β”‚ β”‚ β”œβ”€β”€ MainViewModel.kt # ViewModel state machine + β”‚ β”‚ β”‚ β”œβ”€β”€ MainViewModelFactory.kt # Dependency injection factory + β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothPermissionChecker.kt # Permission checker interface + β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothPermissionHelper.kt # SDK version-aware helper + β”‚ β”‚ β”‚ β”œβ”€β”€ BluetoothDeviceProvider.kt # Bluetooth adapter interface + β”‚ β”‚ β”‚ └── BluetoothDeviceManager.kt # Paired device manager + β”‚ β”‚ β”‚ + β”‚ β”‚ └── MainActivity.kt # Primary Android Activity view layer + β”‚ β”‚ + β”‚ └── test/java/com/abcomm/ # Complete MockK Unit Test Suite β”‚ - β”œβ”€β”€ communication/ - β”‚ β”œβ”€β”€ ConnectionMode.kt # Enum: BLE, WIFI - β”‚ β”œβ”€β”€ ConnectionTarget.kt # Sealed interface: Bluetooth(device), Wifi(host, port) - β”‚ β”œβ”€β”€ ConnectionStatus.kt # Sealed interface: Disconnected, Connecting, Connected, Error - β”‚ β”œβ”€β”€ ConnectionController.kt # Lifecycle management contract - β”‚ β”œβ”€β”€ CommandSender.kt # Command dispatch contract - β”‚ β”œβ”€β”€ ConnectionObservable.kt # Status and response observer contract - β”‚ β”œβ”€β”€ CommunicationProvider.kt # Composite provider interface - β”‚ β”œβ”€β”€ CommunicationProviderRegistry.kt # Dynamic provider resolution contract - β”‚ β”œβ”€β”€ DefaultCommunicationProviderRegistry.kt - β”‚ β”œβ”€β”€ BluetoothService.kt # RFCOMM Bluetooth provider (Coroutines / Dispatchers.IO) - β”‚ └── WifiService.kt # TCP Socket Wi-Fi provider (Coroutines / Dispatchers.IO) + β”œβ”€β”€ docs/ # Sphinx / ReadTheDocs Documentation + β”‚ └── source/ + β”‚ β”œβ”€β”€ conf.py + β”‚ └── index.rst β”‚ - β”œβ”€β”€ settings/ - β”‚ β”œβ”€β”€ AppSettings.kt # Configuration data model and port boundaries - β”‚ β”œβ”€β”€ AppSettingsRepository.kt # Storage abstraction contract - β”‚ └── SharedPreferencesSettingsRepository.kt - β”‚ - β”œβ”€β”€ ui/ - β”‚ β”œβ”€β”€ MainUiState.kt # Immutable UI State data model - β”‚ β”œβ”€β”€ MainViewModel.kt # State machine orchestrating UI & hardware - β”‚ β”œβ”€β”€ MainViewModelFactory.kt # Dependency injection factory - β”‚ β”œβ”€β”€ BluetoothPermissionChecker.kt # Permission checker interface - β”‚ β”œβ”€β”€ BluetoothPermissionHelper.kt # Android SDK version-aware permission helper - β”‚ β”œβ”€β”€ BluetoothDeviceProvider.kt # Bluetooth adapter abstraction interface - β”‚ └── BluetoothDeviceManager.kt # Paired device manager - β”‚ - └── MainActivity.kt # Primary Android Activity view layer + └── scripts/ # Hardware Emulation & Testing Scripts + β”œβ”€β”€ ble/ + β”‚ β”œβ”€β”€ ble_listen.sh # Linux RFCOMM SPP sniffer/server script + β”‚ └── README.md # Bluetooth test setup guide + └── wifi/ + β”œβ”€β”€ wifi_server.py # Python TCP microHIL mock server + └── README.md # Wi-Fi test setup guide -πŸ›  Usage Guide -============== +πŸ›  Usage & Hardware Emulation Guide +==================================== Bluetooth (BLE / RFCOMM) Mode ----------------------------- @@ -200,17 +219,37 @@ Wi-Fi (TCP Socket) Mode 3. Tap **CONNECT**. 4. Telemetry and relay buttons will update automatically upon connection. -Testing with Python Mock Server -------------------------------- +Testing Bluetooth with Linux Laptop +----------------------------------- + +To test Bluetooth connectivity without physical Raspberry Pi Pico hardware, configure a Linux (Ubuntu) laptop as an RFCOMM server: + +.. code-block:: bash + + # In Terminal A on Ubuntu: + chmod +x scripts/ble/ble_listen.sh + ./scripts/ble/ble_listen.sh + + # In Terminal B (to monitor commands sent from phone): + sudo cat /dev/rfcomm10 + +Refer to ``scripts/ble/README.md`` for full Bluetooth pairing and compatibility instructions. -You can test Wi-Fi communication without physical hardware using the included mock server: +Testing Wi-Fi with Python Mock Server +------------------------------------- + +To test Wi-Fi communication without physical hardware, run the Python mock server: .. code-block:: bash # Run the mock server from the repository root - python3 wifi/wifi_server.py + python3 scripts/wifi/wifi_server.py --port 5000 + +1. The script will print the laptop's local IP address (e.g. ``192.168.1.150``). +2. In the ABComm app, switch to **WIFI** mode, enter the printed IP and port ``5000``, and tap **CONNECT**. +3. All button presses will update real-time terminal logs and reflect microHIL firmware behavior. -The mock server binds to ``0.0.0.0:5000`` and emulates real microHIL firmware behavior (board ID, version, channel toggling, and snapshots). +Refer to ``scripts/wifi/README.md`` for further details. πŸ‘₯ Contributing =============== diff --git a/scripts/ble/README.md b/scripts/ble/README.md new file mode 100644 index 0000000..e85e42b --- /dev/null +++ b/scripts/ble/README.md @@ -0,0 +1,73 @@ +# Ubuntu 24.04 Bluetooth Serial Port Setup Guide + +This guide explains how to configure Ubuntu 24.04 to act as a Bluetooth Serial Port (SPP) server to receive data from the ABCommander app. + +## 1. Install Required Tools +```bash +sudo apt update +sudo apt install bluez bluez-tools +``` + +## 2. Enable Compatibility Mode (MANDATORY) +1. Edit the service: `sudo nano /lib/systemd/system/bluetooth.service` +2. Change `ExecStart` line to: `ExecStart=/usr/libexec/bluetooth/bluetoothd --compat` +3. Reload: `sudo systemctl daemon-reload && sudo systemctl restart bluetooth` +4. Permissions: `sudo chmod 777 /var/run/sdp` (repeat after restart). + +## 3. Disable Conflicting Services +Ubuntu's ModemManager often "hijacks" serial ports, causing "Address already in use" errors. +```bash +sudo systemctl stop ModemManager +``` + +## 4. The "Sniffer" Script (ble_listen.sh) +Create a script with these contents for a clean connection: +```bash +#!/bin/bash +echo "--- Resetting Bluetooth Stack ---" +sudo pkill -9 rfcomm +sudo rfcomm release all +sudo hciconfig hci0 down +sudo hciconfig hci0 up +sudo sdptool add --channel=4 SP +echo "Waiting for connection on CHANNEL 4..." +sudo rfcomm listen 10 4 # Uses /dev/rfcomm10 +``` + +--- + +## 5. Step-by-Step Operational Flow + +Follow these steps exactly to ensure a successful connection without "Address already in use" errors: + +### Phase 1: Server Preparation (Ubuntu) +1. **Stop ModemManager:** `sudo systemctl stop ModemManager` +2. **Run the Script:** Execute `./ble_listen.sh` in Terminal A. + - It should say: `Waiting for connection on channel 4`. + - **Do NOT** start the `cat` command yet. + +### Phase 2: App Connection (Android) +3. **Open App:** Launch ABCommander on your phone. +4. **Click Connect:** Select your Ubuntu laptop from the list. +5. **Verify Handshake:** + - Look at Terminal A (Ubuntu). It should change to: + `Connection from [MAC] to /dev/rfcomm10` + - The App status should change to: `Status: Connected to...` and the button should say **Disconnect**. + +### Phase 3: Data Sniffing +6. **Read Data:** Open **Terminal B** (New Tab) and run: + ```bash + sudo cat /dev/rfcomm10 + ``` +7. **Action:** Press buttons in the app. Commands like `1_ON` will appear in Terminal B. + +### Phase 4: Clean Disconnect +8. **Stop Sniffer:** In Terminal B, press `Ctrl+C` to stop `cat`. +9. **Disconnect App:** Click **Disconnect** on the phone. +10. **Reset Server:** In Terminal A, press `Ctrl+C` and run `sudo rfcomm release all` before the next test. + +--- + +## Troubleshooting +- **Address already in use:** Run `sudo rfcomm release all` and ensure no `cat` or `tail` processes are running on `/dev/rfcomm*`. +- **Send failed:** Usually means the physical connection is up but the TTY device (`/dev/rfcomm10`) failed to initialize. Restart the script. diff --git a/scripts/ble/ble_listen.sh b/scripts/ble/ble_listen.sh new file mode 100755 index 0000000..0857faa --- /dev/null +++ b/scripts/ble/ble_listen.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +echo "--- Debugging Bluetooth Connection ---" + +# 1. ČiΕ‘Δ‡enje +sudo pkill -9 rfcomm 2>/dev/null +sudo rfcomm release all 2>/dev/null +sudo systemctl stop ModemManager 2>/dev/null + +# 2. Reset adaptera (opciono ali pomaΕΎe) +sudo hciconfig hci0 down +sudo hciconfig hci0 up + +# 3. Dodaj Serial Port na Kanalu 4 (joΕ‘ dalje od uobičajenih portova) +sudo sdptool add --channel=4 SP + +echo "Listening on /dev/rfcomm10 (Channel 4)..." +# Koristimo rfcomm 10 i kanal 4 +sudo rfcomm listen 10 4 diff --git a/scripts/wifi/README.md b/scripts/wifi/README.md new file mode 100644 index 0000000..d4e5988 --- /dev/null +++ b/scripts/wifi/README.md @@ -0,0 +1,35 @@ +# WiFi TCP/IP Mock Server Guide for ABComm + +This directory contains the Python TCP server script (`wifi_server.py`) that emulates the Raspberry Pi Pico microHIL firmware protocol over WiFi (TCP socket). + +## Prerequisites + +- Python 3 installed on your computer. +- Computer and Android device connected to the same WiFi network (or local hotspot). + +## How to Run + +1. Open terminal in the project root. +2. Run the server: + ```bash + python3 scripts/wifi/wifi_server.py --port 5000 + ``` +3. The script will display its running IP addresses: + ```text + ============================================================ + microHIL WiFi TCP Mock Server Running + ============================================================ + Listening on: 0.0.0.0:5000 + Use one of these IP addresses in the ABComm app: + -> 192.168.1.150 (Port: 5000) + ============================================================ + Waiting for incoming connections... + ``` + +## Connecting from ABComm App + +1. Launch **ABComm** on your Android device. +2. Select **WiFi** mode in the connection selector. +3. Enter the IP address shown by `wifi_server.py` (e.g. `192.168.1.150`) and Port (`5000`). +4. Tap **CONNECT**. +5. Test toggling channels 1-8 or Master ON/OFF buttons. You will see command logs and responses printed in the terminal. diff --git a/scripts/wifi/wifi_server.py b/scripts/wifi/wifi_server.py new file mode 100644 index 0000000..9d50b5f --- /dev/null +++ b/scripts/wifi/wifi_server.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +""" +microHIL TCP/IP Mock Server for ABComm Android Application. + +Implements the Raspberry Pi Pico microHIL firmware protocol over TCP sockets. +Parses commands sent by ABComm and responds with system status messages. +""" + +import sys +import socket +import threading +import argparse + +# Default channel states for 8 channels (False = OFF, True = ON) +channels = [False] * 8 +MICROHIL_VERSION = "microHIL v1.0.0" +MICROHIL_BOARD_ID = "mh:333:2023:0" + +# ANSI Color codes for clean terminal debugging +COLOR_CYAN = "\033[96m" +COLOR_GREEN = "\033[92m" +COLOR_RED = "\033[91m" +COLOR_YELLOW = "\033[93m" +COLOR_RESET = "\033[0m" + + +def format_channel_status(): + status_parts = [] + for i, state in enumerate(channels, start=1): + status_parts.append(f"{i}:{'ON' if state else 'OFF'}") + return "channels: " + " ".join(status_parts) + + +def process_command(cmd_str): + """ + Parses a single microHIL command and returns the response string. + """ + cmd = cmd_str.strip() + if cmd.startswith("<"): + cmd = cmd[1:] + if cmd.endswith(">"): + cmd = cmd[:-1] + cmd = cmd.strip() + if not cmd: + return None + + print(f" {COLOR_YELLOW}[RECV]{COLOR_RESET} <{cmd}>") + + response = None + + # Channel control: mh#ch#<1..8>##end + if cmd.startswith("mh#ch#") and len(cmd) >= 12: + parts = cmd.split("#") + # Format: ["mh", "ch", "", "", "end"] + if len(parts) >= 5 and parts[1] == "ch" and parts[4] == "end": + ch_str = parts[2] + action = parts[3] + if ch_str.isdigit(): + ch_num = int(ch_str) + if 1 <= ch_num <= 8: + if action == "on": + channels[ch_num - 1] = True + response = f"" + elif action == "off": + channels[ch_num - 1] = False + response = f"" + elif action == "stat": + state_str = "ON" if channels[ch_num - 1] else "OFF" + response = f"" + + # Master control: mh#all#on#end or mh#all#off#end or mh#all#stat#end + if cmd == "mh#all#on#end": + for i in range(8): + channels[i] = True + response = "" + elif cmd == "mh#all#off#end": + for i in range(8): + channels[i] = False + response = "" + elif cmd == "mh#all#stat#end": + response = f"" + + # System commands + elif cmd == "mh#sys#id#end": + response = f"" + elif cmd == "mh#sys#version#end": + response = f"" + elif cmd == "mh#sys#reset#end": + for i in range(8): + channels[i] = False + response = "" + + # Mask control: mh#all#mask#10101010#end + elif cmd.startswith("mh#all#mask#") and cmd.endswith("#end"): + mask_str = cmd[len("mh#all#mask#"):-len("#end")] + if len(mask_str) == 8 and all(c in "01" for c in mask_str): + for i in range(8): + channels[i] = (mask_str[i] == '1') + response = f"" + + if response: + print(f" {COLOR_GREEN}[RESP]{COLOR_RESET} {response}") + else: + print(f" {COLOR_RED}[WARN]{COLOR_RESET} Unknown or unhandled command: {cmd}") + response = "" + + return response + + +def handle_client(client_socket, client_address): + print(f"\n{COLOR_CYAN}[+] Client connected from {client_address[0]}:{client_address[1]}{COLOR_RESET}") + buffer = "" + + try: + while True: + data = client_socket.recv(1024) + if not data: + break + + buffer += data.decode("utf-8", errors="ignore") + + # Extract frames between '<' and '>' + while "<" in buffer and ">" in buffer: + start_idx = buffer.find("<") + end_idx = buffer.find(">", start_idx) + + if end_idx != -1: + raw_cmd = buffer[start_idx + 1 : end_idx] + buffer = buffer[end_idx + 1 :] + + resp = process_command(raw_cmd) + if resp: + client_socket.sendall(resp.encode("utf-8")) + else: + # Found '<' but not yet '>', wait for next chunk + buffer = buffer[start_idx:] + break + + # Fallback for un-framed legacy commands terminated by #end + if "<" not in buffer and "#end" in buffer: + idx = buffer.find("#end") + 4 + raw_cmd = buffer[:idx] + buffer = buffer[idx:] + resp = process_command(raw_cmd) + if resp: + client_socket.sendall(resp.encode("utf-8")) + + except Exception as e: + print(f"{COLOR_RED}[!] Error handling client {client_address}: {e}{COLOR_RESET}") + finally: + client_socket.close() + print(f"{COLOR_CYAN}[-] Client {client_address[0]}:{client_address[1]} disconnected{COLOR_RESET}\n") + + +def get_local_ips(): + ips = [] + try: + # Get primary outward IP + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + primary_ip = s.getsockname()[0] + s.close() + ips.append(primary_ip) + except Exception: + pass + + try: + hostname = socket.gethostname() + for ip in socket.gethostbynameex(hostname)[2]: + if ip not in ips and not ip.startswith("127."): + ips.append(ip) + except Exception: + pass + + return ips + + +def main(): + parser = argparse.ArgumentParser(description="microHIL TCP Mock Server for ABComm") + parser.add_argument("--host", type=str, default="0.0.0.0", help="Host IP to bind (default: 0.0.0.0)") + parser.add_argument("--port", type=int, default=5000, help="Port to listen on (default: 5000)") + args = parser.parse_args() + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + try: + server_socket.bind((args.host, args.port)) + server_socket.listen(5) + except Exception as e: + print(f"{COLOR_RED}[!] Failed to bind to {args.host}:{args.port} - {e}{COLOR_RESET}") + sys.exit(1) + + print("=" * 60) + print(f"{COLOR_CYAN} microHIL WiFi TCP Mock Server Running{COLOR_RESET}") + print("=" * 60) + print(f"Listening on: {args.host}:{args.port}") + local_ips = get_local_ips() + if local_ips: + print("Use one of these IP addresses in the ABComm app:") + for ip in local_ips: + print(f" -> {COLOR_GREEN}{ip}{COLOR_RESET} (Port: {args.port})") + print("=" * 60) + print("Waiting for incoming connections...\n") + + try: + while True: + client_sock, client_addr = server_socket.accept() + client_thread = threading.Thread( + target=handle_client, args=(client_sock, client_addr), daemon=True + ) + client_thread.start() + except KeyboardInterrupt: + print(f"\n{COLOR_YELLOW}[*] Shutting down TCP server.{COLOR_RESET}") + finally: + server_socket.close() + + +if __name__ == "__main__": + main()