From 304c749c9eb27710726aa39bc10239f2e74e99b0 Mon Sep 17 00:00:00 2001 From: Doug Martin Date: Tue, 25 Jun 2024 12:24:50 -0400 Subject: [PATCH] feat: Add sensor picker [PT-187809478] --- src/mobile-app/components/sensor.module.scss | 8 ++ src/mobile-app/components/sensor.tsx | 22 +++- src/sensors/device-sensor.ts | 14 ++- src/sensors/devices/device.ts | 15 ++- src/sensors/devices/gdx-sensor.ts | 103 ++++++++++++------- src/sensors/mock-sensor.ts | 14 ++- src/sensors/sensor.ts | 9 +- src/shared/components/data-table-field.tsx | 13 ++- 8 files changed, 141 insertions(+), 57 deletions(-) diff --git a/src/mobile-app/components/sensor.module.scss b/src/mobile-app/components/sensor.module.scss index 30f85dc..474d8e0 100644 --- a/src/mobile-app/components/sensor.module.scss +++ b/src/mobile-app/components/sensor.module.scss @@ -18,6 +18,14 @@ flex-wrap: nowrap; align-items: center; flex-grow: 1; /* to allow the label to take up as much room as possible */ + + // for selectable sensor + select { + margin-left: 7px; + padding: 8px; + border-radius: 5px; + background-color: #fff; + } } .error { diff --git a/src/mobile-app/components/sensor.tsx b/src/mobile-app/components/sensor.tsx index fd38522..2226296 100644 --- a/src/mobile-app/components/sensor.tsx +++ b/src/mobile-app/components/sensor.tsx @@ -51,7 +51,9 @@ interface ISensorComponentProps { setManualEntryMode?: (flag: boolean) => void; isTimeSeries: boolean; timeSeriesCapabilities?: ITimeSeriesCapabilities; + selectableSensorId?: any; setTimeSeriesMeasurementPeriod?: (measurementPeriod: number) => void; + setSelectableSensorId?: (id: any) => void; } const iconClass = { @@ -66,7 +68,7 @@ const iconClassHi = { error: css.errorIcon }; -export const SensorComponent: React.FC = ({sensor, manualEntryMode, setManualEntryMode, isTimeSeries, timeSeriesCapabilities, setTimeSeriesMeasurementPeriod}) => { +export const SensorComponent: React.FC = ({sensor, manualEntryMode, setManualEntryMode, isTimeSeries, timeSeriesCapabilities, selectableSensorId, setTimeSeriesMeasurementPeriod, setSelectableSensorId}) => { const {connected, connecting, deviceName, values, error} = useSensor(sensor); const [devicesFound, setDevicesFound] = useState([]); @@ -133,6 +135,10 @@ export const SensorComponent: React.FC = ({sensor, manual setTimeSeriesMeasurementPeriod?.(newPeriod); }; + const handleSelectSelectableSensor = (e: React.ChangeEvent) => { + setSelectableSensorId?.(e.target.value); + }; + const connect = () => sensor.connect({ onDevicesFound: ({devices, select, cancel}) => { setDevicesFound(devices); @@ -174,10 +180,22 @@ export const SensorComponent: React.FC = ({sensor, manual ); + const renderDeviceName = () => { + if (sensor.selectableSensors.length === 0) { + return <>{deviceName}; + } + + return ( + + ); + }; + const renderConnected = () => (
{renderIcon("connected")} - Connected: {deviceName} + Connected: {renderDeviceName()}
); diff --git a/src/sensors/device-sensor.ts b/src/sensors/device-sensor.ts index 10b901a..edc0057 100644 --- a/src/sensors/device-sensor.ts +++ b/src/sensors/device-sensor.ts @@ -1,6 +1,6 @@ import { Sensor, ISensorValues, ISensorOptions, ISensorCapabilities, ISetConnectedOptions, IPollOptions, IConnectOptions, ITimeSeriesCapabilities } from "./sensor"; -import { Device } from "./devices/device"; +import { Device, ISelectableSensorInfo } from "./devices/device"; import { SensorTag2Device } from "./devices/sensor-tag-cc2650"; import { SensorTagCC1350Device } from "./devices/sensor-tag-cc1350"; import { MultiSensorDevice } from "./devices/multi-sensor"; @@ -93,8 +93,12 @@ export class DeviceSensor extends Sensor { return Promise.resolve(); } - public get timeSeriesCapabilities() { - return this.device?.timeSeriesCapabilities; + public timeSeriesCapabilities(selectableSensorId: any) { + return this.device?.timeSeriesCapabilities(selectableSensorId); + } + + public get selectableSensors(): ISelectableSensorInfo[] { + return this.device?.selectableSensors ?? []; } protected pollValues(options: IPollOptions): Promise { @@ -119,9 +123,9 @@ export class DeviceSensor extends Sensor { }); } - public collectTimeSeries(measurementPeriod: number, callback: (values: IDataTableTimeData[]) => void): () => void { + public collectTimeSeries(measurementPeriod: number, selectableSensorId: any, callback: (values: IDataTableTimeData[]) => void): () => void { if (this.device) { - return this.device.collectTimeSeries(measurementPeriod, callback); + return this.device.collectTimeSeries(measurementPeriod, selectableSensorId, callback); } return () => { // noop diff --git a/src/sensors/devices/device.ts b/src/sensors/devices/device.ts index c50d175..1a867b9 100644 --- a/src/sensors/devices/device.ts +++ b/src/sensors/devices/device.ts @@ -9,12 +9,18 @@ export interface IDeviceOptions { requestedCapabilities: ISensorCapabilities; } +export interface ISelectableSensorInfo { + name: string; + internalId: any; +} + export class Device { protected _name: string; protected _deviceName: string; protected _capabilities: ISensorCapabilities; protected _requestedCapabilities: ISensorCapabilities; protected _serviceUUID: number | string; + protected _selectableSensors: ISelectableSensorInfo[]; constructor(options: IDeviceOptions) { this._name = options.name; @@ -22,6 +28,7 @@ export class Device { this._capabilities = options.capabilities; this._requestedCapabilities = options.capabilities; this._serviceUUID = options.serviceUUID; + this._selectableSensors = []; } public get name() { @@ -36,11 +43,15 @@ export class Device { return this._requestedCapabilities; } - public get timeSeriesCapabilities(): ITimeSeriesCapabilities|undefined { + public timeSeriesCapabilities(selectableSensorId: any): ITimeSeriesCapabilities|undefined { return undefined; // set in each device } - public collectTimeSeries(measurementPeriod: number, callback: (values: IDataTableTimeData[]) => void): () => void { + public get selectableSensors(): ISelectableSensorInfo[] { + return []; // set in each device + } + + public collectTimeSeries(measurementPeriod: number, selectableSensorId: any, callback: (values: IDataTableTimeData[]) => void): () => void { throw new Error("collectTimeSeries() method not overridden!"); } diff --git a/src/sensors/devices/gdx-sensor.ts b/src/sensors/devices/gdx-sensor.ts index 65f1889..59dcdfd 100644 --- a/src/sensors/devices/gdx-sensor.ts +++ b/src/sensors/devices/gdx-sensor.ts @@ -1,4 +1,4 @@ -import { Device } from "./device"; +import { Device, ISelectableSensorInfo } from "./device"; import godirect from "@vernier/godirect/dist/godirect.min.cjs"; import { ISensorCapabilities, ISensorValues, ITimeSeriesCapabilities } from "../sensor"; import { IDataTableTimeData } from "../../shared/components/data-table-field"; @@ -14,7 +14,6 @@ const goDirectDevicePrefixes = ["GDX-TMP"]; export class GDXSensorDevice extends Device { private gdxDevice: any; - private _timeSeriesCapabilities: ITimeSeriesCapabilities|undefined = undefined; constructor(requestedCapabilities: ISensorCapabilities) { super({ @@ -39,38 +38,83 @@ export class GDXSensorDevice extends Device { return [goDirectServiceUUID]; } - public get timeSeriesCapabilities(): ITimeSeriesCapabilities|undefined { - return this._timeSeriesCapabilities; + public timeSeriesCapabilities(selectableSensorId: any): ITimeSeriesCapabilities|undefined { + if (!this.gdxDevice) { + return; + } + + selectableSensorId = Number(selectableSensorId ?? this.gdxDevice.sensors[0].number); + const selectedSensor = this.gdxDevice?.sensors.find((s: any) => s.number === selectableSensorId); + + if (!selectedSensor) { + return undefined; + } + + let defaultMeasurementPeriod = 50; + const measurementInfo = selectedSensor.specs?.measurementInfo; + const measurement: string = selectedSensor.name; + const valueKey = measurement.toLowerCase(); + const minMeasurementPeriod = this.gdxDevice.minMeasurementPeriod; + defaultMeasurementPeriod = Math.max(minMeasurementPeriod, defaultMeasurementPeriod); + return { + measurementPeriod: defaultMeasurementPeriod, + minMeasurementPeriod, + defaultMeasurementPeriod, + measurement, + valueKey, + units: selectedSensor.unit, + minValue: measurementInfo.minValue ?? 0, + maxValue: measurementInfo.maxValue ?? 0 + }; + } + + public get selectableSensors(): ISelectableSensorInfo[] { + if (!this.gdxDevice) { + return []; + } + + return this.gdxDevice.sensors.map((s: any) => ({name: s.name, internalId: s.number})); } - public collectTimeSeries(measurementPeriod: number, callback: (values: IDataTableTimeData[]) => void): () => void { - const sensor = this.gdxDevice?.sensors.find((s: any) => s.enabled); + public collectTimeSeries(measurementPeriod: number, selectableSensorId: any, callback: (values: IDataTableTimeData[]) => void): () => void { + let capabilities = this.timeSeriesCapabilities(selectableSensorId); - if (!this.gdxDevice || !sensor || !this.timeSeriesCapabilities) { + if (!this.gdxDevice || !capabilities) { return () => { // noop }; } + selectableSensorId = Number(selectableSensorId ?? this.gdxDevice.sensors[0].number); + let time = 0; const delta = measurementPeriod / 1000; const values: IDataTableTimeData[] = []; - const capabilities = {...this.timeSeriesCapabilities, measurementPeriod}; + capabilities = {...capabilities, measurementPeriod}; - const handleChange = () => { - if (values.length === 0) { - values.push({time, value: sensor.value, capabilities}); - } else { - values.push({time, value: sensor.value}); + const handleChange = (sensor: any) => { + if (sensor.number === selectableSensorId) { + if (values.length === 0) { + values.push({time, value: sensor.value, capabilities}); + } else { + values.push({time, value: sensor.value}); + } + callback(values); + time += delta; } - callback(values); - time += delta; }; - sensor.on("value-changed", handleChange); + this.gdxDevice.sensors.forEach((sensor: any) => { + if (sensor.number === selectableSensorId) { + sensor.on("value-changed", handleChange); + } + }); + this.gdxDevice.start(measurementPeriod); return () => { - sensor.off("value-changed", handleChange); + this.gdxDevice?.sensors.forEach((sensor: any) => { + sensor.off("value-changed", handleChange); + }); }; } @@ -95,29 +139,12 @@ export class GDXSensorDevice extends Device { return; } - let defaultMeasurementPeriod = 50; - const firstSensor = gdxDevice?.sensors[0]; - if (firstSensor) { - const measurementInfo = firstSensor.specs?.measurementInfo; - const measurement: string = firstSensor.name; - const valueKey = measurement.toLowerCase(); - const minMeasurementPeriod = gdxDevice.minMeasurementPeriod; - defaultMeasurementPeriod = Math.max(minMeasurementPeriod, defaultMeasurementPeriod); - this._timeSeriesCapabilities = { - measurementPeriod: defaultMeasurementPeriod, - minMeasurementPeriod, - defaultMeasurementPeriod, - measurement, - valueKey, - units: firstSensor.unit, - minValue: measurementInfo.minValue ?? 0, - maxValue: measurementInfo.maxValue ?? 0 - }; - } else { - this._timeSeriesCapabilities = undefined; - } + const defaultMeasurementPeriod = this.timeSeriesCapabilities(gdxDevice.sensors[0].number); this.gdxDevice = gdxDevice; + this.gdxDevice.sensors.forEach((sensor: any) => { + sensor?.setEnabled(true); + }); this.gdxDevice.start(defaultMeasurementPeriod); resolve(); diff --git a/src/sensors/mock-sensor.ts b/src/sensors/mock-sensor.ts index 1ec534d..d468fbf 100644 --- a/src/sensors/mock-sensor.ts +++ b/src/sensors/mock-sensor.ts @@ -1,4 +1,5 @@ import { IDataTableTimeData } from "../shared/components/data-table-field"; +import { ISelectableSensorInfo } from "./devices/device"; import { Sensor, ISensorOptions, ISensorValues, IPollOptions, IConnectOptions, ITimeSeriesCapabilities } from "./sensor"; type MockValueDirection = "up" | "down"; @@ -132,7 +133,7 @@ export class MockSensor extends Sensor { return Promise.resolve(values); } - public get timeSeriesCapabilities(): ITimeSeriesCapabilities { + public timeSeriesCapabilities(selectableSensorId: any): ITimeSeriesCapabilities { const defaultMeasurementPeriod = 50; return { measurementPeriod: defaultMeasurementPeriod, @@ -146,11 +147,18 @@ export class MockSensor extends Sensor { }; } - public collectTimeSeries(measurementPeriod: number, callback: (values: IDataTableTimeData[]) => void): () => void { + public get selectableSensors(): ISelectableSensorInfo[] { + return [ + {name: "Mocked Sensor: Force", internalId: 0}, + {name: "Mocked Sensor: Temperature", internalId: 1}, + ]; + } + + public collectTimeSeries(measurementPeriod: number, selectableSensorId: any, callback: (values: IDataTableTimeData[]) => void): () => void { let time = 0; const delta = measurementPeriod / 1000; const values: IDataTableTimeData[] = []; - const capabilities = {...this.timeSeriesCapabilities, measurementPeriod}; + const capabilities = {...this.timeSeriesCapabilities(selectableSensorId), measurementPeriod}; const callCallback = () => { const value = this.mockValues.timeSeries; diff --git a/src/sensors/sensor.ts b/src/sensors/sensor.ts index 0bb6fe0..36b4edb 100644 --- a/src/sensors/sensor.ts +++ b/src/sensors/sensor.ts @@ -1,5 +1,6 @@ import EventEmitter from "eventemitter3"; import { IDataTableTimeData } from "../shared/components/data-table-field"; +import { ISelectableSensorInfo } from "./devices/device"; export interface ISensorCapabilities { illuminance?: boolean; @@ -119,10 +120,14 @@ export class Sensor extends EventEmitter { return this._capabilities; } - public get timeSeriesCapabilities(): ITimeSeriesCapabilities|undefined { + public timeSeriesCapabilities(selectableSensorId: any): ITimeSeriesCapabilities|undefined { return undefined; // set in device } + public get selectableSensors(): ISelectableSensorInfo[] { + return []; // set in device + } + public get pollInterval() { return this._pollInterval; } @@ -159,7 +164,7 @@ export class Sensor extends EventEmitter { } } - public collectTimeSeries(measurementPeriod: number, callback: (values: IDataTableTimeData[]) => void): () => void { + public collectTimeSeries(measurementPeriod: number, selectableSensorId: any, callback: (values: IDataTableTimeData[]) => void): () => void { throw new Error("collectTimeSeries() method not overridden!"); } diff --git a/src/shared/components/data-table-field.tsx b/src/shared/components/data-table-field.tsx index 314779a..4d2c4fc 100644 --- a/src/shared/components/data-table-field.tsx +++ b/src/shared/components/data-table-field.tsx @@ -186,6 +186,7 @@ export const DataTableField: React.FC = props => { }, 0) : 0; return result; }, [isTimeSeries, formData]); + const [selectableSensorId, setSelectableSensorId] = useState(); // listen for prop changes from uploads useEffect(() => { @@ -198,17 +199,17 @@ export const DataTableField: React.FC = props => { clearInterval(waitForSensorIntervalRef.current); if (sensor && sensorOutput.connected && isTimeSeries) { // wait for sensor to come online and set its time series capabilities - waitForSensorIntervalRef.current = setInterval(() => { - const result = sensor.timeSeriesCapabilities; + waitForSensorIntervalRef.current = window.setInterval(() => { + const result = sensor.timeSeriesCapabilities(selectableSensorId); if (result) { setTimeSeriesCapabilities({...result}); clearInterval(waitForSensorIntervalRef.current); } - }); + }, 10); } else { setTimeSeriesCapabilities(undefined); } - }, [sensor, sensorOutput.connected, isTimeSeries, setTimeSeriesCapabilities]); + }, [sensor, sensorOutput.connected, isTimeSeries, setTimeSeriesCapabilities, selectableSensorId]); const setTimeSeriesMeasurementPeriod = (newPeriod: number) => { setTimeSeriesCapabilities(prev => prev ? {...prev, measurementPeriod: newPeriod} : prev); @@ -354,7 +355,7 @@ export const DataTableField: React.FC = props => { return; } - stopTimeSeriesFnRef.current = sensor.collectTimeSeries(timeSeriesCapabilities.measurementPeriod, (values) => { + stopTimeSeriesFnRef.current = sensor.collectTimeSeries(timeSeriesCapabilities.measurementPeriod, selectableSensorId, (values) => { const newData = formData.slice(); newData[rowIdx] = {timeSeries: values}; if (values.length <= MaxNumberOfTimeSeriesValues) { @@ -549,7 +550,9 @@ export const DataTableField: React.FC = props => { setManualEntryMode={showShowSensorButton ? undefined : setManualEntryMode} isTimeSeries={isTimeSeries} timeSeriesCapabilities={timeSeriesCapabilities} + selectableSensorId={selectableSensorId} setTimeSeriesMeasurementPeriod={setTimeSeriesMeasurementPeriod} + setSelectableSensorId={setSelectableSensorId} /> : undefined} {title ?
{title}
: undefined}