velxio/frontend/src/simulation/sensorControlConfig.ts

467 lines
13 KiB
TypeScript
Raw Normal View History

/**
* sensorControlConfig.ts — defines the interactive controls shown in the
* SensorControlPanel for each sensor component type.
*
* Used by:
* - SensorControlPanel.tsx (renders the controls)
* - SimulatorCanvas.tsx (decides whether to show the panel on click)
*/
export interface SliderControl {
type: 'slider';
key: string;
label: string;
min: number;
max: number;
step: number;
unit: string;
defaultValue: number;
/** Optional custom formatter — e.g. to show "24.0°C" instead of "24" */
formatValue?: (v: number) => string;
}
export interface ButtonControl {
type: 'button';
key: string;
label: string;
}
export type SensorControl = SliderControl | ButtonControl;
export interface SensorControlDef {
title: string;
controls: SensorControl[];
defaultValues: Record<string, number | boolean>;
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
const oneDecimal = (v: number) => v.toFixed(1);
const twoDecimal = (v: number) => v.toFixed(2);
// ─── Sensor Control Definitions ──────────────────────────────────────────────
export const SENSOR_CONTROLS: Record<string, SensorControlDef> = {
// ── MPU-6050 6-axis IMU ────────────────────────────────────────────────────
mpu6050: {
title: 'MPU6050 Accelerometer + Gyroscope',
controls: [
// Acceleration
{
type: 'slider',
key: 'accelX',
label: 'X',
min: -2,
max: 2,
step: 0.01,
unit: 'g',
defaultValue: 0,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'accelY',
label: 'Y',
min: -2,
max: 2,
step: 0.01,
unit: 'g',
defaultValue: 0,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'accelZ',
label: 'Z',
min: -2,
max: 2,
step: 0.01,
unit: 'g',
defaultValue: 1,
formatValue: oneDecimal,
},
// Rotation (gyro)
{
type: 'slider',
key: 'gyroX',
label: 'X',
min: -250,
max: 250,
step: 1,
unit: '°/sec',
defaultValue: 0,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'gyroY',
label: 'Y',
min: -250,
max: 250,
step: 1,
unit: '°/sec',
defaultValue: 0,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'gyroZ',
label: 'Z',
min: -250,
max: 250,
step: 1,
unit: '°/sec',
defaultValue: 0,
formatValue: oneDecimal,
},
// Temperature
{
type: 'slider',
key: 'temp',
label: 'Temperature',
min: -40,
max: 85,
step: 1,
unit: '°C',
defaultValue: 24,
formatValue: oneDecimal,
},
],
defaultValues: { accelX: 0, accelY: 0, accelZ: 1, gyroX: 0, gyroY: 0, gyroZ: 0, temp: 24 },
},
// ── DHT22 Temperature / Humidity ──────────────────────────────────────────
dht22: {
title: 'DHT22 Temperature & Humidity',
controls: [
{
type: 'slider',
key: 'temperature',
label: 'Temperature',
min: -40,
max: 80,
step: 0.5,
unit: '°C',
defaultValue: 25,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'humidity',
label: 'Humidity',
min: 0,
max: 100,
step: 0.5,
unit: '%',
defaultValue: 50,
formatValue: oneDecimal,
},
],
defaultValues: { temperature: 25, humidity: 50 },
},
// ── BMP280 Barometric Pressure + Temperature ───────────────────────────────
bmp280: {
title: 'BMP280 Barometric Pressure Sensor',
controls: [
{
type: 'slider',
key: 'temperature',
label: 'Temperature',
min: -40,
max: 85,
step: 1,
unit: '°C',
defaultValue: 24,
formatValue: oneDecimal,
},
{
type: 'slider',
key: 'pressure',
label: 'Pressure',
min: 300,
max: 1100,
step: 0.25,
unit: 'hPa',
defaultValue: 1013.25,
formatValue: twoDecimal,
},
],
defaultValues: { temperature: 24, pressure: 1013.25 },
},
feat(components): DS3231 RTC module + GPS NEO-6M NMEA talker DS3231 (I2C 0x68): - New velxio-ds3231 Web Component (4-pin module: GND/VCC/SDA/SCL) with pinInfo per CLAUDE.md 6a; registered in elements-register + main.tsx. - Catalog entry in scripts/component-overrides.json (_customComponents) mirrored into components-metadata.json exactly as the generator emits it (verified by cloning wokwi-elements and running generate:metadata: zero drift). - The existing VirtualDS3231 device sim (DS1307-compatible time regs 0x00-0x06 + control 0x0E / status 0x0F / temperature 0x11-0x12 at 0.25 C steps) is now user-reachable from the picker; the AVR/RP2040 attach path gains live temperature updates via the SensorControlPanel (new ds3231 slider in sensorControlConfig). GPS NEO-6M (UART talker, 9600 baud): - New velxio-gps-neo6m Web Component (VCC/RX/TX/GND, pulsing PPS LED). - New part sim GpsParts.ts: emits a valid GPGGA+GPRMC cycle per second (correct XOR checksums, ddmm.mmmmm coords, UTC clock advancing 1 s/cycle) with lat/lng/altitude/speed configurable via property dialog and live via a new SensorControlPanel entry. Defaults: Madrid 40.4168 N 3.7038 W. - Injection routes, chosen by classifying the wired board pin: 1) hardware UART RX -> new uniform sim.feedUart(uart, data) seam (AVR USART0, RP2040 uart0/1 via rp2040js feedByte, ESP32 uart0/2 and STM32 USART1/2 via their bridge shims); byte feed is paced in 8-byte wall-clock chunks so 32-byte RX FIFOs never overflow; 2) any other digital pin on a cycle-accurate sim -> real 8N1 bit-banged waveform via schedulePinChange, so SoftwareSerial decodes it; 3) feedUart rejection (e.g. Mega USART1-3, not modelled by avr8js) degrades permanently to the bit-banged path. - Interconnect already probed sim.feedUart; implementing it also upgrades cross-board UART byte shortcuts for RP2040 uart1. Tests: new gps-neo6m.test.ts (16 tests: NMEA builders, byte injection on Uno RX0 / ESP32 RX2, Mega RX1 fallback, waveform decode back to $GPGGA, live position updates, PPS pulse) + 3 new ds3231 AVR-path tests in protocol-parts.test.ts. Full frontend suite: 2294 passed, 2 pre-existing Galaksija Z80 timeouts that pass in isolation. npx tsc --noEmit clean.
2026-08-01 01:38:06 +07:00
// ── DS3231 RTC (on-chip temperature sensor) ────────────────────────────────
ds3231: {
title: 'DS3231 RTC Temperature',
controls: [
{
type: 'slider',
key: 'temperature',
label: 'Temperature',
min: -40,
max: 85,
step: 0.25,
unit: '°C',
defaultValue: 25,
formatValue: twoDecimal,
},
],
defaultValues: { temperature: 25 },
},
// ── GPS NEO-6M (position fed into the NMEA stream) ─────────────────────────
'gps-neo6m': {
title: 'GPS NEO-6M Position',
controls: [
{
type: 'slider',
key: 'lat',
label: 'Latitude',
min: -90,
max: 90,
step: 0.0001,
unit: '°',
defaultValue: 40.4168,
formatValue: (v: number) => v.toFixed(4),
},
{
type: 'slider',
key: 'lng',
label: 'Longitude',
min: -180,
max: 180,
step: 0.0001,
unit: '°',
defaultValue: -3.7038,
formatValue: (v: number) => v.toFixed(4),
},
{
type: 'slider',
key: 'altitude',
label: 'Altitude',
min: -100,
max: 9000,
step: 1,
unit: 'm',
defaultValue: 667,
},
{
type: 'slider',
key: 'speed',
label: 'Speed',
min: 0,
max: 200,
step: 0.5,
unit: 'kn',
defaultValue: 0,
formatValue: oneDecimal,
},
],
defaultValues: { lat: 40.4168, lng: -3.7038, altitude: 667, speed: 0 },
},
// ── HC-SR04 Ultrasonic Distance ───────────────────────────────────────────
'hc-sr04': {
title: 'Ultrasonic Distance Sensor',
controls: [
{
type: 'slider',
key: 'distance',
label: 'Distance',
min: 2,
max: 400,
step: 1,
unit: 'cm',
defaultValue: 10,
},
],
defaultValues: { distance: 10 },
},
// ── Photoresistor (LDR) ───────────────────────────────────────────────────
'photoresistor-sensor': {
title: 'Photoresistor (LDR)',
controls: [
{
type: 'slider',
key: 'lux',
label: 'Illumination',
min: 0,
max: 1000,
step: 1,
unit: 'lux',
defaultValue: 500,
},
],
defaultValues: { lux: 500 },
},
// ── Photodiode ────────────────────────────────────────────────────────────
photodiode: {
title: 'Photodiode',
controls: [
{
type: 'slider',
key: 'lux',
label: 'Illumination',
min: 0,
max: 1000,
step: 1,
unit: 'lux',
defaultValue: 500,
},
],
defaultValues: { lux: 500 },
},
// ── PIR Motion Sensor ─────────────────────────────────────────────────────
'pir-motion-sensor': {
title: 'PIR Motion Sensor',
controls: [{ type: 'button', key: 'trigger', label: 'Simulate motion' }],
defaultValues: {},
},
// ── NTC Temperature Sensor ────────────────────────────────────────────────
'ntc-temperature-sensor': {
title: 'NTC Temperature Sensor',
controls: [
{
type: 'slider',
key: 'temperature',
label: 'Temperature',
min: -40,
max: 125,
step: 1,
unit: '°C',
defaultValue: 25,
formatValue: oneDecimal,
},
],
defaultValues: { temperature: 25 },
},
// ── Gas Sensor (MQ-series) ────────────────────────────────────────────────
'gas-sensor': {
title: 'Gas Sensor (MQ-series)',
controls: [
{
type: 'slider',
key: 'gasLevel',
label: 'Gas Level',
min: 0,
max: 1023,
step: 1,
unit: '',
defaultValue: 100,
},
],
defaultValues: { gasLevel: 100 },
},
// ── Flame Sensor ──────────────────────────────────────────────────────────
'flame-sensor': {
title: 'Flame Sensor',
controls: [
{
type: 'slider',
key: 'intensity',
label: 'Flame Intensity',
min: 0,
max: 1023,
step: 1,
unit: '',
defaultValue: 0,
},
],
defaultValues: { intensity: 0 },
},
// ── Big Sound Sensor (FC-04) ──────────────────────────────────────────────
'big-sound-sensor': {
title: 'Sound Sensor',
controls: [
{
type: 'slider',
key: 'soundLevel',
label: 'Sound Level',
min: 0,
max: 1023,
step: 1,
unit: '',
defaultValue: 512,
},
],
defaultValues: { soundLevel: 512 },
},
// ── Small Sound Sensor (KY-038) ───────────────────────────────────────────
'small-sound-sensor': {
title: 'Sound Sensor (KY-038)',
controls: [
{
type: 'slider',
key: 'soundLevel',
label: 'Sound Level',
min: 0,
max: 1023,
step: 1,
unit: '',
defaultValue: 512,
},
],
defaultValues: { soundLevel: 512 },
},
// ── Tilt Switch ───────────────────────────────────────────────────────────
'tilt-switch': {
title: 'Tilt Switch',
controls: [{ type: 'button', key: 'toggle', label: 'Toggle tilt' }],
defaultValues: {},
},
// ── Analog Joystick ───────────────────────────────────────────────────────
'analog-joystick': {
title: 'Analog Joystick',
controls: [
{
type: 'slider',
key: 'xAxis',
label: 'X Axis',
min: -512,
max: 512,
step: 1,
unit: '',
defaultValue: 0,
},
{
type: 'slider',
key: 'yAxis',
label: 'Y Axis',
min: -512,
max: 512,
step: 1,
unit: '',
defaultValue: 0,
},
],
defaultValues: { xAxis: 0, yAxis: 0 },
},
};
// ── Overlay seam ─────────────────────────────────────────────────────────────
// A private build (velxio.com) registers sensor-control definitions for the
// sensors it ships outside the OSS tree (e.g. the DFRobot Gravity analog
// family). Same contract as proBoardRegistry / registerComponentDoc: dead code
// in a pure OSS build. Read every SENSOR_CONTROLS lookup through
// getSensorControl() so overlay-registered sensors surface their slider panel.
const proSensorControls: Record<string, SensorControlDef> = {};
export function registerSensorControls(defs: Record<string, SensorControlDef>): void {
Object.assign(proSensorControls, defs);
}
export function getSensorControl(id: string | null | undefined): SensorControlDef | undefined {
if (!id) return undefined;
return SENSOR_CONTROLS[id] ?? proSensorControls[id];
}