@robotical/sensors-dashboard
Version:
A dashboard tool for depicting Marty data by Robotical
245 lines (214 loc) • 7.7 kB
text/typescript
import CogInterface from "../CogInterface";
import Addon from "../../models/addons/Addon";
import AddonSub from "../../models/addons/AddonSub";
import {
ACCELEROMETER_NAME,
ACCELEROMETER_NAME_X,
ACCELEROMETER_NAME_Y,
ACCELEROMETER_NAME_Z,
COG_BUTTON_CLICK_NAME,
COG_IR_BUTTON,
COG_IR_LEFT,
COG_IR_RIGHT,
COG_LIGHT_AMB,
COG_LIGHT_NAME,
COG_LIGHT_SENSE_NAME,
COG_MOVEMENT_TYPE_NAME,
COG_OBJECT_SENSE_NAME,
COG_ROTATION_NAME,
COG_STATE_NAME,
COG_TILT_NAME,
GYROSCOPE_NAME,
GYROSCOPE_NAME_X,
GYROSCOPE_NAME_Y,
GYROSCOPE_NAME_Z,
} from "../../utils/types/addon-names";
import type Cog from "@robotical/webapp-types/dist-types/src/application/RAFTs/Cog/Cog";
import type PublishedDataAnalyser from "@robotical/webapp-types/dist-types/src/application/RAFTs/Cog/PublishedDataAnalyser";
import { type SimplifiedCogStateInfo } from "@robotical/roboticaljs";
type CogState = PublishedDataAnalyser["cogState"];
const UPDATE_INTERVAL_MS = 750;
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 pickWithChance = <T,>(current: T, options: readonly T[], changeChance: number): T => {
if (Math.random() > changeChance) return current;
return options[Math.floor(Math.random() * options.length)];
};
const createInitialLight = (): SimplifiedCogStateInfo["light"] => ({
amb0: 120,
ir0: 140,
ir1: 135,
ir2: 20,
});
const createInitialAccel = (): SimplifiedCogStateInfo["accelerometer"] => ({
ax: 0.05,
ay: -0.08,
az: 0.97,
});
const createInitialGyro = (): SimplifiedCogStateInfo["gyroscope"] => ({
gx: -0.5,
gy: 0.4,
gz: 0.1,
});
const createInitialPower = (): SimplifiedCogStateInfo["power"] => ({
battV: 7.4,
usb: "disconnected",
});
const createInitialCogState = (): CogState => ({
tilt: "none",
movementType: "none",
rotation: "none",
buttonClick: "none",
objectSense: "none",
lightSense: "mid",
steering: "none",
});
const buildCogAddonList = (
light: SimplifiedCogStateInfo["light"],
accel: SimplifiedCogStateInfo["accelerometer"],
gyro: SimplifiedCogStateInfo["gyroscope"],
cogState: CogState
) => {
const lightAddon = new Addon(COG_LIGHT_NAME, [
new AddonSub(COG_LIGHT_AMB, light.amb0),
new AddonSub(COG_IR_LEFT, light.ir0),
new AddonSub(COG_IR_RIGHT, light.ir1),
new AddonSub(COG_IR_BUTTON, light.ir2),
]);
const accelAddon = new Addon(ACCELEROMETER_NAME, [
new AddonSub(ACCELEROMETER_NAME_X, accel.ax),
new AddonSub(ACCELEROMETER_NAME_Y, accel.ay),
new AddonSub(ACCELEROMETER_NAME_Z, accel.az),
]);
const gyroAddon = new Addon(GYROSCOPE_NAME, [
new AddonSub(GYROSCOPE_NAME_X, gyro.gx),
new AddonSub(GYROSCOPE_NAME_Y, gyro.gy),
new AddonSub(GYROSCOPE_NAME_Z, gyro.gz),
]);
const stateAddon = new Addon(COG_STATE_NAME, [
new AddonSub(COG_TILT_NAME, cogState.tilt),
new AddonSub(COG_MOVEMENT_TYPE_NAME, cogState.movementType),
new AddonSub(COG_ROTATION_NAME, cogState.rotation),
new AddonSub(
COG_BUTTON_CLICK_NAME,
cogState.buttonClick === "none" ? "release" : cogState.buttonClick
),
new AddonSub(COG_OBJECT_SENSE_NAME, cogState.objectSense),
new AddonSub(COG_LIGHT_SENSE_NAME, cogState.lightSense),
]);
return [lightAddon, accelAddon, gyroAddon, stateAddon];
};
export class MockCogInterface extends CogInterface {
private updateTimer: number | null = null;
private currentLight = createInitialLight();
private currentAccel = createInitialAccel();
private currentGyro = createInitialGyro();
private currentPower = createInitialPower();
private currentCogState = createInitialCogState();
private latestAddonList = buildCogAddonList(
this.currentLight,
this.currentAccel,
this.currentGyro,
this.currentCogState
);
constructor() {
super({} as Cog, { autoSubscribe: false });
}
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 = buildCogAddonList(
this.currentLight,
this.currentAccel,
this.currentGyro,
this.currentCogState
);
this.setLight({ ...this.currentLight });
this.setAccel({ ...this.currentAccel });
this.setGyro({ ...this.currentGyro });
this.setCogPublishedState({ ...this.currentCogState });
}
private tick() {
this.currentLight = this.mutateLight(this.currentLight);
this.currentAccel = this.mutateAccel(this.currentAccel);
this.currentGyro = this.mutateGyro(this.currentGyro);
this.currentPower = this.mutatePower(this.currentPower);
this.currentCogState = this.mutateCogState(this.currentCogState);
this.dispatchCurrentState();
}
private mutateLight(light: SimplifiedCogStateInfo["light"]): SimplifiedCogStateInfo["light"] {
return {
amb0: mutateNumber(light.amb0, 20, 250, 12),
ir0: mutateNumber(light.ir0, 10, 380, 18),
ir1: mutateNumber(light.ir1, 10, 380, 18),
ir2: mutateNumber(light.ir2, 0, 180, 12),
};
}
private mutateAccel(
accel: SimplifiedCogStateInfo["accelerometer"]
): SimplifiedCogStateInfo["accelerometer"] {
return {
ax: mutateNumber(accel.ax, -1.1, 1.1, 0.07),
ay: mutateNumber(accel.ay, -1.1, 1.1, 0.07),
az: mutateNumber(accel.az, 0.6, 1.1, 0.05),
};
}
private mutateGyro(gyro: SimplifiedCogStateInfo["gyroscope"]): SimplifiedCogStateInfo["gyroscope"] {
return {
gx: mutateNumber(gyro.gx, -2.5, 2.5, 0.2),
gy: mutateNumber(gyro.gy, -2.5, 2.5, 0.2),
gz: mutateNumber(gyro.gz, -2.5, 2.5, 0.3),
};
}
private mutatePower(power: SimplifiedCogStateInfo["power"]): SimplifiedCogStateInfo["power"] {
return {
battV: mutateNumber(power.battV, 6.8, 8, 0.05),
usb: power.usb,
};
}
private mutateCogState(prev: CogState): CogState {
const tiltOptions: readonly CogState["tilt"][] = ["none", "forward", "backward", "left", "right"];
const movementOptions: readonly CogState["movementType"][] = ["none", "shake", "move"];
const rotationOptions: readonly CogState["rotation"][] = ["none", "clockwise", "counterClockwise"];
const objectSenseOptions: readonly CogState["objectSense"][] = ["none", "left", "right"];
const lightSenseOptions: readonly CogState["lightSense"][] = ["none", "low", "mid", "high"];
let buttonClick = prev.buttonClick;
if (prev.buttonClick === "none" && Math.random() < 0.08) {
buttonClick = "click";
} else if (prev.buttonClick === "click") {
buttonClick = "release";
} else if (prev.buttonClick === "release") {
buttonClick = Math.random() < 0.3 ? "none" : "release";
} else if (Math.random() < 0.05) {
buttonClick = "none";
}
return {
tilt: pickWithChance(prev.tilt, tiltOptions, 0.12),
movementType: pickWithChance(prev.movementType, movementOptions, 0.1),
rotation: pickWithChance(prev.rotation, rotationOptions, 0.1),
buttonClick,
objectSense: pickWithChance(prev.objectSense, objectSenseOptions, 0.08),
lightSense: pickWithChance(prev.lightSense, lightSenseOptions, 0.1),
steering: prev.steering,
};
}
}
export default MockCogInterface;