Skip to content

Commit 55bcfda

Browse files
committed
Add HCI bridge.
Signed-off-by: iabdalkader <[email protected]>
1 parent 9c4100f commit 55bcfda

File tree

3 files changed

+267
-0
lines changed

3 files changed

+267
-0
lines changed

examples/hci/hci.ino

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
This file is part of the Arduino_RouterBridge library.
3+
4+
Copyright (c) 2025 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
10+
*/
11+
12+
#include <Arduino_RouterBridge.h>
13+
14+
void setup() {
15+
Serial.begin(115200);
16+
while (!Serial) {
17+
delay(10);
18+
}
19+
20+
Serial.println("Arduino HCI Example - Read Local Version");
21+
22+
if (!Bridge.begin()) {
23+
Serial.println("Failed to setup Bridge");
24+
return;
25+
}
26+
27+
if (!HCI.begin("hci0")) {
28+
Serial.println("Failed to open HCI device");
29+
return;
30+
}
31+
32+
Serial.println("HCI device opened successfully");
33+
34+
delay(1000);
35+
readLocalVersion();
36+
}
37+
38+
void loop() {
39+
// Nothing to do in loop
40+
delay(1000);
41+
}
42+
43+
void readLocalVersion() {
44+
uint8_t cmd[4];
45+
cmd[0] = 0x01; // HCI Command Packet Type
46+
cmd[1] = 0x01; // OCF = 0x0001 (lower byte)
47+
cmd[2] = 0x10; // OGF = 0x04 (0x04 << 2 = 0x10 in upper 6 bits)
48+
cmd[3] = 0x00; // Parameter length = 0
49+
50+
Serial.println("Sending HCI Read Local Version command...");
51+
52+
int sent = HCI.send(cmd, sizeof(cmd));
53+
if (sent < 0) {
54+
Serial.println("Failed to send HCI command");
55+
return;
56+
}
57+
58+
Serial.print("Sent ");
59+
Serial.print(sent);
60+
Serial.println(" bytes");
61+
62+
// Wait for response with timeout
63+
Serial.println("Waiting for response...");
64+
int avail = 0;
65+
unsigned long startTime = millis();
66+
while (avail == 0 && (millis() - startTime) < 1000) { // 1 second timeout
67+
avail = HCI.available();
68+
if (avail == 0) {
69+
delay(10); // Small delay between polls
70+
}
71+
}
72+
73+
Serial.print("Available bytes: ");
74+
Serial.println(avail);
75+
76+
if (avail == 0) {
77+
Serial.println("Timeout: No response received");
78+
return;
79+
}
80+
81+
// Read response
82+
uint8_t response[255];
83+
int received = HCI.recv(response, sizeof(response));
84+
85+
if (received > 0) {
86+
Serial.print("Received ");
87+
Serial.print(received);
88+
Serial.println(" bytes:");
89+
90+
// Print response in hex
91+
for (int i = 0; i < received; i++) {
92+
if (response[i] < 0x10) Serial.print("0");
93+
Serial.print(response[i], HEX);
94+
Serial.print(" ");
95+
}
96+
Serial.println();
97+
98+
// Parse Command Complete Event
99+
// Event format: Packet Type, Event Code, Param Length, Num_HCI_Command_Packets, Opcode, Status, ...
100+
if (received >= 6 && response[0] == 0x04 && response[1] == 0x0E) {
101+
Serial.println("Command Complete Event received");
102+
Serial.print("Status: 0x");
103+
Serial.println(response[6], HEX);
104+
}
105+
} else {
106+
Serial.println("No response received");
107+
}
108+
}

src/Arduino_RouterBridge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
#include "monitor.h"
1818
#include "tcp_client.h"
1919
#include "tcp_server.h"
20+
#include "hci.h"
2021

2122
#endif //ARDUINO_ROUTER_BRIDGE_H

