Skip to content

Commit a87f70d

Browse files
committed
fix connection.
Signed-off-by: ncerzzk <huangcmzzk@gmail.com>
1 parent 6541bd0 commit a87f70d

7 files changed

Lines changed: 163 additions & 25 deletions

File tree

css/style.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,19 @@ body {
269269
margin-bottom: 15px;
270270
}
271271

272+
.serial-config {
273+
display: flex;
274+
flex-direction: column;
275+
gap: 6px;
276+
margin-bottom: 12px;
277+
}
278+
279+
.serial-config-label {
280+
font-size: 11px;
281+
color: var(--text-muted);
282+
text-transform: uppercase;
283+
}
284+
272285
.mode-btn {
273286
flex: 1;
274287
padding: 10px;
@@ -407,6 +420,20 @@ body {
407420
color: var(--text-color);
408421
}
409422

423+
.build-meta {
424+
display: flex;
425+
justify-content: space-between;
426+
align-items: center;
427+
font-family: monospace;
428+
font-size: 11px;
429+
color: var(--text-muted);
430+
padding: 0 4px;
431+
}
432+
433+
.build-value {
434+
color: var(--text-color);
435+
}
436+
410437
/* Responsive */
411438
@media (max-width: 1000px) {
412439
.main-content {

index.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ <h1>ELRS Web Remote</h1>
2929
<!-- Mode Selector -->
3030
<div class="panel">
3131
<h3>Input Mode</h3>
32+
<div class="serial-config">
33+
<label class="serial-config-label" for="baudRateSelect">Baud Rate</label>
34+
<select class="gamepad-select" id="baudRateSelect">
35+
<option value="5250000" selected>5250000 (ELRS Default)</option>
36+
<option value="400000">400000</option>
37+
<option value="420000">420000</option>
38+
<option value="3750000">3750000</option>
39+
<option value="2250000">2250000</option>
40+
<option value="1870000">1870000</option>
41+
<option value="921600">921600</option>
42+
<option value="115200">115200</option>
43+
</select>
44+
</div>
3245
<div class="mode-selector">
3346
<button class="mode-btn active" id="modeVirtual">Virtual Joystick</button>
3447
<button class="mode-btn" id="modeGamepad">Gamepad</button>
@@ -119,6 +132,11 @@ <h3>Actions</h3>
119132
<h3>Log</h3>
120133
<div class="log-panel" id="logPanel"></div>
121134
</div>
135+
136+
<div class="build-meta">
137+
<span class="build-label">Commit</span>
138+
<span class="build-value" id="buildCommit">5995baf</span>
139+
</div>
122140
</aside>
123141
</main>
124142
</div>

js/crsf.js

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export function buildRCPacket(channels) {
168168
const packet = new Uint8Array(2 + 1 + payloadSize + 1); // addr + len + type + payload + crc
169169

170170
let idx = 0;
171-
packet[idx++] = CRSF_ADDRESS_FLIGHT_CONTROLLER;
171+
packet[idx++] = CRSF_ADDRESS_RADIO_TRANSMITTER;
172172
packet[idx++] = payloadSize + 2; // type + payload + crc
173173
packet[idx++] = CRSF_FRAMETYPE_RC_CHANNELS_PACKED;
174174
packet.set(packedChannels, idx);
@@ -189,20 +189,12 @@ export function buildBindPacket() {
189189
CRSF_COMMAND_SUBCMD_RX,
190190
CRSF_COMMAND_SUBCMD_RX_BIND
191191
]);
192-
193-
const packet = new Uint8Array(2 + 1 + payload.length + 1);
194-
let idx = 0;
195-
packet[idx++] = CRSF_ADDRESS_CRSF_RECEIVER;
196-
packet[idx++] = payload.length + 2;
197-
packet[idx++] = CRSF_FRAMETYPE_COMMAND;
198-
packet.set(payload, idx);
199-
idx += payload.length;
200-
201-
// Calculate CRC from type onwards
202-
const crc = calcCRC(packet.subarray(2, 2 + 1 + payload.length));
203-
packet[idx] = crc;
204-
205-
return packet;
192+
return buildExtendedFrame(
193+
CRSF_FRAMETYPE_COMMAND,
194+
CRSF_ADDRESS_CRSF_TRANSMITTER,
195+
CRSF_ADDRESS_RADIO_TRANSMITTER,
196+
payload
197+
);
206198
}
207199

208200
/**
@@ -224,7 +216,7 @@ export function buildExtendedFrame(frameType, destAddr, origAddr, payload = new
224216
const packet = new Uint8Array(2 + frameSize); // addr + len + type + dest + orig + payload + crc
225217

226218
let idx = 0;
227-
packet[idx++] = origAddr; // device_addr (我们作为发送方)
219+
packet[idx++] = origAddr; // handset serial address
228220
packet[idx++] = frameSize; // frame_size (after this byte: type + dest + orig + payload + crc)
229221
packet[idx++] = frameType; // type
230222
packet[idx++] = destAddr; // dest_addr (扩展头)
@@ -243,11 +235,10 @@ export function buildExtendedFrame(frameType, destAddr, origAddr, payload = new
243235
* Build a DEVICE_PING packet (handset → TX)
244236
*/
245237
export function buildDevicePing() {
246-
// DEVICE_PING is an extended frame with empty payload
247238
return buildExtendedFrame(
248239
CRSF_FRAMETYPE_DEVICE_PING,
249-
CRSF_ADDRESS_CRSF_TRANSMITTER, // dest: TX module
250-
CRSF_ADDRESS_RADIO_TRANSMITTER // orig: handset/radio
240+
CRSF_ADDRESS_CRSF_TRANSMITTER,
241+
CRSF_ADDRESS_RADIO_TRANSMITTER
251242
);
252243
}
253244

js/elrs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class ELRS {
1616
telemetry: [],
1717
linkStats: [],
1818
connected: [],
19+
ready: [],
1920
disconnected: [],
2021
error: [],
2122
devicePing: [],
@@ -90,6 +91,7 @@ export class ELRS {
9091
this.parser.reset();
9192
this.readLoop();
9293
this.startHandshake();
94+
this.emit('connected');
9395

9496
return true;
9597
} catch (error) {
@@ -107,7 +109,7 @@ export class ELRS {
107109
}
108110

109111
this.handshakeState = 'ready';
110-
this.emit('connected');
112+
this.emit('ready');
111113
}
112114

113115
/**

js/main.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import { getGamepadManager } from './gamepad.js';
1010
// Global state
1111
const state = {
1212
mode: 'virtual', // 'virtual' or 'gamepad'
13-
connected: false
13+
connected: false,
14+
baudRate: 5250000
1415
};
1516

1617
// Get instances
@@ -29,16 +30,20 @@ const elements = {
2930
gamepadInfo: document.getElementById('gamepadInfo'),
3031
gamepadSelect: document.getElementById('gamepadSelect'),
3132
gamepadStatus: document.getElementById('gamepadStatus'),
33+
baudRateSelect: document.getElementById('baudRateSelect'),
3234
bindBtn: document.getElementById('bindBtn'),
3335
wifiBtn: document.getElementById('wifiBtn'),
3436
resetBtn: document.getElementById('resetBtn'),
35-
logPanel: document.getElementById('logPanel')
37+
logPanel: document.getElementById('logPanel'),
38+
buildCommit: document.getElementById('buildCommit')
3639
};
3740

3841
// Initialize
3942
function init() {
4043
log('Initializing ELRS Web Remote...');
4144
elements.wifiBtn.title = 'WiFi mode command is not implemented yet';
45+
elements.buildCommit.textContent = '5995baf';
46+
elements.baudRateSelect.value = String(state.baudRate);
4247

4348
// Create virtual joystick
4449
joystick = new VirtualJoystick('joystickContainer');
@@ -125,7 +130,8 @@ function setupUIEvents() {
125130
// Connect button
126131
elements.connectBtn.addEventListener('click', async () => {
127132
try {
128-
await elrs.connect(420000);
133+
log(`Opening serial port at ${state.baudRate} baud`);
134+
await elrs.connect(state.baudRate);
129135
} catch (error) {
130136
if (error.name !== 'NotFoundError') {
131137
log(`Connection failed: ${error.message}`);
@@ -155,6 +161,14 @@ function setupUIEvents() {
155161
}
156162
});
157163

164+
elements.baudRateSelect.addEventListener('change', (e) => {
165+
const baudRate = parseInt(e.target.value, 10);
166+
if (!Number.isNaN(baudRate)) {
167+
state.baudRate = baudRate;
168+
log(`Baud rate set to ${baudRate}`);
169+
}
170+
});
171+
158172
// Bind button
159173
elements.bindBtn.addEventListener('click', async () => {
160174
if (state.connected) {
@@ -211,7 +225,7 @@ function updateConnectionUI(connected) {
211225
elements.connectBtn.disabled = connected;
212226
elements.disconnectBtn.disabled = !connected;
213227
elements.bindBtn.disabled = !connected;
214-
elements.wifiBtn.disabled = !connected || !elrs.supportsWifiMode();
228+
elements.wifiBtn.disabled = !connected;
215229

216230
if (connected) {
217231
// Start sending RC data

test/crsf.test.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test('buildRCPacket emits a complete 26-byte frame with valid CRC', () => {
3737
const frame = CRSF.buildRCPacket(channels);
3838

3939
assert.equal(frame.length, 26);
40-
assert.equal(frame[0], CRSF.CRSF_ADDRESS_FLIGHT_CONTROLLER);
40+
assert.equal(frame[0], CRSF.CRSF_ADDRESS_RADIO_TRANSMITTER);
4141
assert.equal(frame[1], 24);
4242
assert.equal(frame[2], CRSF.CRSF_FRAMETYPE_RC_CHANNELS_PACKED);
4343
assert.equal(frame.at(-1), expectedCRC(frame));
@@ -72,6 +72,7 @@ test('extended DEVICE_PING frames are complete and parse correctly', () => {
7272

7373
assert.equal(frame.length, 6);
7474
assert.equal(frame[1], 4);
75+
assert.equal(frame[0], CRSF.CRSF_ADDRESS_RADIO_TRANSMITTER);
7576
assert.equal(frame.at(-1), expectedCRC(frame));
7677
assert.ok(telemetry);
7778
assert.equal(telemetry.type, 'device_ping');
@@ -90,11 +91,26 @@ test('extended DEVICE_INFO frames preserve device name', () => {
9091
const telemetry = CRSF.parseTelemetry(frame);
9192

9293
assert.ok(telemetry);
94+
assert.equal(frame[0], CRSF.CRSF_ADDRESS_RADIO_TRANSMITTER);
9395
assert.equal(telemetry.type, 'device_info');
9496
assert.equal(telemetry.data.deviceName, 'WebRadio');
9597
assert.equal(frame.at(-1), expectedCRC(frame));
9698
});
9799

100+
test('bind command matches handset extended-command addressing', () => {
101+
const frame = CRSF.buildBindPacket();
102+
const telemetry = CRSF.parseTelemetry(frame);
103+
104+
assert.equal(frame[0], CRSF.CRSF_ADDRESS_RADIO_TRANSMITTER);
105+
assert.equal(telemetry.type, 'unknown');
106+
assert.equal(telemetry.destAddr, CRSF.CRSF_ADDRESS_CRSF_TRANSMITTER);
107+
assert.equal(telemetry.origAddr, CRSF.CRSF_ADDRESS_RADIO_TRANSMITTER);
108+
assert.deepEqual(Array.from(telemetry.data), [
109+
CRSF.CRSF_COMMAND_SUBCMD_RX,
110+
CRSF.CRSF_COMMAND_SUBCMD_RX_BIND
111+
]);
112+
});
113+
98114
test('stream parser emits telemetry once a full frame arrives', () => {
99115
const frame = CRSF.buildLinkStatistics({
100116
uplink_RSSI_1: 50,

test/elrs.test.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
4+
import { ELRS } from '../js/elrs.js';
5+
6+
test('connect emits connected as soon as the serial port opens', async () => {
7+
const originalNavigator = globalThis.navigator;
8+
const port = {
9+
readable: {
10+
getReader() {
11+
return {
12+
async read() {
13+
return { done: true };
14+
},
15+
releaseLock() {},
16+
async cancel() {}
17+
};
18+
}
19+
},
20+
writable: {
21+
getWriter() {
22+
return {
23+
async write() {},
24+
releaseLock() {}
25+
};
26+
}
27+
},
28+
async open() {},
29+
async close() {}
30+
};
31+
32+
Object.defineProperty(globalThis, 'navigator', {
33+
configurable: true,
34+
value: {
35+
serial: {
36+
async requestPort() {
37+
return port;
38+
}
39+
}
40+
}
41+
});
42+
43+
try {
44+
const elrs = new ELRS();
45+
let connectedCount = 0;
46+
let readyCount = 0;
47+
48+
elrs.on('connected', () => {
49+
connectedCount += 1;
50+
});
51+
elrs.on('ready', () => {
52+
readyCount += 1;
53+
});
54+
55+
await elrs.connect();
56+
assert.equal(connectedCount, 1);
57+
assert.equal(readyCount, 0);
58+
59+
elrs.markHandshakeReady();
60+
assert.equal(connectedCount, 1);
61+
assert.equal(readyCount, 1);
62+
63+
await elrs.disconnect();
64+
} finally {
65+
Object.defineProperty(globalThis, 'navigator', {
66+
configurable: true,
67+
value: originalNavigator
68+
});
69+
}
70+
});

0 commit comments

Comments
 (0)