Skip to content

Commit

Permalink
feat: Introduce alarms (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
NorbertNader committed Sep 20, 2022
1 parent f2450bc commit dba8f27
Show file tree
Hide file tree
Showing 89 changed files with 2,098 additions and 317 deletions.
35 changes: 35 additions & 0 deletions docs/AWSIoTSiteWiseSource.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ Each asset contains the following fields:

Type: String

#### Alarms

AWS IoT SiteWise has a concept of [alarms](https://docs.aws.amazon.com/iot-sitewise/latest/userguide/industrial-alarms.html).

The source of alarms in IoT Application Kit is AWS IoT Events.

AWS IoT Events alarms are able to process and alarm on AWS IoT SiteWise data.

To query for an alarm you have to know the **AlarmState Property ID**. The **AlarmState Property ID** can be found in the AWS IoT SiteWise console on the **Models** page. Find the model which the alarm was created on. Then under the **Alarm definitions** tab you should see your alarm. Use the **AlarmState Property ID** as the `propertyId` in the asset property query.

```
query.timeSeriesData({
assets: [{
assetId: 'id',
properties: [{ propertyId: 'alarmStatePropertyId' }]
}]
})
```

What this entails:
- **streamType** for the **alarmStatePropertyId dataStream** will be set to `'ALARM'`.
- if the **inputPropertyId** is requested in the **AssetQuery** an **associatedStream** will be added to the **inputPropertyId dataStream**:
```
associatedStreams: [ ..., { id: toId({ assetId, propertyId: alarmStatePropertyId }), type: 'ALARM' } ]
```
- **thresholds** will be constructed to represent the alarm:
- A threshold for the **inputPropertyId dataStream**, used on charts with a y axis e.g. on `iot-line-chart`.
- Thresholds for every **AWS IoT Events AlarmState** for components that visualize alarms e.g. `iot-status-timeline`. More on alarm state [here](https://docs.aws.amazon.com/iotevents/latest/apireference/API_iotevents-data_AlarmState.html).

![status grid with alarms](./imgs/statusGridWithAlarms.png)

![status timeline with alarms](./imgs/statusTimelineWithAlarms.png)

![line chart with alarms](./imgs/lineChartWithAlarms.png)

### TimeSeriesDataSettings parameter

(Optional) Specifies how IoT Application Kit requests time series data. Learn more about how to configure TimeSeriesDataSettings, see TimeSeriesDataSettings under [Core](https://github.com/awslabs/iot-app-kit/tree/main/docs/Core.md).
Expand Down
Binary file added docs/imgs/lineChartWithAlarms.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 docs/imgs/statusGridWithAlarms.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 docs/imgs/statusTimelineWithAlarms.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export namespace Components {
interface IotTestRoutes {
}
interface IotTimeSeriesConnector {
"annotations": Annotations;
"assignDefaultColors": boolean | undefined;
"initialViewport": Viewport;
"provider": Provider<TimeSeriesData[]>;
Expand Down Expand Up @@ -433,6 +434,7 @@ declare namespace LocalJSX {
interface IotTestRoutes {
}
interface IotTimeSeriesConnector {
"annotations"?: Annotations;
"assignDefaultColors"?: boolean | undefined;
"initialViewport"?: Viewport;
"provider"?: Provider<TimeSeriesData[]>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { combineAnnotations } from './combineAnnotations';
import { TIME_SERIES_DATA_WITH_ALARMS } from '@iot-app-kit/source-iotsitewise';

it('correctly combines annotations annotations', () => {
const combinedAnnotations = combineAnnotations(
{
colorDataAcrossThresholds: true,
show: true,
thresholdOptions: true,
},
{
y: TIME_SERIES_DATA_WITH_ALARMS.annotations.y,
}
);

expect(combinedAnnotations).toEqual({
colorDataAcrossThresholds: true,
show: true,
thresholdOptions: true,
y: TIME_SERIES_DATA_WITH_ALARMS.annotations.y,
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Annotations } from '@synchro-charts/core';

export const combineAnnotations = (prev: Annotations, cur: Annotations): Annotations => {
return {
...prev,
y: [...(prev?.y || []), ...(cur?.y || [])],
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
it('todo', () => {
// TODO
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Annotations, YAnnotation } from '@synchro-charts/core';
import { DataStream } from '@iot-app-kit/core';

export const getAlarmStreamAnnotations = ({
annotations,
dataStreams,
}: {
annotations: Annotations;
dataStreams: DataStream[];
}): { y: YAnnotation[] | undefined } => ({
y: (annotations as Annotations).y?.filter((yAnnotation) => {
return (
'dataStreamIds' in yAnnotation &&
yAnnotation.dataStreamIds?.some((dataStreamId) =>
dataStreams.some((dataStream) => dataStream.streamType === 'ALARM' && dataStreamId === dataStream.id)
)
);
}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const barChartSpecPage = async (propOverrides: Partial<Components.IotBarChart> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ export class IotBarChart {
provider={this.provider}
styleSettings={this.styleSettings}
assignDefaultColors
renderFunc={({ dataStreams }) => (
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => (
<sc-bar-chart
widgetId={this.widgetId}
viewport={this.viewport}
Expand All @@ -112,7 +113,7 @@ export class IotBarChart {
legend={this.legend}
gestures={this.gestures}
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
annotations={annotations}
isEditing={this.isEditing}
trends={this.trends}
messageOverrides={this.messageOverrides}
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/components/iot-kpi/iot-kpi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const kpiSpecPage = async (propOverrides: Partial<Components.IotKpi> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
5 changes: 3 additions & 2 deletions packages/components/src/components/iot-kpi/iot-kpi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ export class IotKpi {
<iot-time-series-connector
provider={this.provider}
styleSettings={this.styleSettings}
renderFunc={({ dataStreams }) => (
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => (
<sc-kpi
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
annotations={annotations}
viewport={this.viewport}
isEditing={this.isEditing}
widgetId={this.widgetId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { initialize } from '@iot-app-kit/source-iotsitewise';
import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-series-connector';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const lineChartSpecPage = async (propOverrides: Partial<Components.IotKpi> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export class IotLineChart {
provider={this.provider}
styleSettings={this.styleSettings}
assignDefaultColors
renderFunc={({ dataStreams }) => {
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => {
return (
<sc-line-chart
widgetId={this.widgetId}
Expand All @@ -105,7 +106,7 @@ export class IotLineChart {
legend={this.legend}
gestures={this.gestures}
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
annotations={annotations}
isEditing={this.isEditing}
trends={this.trends}
messageOverrides={this.messageOverrides}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMockSiteWiseSDK, initialize } from '@iot-app-kit/source-iotsitewise';
import { createMockSiteWiseSDK, createMockIoTEventsSDK, initialize } from '@iot-app-kit/source-iotsitewise';
import { newSpecPage } from '@stencil/core/testing';
import { IotResourceExplorer } from './iot-resource-explorer';
import { Components } from '../../components.d';
Expand All @@ -14,6 +14,7 @@ const resourceExplorerSpec = async (
) => {
const { query } = initialize({
iotSiteWiseClient: iotSiteWiseClient,
iotEventsClient: createMockIoTEventsSDK(),
});
const page = await newSpecPage({
components: [IotResourceExplorer],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const scatterChartSpecPage = async (propOverrides: Partial<Components.IotScatterChart> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ export class IotScatterChart {
provider={this.provider}
styleSettings={this.styleSettings}
assignDefaultColors
renderFunc={({ dataStreams }) => {
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => {
return (
<sc-scatter-chart
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
annotations={annotations}
viewport={this.viewport}
isEditing={this.isEditing}
widgetId={this.widgetId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const statusGridSpecPage = async (propOverrides: Partial<Components.IotKpi> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ export class IotStatusGrid {
<iot-time-series-connector
provider={this.provider}
styleSettings={this.styleSettings}
renderFunc={({ dataStreams }) => (
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => (
<sc-status-grid
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
annotations={annotations}
viewport={this.viewport}
isEditing={this.isEditing}
widgetId={this.widgetId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const statusTimelineSpecPage = async (propOverrides: Partial<Components.IotStatusTimeline> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
combineProviders,
} from '@iot-app-kit/core';
import { v4 as uuidv4 } from 'uuid';
import { combineAnnotations } from '../common/combineAnnotations';
import { getAlarmStreamAnnotations } from '../common/getAlarmStreamAnnotations';

@Component({
tag: 'iot-status-timeline',
Expand Down Expand Up @@ -94,23 +96,30 @@ export class IotStatusTimeline {
provider={this.provider}
styleSettings={this.styleSettings}
assignDefaultColors
renderFunc={({ dataStreams }) => (
<sc-status-timeline
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={this.annotations}
viewport={this.viewport}
isEditing={this.isEditing}
widgetId={this.widgetId}
gestures={this.gestures}
movement={this.movement}
scale={this.scale}
layout={this.layout}
size={this.size}
axis={this.axis}
messageOverrides={this.messageOverrides}
alarms={this.alarms}
/>
)}
annotations={this.annotations}
renderFunc={({ dataStreams, annotations }) => {
const alarmStreamAnnotations = getAlarmStreamAnnotations({ annotations, dataStreams });

return (
<sc-status-timeline
dataStreams={dataStreams as SynchroChartsDataStream[]}
annotations={
this.annotations ? combineAnnotations(this.annotations, alarmStreamAnnotations) : alarmStreamAnnotations
}
viewport={this.viewport}
isEditing={this.isEditing}
widgetId={this.widgetId}
gestures={this.gestures}
movement={this.movement}
scale={this.scale}
layout={this.layout}
size={this.size}
axis={this.axis}
messageOverrides={this.messageOverrides}
alarms={this.alarms}
/>
);
}}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IotTimeSeriesConnector } from '../iot-time-series-connector/iot-time-se
import { CustomHTMLElement } from '../../testing/types';
import { update } from '../../testing/update';
import { mockSiteWiseSDK } from '../../testing/mocks/siteWiseSDK';
import { mockEventsSDK } from '../../testing/mocks/eventsSDK';

const viewport: MinimalLiveViewport = {
duration: 1000,
Expand All @@ -15,6 +16,7 @@ const viewport: MinimalLiveViewport = {
const tableSpecPage = async (propOverrides: Partial<Components.IotKpi> = {}) => {
const { query } = initialize({
iotSiteWiseClient: mockSiteWiseSDK,
iotEventsClient: mockEventsSDK,
});

const page = await newSpecPage({
Expand Down
Loading

0 comments on commit dba8f27

Please sign in to comment.