diff --git a/examples/hci/hci.ino b/examples/hci/hci.ino new file mode 100644 index 0000000..7a52d8c --- /dev/null +++ b/examples/hci/hci.ino @@ -0,0 +1,108 @@ +/* + This file is part of the Arduino_RouterBridge library. + + Copyright (c) 2025 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +*/ + +#include + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + + Serial.println("Arduino HCI Example - Read Local Version"); + + if (!Bridge.begin()) { + Serial.println("Failed to setup Bridge"); + return; + } + + if (!HCI.begin("hci0")) { + Serial.println("Failed to open HCI device"); + return; + } + + Serial.println("HCI device opened successfully"); + + delay(1000); + readLocalVersion(); +} + +void loop() { + // Nothing to do in loop + delay(1000); +} + +void readLocalVersion() { + uint8_t cmd[4]; + cmd[0] = 0x01; // HCI Command Packet Type + cmd[1] = 0x01; // OCF = 0x0001 (lower byte) + cmd[2] = 0x10; // OGF = 0x04 (0x04 << 2 = 0x10 in upper 6 bits) + cmd[3] = 0x00; // Parameter length = 0 + + Serial.println("Sending HCI Read Local Version command..."); + + int sent = HCI.send(cmd, sizeof(cmd)); + if (sent < 0) { + Serial.println("Failed to send HCI command"); + return; + } + + Serial.print("Sent "); + Serial.print(sent); + Serial.println(" bytes"); + + // Wait for response with timeout + Serial.println("Waiting for response..."); + int avail = 0; + unsigned long startTime = millis(); + while (avail == 0 && (millis() - startTime) < 1000) { // 1 second timeout + avail = HCI.available(); + if (avail == 0) { + delay(10); // Small delay between polls + } + } + + Serial.print("Available bytes: "); + Serial.println(avail); + + if (avail == 0) { + Serial.println("Timeout: No response received"); + return; + } + + // Read response + uint8_t response[255]; + int received = HCI.recv(response, sizeof(response)); + + if (received > 0) { + Serial.print("Received "); + Serial.print(received); + Serial.println(" bytes:"); + + // Print response in hex + for (int i = 0; i < received; i++) { + if (response[i] < 0x10) Serial.print("0"); + Serial.print(response[i], HEX); + Serial.print(" "); + } + Serial.println(); + + // Parse Command Complete Event + // Event format: Packet Type, Event Code, Param Length, Num_HCI_Command_Packets, Opcode, Status, ... + if (received >= 6 && response[0] == 0x04 && response[1] == 0x0E) { + Serial.println("Command Complete Event received"); + Serial.print("Status: 0x"); + Serial.println(response[6], HEX); + } + } else { + Serial.println("No response received"); + } +} diff --git a/src/Arduino_RouterBridge.h b/src/Arduino_RouterBridge.h index 3174d0f..e2dcbf7 100644 --- a/src/Arduino_RouterBridge.h +++ b/src/Arduino_RouterBridge.h @@ -17,5 +17,6 @@ #include "monitor.h" #include "tcp_client.h" #include "tcp_server.h" +#include "hci.h" #endif //ARDUINO_ROUTER_BRIDGE_H diff --git a/src/hci.h b/src/hci.h new file mode 100644 index 0000000..b313f8e --- /dev/null +++ b/src/hci.h @@ -0,0 +1,158 @@ +/* + This file is part of the Arduino_RouterBridge library. + + Copyright (c) 2025 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +*/ + +#pragma once + +#ifndef BRIDGE_HCI_H +#define BRIDGE_HCI_H + +#include +#include "bridge.h" + +#define HCI_OPEN_METHOD "hci/open" +#define HCI_CLOSE_METHOD "hci/close" +#define HCI_SEND_METHOD "hci/send" +#define HCI_RECV_METHOD "hci/recv" +#define HCI_AVAIL_METHOD "hci/avail" +#define HCI_BUFFER_SIZE 1024 // Matches Linux kernel HCI_MAX_ACL_SIZE (1024 bytes) + +// Lightweight binary view to avoid dynamic allocation during serialization +struct BinaryView { + const uint8_t *data; + size_t size; + + BinaryView(const uint8_t *d, size_t s) : data(d), size(s) { + + } + + // MsgPack serialization support + void to_msgpack(MsgPack::Packer &packer) const { + packer.pack(data, size); + } +}; + +template class BridgeHCI { + BridgeClass *bridge; + struct k_mutex hci_mutex; + bool initialized = false; + MsgPack::bin_t recv_buffer; + +public: + explicit BridgeHCI(BridgeClass &bridge): bridge(&bridge) { + + } + + bool begin(const char *device = "hci0") { + k_mutex_init(&hci_mutex); + + // Pre-allocate recv buffer to avoid allocations during recv calls + recv_buffer.reserve(BufferSize); + + if (!(*bridge) && !bridge->begin()) { + return false; + } + + bool result; + if (bridge->call(HCI_OPEN_METHOD, String(device)).result(result)) { + initialized = result; + } + + return initialized; + } + + void end() { + if (!initialized) { + return; + } + + k_mutex_lock(&hci_mutex, K_FOREVER); + + bool result; + bridge->call(HCI_CLOSE_METHOD).result(result); + initialized = false; + + k_mutex_unlock(&hci_mutex); + } + + explicit operator bool() const { + return initialized; + } + + int send(const uint8_t *buffer, size_t size) { + if (!initialized) { + return -1; + } + + k_mutex_lock(&hci_mutex, K_FOREVER); + + BinaryView send_buffer(buffer, size); + size_t bytes_sent; + const bool ret = bridge->call(HCI_SEND_METHOD, send_buffer).result(bytes_sent); + + k_mutex_unlock(&hci_mutex); + + if (ret) { + return bytes_sent; + } + return -1; + } + + int recv(uint8_t *buffer, size_t max_size) { + if (!initialized) { + return -1; + } + + k_mutex_lock(&hci_mutex, K_FOREVER); + + recv_buffer.clear(); + bool ret = bridge->call(HCI_RECV_METHOD, max_size).result(recv_buffer); + + if (ret) { + size_t bytes_to_copy = recv_buffer.size() < max_size ? recv_buffer.size() : max_size; + // Use memcpy for faster bulk copy + if (bytes_to_copy > 0) { + memcpy(buffer, recv_buffer.data(), bytes_to_copy); + } + k_mutex_unlock(&hci_mutex); + return bytes_to_copy; + } + + k_mutex_unlock(&hci_mutex); + return 0; + } + + int available() { + if (!initialized) { + return 0; + } + + k_mutex_lock(&hci_mutex, K_FOREVER); + + bool result; + bool ret = bridge->call(HCI_AVAIL_METHOD).result(result); + + k_mutex_unlock(&hci_mutex); + + return ret && result; + } + +}; + +extern BridgeClass Bridge; + +namespace RouterBridge { + BridgeHCI<> HCI(Bridge); +} + +// Make available in global namespace for backward compatibility +using RouterBridge::HCI; + +#endif // BRIDGE_HCI_H