UNPKG

@robotical/sensors-dashboard

Version:

A dashboard tool for depicting Marty data by Robotical

255 lines (229 loc) 7.29 kB
import MartyInterface from "../MartyInterface"; import Addon from "../../models/addons/Addon"; import AddonSub from "../../models/addons/AddonSub"; import renameValueLabel from "../../utils/rename-value-label"; import EXCLUDED_ADDONS from "../../utils/constants/excluded-addons"; import whoAmINameMap from "../../utils/constants/whoAmI-names"; import SERVO_NAMES_MAP from "../../utils/constants/servo-names"; import { ACCELEROMETER_NAME, ACCELEROMETER_NAME_X, ACCELEROMETER_NAME_Y, ACCELEROMETER_NAME_Z, MOTOR_CURRENT_NAME, MOTOR_POSITION_NAME, } from "../../utils/types/addon-names"; import type MartyType from "@robotical/webapp-types/dist-types/src/application/RAFTs/Marty/Marty"; import { type ROSSerialAddOnStatusList, type ROSSerialIMU, type ROSSerialSmartServos, } from "@robotical/roboticaljs"; type MartyAddon = ROSSerialAddOnStatusList["addons"][number]; type MartyServo = ROSSerialSmartServos["smartServos"][number]; const UPDATE_INTERVAL_MS = 750; const cloneAddons = (addons: MartyAddon[]): MartyAddon[] => addons.map((addon) => ({ ...addon, vals: { ...addon.vals }, })); const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max); const mutateNumber = (value: number, min: number, max: number, maxDelta: number) => { const delta = (Math.random() * 2 - 1) * maxDelta; const next = value + delta; return Number(clamp(next, min, max).toFixed(2)); }; const randomBoolean = (current: number, flipChance = 0.15) => { if (Math.random() < flipChance) { return current > 0.5 ? 0 : 1; } return current; }; const createInitialAddons = (): MartyAddon[] => [ { id: 0, deviceTypeID: 0, whoAmI: "IRFoot", name: "Touch", status: 0, vals: { Touch: 0, Touch_IR: 180, Touch_Ambient: 200, }, }, { id: 1, deviceTypeID: 0, whoAmI: "coloursensor", name: "Color", status: 0, vals: { ColorClear: 120, ColorRed: 60, ColorGreen: 75, ColorBlue: 80, }, }, { id: 2, deviceTypeID: 0, whoAmI: "lightsensor", name: "Light", status: 0, vals: { LightReading1: 90, LightReading2: 110, LightReading3: 105, }, }, ]; const createInitialServos = (): MartyServo[] => Array.from({ length: SERVO_NAMES_MAP.length }, (_, id) => ({ id, pos: 90, current: 350, status: 0, })); const createInitialAccel = (): ROSSerialIMU["accel"] => ({ x: 0.04, y: -0.05, z: 0.98, }); const buildMartyAddonList = ( addons: MartyAddon[], servos: MartyServo[], accel: ROSSerialIMU["accel"] ) => { const addonObjects: Addon[] = []; for (const addon of addons) { const whoAmI = whoAmINameMap(addon.whoAmI); if (EXCLUDED_ADDONS.includes(whoAmI)) continue; const subAddons: AddonSub[] = []; for (const valKey in addon.vals) { const value = addon.vals[valKey]; const addonInputName = renameValueLabel(valKey, addon.name); subAddons.push(new AddonSub(addonInputName, typeof value === "number" ? value : 0)); } addonObjects.push(new Addon(whoAmI, subAddons)); } const posAddonSub: AddonSub[] = []; const currAddonSub: AddonSub[] = []; for (const servo of servos) { const servoName = SERVO_NAMES_MAP[servo.id] || `servo-${servo.id}`; posAddonSub.push(new AddonSub(servoName, servo.pos)); currAddonSub.push(new AddonSub(servoName, servo.current)); } addonObjects.push(new Addon(MOTOR_POSITION_NAME, posAddonSub)); addonObjects.push(new Addon(MOTOR_CURRENT_NAME, currAddonSub)); const accelAddonSub: AddonSub[] = [ new AddonSub(ACCELEROMETER_NAME_X, accel.x), new AddonSub(ACCELEROMETER_NAME_Y, accel.y), new AddonSub(ACCELEROMETER_NAME_Z, accel.z), ]; addonObjects.push(new Addon(ACCELEROMETER_NAME, accelAddonSub)); return addonObjects; }; export class MockMartyInterface extends MartyInterface { private updateTimer: number | null = null; private currentAddons: MartyAddon[]; private currentServos: MartyServo[]; private currentAccel: ROSSerialIMU["accel"]; private latestAddonList: Addon[] = []; constructor() { super({} as MartyType, { autoSubscribe: false }); this.currentAddons = createInitialAddons(); this.currentServos = createInitialServos(); this.currentAccel = createInitialAccel(); this.latestAddonList = buildMartyAddonList( this.currentAddons, this.currentServos, this.currentAccel ); } start() { this.dispatchCurrentState(); this.updateTimer = window.setInterval(() => { this.tick(); }, UPDATE_INTERVAL_MS); } getAvailableAddons() { return this.latestAddonList; } unsubscribeFromPublishedData() { if (this.updateTimer !== null) { clearInterval(this.updateTimer); this.updateTimer = null; } } private dispatchCurrentState() { this.latestAddonList = buildMartyAddonList( this.currentAddons, this.currentServos, this.currentAccel ); this.setAddons(cloneAddons(this.currentAddons)); this.setServos({ smartServos: this.currentServos.map((servo) => ({ ...servo })), } as ROSSerialSmartServos); this.setAccel({ accel: { ...this.currentAccel }, } as ROSSerialIMU); } private tick() { this.currentAddons = this.currentAddons.map((addon) => this.mutateAddon(addon)); this.currentServos = this.currentServos.map((servo) => this.mutateServo(servo)); this.currentAccel = this.mutateAccel(this.currentAccel); this.dispatchCurrentState(); } private mutateAddon(addon: MartyAddon): MartyAddon { if (addon.name === "Touch") { return { ...addon, vals: { Touch: randomBoolean(Number(addon.vals.Touch ?? 0)), Touch_IR: mutateNumber(Number(addon.vals.Touch_IR ?? 0), 80, 320, 20), Touch_Ambient: mutateNumber(Number(addon.vals.Touch_Ambient ?? 0), 80, 320, 15), }, }; } if (addon.name === "Color") { return { ...addon, vals: { ColorClear: mutateNumber(Number(addon.vals.ColorClear ?? 0), 20, 255, 18), ColorRed: mutateNumber(Number(addon.vals.ColorRed ?? 0), 10, 255, 18), ColorGreen: mutateNumber(Number(addon.vals.ColorGreen ?? 0), 10, 255, 18), ColorBlue: mutateNumber(Number(addon.vals.ColorBlue ?? 0), 10, 255, 18), }, }; } if (addon.name === "Light") { return { ...addon, vals: { LightReading1: mutateNumber(Number(addon.vals.LightReading1 ?? 0), 0, 255, 15), LightReading2: mutateNumber(Number(addon.vals.LightReading2 ?? 0), 0, 255, 15), LightReading3: mutateNumber(Number(addon.vals.LightReading3 ?? 0), 0, 255, 15), }, }; } return addon; } private mutateServo(servo: MartyServo): MartyServo { return { ...servo, pos: mutateNumber(servo.pos, -90, 90, 6), current: mutateNumber(servo.current, 150, 700, 40), }; } private mutateAccel(accel: ROSSerialIMU["accel"]): ROSSerialIMU["accel"] { return { x: mutateNumber(accel.x, -1.2, 1.2, 0.08), y: mutateNumber(accel.y, -1.2, 1.2, 0.08), z: mutateNumber(accel.z, 0.7, 1.2, 0.05), }; } } export default MockMartyInterface;