feat(sensors): runtime seam for overlay-registered sensor controls

registerSensorControls() lets a private build add SensorControlDef entries for
sensors it ships outside the OSS tree (e.g. the DFRobot Gravity analog family)
so they get the live slider panel; every SENSOR_CONTROLS lookup now goes
through getSensorControl(id) which falls back to the registered map. Dead code
in a pure OSS build, same contract as proBoardRegistry / registerComponentDoc.
This commit is contained in:
David Montero Crespo 2026-07-23 21:32:14 +02:00
parent 69819ee614
commit 116afcab98
4 changed files with 27 additions and 10 deletions

View File

@ -9,7 +9,7 @@
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
SENSOR_CONTROLS,
getSensorControl,
type SensorControl,
type SliderControl,
} from '../../simulation/sensorControlConfig';
@ -49,7 +49,7 @@ export const SensorControlPanel: React.FC<SensorControlPanelProps> = ({
onClose,
}) => {
const { t } = useTranslation();
const def = SENSOR_CONTROLS[metadataId];
const def = getSensorControl(metadataId);
// Local slider/button state — hydrated from the registry's last-known
// values for this componentId (so reopening a sensor or switching between

View File

@ -11,7 +11,7 @@ import { ComponentPickerModal } from '../ComponentPickerModal';
import { ComponentPropertyDialog } from './ComponentPropertyDialog';
import { CustomChipDialog } from '../customChips/CustomChipDialog';
import { SensorControlPanel } from './SensorControlPanel';
import { SENSOR_CONTROLS } from '../../simulation/sensorControlConfig';
import { SENSOR_CONTROLS, getSensorControl } from '../../simulation/sensorControlConfig';
import { DynamicComponent, createComponentFromMetadata } from '../DynamicComponent';
import { InstrumentComponent } from '../components-instruments/InstrumentComponent';
import { ComponentRegistry } from '../../services/ComponentRegistry';
@ -996,7 +996,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
const component = componentsRef.current.find((c) => c.id === touchId);
if (component) {
if (interactionRunningRef.current) {
if (SENSOR_CONTROLS[component.metadataId] !== undefined) {
if (getSensorControl(component.metadataId) !== undefined) {
setSensorControlComponentId(touchId);
setSensorControlMetadataId(component.metadataId);
}
@ -1417,7 +1417,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
// panel). Mirrors the touch tap flow above.
if (interactionRunning) {
const component = components.find((c) => c.id === componentId);
const isSensor = !!component && SENSOR_CONTROLS[component.metadataId] !== undefined;
const isSensor = !!component && getSensorControl(component.metadataId) !== undefined;
if (!isSensor) return;
}
@ -1710,7 +1710,7 @@ export const SimulatorCanvas = ({ headerSlot }: SimulatorCanvasProps = {}) => {
// handle its own clicks, so we suppress the property
// dialog entirely. This is the path that also unblocks
// wokwi-slide-switch toggling in the digital examples.
if (SENSOR_CONTROLS[component.metadataId] !== undefined) {
if (getSensorControl(component.metadataId) !== undefined) {
setSensorControlComponentId(draggedComponentId);
setSensorControlMetadataId(component.metadataId);
}

View File

@ -377,3 +377,20 @@ export const SENSOR_CONTROLS: Record<string, SensorControlDef> = {
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];
}

View File

@ -53,7 +53,7 @@ import {
updateWires as icUpdateWires,
setInterconnectRuntime,
} from '../simulation/Interconnect';
import { SENSOR_CONTROLS } from '../simulation/sensorControlConfig';
import { SENSOR_CONTROLS, getSensorControl } from '../simulation/sensorControlConfig';
import { dispatchSensorUpdate } from '../simulation/SensorUpdateRegistry';
// ── Sensor pre-registration ──────────────────────────────────────────────────
@ -2150,18 +2150,18 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
// 2.5V) and refreshes the panel's cached value; bumping sensorResetNonce
// remounts the open SensorControlPanel so its slider snaps back too.
const sensorComps = get().components.filter(
(c) => c.metadataId && SENSOR_CONTROLS[c.metadataId],
(c) => c.metadataId && getSensorControl(c.metadataId),
);
if (sensorComps.length > 0) {
set((s) => ({
components: s.components.map((c) => {
const def = c.metadataId ? SENSOR_CONTROLS[c.metadataId] : undefined;
const def = getSensorControl(c.metadataId);
return def ? { ...c, properties: { ...c.properties, ...def.defaultValues } } : c;
}),
sensorResetNonce: s.sensorResetNonce + 1,
}));
for (const c of sensorComps) {
dispatchSensorUpdate(c.id, SENSOR_CONTROLS[c.metadataId].defaultValues);
dispatchSensorUpdate(c.id, getSensorControl(c.metadataId)!.defaultValues);
}
}
},