Skip to content

Commit

Permalink
feat: Add sensor picker [PT-187809478]
Browse files Browse the repository at this point in the history
  • Loading branch information
dougmartin committed Jun 25, 2024
1 parent 6c49aaa commit 304c749
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 57 deletions.
8 changes: 8 additions & 0 deletions src/mobile-app/components/sensor.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
22 changes: 20 additions & 2 deletions src/mobile-app/components/sensor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -66,7 +68,7 @@ const iconClassHi = {
error: css.errorIcon
};

export const SensorComponent: React.FC<ISensorComponentProps> = ({sensor, manualEntryMode, setManualEntryMode, isTimeSeries, timeSeriesCapabilities, setTimeSeriesMeasurementPeriod}) => {
export const SensorComponent: React.FC<ISensorComponentProps> = ({sensor, manualEntryMode, setManualEntryMode, isTimeSeries, timeSeriesCapabilities, selectableSensorId, setTimeSeriesMeasurementPeriod, setSelectableSensorId}) => {
const {connected, connecting, deviceName, values, error} = useSensor(sensor);

const [devicesFound, setDevicesFound] = useState<IConnectDevice[]>([]);
Expand Down Expand Up @@ -133,6 +135,10 @@ export const SensorComponent: React.FC<ISensorComponentProps> = ({sensor, manual
setTimeSeriesMeasurementPeriod?.(newPeriod);
};

const handleSelectSelectableSensor = (e: React.ChangeEvent<HTMLSelectElement>) => {
setSelectableSensorId?.(e.target.value);
};

const connect = () => sensor.connect({
onDevicesFound: ({devices, select, cancel}) => {
setDevicesFound(devices);
Expand Down Expand Up @@ -174,10 +180,22 @@ export const SensorComponent: React.FC<ISensorComponentProps> = ({sensor, manual
</div>
);

const renderDeviceName = () => {
if (sensor.selectableSensors.length === 0) {
return <>{deviceName}</>;
}

return (
<select onChange={handleSelectSelectableSensor}>
{sensor.selectableSensors.map(s => <option key={s.internalId} value={s.internalId}>{s.name}</option>)}
</select>
);
};

const renderConnected = () => (
<div className={css.connectionLabel}>
{renderIcon("connected")}
Connected: {deviceName}
Connected: {renderDeviceName()}
</div>
);

Expand Down
14 changes: 9 additions & 5 deletions src/sensors/device-sensor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<ISensorValues> {
Expand All @@ -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
Expand Down
15 changes: 13 additions & 2 deletions src/sensors/devices/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@ 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;
this._deviceName = options.deviceName;
this._capabilities = options.capabilities;
this._requestedCapabilities = options.capabilities;
this._serviceUUID = options.serviceUUID;
this._selectableSensors = [];
}

public get name() {
Expand All @@ -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!");
}

Expand Down
103 changes: 65 additions & 38 deletions src/sensors/devices/gdx-sensor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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({
Expand All @@ -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);
});
};
}

Expand All @@ -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();
Expand Down
14 changes: 11 additions & 3 deletions src/sensors/mock-sensor.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions src/sensors/sensor.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -119,10 +120,14 @@ export class Sensor extends EventEmitter<SensorEvent> {
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;
}
Expand Down Expand Up @@ -159,7 +164,7 @@ export class Sensor extends EventEmitter<SensorEvent> {
}
}

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!");
}

Expand Down
Loading

0 comments on commit 304c749

Please sign in to comment.