Add websocket connection support, add v3 protocol support, fix syncMessage console error

This commit is contained in:
andrewheadricke 2025-10-28 11:21:49 +11:00
parent f6fea65063
commit a4ccd3692a
5 changed files with 136 additions and 12 deletions

View file

@ -11,6 +11,7 @@ It can also be used in NodeJS to connect to MeshCore Companion devices over TCP/
- Web Browser
- BLE: [WebBleConnection()](./src/connection/web_ble_connection.js)
- USB/Serial: [WebSerialConnection()](./src/connection/web_serial_connection.js)
- WebSocket: [WebSocketConnection()](./src/connection/websocket_connection.js)
- NodeJS
- TCP/WiFi: [TCPConnection("host", "port")](./src/connection/tcp_connection.js)
- USB/Serial: [NodeJSSerialConnection("/dev/ttyUSB0")](./src/connection/nodejs_serial_connection.js)

View file

@ -38,6 +38,9 @@
<button v-if="!connection" @click="askForBleDevice" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Connect (BLE)
</button>
<button v-if="!connection" @click="askForWebSocketDevice" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Connect (WebSocket)
</button>
<button v-if="connection" @click="disconnect" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
Disconnect
</button>
@ -198,6 +201,7 @@
import Constants from "./src/constants.js";
import WebSerialConnection from "./src/connection/web_serial_connection.js";
import WebBleConnection from "./src/connection/web_ble_connection.js";
import WebSocketConnection from "./src/connection/websocket_connection.js";
import BufferUtils from "./src/buffer_utils.js";
Vue.createApp({
data() {
@ -228,6 +232,11 @@
// this.connection.on("tx", (data) => console.log("tx", data));
// this.connection.on("rx", (data) => console.log("rx", data));
},
async askForWebSocketDevice() {
this.connection = await WebSocketConnection.open();
this.connection.on("connected", () => this.onConnected());
this.connection.on("disconnected", () => this.onDisconnected());
},
async disconnect() {
if(this.connection){
await this.connection.close();
@ -390,7 +399,7 @@
console.log("syncNextMessage", message);
// check if contact message
if(message.contactMessage){
if(message!= null && message.contactMessage){
// check if from cli contact
if(this.cliContact && BufferUtils.areBuffersEqual(message.contactMessage.pubKeyPrefix, this.cliContact.publicKey.subarray(0, 6))){

View file

@ -39,7 +39,7 @@ class Connection extends EventEmitter {
data.writeByte(Constants.CommandCodes.AppStart);
data.writeByte(1); // appVer
data.writeBytes(new Uint8Array(6)); // reserved
data.writeString("test"); // appName
data.writeString("test client"); // appName
await this.sendToRadioFrame(data.toBytes());
}
@ -334,6 +334,10 @@ class Connection extends EventEmitter {
this.onContactMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ChannelMsgRecv){
this.onChannelMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ContactMsgRecv3){
this.onContactMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ChannelMsgRecv3){
this.onChannelMsgRecvResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.ContactsStart){
this.onContactsStartResponse(bufferReader);
} else if(responseCode === Constants.ResponseCodes.Contact){
@ -635,23 +639,50 @@ class Connection extends EventEmitter {
}
onContactMsgRecvResponse(bufferReader) {
this.emit(Constants.ResponseCodes.ContactMsgRecv, {
if (Constants.SupportedCompanionProtocolVersion >= 3) {
this.emit(Constants.ResponseCodes.ContactMsgRecv3, {
snr: bufferReader.readByte() / 4.0,
reserved1: bufferReader.readByte(),
reserved2: bufferReader.readByte(),
pubKeyPrefix: bufferReader.readBytes(6),
pathLen: bufferReader.readByte(),
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
}
onChannelMsgRecvResponse(bufferReader) {
this.emit(Constants.ResponseCodes.ChannelMsgRecv, {
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
});
} else {
this.emit(Constants.ResponseCodes.ContactMsgRecv, {
pubKeyPrefix: bufferReader.readBytes(6),
pathLen: bufferReader.readByte(),
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
});
}
}
onChannelMsgRecvResponse(bufferReader) {
if (Constants.SupportedCompanionProtocolVersion >= 3) {
this.emit(Constants.ResponseCodes.ChannelMsgRecv3, {
snr: bufferReader.readByte() / 4.0,
reserved1: bufferReader.readByte(),
reserved2: bufferReader.readByte(),
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
} else {
this.emit(Constants.ResponseCodes.ChannelMsgRecv, {
channelIdx: bufferReader.readInt8(), // reserved (0 for now, ie. 'public')
pathLen: bufferReader.readByte(), // 0xFF if was sent direct, otherwise hop count for flood-mode
txtType: bufferReader.readByte(),
senderTimestamp: bufferReader.readUInt32LE(),
text: bufferReader.readString(),
});
}
}
getSelfInfo(timeoutMillis = null) {
@ -963,6 +994,8 @@ class Connection extends EventEmitter {
const onContactMessageReceived = (message) => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve({
contactMessage: message,
@ -973,6 +1006,8 @@ class Connection extends EventEmitter {
const onChannelMessageReceived = (message) => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve({
channelMessage: message,
@ -983,6 +1018,8 @@ class Connection extends EventEmitter {
const onNoMoreMessagesReceived = () => {
this.off(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.off(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.off(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.off(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
resolve(null);
}
@ -990,6 +1027,8 @@ class Connection extends EventEmitter {
// listen for events
this.once(Constants.ResponseCodes.ContactMsgRecv, onContactMessageReceived);
this.once(Constants.ResponseCodes.ChannelMsgRecv, onChannelMessageReceived);
this.once(Constants.ResponseCodes.ContactMsgRecv3, onContactMessageReceived);
this.once(Constants.ResponseCodes.ChannelMsgRecv3, onChannelMessageReceived);
this.once(Constants.ResponseCodes.NoMoreMessages, onNoMoreMessagesReceived);
// sync next message from device

View file

@ -0,0 +1,73 @@
import Connection from "./connection.js";
// Easy way to test this is to run `websocat -s 5000`
class WebSocketConnection extends Connection {
constructor(url) {
super();
let self = this
this.isClosing = false
let socket = new WebSocket(url)
socket.onopen = (event) => {
//console.log("connected")
this.onConnected();
}
socket.onerror = function(error) {
//console.log(error);
self.isClosing = true
self.onDisconnected();
}
socket.onmessage = async function(event) {
//console.log('got message', event.data)
let buf = await event.data.arrayBuffer();
self.onFrameReceived(buf);
}
socket.onclose = function() {
if (!self.isClosing) {
self.onDisconnected();
}
}
this.socket = socket
}
static async open() {
// ensure browser supports web bluetooth
let url = prompt("Enter WebSocket URL", "ws://127.0.0.1:5000")
if (url.startsWith("ws://") || url.startsWith("wss://")) {
} else {
url = "ws://" + url
}
return new WebSocketConnection(url);
}
async close() {
try {
this.isClosing = true
this.socket.close()
} catch(e) {
//console.log("close error", e)
// ignore error when disconnecting
}
}
async write(bytes) {
try {
this.socket.send(bytes)
} catch(e) {
console.log("failed to write to ble device", e);
}
}
async sendToRadioFrame(frame) {
this.emit("tx", frame);
await this.write(frame);
}
}
export default WebSocketConnection;

View file

@ -1,6 +1,6 @@
class Constants {
static SupportedCompanionProtocolVersion = 1;
static SupportedCompanionProtocolVersion = 3;
static SerialFrameTypes = {
Incoming: 0x3e, // ">"
@ -71,6 +71,8 @@ class Constants {
DeviceInfo: 13,
PrivateKey: 14,
Disabled: 15,
ContactMsgRecv3: 16,
ChannelMsgRecv3: 17,
ChannelInfo: 18,
SignStart: 19,
Signature: 20,