@robotical/ricjs
Version:
Javascript/TS library for Robotical RIC
312 lines • 11.5 kB
JavaScript
"use strict";
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// RICJS
// Communications Library
//
// Rob Dobson, Chris Greening & Alexander (Sandy) Enoch 2020-2023
// (C) 2020-2023
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const RICLog_1 = tslib_1.__importDefault(require("./RICLog"));
//import RICUtils from "./RICUtils";
//import SerialPort from "@types/w3c-web-serial";
const RICConnEvents_1 = require("./RICConnEvents");
class RICChannelWebSerial {
constructor() {
// Message handler
this._ricMsgHandler = null;
this._port = null;
// Last message tx time
// private _msgTxTimeLast = Date.now();
// private _msgTxMinTimeBetweenMs = 15;
// Is connected
this._isConnected = false;
this._connPaused = false;
this._serialBuffer = [];
this._escapeSeqCode = 0;
this._OVERASCII_ESCAPE_1 = 0x85;
this._OVERASCII_ESCAPE_2 = 0x8E;
this._OVERASCII_ESCAPE_3 = 0x8F;
this._OVERASCII_MOD_CODE = 0x20;
// Conn event fn
this._onConnEvent = null;
// File Handler parameters
this._requestedBatchAckSize = 1;
this._requestedFileBlockSize = 1200;
}
fhBatchAckSize() { return this._requestedBatchAckSize; }
fhFileBlockSize() { return this._requestedFileBlockSize; }
// isConnected
isConnected() {
return this._isConnected;
}
// Set message handler
setMsgHandler(ricMsgHandler) {
this._ricMsgHandler = ricMsgHandler;
}
// Serial interface will require subscription, but don't start it by default
requiresSubscription() {
return false;
}
// Set onConnEvent handler
setOnConnEvent(connEventFn) {
this._onConnEvent = connEventFn;
}
// Get connected locator
getConnectedLocator() {
return this._port || "";
}
// Connect to a device
async connect(locator) {
// Debug
RICLog_1.default.debug("RICChannelWebSerial.connect " + locator.toString());
// Check already connected
if (await this.isConnected()) {
return true;
}
try {
if (!this._port || locator != "reusePort") {
const port = await navigator.serial.requestPort();
this._port = port;
}
// Connect
try {
RICLog_1.default.info("opening port");
await this._port.open({ baudRate: 115200 });
}
catch (err) {
if (err.name == "InvalidStateError") {
RICLog_1.default.debug(`Opening port failed - already open ${err}`);
}
else {
RICLog_1.default.error(`Opening port failed: ${err}`);
throw err;
}
}
this._isConnected = true;
// start read loop
this._readLoop();
this._port.addEventListener('disconnect', (event) => {
RICLog_1.default.debug("WebSerial disconnect " + JSON.stringify(event));
if (this._onConnEvent)
this._onConnEvent(RICConnEvents_1.RICConnEvent.CONN_DISCONNECTED_RIC);
});
// TODO: handle errors
}
catch (err) {
RICLog_1.default.error("RICChannelWebSerial.connect fail. Error: " + JSON.stringify(err));
return false;
}
return true;
}
// Disconnect
async disconnect() {
// Not connected
this._isConnected = false;
RICLog_1.default.debug(`RICChannelWebSerial.disconnect attempting to close webserial`);
while (this._reader) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
// Disconnect webserial
try {
if (this._port)
await this._port.close();
}
catch (err) {
console.debug(`Error closing port ${err}`);
}
RICLog_1.default.debug("WebSerial port closed");
return;
}
pauseConnection(pause) {
this._connPaused = pause;
}
_overasciiDecodeByte(ch) {
// Check if in escape sequence
if (this._escapeSeqCode != 0) {
const prevEscCode = this._escapeSeqCode;
this._escapeSeqCode = 0;
if (prevEscCode == 1) {
return (ch ^ this._OVERASCII_MOD_CODE) & 0x7f;
}
else if (prevEscCode == 2) {
return ch ^ this._OVERASCII_MOD_CODE;
}
return ch;
}
else if (ch == this._OVERASCII_ESCAPE_1) {
this._escapeSeqCode = 1;
return -1;
}
else if (ch == this._OVERASCII_ESCAPE_2) {
this._escapeSeqCode = 2;
return -1;
}
else if (ch == this._OVERASCII_ESCAPE_3) {
this._escapeSeqCode = 3;
return -1;
}
else {
return ch & 0x7f;
}
}
_overasciiEncode(inData) {
/*
Encode frame
Values 0x00-0x0F map to ESCAPE_CODE_1, VALUE_XOR_20H_AND_MSB_SET
Values 0x10-0x7f map to VALUE_WITH_MSB_SET
Values 0x80-0x8F map to ESCAPE_CODE_2, VALUE_XOR_20H
Values 0x90-0xff map to ESCAPE_CODE_3, VALUE
Value ESCAPE_CODE_1 maps to ESCAPE_CODE_1 + VALUE
Args:
inData: data to encode (bytes)
Returns:
encoded frame (bytes)
*/
// Iterate over frame
const encodedFrame = [];
for (let i = 0; i < inData.length; i++) {
if (inData[i] <= 0x0f) {
encodedFrame.push(this._OVERASCII_ESCAPE_1);
encodedFrame.push((inData[i] ^ this._OVERASCII_MOD_CODE) | 0x80);
}
else if ((inData[i] >= 0x10) && (inData[i] <= 0x7f)) {
encodedFrame.push(inData[i] | 0x80);
}
else if ((inData[i] >= 0x80) && (inData[i] <= 0x8f)) {
encodedFrame.push(this._OVERASCII_ESCAPE_2);
encodedFrame.push(inData[i] ^ this._OVERASCII_MOD_CODE);
}
else {
encodedFrame.push(this._OVERASCII_ESCAPE_3);
encodedFrame.push(inData[i]);
}
}
return new Uint8Array(encodedFrame);
}
// Handle notifications
_onMsgRx(msg) {
if (msg === null)
return;
// Debug
//const decoder = new TextDecoder();
//RICLog.debug(`RICChannelWebSerial._onMsgRx ${decoder.decode(msg)}`);
const overasciiBuffer = [];
for (let i = 0; i < msg.length; i++) {
if (msg[i] > 127) {
const ch = this._overasciiDecodeByte(msg[i]);
if (ch != -1) {
overasciiBuffer.push(ch);
}
}
else {
this._serialBuffer.push(msg[i]);
}
}
if (this._ricMsgHandler)
this._ricMsgHandler.handleNewRxMsg(new Uint8Array(overasciiBuffer));
// any output over the non overascii channel will be delimited by a new line character
// scan for this, and log any lines as they occur before removing them from the buffer
if (this._serialBuffer.includes(0x0a)) {
const decoder = new TextDecoder();
RICLog_1.default.debug(decoder.decode(new Uint8Array(this._serialBuffer.slice(0, this._serialBuffer.indexOf(0x0a)))));
this._serialBuffer.splice(0, this._serialBuffer.indexOf(0x0a) + 1);
}
}
// Send a message
async sendTxMsg(msg, sendWithResponse) {
// Check connected
if (!this._isConnected || !this._port)
return false;
// Debug
const decoder = new TextDecoder();
RICLog_1.default.verbose(`RICChannelWebSerial.sendTxMsg ${msg.toString()} ${decoder.decode(msg)} sendWithResp ${sendWithResponse.toString()}`);
try {
if (this._port.writable != null) {
const writer = this._port.writable.getWriter();
await writer.write(this._overasciiEncode(msg));
writer.releaseLock();
}
}
catch (err) {
RICLog_1.default.warn("sendMsg error: " + JSON.stringify(err));
return false;
}
return true;
}
async sendTxMsgNoAwait(msg, sendWithResponse) {
// Check connected
if (!this._isConnected || !this._port)
return false;
// Debug
RICLog_1.default.verbose(`RICChannelWebSerial.sendTxMsgNoAwait ${msg.toString()} sendWithResp ${sendWithResponse.toString()}`);
try {
if (this._port.writable != null) {
const writer = this._port.writable.getWriter();
writer.write(msg).then(() => { writer.releaseLock(); });
}
}
catch (err) {
RICLog_1.default.error("sendMsg error: " + JSON.stringify(err));
}
return true;
}
async _readLoop() {
RICLog_1.default.debug("Starting read loop");
if (!this._port)
return;
let retries = 10;
try {
if (!this._port.readable) {
RICLog_1.default.error("RICChannelWebSerial _readLoop port is not readble");
return;
}
this._reader = this._port.readable.getReader();
while (this._port.readable && this._isConnected) {
if (this._connPaused) {
if (this._reader) {
this._reader.releaseLock();
this._reader = undefined;
}
await new Promise(resolve => setTimeout(resolve, 100));
continue;
}
try {
if (!this._reader)
this._reader = this._port.readable.getReader();
const { value, done } = await this._reader.read();
if (done) {
this._reader.releaseLock();
break;
}
if (!value || value.length === 0) {
continue;
}
this._onMsgRx(new Uint8Array(value));
}
catch (err) {
RICLog_1.default.error("read loop issue: " + JSON.stringify(err));
retries -= 1;
if (!retries)
break;
await new Promise(resolve => setTimeout(resolve, 100));
this._reader = this._port.readable.getReader();
}
}
if (this._reader)
this._reader.releaseLock();
this._reader = undefined;
}
catch (err) {
RICLog_1.default.error("Read loop got disconnected. err: " + JSON.stringify(err));
}
// Disconnected!
this._isConnected = false;
RICLog_1.default.debug("Finished read loop");
}
}
exports.default = RICChannelWebSerial;
//# sourceMappingURL=RICChannelWebSerial.js.map