@robotical/ricjs
Version:
Javascript/TS library for Robotical RIC
274 lines (244 loc) • 13.7 kB
text/typescript
import { acceptCheckCorrectRIC, connectBLE, connectWiFi, connectWebSerial, disconnect, rejectCheckCorrectRIC, startCheckCorrectRIC } from './connect';
import { sendREST, streamSoundFile } from './stream';
import { imuStatusFormat, robotStatusFormat, servoStatusFormat, addonListFormat, tableFormat, sysInfoGet, connPerfTest, setReconnect, pixGetColourStr, commsStatusFormat, powerStatusFormat, addonValListFormat, magnetoStatusFormat } from './system';
import { RICConnEvent } from '../../../src/RICConnEvents';
import { RICUpdateEvent } from '../../../src/RICUpdateEvents';
import RICConnector from '../../../src/RICConnector';
import { fileDownloader, otaUpdateCancel, otaUpdateCheck, otaUpdateStart } from './update';
let startTime = Date.now();
function eventListener(eventType: string, eventEnum: RICConnEvent | RICUpdateEvent, eventName: string, eventData?: object | string | null) {
const eventField = document.getElementById("event-field") as HTMLElement;
if (eventField) {
if (eventField.innerHTML.length === 0) {
eventField.innerHTML = "<div>Events</div>";
}
const timeStr = ((Date.now() - startTime) / 1000).toFixed(1);
eventField.innerHTML += `<div><span class="event-time-info">${timeStr}</span><span class="event-info">${eventName}</span><span class="event-info">${eventData?JSON.stringify(eventData):""}</span></div>`;
}
// Handle specific events
const checkField = document.getElementById("check-correct-ric-container") as HTMLElement;
if (checkField) {
if (eventType === 'conn') {
switch (eventEnum) {
case RICConnEvent.CONN_VERIFYING_CORRECT_RIC:
{
checkField.innerHTML = `<div>Check LEDs</div>`;
const eventLeds = eventData! as Array<string>;
for (let idx = 0; idx < eventLeds.length; idx++) {
checkField.innerHTML += pixGetColourStr(idx, eventLeds[idx]);
}
break;
}
case RICConnEvent.CONN_VERIFIED_CORRECT_RIC:
{
checkField.innerHTML = `<div>Check LEDs ACCEPTED</div>`;
break;
}
case RICConnEvent.CONN_REJECTED_RIC:
{
checkField.innerHTML = `<div>Check LEDs REJECTED</div>`;
break;
}
}
}
}
}
globalThis.ricConnector = new RICConnector();
if (globalThis.ricConnector) {
globalThis.ricConnector.setupUpdateManager("2.0.0",
`https://updates.robotical.io/live/martyv2/rev{HWRevNo}/current_version.json`,
"",
fileDownloader);
globalThis.ricConnector.setEventListener(eventListener);
}
globalThis.ricPrevData = {};
function formatStatus(name: string, status: any, validMs:number | undefined | null, formatFn: any, elId: string) {
if (!globalThis.ricConnector.isConnected() || !status) {
if (globalThis.ricPrevData[name]) {
document.getElementById(elId).innerHTML = "";
delete globalThis.ricPrevData[name];
}
return;
}
if (validMs === 0) {
document.getElementById(elId).innerHTML = "";
return;
}
const curStatusJSON = JSON.stringify(status);
if (!(name in globalThis.ricPrevData) || (globalThis.ricPrevData[name] !== curStatusJSON)) {
const newStatusHTML = formatFn(name, status);
if (newStatusHTML !== "") {
const container = document.getElementById(elId);
container.innerHTML = newStatusHTML;
globalThis.ricPrevData[name] = curStatusJSON;
if ((validMs === null) || (validMs === undefined) || (Date.now() < validMs + 2000)) {
container.classList.add("status-valid");
} else {
container.classList.remove("status-invalid");
}
}
}
}
function updateStatus() {
const statusContainer = document.getElementById('time-status-container');
statusContainer.innerHTML = "";
const status = document.createElement('div');
const timeStr = ((Date.now() - startTime) / 1000).toFixed(1);
const connStr = globalThis.ricConnector.isConnected() ? "Connected to " + globalThis.ricConnector.getConnMethod() : "Disconnected";
const connClass = globalThis.ricConnector.isConnected() ? "status-conn" : "status-disconn";
status.innerHTML = `<div>Elapsed time ${timeStr}</div><div class="${connClass}">${connStr}</div>`;
status.classList.add('status');
statusContainer.appendChild(status);
const ricState = globalThis.ricConnector.getRICState();
const ricSystem = globalThis.ricConnector.getRICSystem()
formatStatus("commsStats", globalThis.ricConnector.getCommsStats(), null, commsStatusFormat, "comms-stats-container");
formatStatus("robotStatus", ricState.robotStatus, ricState.robotStatusValidMs, robotStatusFormat, "robot-status-container");
formatStatus("powerStatus", ricState.power, ricState.powerValidMs, powerStatusFormat, "power-status-container");
formatStatus("imuStatus", ricState.imuData, ricState.imuDataValidMs, imuStatusFormat, "imu-status-container");
formatStatus("magnetoStatus", ricState.magnetoData, ricState.magnetoDataValidMs, magnetoStatusFormat, "magneto-status-container");
formatStatus("servoStatus", ricState.smartServos, ricState.smartServosValidMs, servoStatusFormat, "servo-status-container");
formatStatus("sysInfoStatus", ricSystem.getCachedSystemInfo(), ricSystem.getCachedSystemInfo()?.validMs, tableFormat, "sysinfo-list-container");
formatStatus("addonsStatus", ricSystem.getCachedAddOnList(), null, addonListFormat, "addon-list-container");
formatStatus("addonsValStatus", ricState.addOnInfo.addons, null, addonValListFormat, "addon-val-list-container");
formatStatus("calibStatus", ricSystem.getCachedCalibInfo(), ricSystem.getCachedCalibInfo()?.validMs, tableFormat, "calib-list-container");
formatStatus("nameStatus", ricSystem.getCachedRICName(), ricSystem.getCachedRICName()?.validMs, tableFormat, "friendlyname-list-container");
formatStatus("wifiStatus", ricSystem.getCachedWifiStatus(), ricSystem.getCachedWifiStatus().validMs, tableFormat, "wifi-status-container");
setTimeout(updateStatus, 200);
}
function addButtons(defs: Array<{ name: string, button: string, func: any, params: Array<string | number | boolean> }>, container: Element) {
defs.forEach(def => {
const buttonDiv = document.createElement('div');
buttonDiv.classList.add('button-row');
let buttonText = def.button;
if (buttonText === "%1") {
buttonText = def.params[0] as string;
}
buttonDiv.innerHTML = `<div class = "button-container"><span class="example-name">${def.name}</span><button class="list-button">${buttonText}</button></div>`;
buttonDiv.addEventListener('click', () => {
def.func(def.params);
});
container.appendChild(buttonDiv);
});
}
function addFields(defs: Array<{ name: string, elId: string }>, container: Element): void {
defs.forEach(def => {
const fieldDiv = document.createElement('div');
fieldDiv.classList.add('field-row');
fieldDiv.innerHTML = `<div class = "field-container"><span class="example-name">${def.name}</span><input id="${def.elId}" class="list-field" type="text"></div>`;
container.appendChild(fieldDiv);
});
}
function genStatusBlock(id: string, elclass: string | Array<string>, parent: Element): Element {
const statusBlock = document.createElement('div');
if (typeof elclass === 'string') {
statusBlock.classList.add(elclass);
} else {
elclass.forEach(cls => {
statusBlock.classList.add(cls);
});
}
statusBlock.id = id;
parent.appendChild(statusBlock);
return statusBlock;
}
function component() {
const element = document.createElement('div');
element.classList.add('main-container');
const titleEl = document.createElement('h1');
titleEl.innerHTML = "RICJS Example";
titleEl.classList.add('title');
element.appendChild(titleEl);
const infoColumns = document.createElement('div');
infoColumns.classList.add('info-columns');
const statusContainer = document.createElement('div');
statusContainer.classList.add('status-container');
statusContainer.id = 'status-container';
genStatusBlock('event-field', ['info-status-container', 'info-status-scroll'], statusContainer);
genStatusBlock('time-status-container', 'info-status-container', statusContainer);
genStatusBlock('check-correct-ric-container', ['info-status-container', 'info-status-scroll'], statusContainer);
genStatusBlock('update-container', ['info-status-container', 'info-status-scroll'], statusContainer);
genStatusBlock('conn-perf-status-container', 'info-status-container', statusContainer);
genStatusBlock('robot-status-container', 'info-status-container', statusContainer);
genStatusBlock('power-status-container', 'info-status-container', statusContainer);
genStatusBlock('imu-status-container', 'info-status-container', statusContainer);
genStatusBlock('magneto-status-container', 'info-status-container', statusContainer);
genStatusBlock('servo-status-container', 'info-status-container', statusContainer);
genStatusBlock('sysinfo-list-container', 'info-status-container', statusContainer);
genStatusBlock('addon-list-container', 'info-status-container', statusContainer);
genStatusBlock('addon-val-list-container', 'info-status-container', statusContainer);
genStatusBlock('calib-list-container', 'info-status-container', statusContainer);
genStatusBlock('friendlyname-list-container', 'info-status-container', statusContainer);
genStatusBlock('wifi-status-container', 'info-status-container', statusContainer);
genStatusBlock('comms-stats-container', 'info-status-container', statusContainer);
genStatusBlock('response-field', ['info-status-container', 'info-status-scroll'], statusContainer);
const buttonsContainer = document.createElement('div');
buttonsContainer.classList.add('buttons-container');
// Buttons
const bleConnDefs = [
{ name: "Disconnect", button: "Disconnect", func: disconnect, params: [] as Array<string> },
{ name: "Connect BLE", button: "Connect", func: connectBLE, params: [] as Array<string> },
]
const wifiIPDefs = [
{ name: "Wifi IP", elId: "wifi-ip" },
]
const wifiPWDefs = [
{ name: "Wifi PW", elId: "wifi-pw" },
]
const wifiConnDefs = [
{ name: "Connect WiFi", button: "Connect", func: connectWiFi, params: [] as Array<string> },
]
const webserialConnDefs = [
{ name: "Connect WebSerial", button: "Connect", func: connectWebSerial, params: [] as Array<string>},
]
const buttonDefs = [
{ name: "BLE Perf", button: "Perf Test BLE", func: connPerfTest, params: [] },
{ name: "Enable reconnect", button: "Reconnect 10s", func: setReconnect, params: [true, 10] },
{ name: "Disable reconnect", button: "No Reconnect", func: setReconnect, params: [false, 0] },
{ name: "Correct RIC?", button: "Check LEDs", func: startCheckCorrectRIC, params: [false, 0] },
{ name: "Correct RIC?", button: "Accept RIC", func: acceptCheckCorrectRIC, params: [false, 0] },
{ name: "Correct RIC?", button: "Reject RIC", func: rejectCheckCorrectRIC, params: [false, 0] },
{ name: "Get SysInfo", button: "Get SysInfo", func: sysInfoGet, params: [] },
{ name: "Update", button: "Check", func: otaUpdateCheck, params: [] },
{ name: "Update", button: "Perform", func: otaUpdateStart, params: [] },
{ name: "Update", button: "Cancel", func: otaUpdateCancel, params: [] },
{ name: "Stream MP3", button: "%1", func: streamSoundFile, params: ["test440ToneQuietShort.mp3"] },
{ name: "Stream MP3", button: "%1", func: streamSoundFile, params: ["completed_tone_low_br.mp3"] },
{ name: "Stream MP3", button: "%1", func: streamSoundFile, params: ["unplgivy.mp3"] },
{ name: "Circle", button: "%1", func: sendREST, params: ["traj/circle"] },
{ name: "Kick", button: "%1", func: sendREST, params: ["traj/kick"] },
{ name: "Walk", button: "%1", func: sendREST, params: ["traj/dance"] },
{ name: "Wiggle", button: "%1", func: sendREST, params: ["traj/wiggle"] },
{ name: "Eyes Wide", button: "%1", func: sendREST, params: ["traj/eyesWide"] },
{ name: "Eyes Normal", button: "%1", func: sendREST, params: ["traj/eyesNormal"] },
{ name: "5V On", button: "%1", func: sendREST, params: ["pwrctrl/5von"] },
{ name: "5V Off", button: "%1", func: sendREST, params: ["pwrctrl/5voff"] },
{ name: "WiFi Scan", button: "Start", func: sendREST, params: ["wifiscan/start"] },
{ name: "WiFi Scan", button: "Results", func: sendREST, params: ["wifiscan/results"] },
{ name: "Send File", button: "%1", func: sendFile, params: ["unplgivy.mp3"]},
{ name: "Send File", button: "%1", func: sendFile, params: ["soundtest_44100_48kbps.mp3"]},
{ name: "Send File", button: "%1", func: sendFile, params: ["soundtest_44100_192kbps.mp3"]},
]
// Add buttonDefs
addButtons(webserialConnDefs, buttonsContainer);
addButtons(bleConnDefs, buttonsContainer);
addFields(wifiIPDefs, buttonsContainer);
addButtons(wifiConnDefs, buttonsContainer);
addFields(wifiPWDefs, buttonsContainer);
addButtons(buttonDefs, buttonsContainer);
infoColumns.appendChild(buttonsContainer);
infoColumns.appendChild(statusContainer);
element.appendChild(infoColumns);
startTime = Date.now();
setTimeout(updateStatus, 0);
return element;
}
async function sendFile(params: Array<string>): Promise<void>{
const fileName = params[0];
const filePath = "./assets/files/" + fileName;
const fileData = await fetch(filePath);
console.log(fileData);
const fileBuffer = await fileData.arrayBuffer();
const fileContents = new Uint8Array(fileBuffer);
await globalThis.ricConnector.sendFile(fileName, fileContents, (sent, total, progress)=>{console.debug(`fileSend sent ${sent} total ${total} progress ${progress}%`)});
}
document.body.appendChild(component());