src/hci.h

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
This file is part of the Arduino_RouterBridge library.
3+
4+
Copyright (c) 2025 Arduino SA
5+
6+
This Source Code Form is subject to the terms of the Mozilla Public
7+
License, v. 2.0. If a copy of the MPL was not distributed with this
8+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
10+
*/
11+
12+
#pragma once
13+
14+
#ifndef BRIDGE_HCI_H
15+
#define BRIDGE_HCI_H
16+
17+
#include <string.h>
18+
#include "bridge.h"
19+
20+
#define HCI_OPEN_METHOD "hci/open"
21+
#define HCI_CLOSE_METHOD "hci/close"
22+
#define HCI_SEND_METHOD "hci/send"
23+
#define HCI_RECV_METHOD "hci/recv"
24+
#define HCI_AVAIL_METHOD "hci/avail"
25+
#define HCI_BUFFER_SIZE 1024 // Matches Linux kernel HCI_MAX_ACL_SIZE (1024 bytes)
26+
27+
// Lightweight binary view to avoid dynamic allocation during serialization
28+
struct BinaryView {
29+
const uint8_t *data;
30+
size_t size;
31+
32+
BinaryView(const uint8_t *d, size_t s) : data(d), size(s) {
33+
34+
}
35+
36+
// MsgPack serialization support
37+
void to_msgpack(MsgPack::Packer &packer) const {
38+
packer.pack(data, size);
39+
}
40+
};
41+
42+
template<size_t BufferSize=HCI_BUFFER_SIZE> class BridgeHCI {
43+
BridgeClass *bridge;
44+
struct k_mutex hci_mutex;
45+
bool initialized = false;
46+
MsgPack::bin_t<uint8_t> recv_buffer;
47+
48+
public:
49+
explicit BridgeHCI(BridgeClass &bridge): bridge(&bridge) {
50+
51+
}
52+
53+
bool begin(const char *device = "hci0") {
54+
k_mutex_init(&hci_mutex);
55+
56+
// Pre-allocate recv buffer to avoid allocations during recv calls
57+
recv_buffer.reserve(BufferSize);
58+
59+
if (!(*bridge) && !bridge->begin()) {
60+
return false;
61+
}
62+
63+
bool result;
64+
if (bridge->call(HCI_OPEN_METHOD, String(device)).result(result)) {
65+
initialized = result;
66+
}
67+
68+
return initialized;
69+
}
70+
71+
void end() {
72+
if (!initialized) {
73+
return;
74+
}
75+
76+
k_mutex_lock(&hci_mutex, K_FOREVER);
77+
78+
bool result;
79+
bridge->call(HCI_CLOSE_METHOD).result(result);
80+
initialized = false;
81+
82+
k_mutex_unlock(&hci_mutex);
83+
}
84+
85+
explicit operator bool() const {
86+
return initialized;
87+
}
88+
89+
int send(const uint8_t *buffer, size_t size) {
90+
if (!initialized) {
91+
return -1;
92+
}
93+
94+
k_mutex_lock(&hci_mutex, K_FOREVER);
95+
96+
BinaryView send_buffer(buffer, size);
97+
size_t bytes_sent;
98+
const bool ret = bridge->call(HCI_SEND_METHOD, send_buffer).result(bytes_sent);
99+
100+
k_mutex_unlock(&hci_mutex);
101+
102+
if (ret) {
103+
return bytes_sent;
104+
}
105+
return -1;
106+
}
107+
108+
int recv(uint8_t *buffer, size_t max_size) {
109+
if (!initialized) {
110+
return -1;
111+
}
112+
113+
k_mutex_lock(&hci_mutex, K_FOREVER);
114+
115+
recv_buffer.clear();
116+
bool ret = bridge->call(HCI_RECV_METHOD, max_size).result(recv_buffer);
117+
118+
if (ret) {
119+
size_t bytes_to_copy = recv_buffer.size() < max_size ? recv_buffer.size() : max_size;
120+
// Use memcpy for faster bulk copy
121+
if (bytes_to_copy > 0) {
122+
memcpy(buffer, recv_buffer.data(), bytes_to_copy);
123+
}
124+
k_mutex_unlock(&hci_mutex);
125+
return bytes_to_copy;
126+
}
127+
128+
k_mutex_unlock(&hci_mutex);
129+
return 0;
130+
}
131+
132+
int available() {
133+
if (!initialized) {
134+
return 0;
135+
}
136+
137+
k_mutex_lock(&hci_mutex, K_FOREVER);
138+
139+
bool result;
140+
bool ret = bridge->call(HCI_AVAIL_METHOD).result(result);
141+
142+
k_mutex_unlock(&hci_mutex);
143+
144+
return ret && result;
145+
}
146+
147+
};
148+
149+
extern BridgeClass Bridge;
150+
151+
namespace RouterBridge {
152+
BridgeHCI<> HCI(Bridge);
153+
}
154+
155+
// Make available in global namespace for backward compatibility
156+
using RouterBridge::HCI;
157+
158+
#endif // BRIDGE_HCI_H

0 commit comments

Comments
 (0)