feat: GetMac command + MacAddress response
New protocol command (tag 8): GetMac returns the board's 6-byte hardware address. Useful when multiple dongles are connected to one host. Response (tag 6): MacAddress containing 6 raw bytes. Updated: PROTOCOL.md, CHANGELOG, donglora.py decoder, all_commands.py.
This commit is contained in:
7 files changed
+33
-7
No files matched your search
+1
-1
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Transparent LoRa radio over USB CDC-ACM (VID `1209`, PID `5741`)
|
- Transparent LoRa radio over USB CDC-ACM (VID `1209`, PID `5741`)
|
||||||
- COBS-framed fixed-size LE binary protocol (8 commands, 6 responses)
|
- COBS-framed fixed-size LE binary protocol (9 commands, 7 responses)
|
||||||
- Radio configuration: frequency, bandwidth, spreading factor, coding rate, TX power
|
- Radio configuration: frequency, bandwidth, spreading factor, coding rate, TX power
|
||||||
- Continuous RX mode with RSSI/SNR per packet
|
- Continuous RX mode with RSSI/SNR per packet
|
||||||
- Single-shot TX with optional per-packet config override
|
- Single-shot TX with optional per-packet config override
|
||||||
|
|||||||
+7
-4
@@ -59,6 +59,7 @@ Each command is a tag byte followed by fixed-size fields.
|
|||||||
| 5 | Transmit | has_config (1) + [RadioConfig if 1] + len (u16 LE) + payload |
|
| 5 | Transmit | has_config (1) + [RadioConfig if 1] + len (u16 LE) + payload |
|
||||||
| 6 | DisplayOn | — |
|
| 6 | DisplayOn | — |
|
||||||
| 7 | DisplayOff | — |
|
| 7 | DisplayOff | — |
|
||||||
|
| 8 | GetMac | — |
|
||||||
|
|
||||||
## Responses (Firmware → Host)
|
## Responses (Firmware → Host)
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ Each command is a tag byte followed by fixed-size fields.
|
|||||||
| 3 | TxDone | — |
|
| 3 | TxDone | — |
|
||||||
| 4 | Ok | — |
|
| 4 | Ok | — |
|
||||||
| 5 | Error | code (u8) |
|
| 5 | Error | code (u8) |
|
||||||
|
| 6 | MacAddress | 6 raw bytes (board's MAC/device address) |
|
||||||
|
|
||||||
## Bandwidth
|
## Bandwidth
|
||||||
|
|
||||||
@@ -128,9 +130,10 @@ Read back the response, COBS-decode it. First byte `0x04` = Ok.
|
|||||||
1. Open USB serial port (find by VID:PID `1209:5741`)
|
1. Open USB serial port (find by VID:PID `1209:5741`)
|
||||||
2. Implement COBS encode/decode (libraries exist for every language)
|
2. Implement COBS encode/decode (libraries exist for every language)
|
||||||
3. Pack commands with `struct.pack` or equivalent — all fields are fixed-size LE
|
3. Pack commands with `struct.pack` or equivalent — all fields are fixed-size LE
|
||||||
4. Send `SetConfig`, read back `Ok`
|
4. Optionally send `GetMac`, read back `MacAddress` (useful with multiple dongles)
|
||||||
5. Send `StartRx`, read back `Ok`
|
5. Send `SetConfig`, read back `Ok`
|
||||||
6. Loop: read frames, decode `RxPacket` (rssi, snr, payload at known offsets)
|
6. Send `StartRx`, read back `Ok`
|
||||||
7. To transmit: send `Transmit`, read back `TxDone`
|
7. Loop: read frames, decode `RxPacket` (rssi, snr, payload at known offsets)
|
||||||
|
8. To transmit: send `Transmit`, read back `TxDone`
|
||||||
|
|
||||||
See `examples/` for working Python implementations.
|
See `examples/` for working Python implementations.
|
||||||
@@ -42,7 +42,10 @@ try:
|
|||||||
print("\n── DisplayOn ──")
|
print("\n── DisplayOn ──")
|
||||||
print(dl.send(ser, "DisplayOn"))
|
print(dl.send(ser, "DisplayOn"))
|
||||||
|
|
||||||
print("\nAll 8 commands exercised successfully.")
|
print("\n── GetMac ──")
|
||||||
|
print(dl.send(ser, "GetMac"))
|
||||||
|
|
||||||
|
print("\nAll 9 commands exercised successfully.")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nInterrupted.")
|
print("\nInterrupted.")
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ def encode_command(cmd: str, **kwargs) -> bytes:
|
|||||||
tags = {
|
tags = {
|
||||||
"Ping": 0, "GetConfig": 1, "SetConfig": 2, "StartRx": 3,
|
"Ping": 0, "GetConfig": 1, "SetConfig": 2, "StartRx": 3,
|
||||||
"StopRx": 4, "Transmit": 5, "DisplayOn": 6, "DisplayOff": 7,
|
"StopRx": 4, "Transmit": 5, "DisplayOn": 6, "DisplayOff": 7,
|
||||||
|
"GetMac": 8,
|
||||||
}
|
}
|
||||||
out = bytes([tags[cmd]])
|
out = bytes([tags[cmd]])
|
||||||
if cmd == "SetConfig":
|
if cmd == "SetConfig":
|
||||||
@@ -144,6 +145,11 @@ def decode_response(data: bytes) -> dict:
|
|||||||
elif tag == 5:
|
elif tag == 5:
|
||||||
code = rest[0] if rest else -1
|
code = rest[0] if rest else -1
|
||||||
return {"type": "Error", "code": code}
|
return {"type": "Error", "code": code}
|
||||||
|
elif tag == 6:
|
||||||
|
if len(rest) >= 6:
|
||||||
|
mac = ":".join(f"{b:02X}" for b in rest[:6])
|
||||||
|
return {"type": "MacAddress", "mac": mac}
|
||||||
|
return {"type": "MacAddress", "raw": rest.hex()}
|
||||||
else:
|
else:
|
||||||
return {"type": f"Unknown({tag})"}
|
return {"type": f"Unknown({tag})"}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ pub enum Command {
|
|||||||
},
|
},
|
||||||
DisplayOn,
|
DisplayOn,
|
||||||
DisplayOff,
|
DisplayOff,
|
||||||
|
GetMac,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
@@ -178,6 +179,7 @@ impl Command {
|
|||||||
}
|
}
|
||||||
6 => Some(Self::DisplayOn),
|
6 => Some(Self::DisplayOn),
|
||||||
7 => Some(Self::DisplayOff),
|
7 => Some(Self::DisplayOff),
|
||||||
|
8 => Some(Self::GetMac),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +199,7 @@ pub enum Response {
|
|||||||
TxDone,
|
TxDone,
|
||||||
Ok,
|
Ok,
|
||||||
Error(ErrorCode),
|
Error(ErrorCode),
|
||||||
|
MacAddress([u8; 6]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
@@ -232,6 +235,11 @@ impl Response {
|
|||||||
buf[1] = code as u8;
|
buf[1] = code as u8;
|
||||||
2
|
2
|
||||||
}
|
}
|
||||||
|
Self::MacAddress(mac) => {
|
||||||
|
buf[0] = 6;
|
||||||
|
buf[1..7].copy_from_slice(&mac);
|
||||||
|
7
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -219,7 +219,7 @@ async fn handle_cmd(
|
|||||||
status.sender().send(state.clone());
|
status.sender().send(state.clone());
|
||||||
responses.send(Response::Ok).await;
|
responses.send(Response::Ok).await;
|
||||||
}
|
}
|
||||||
Command::DisplayOn | Command::DisplayOff => {}
|
Command::DisplayOn | Command::DisplayOff | Command::GetMac => {}
|
||||||
Command::Transmit { config, payload } => {
|
Command::Transmit { config, payload } => {
|
||||||
let tx_config = config.map(|c| c.resolve_power(Board::TX_POWER_RANGE)).or(state.config);
|
let tx_config = config.map(|c| c.resolve_power(Board::TX_POWER_RANGE)).or(state.config);
|
||||||
if let Some(cfg) = tx_config {
|
if let Some(cfg) = tx_config {
|
||||||
|
|||||||
@@ -207,6 +207,12 @@ async fn route_command(
|
|||||||
responses.send(Response::Error(ErrorCode::NoDisplay)).await;
|
responses.send(Response::Error(ErrorCode::NoDisplay)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Command::GetMac => {
|
||||||
|
use crate::board::{Board, LoRaBoard};
|
||||||
|
responses
|
||||||
|
.send(Response::MacAddress(Board::mac_address()))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
commands.send(other).await;
|
commands.send(other).await;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user