diff --git a/README.md b/README.md index c3c51d6..df9819c 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/index.html b/index.html index 05ec84a..a2195ab 100644 --- a/index.html +++ b/index.html @@ -38,6 +38,9 @@ + @@ -55,7 +58,7 @@
-
+
@@ -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))){ diff --git a/src/connection/connection.js b/src/connection/connection.js index 90132cc..0d274fc 100644 --- a/src/connection/connection.js +++ b/src/connection/connection.js @@ -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 diff --git a/src/connection/websocket_connection.js b/src/connection/websocket_connection.js new file mode 100644 index 0000000..88c79fe --- /dev/null +++ b/src/connection/websocket_connection.js @@ -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; diff --git a/src/constants.js b/src/constants.js index 2eb2706..229a4b3 100644 --- a/src/constants.js +++ b/src/constants.js @@ -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, diff --git a/src/index.js b/src/index.js index af95004..ff42c31 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,7 @@ import SerialConnection from "./connection/serial_connection.js"; import NodeJSSerialConnection from "./connection/nodejs_serial_connection.js"; import WebSerialConnection from "./connection/web_serial_connection.js"; import TCPConnection from "./connection/tcp_connection.js"; +import WebSocketConnection from "./connection/websocket_connection.js"; import Constants from "./constants.js"; import Advert from "./advert.js"; import Packet from "./packet.js"; @@ -17,6 +18,7 @@ export { NodeJSSerialConnection, WebSerialConnection, TCPConnection, + WebSocketConnection, Constants, Advert, Packet,