Skip to content

Commit

Permalink
feat(): use sensor data as context in widgets
Browse files Browse the repository at this point in the history
  • Loading branch information
mmatloch committed Jun 4, 2024
1 parent 0fc9a46 commit 967a203
Show file tree
Hide file tree
Showing 15 changed files with 103 additions and 14 deletions.
1 change: 1 addition & 0 deletions packages/frontend/src/definitions/entities/widgetTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { GenericEntity } from '../commonTypes';
export interface WidgetTextLine {
deviceId: number | null;
eventId: number | null;
useDeviceSensorData: boolean;
value: string;
id: string;
styles: Record<string, unknown>;
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/definitions/localeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ export interface Locale {
icon: string;
};
addTextLine: string;
useDeviceSensorData: string;
addAction: string;
removeAction: string;
actionOnDefinition: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import DeviceAutocomplete from '@components/devices/DeviceAutocomplete';
import DeviceAutocompleteWrapper from '@components/devices/DeviceAutocompleteWrapper';
import FormCheckbox from '@components/forms/FormCheckbox';
import { Device } from '@definitions/entities/deviceTypes';
import { useTextLinesForm } from '@features/widgets/hooks/useTextLinesForm';
import { useWidgetForm } from '@features/widgets/hooks/useWidgetForm';
import { Stack } from '@mui/material';
import { useTranslation } from 'react-i18next';

interface Props {
lineIndex: number;
}

export const TextLineFromDeviceContext = ({ lineIndex }: Props) => {
const { t } = useTranslation();
const { t } = useTranslation(['generic', 'widgets']);
const { update } = useTextLinesForm();
const { watch } = useWidgetForm();
const textLines = watch('textLines');

const handleDeviceSelect = (_e: unknown, device: Device) => {
const useDeviceSensorDataName = `textLines.${lineIndex}.useDeviceSensorData`;

const handleDeviceSelect = (_e: unknown, device?: Device) => {
update(lineIndex, {
id: textLines[lineIndex].id,
value: textLines[lineIndex].value,
deviceId: device._id,
deviceId: device?._id ?? null,
useDeviceSensorData: textLines[lineIndex].useDeviceSensorData,
eventId: null,
styles: textLines[lineIndex].styles,
});
Expand All @@ -29,15 +34,33 @@ export const TextLineFromDeviceContext = ({ lineIndex }: Props) => {

if (currentDeviceId) {
return (
<DeviceAutocompleteWrapper
deviceId={currentDeviceId}
onChange={handleDeviceSelect}
InputProps={{ label: t('search.selecting.selectDevice') }}
/>
<Stack>
<DeviceAutocompleteWrapper
deviceId={currentDeviceId}
onChange={handleDeviceSelect}
InputProps={{ label: t('generic:search.selecting.selectDevice') }}
/>
<FormCheckbox
name={useDeviceSensorDataName}
label={t('widgets:creator.useDeviceSensorData')}
margin="dense"
/>
</Stack>
);
}

return (
<DeviceAutocomplete onChange={handleDeviceSelect} InputProps={{ label: t('search.selecting.selectDevice') }} />
<Stack>
<DeviceAutocomplete
onChange={handleDeviceSelect}
InputProps={{ label: t('search.selecting.selectDevice') }}
/>

<FormCheckbox
name={useDeviceSensorDataName}
label={t('widgets:creator.useDeviceSensorData')}
margin="dense"
/>
</Stack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ export const TextLineFromEventContext = ({ lineIndex }: Props) => {
const { watch } = useWidgetForm();
const textLines = watch('textLines');

const handleEventSelect = (_e: unknown, event: Event) => {
const handleEventSelect = (_e: unknown, event?: Event) => {
update(lineIndex, {
id: textLines[lineIndex].id,
value: textLines[lineIndex].value,
eventId: event._id,
eventId: event?._id ?? null,
deviceId: null,
useDeviceSensorData: false,
styles: textLines[lineIndex].styles,
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const WidgetTextLineForm = () => {
append({
value: '',
deviceId: null,
useDeviceSensorData: false,
eventId: null,
id: window.crypto.randomUUID(),
styles: {},
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export const EnglishLocale: Locale = {
icon: 'home.png',
},
addTextLine: 'Add text line',
useDeviceSensorData: 'Use Sensor Data as the context',
addAction: 'Add action',
removeAction: 'Remove action',
actionOnDefinition: 'Switch on action definition',
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/locales/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export const PolishLocale: Locale = {
icon: 'home.png',
},
addTextLine: 'Dodaj wiersz tekstu',
useDeviceSensorData: 'Użyj danych czujnika jako kontekstu',
addAction: 'Dodaj akcję',
removeAction: 'Usuń akcję',
actionOnDefinition: 'Określenie akcji włącznika',
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/definitions/deviceDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum DevicePowerSource {

export enum DeviceProtocol {
Zigbee = 'ZIGBEE',
Virtual = 'VIRTUAL',
}

export enum DeviceState {
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/entities/widgetEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const widgetTextLineSchema = Type.Object({
id: Type.String(),
value: Type.String(),
deviceId: Type.Union([Type.Null(), Type.Integer()]),
useDeviceSensorData: Type.Boolean(),
eventId: Type.Union([Type.Null(), Type.Integer()]),
styles: Type.Record(Type.String(), Type.Unknown(), {
default: {},
Expand Down
2 changes: 2 additions & 0 deletions packages/server/src/events/sdks/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { createDevicesSdk } from './devicesSdk';
import { createEventsSdk } from './eventsSdk';
import { createSysInfoSdk } from './sysInfoSdk';

export type EventRunSdk = Record<string, unknown>;

export const createEventRunSdk = (): EventRunSdk => {
return {
devices: createDevicesSdk(),
events: createEventsSdk(),
sysInfo: createSysInfoSdk(),
};
};
5 changes: 5 additions & 0 deletions packages/server/src/events/sdks/sysInfoSdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import systeminformation from 'systeminformation';

export const createSysInfoSdk = () => {
return systeminformation;
};
25 changes: 24 additions & 1 deletion packages/server/src/services/sensorDataService.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { In } from 'typeorm';

import type { SensorData, SensorDataDto } from '../entities/sensorDataEntity';
import { createSensorDataRepository } from '../repositories/sensorDataRepository';
import type { GenericService } from './genericService';

export interface SensorDataService
extends Pick<GenericService<SensorData, SensorDataDto>, 'create' | 'search' | 'searchAndCount'> {}
extends Pick<GenericService<SensorData, SensorDataDto>, 'create' | 'search' | 'searchAndCount'> {
getLatestForDevices: (deviceIds: number[]) => Promise<SensorData[]>;
}

export const createSensorDataService = (): SensorDataService => {
const repository = createSensorDataRepository();
Expand All @@ -22,9 +26,28 @@ export const createSensorDataService = (): SensorDataService => {
return repository.findAndCount(query);
};

const getLatestForDevices: SensorDataService['getLatestForDevices'] = (deviceIds) => {
if (!deviceIds.length) {
return Promise.resolve([]);
}

return repository
.createQueryBuilder()
.where({
deviceId: In(deviceIds),
})
.distinctOn(['"deviceId"'])
.orderBy({
'"deviceId"': 'ASC',
'"_createdAt"': 'DESC',
})
.getMany();
};

return {
create,
search,
searchAndCount,
getLatestForDevices,
};
};
32 changes: 30 additions & 2 deletions packages/server/src/services/widgetsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FindManyOptions, In } from 'typeorm';

import { Device } from '../entities/deviceEntity';
import { Event } from '../entities/eventEntity';
import { SensorData } from '../entities/sensorDataEntity';
import { Widget, WidgetActionDto, WidgetActionEntry, WidgetDto, WidgetWithActionState } from '../entities/widgetEntity';
import { Errors } from '../errors';
import { eventTriggerInNewContext } from '../events/eventTriggerInNewContext';
Expand All @@ -13,6 +14,7 @@ import { WidgetProcessContext, createWidgetProcessor } from '../widgets/widgetPr
import { createDevicesService } from './devicesService';
import { createEventsService } from './eventsService';
import type { GenericService } from './genericService';
import { createSensorDataService } from './sensorDataService';

export interface WidgetsService
extends Omit<GenericService<WidgetWithActionState, WidgetDto>, 'search' | 'searchAndCount'> {
Expand All @@ -26,6 +28,7 @@ export interface WidgetsService
export const createWidgetsService = (): WidgetsService => {
const repository = createWidgetsRepository();
const devicesService = createDevicesService();
const sensorDataService = createSensorDataService();
const eventsService = createEventsService();

const create: WidgetsService['create'] = async (dto) => {
Expand Down Expand Up @@ -75,7 +78,20 @@ export const createWidgetsService = (): WidgetsService => {

const parseTextLines = async (widgets: Widget[] | WidgetDto[]) => {
const deviceIds = widgets
.map((widget) => widget.textLines.map((textLine) => textLine.deviceId))
.map((widget) =>
widget.textLines
.filter((textLine) => textLine.deviceId && !textLine.useDeviceSensorData)
.map((textLine) => textLine.deviceId),
)
.flat()
.filter(isNumber);

const sensorDataDeviceIds = widgets
.map((widget) =>
widget.textLines
.filter((textLine) => textLine.deviceId && textLine.useDeviceSensorData)
.map((textLine) => textLine.deviceId),
)
.flat()
.filter(isNumber);

Expand All @@ -90,14 +106,24 @@ export const createWidgetsService = (): WidgetsService => {
},
});

const sensorData = await sensorDataService.getLatestForDevices(sensorDataDeviceIds);

const events = await eventsService.search({
where: {
_id: In(eventIds),
},
});

const getTextLineContext = (textLine: WidgetDto['textLines'][0]): Device | Event | Record<string, unknown> => {
console.log(sensorData, sensorDataDeviceIds);

const getTextLineContext = (
textLine: WidgetDto['textLines'][0],
): Device | Event | SensorData | Record<string, unknown> => {
if (textLine.deviceId) {
if (textLine.useDeviceSensorData) {
return sensorData.find((sensorData) => sensorData.deviceId === textLine.deviceId) || {};
}

return devices.find((device) => device._id === textLine.deviceId) || {};
}

Expand All @@ -112,6 +138,8 @@ export const createWidgetsService = (): WidgetsService => {
widget.textLines.forEach((textLine) => {
const context = getTextLineContext(textLine);

console.log(textLine.id, context);

textLine.value = parseWidgetText(textLine.value, context);
});
});
Expand Down
Binary file added packages/static/greenhouse_sun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/static/greenhouse_water.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 967a203

Please sign in to comment.