-
- {this.state.showChart &&
+
+ {timeseries.length > 0 &&
- {this.props.sensors.loading && !this.props.sensors.error && }
+ {(this.props.weather.loading || (this.props.sensors.loading && !this.props.sensors.error)) && }
-
- this.setState({ tracker })}
- onTimeRangeChanged={timerange => this.updateTimeRange(timerange)}
- enablePanZoom
- >
-
-
-
- {this.renderLineGraph()}
-
+
+
+
+
-
-
-
+
+ {this.renderLineGraph(timeseries)}
+ {baselines}
+
+
+
+
+
{bottomText}
+
+ );
+ }
+
+ renderLatestReadings = () => {
+ const { latestReadings } = this.state;
+ if (Object.keys(latestReadings).length === 0) {
+ return 'No latest readings';
+ }
+ const deviceIds = Object.keys(latestReadings);
+ // The id of the device to displayed
+ const latestReadingDeviceId = this.state.activeTab !== '' ? this.state.activeTab : deviceIds[0];
+ const latestReadingDevice = latestReadings[latestReadingDeviceId];
+ return (
+
+
+
+
+
+
+
+ | Timestamp |
+ Measurement |
+ Value |
+
+
+
+ {latestReadingDevice.temperature ? (
+
+ | {latestReadingDevice.temperature.ts} |
+ °F Temperature |
+ {latestReadingDevice.temperature.value} |
+
+ ) : null}
+ {latestReadingDevice.humidity ? (
+
+ | {latestReadingDevice.humidity.ts} |
+ % Humidity |
+ {latestReadingDevice.humidity.value} |
+
+ ) : null}
+ {latestReadingDevice.co2 ? (
+
+ | {latestReadingDevice.co2.ts} |
+ PPM CO2 |
+ {latestReadingDevice.co2.value} |
+
+ ) : null}
+ {latestReadingDevice.chemicals ? (
+
+ | {latestReadingDevice.chemicals.ts} |
+ PPB Chemicals |
+ {latestReadingDevice.chemicals.value} |
+
+ ) : null}
+ {latestReadingDevice.dust ? (
+
+ | {latestReadingDevice.dust.ts} |
+ mcg3 Dust |
+ {latestReadingDevice.dust.value} |
+
+ ) : null}
+
+
+
+
+
+ );
+ }
+
+
+ render() {
+ let renderedGraph = null;
+ switch (this.state.activeGraphTab) {
+ case 'Temperature':
+ renderedGraph = this.renderGraph(
+ this.state.temperatureTimeseries,
+ 0,
+ 100,
+ '°F',
+ [
+ (
),
+ (
),
+ ],
+ 'Thresholds determined using the OSHA. 2003 standard',
+ );
+ break;
+ case 'Humidity':
+ renderedGraph = this.renderGraph(
+ this.state.humidityTimeseries,
+ 0,
+ 100,
+ '% Humidity',
+ [
+ (
),
+ (
),
+ ],
+ 'Thresholds determined using the OSHA. 2003 standard',
+ );
+ break;
+ case 'CO2':
+ renderedGraph = this.renderGraph(
+ this.state.co2Timeseries,
+ 300,
+ 1200,
+ 'PPM CO2',
+ [
+ (
),
+ ],
+ 'Threshold determined using the ASHRAE 2010 standard',
+ );
+ break;
+ case 'Chemicals':
+ renderedGraph = this.renderGraph(
+ this.state.chemicalsTimeseries,
+ 0,
+ 1000,
+ 'PPB Chemicals',
+ [
+ (
),
+ ],
+ 'Threshold determined using the EPA 2014 standard',
+ );
+ break;
+ case 'Dust':
+ renderedGraph = this.renderGraph(
+ this.state.dustTimeseries,
+ 5,
+ 40,
+ 'mcg3 Dust',
+ [
+ (
),
+ ],
+ 'Threshold determined using the EPA 2014 standard',
+ );
+ break;
+
+ default:
+ break;
+ }
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+ return (
+
+
+
+
+ Sensor Data
+
+
+
+ From
+
+
+
+
+ To
+
+
+
+
+ {this.state.firstLoad ? : renderedGraph}
+
+
+ (
+ this.setState({ latestReadingsOpen: !this.state.latestReadingsOpen })
+ )}
+ style={{ cursor: 'pointer' }}
+ >
+ Latest Readings {' '}
+
+
+
+ {this.state.firstLoad ? : this.renderLatestReadings()}
+
+
);
}
@@ -386,7 +743,6 @@ SensorGraph.propTypes = {
className: PropTypes.string,
loadAllNodeData: PropTypes.func,
loadWeather: PropTypes.func,
- setRenderIaq: PropTypes.func,
sensors: PropTypes.object, // eslint-disable-line
buildingArea: PropTypes.object, // eslint-disable-line
weather: PropTypes.object, // eslint-disable-line
diff --git a/src/containers/Sensors/SensorGraphIAQ.js b/src/containers/Sensors/SensorGraphIAQ.js
deleted file mode 100644
index 8c4aa948de9db7e82c0d72441ba218e5386411d2..0000000000000000000000000000000000000000
--- a/src/containers/Sensors/SensorGraphIAQ.js
+++ /dev/null
@@ -1,777 +0,0 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { bindActionCreators } from 'redux';
-import { connect } from 'react-redux';
-import debounce from 'lodash.debounce';
-import {
- Table, Card, Nav, NavItem, NavLink,
- UncontrolledTooltip,
-} from 'reactstrap';
-import {
- Charts,
- ChartContainer,
- ChartRow,
- YAxis,
- LineChart,
- Baseline,
- styler,
- Legend,
-} from 'react-timeseries-charts';
-import { TimeSeries, TimeRange } from 'pondjs';
-import { Icon } from 'react-fa';
-import { loadAllNodeData } from './actions';
-import { loadWeather } from '../Weather/actions';
-import { subtractDaysFromNow } from '../../utils/date';
-import { GRAPH_COLORS } from './colors';
-import Loading from '../../components/Loading';
-import {
- SENSOR_TYPES,
-} from '../../components/SensorInstall/constants';
-
-
-class SensorGraphIAQ extends Component {
- NUM_DAYS = 60; // eslint-disable-line
-
- constructor(props) {
- super(props);
- const today = new Date();
-
- this.state = {
- temperatureTimeseries: [],
- humidityTimeseries: [],
- co2Timeseries: [],
- chemicalsTimeseries: [],
- dustTimeseries: [],
- latestReadings: {},
- timerange: new TimeRange([subtractDaysFromNow(this.NUM_DAYS - 1), today]),
- from: subtractDaysFromNow(this.NUM_DAYS),
- today,
- lineStyles: {},
- collapseLatest: false,
- activeTab: '',
- };
- }
-
- componentDidMount() {
- this.props.loadAllNodeData({
- building_id: this.props.buildingId,
- nodes: '',
- data: '',
- from: this.state.from.toUTCString(),
- });
- this.props.loadWeather({
- measurement: 'temperature',
- interval: 'hourly',
- location: 'New_York:NY',
- });
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps === this.props) {
- return;
- }
- this.generateData(nextProps);
- }
-
- sensorDataLoading = (props) => (
- props.sensors.loading || props.sensors.sensorData.length === 0
- )
-
- generateData = (props) => {
- if (this.sensorDataLoading(props)) {
- return;
- }
-
- const { sensorData } = props.sensors;
- const latestReadings = {};
- const lines = sensorData.filter(gateway => (
- gateway.sensor_type === SENSOR_TYPES.awair && gateway.data.length > 0
- )).reduce((acc, gateway) => {
- const timeSeriesData = this.generateTimeSeriesFromGateway(gateway, props);
- Object.keys(timeSeriesData.timeseries).map((key) => {
- acc[key].push(timeSeriesData.timeseries[key]);
- return key;
- });
- // Add the latest readings
- latestReadings[timeSeriesData.latestReading.id] = timeSeriesData.latestReading.reading;
- return acc;
- }, {
- temperature: [],
- humidity: [],
- co2: [],
- chemicals: [],
- dust: [],
- });
- this.setState({ latestReadings });
-
- const weatherPoints = props.weather.weatherData.map(val => (
- [new Date(val.time), val.fields.value]
- ));
- if (weatherPoints.length > 0) {
- lines.temperature.push(new TimeSeries({
- name: 'Outdoor Temperature',
- columns: ['time', 'Outdoor Temperature'],
- points: weatherPoints,
- }));
- }
-
- if (Object.keys(lines).length === 0) {
- return;
- }
- const styles = {};
- Object.keys(lines).forEach((type) => {
- lines[type].forEach((line, index) => {
- styles[line.name()] = this.generateLineStyle(line, index);
- });
- });
-
- this.setState({
- temperatureTimeseries: lines.temperature,
- humidityTimeseries: lines.humidity,
- co2Timeseries: lines.co2,
- chemicalsTimeseries: lines.chemicals,
- dustTimeseries: lines.dust,
- lineStyles: styles,
- });
- }
-
- findDeviceInArea = (incArea, node) => {
- let spaceInArea = null;
- let spaceArea = null;
-
- // find space in area
- incArea.every(area => {
-
- spaceInArea = area.spaces.reduce((acc, spc) => {
- if (spc.id === node.space_id) {
- return spc;
- }
- return acc;
- }, {});
-
- if (Object.keys(spaceInArea).length !== 0) {
- spaceArea = area;
- return false;
- }
-
- return true;
- });
-
- return { spaceArea, spaceInArea };
- }
-
- findDeviceLocation = (sensorNode, props) => {
- const { buildingArea } = props;
- let spaceOfNode = null;
- let deviceName = '';
-
- if (!buildingArea.apartmentsLoading) {
- const temp = this.findDeviceInArea(buildingArea.apartments, sensorNode);
-
- if (temp.spaceArea) {
- spaceOfNode = temp;
- deviceName = `Apt ${spaceOfNode.spaceArea.number} ${spaceOfNode.spaceInArea.description}`;
- }
- }
-
- if (!buildingArea.commonAreasLoading) {
- const temp = this.findDeviceInArea(buildingArea.commonAreas, sensorNode);
-
- if (temp.spaceArea) {
- spaceOfNode = temp;
- deviceName = `Common area ${spaceOfNode.spaceInArea.description}`;
- }
- }
-
-
- if (!buildingArea.serviceAreasLoading) {
- const temp = this.findDeviceInArea(buildingArea.serviceAreas, sensorNode);
-
- if (temp.spaceArea) {
- spaceOfNode = temp;
- deviceName = `Service area ${spaceOfNode.spaceInArea.description}`;
- }
- }
-
- return { spaceOfNode, deviceName };
- }
-
- generateTimeSeriesFromGateway = (gateway, props) => {
- /* eslint-disable quote-props */
- const unitToMeasurement = {
- '°F': 'temperature',
- '%': 'humidity',
- 'ppm': 'co2',
- 'ppb': 'chemicals',
- 'mcg3': 'dust',
- };
- const measurements = gateway.data.reduce((acc, i) => {
- acc[unitToMeasurement[i.unit]].push(i);
- return acc;
- }, {
- temperature: [],
- humidity: [],
- co2: [],
- chemicals: [],
- dust: [],
- });
- const latestReading = {};
- let name = '';
- const timeseries = Object.keys(measurements).reduce((acc, measurement) => {
- const device = {
- space_id: gateway.space_id,
- id: gateway.id,
- data: measurements[measurement],
- };
- const spaceOfNode = this.findDeviceLocation(device, props);
- name = spaceOfNode.deviceName ? spaceOfNode.deviceName : `device ${device.id}`;
-
- // Get the latest reading
- latestReading[measurement] = {
- ts: device.data[device.data.length - 1].ts,
- value: device.data[device.data.length - 1].value,
- };
- latestReading.name = name;
-
- // Generate the time series
- acc[measurement] = this.generateTimeSeries(
- device.data,
- `${name} - ${measurement}`,
- );
- return acc;
- }, {});
- return {
- timeseries,
- latestReading: {
- id: gateway.id,
- reading: latestReading,
- },
- };
- }
-
- generateTimeSeries = (data, deviceName) => {
- // Get all points
- let prevPoint = null;
- const points = data.reduce((acc, i) => {
- if (prevPoint) {
- const curDate = new Date(i.ts);
- const prevDate = new Date(prevPoint.ts);
- const diffSeconds = (curDate.getTime() - prevDate.getTime()) / 1000;
- // If no data for more than 15 minutes, add null values
- if (diffSeconds > 900) {
- // Create a new date object 5 minutes ahead of the previous date
- let newDate = new Date(prevDate.getTime() + (300 * 1000));
- while (newDate.getTime() < curDate.getTime()) {
- acc.push([newDate, NaN]);
- newDate = new Date(newDate.getTime() + (300 * 1000));
- }
- }
- }
- acc.push([new Date(i.ts), i.value]);
-
- prevPoint = i;
- return acc;
- }, []);
-
- return new TimeSeries({
- name: deviceName,
- columns: ['time', deviceName],
- points,
- });
- }
-
- generateLineStyle = (line, index) => ({
- key: line.name(),
- color: GRAPH_COLORS[index % GRAPH_COLORS.length],
- width: 1,
- })
-
- fetchData = debounce((from) => {
- this.props.loadAllNodeData({
- building_id: this.props.buildingId,
- nodes: '',
- data: '',
- from: from.toUTCString(),
- });
- }, 500);
-
- updateTimeRange = (timerange) => {
- if (this.state.timeseries.length === 0) {
- return;
- }
-
- const endDate = timerange.end() > this.state.today ? this.state.today : timerange.end();
-
- if (timerange.begin() < this.state.from) {
- this.fetchData(timerange.begin());
- }
-
- this.setState({
- timerange: new TimeRange([timerange.begin(), endDate]),
- from: timerange.begin(),
- });
- }
-
- renderLineGraph = (timeseries) => {
- if (timeseries.length === 0) {
- return (
-
- );
- }
-
-
- const lineGraph = timeseries.map(line => (
-
- ));
-
- return lineGraph;
- }
-
- renderLatestReadings = () => {
- const { latestReadings } = this.state;
- if (Object.keys(latestReadings).length === 0) {
- return null;
- }
- const deviceIds = Object.keys(latestReadings);
- // The id of the device to displayed
- const latestReadingDeviceId = this.state.activeTab !== '' ? this.state.activeTab : deviceIds[0];
- const latestReadingDevice = latestReadings[latestReadingDeviceId];
- return (
-
-
Latest Readings
-
-
-
-
-
-
- | Timestamp |
- Measurement |
- Value |
-
-
-
-
- | {latestReadingDevice.temperature.ts} |
- °F Temperature |
- {latestReadingDevice.temperature.value} |
-
-
- | {latestReadingDevice.humidity.ts} |
- % Humidity |
- {latestReadingDevice.humidity.value} |
-
-
- | {latestReadingDevice.co2.ts} |
- PPM CO2 |
- {latestReadingDevice.co2.value} |
-
-
- | {latestReadingDevice.chemicals.ts} |
- PPB Chemicals |
- {latestReadingDevice.chemicals.value} |
-
-
- | {latestReadingDevice.dust.ts} |
- mcg3 Dust |
- {latestReadingDevice.dust.value} |
-
-
-
-
-
-
- );
- }
-
- render() {
- if (this.props.sensors.loading) {
- return
;
- }
- return (
-
-
- {this.renderLatestReadings()}
-
-
-
- Temperature
- {' '}
-
-
-
-
- Threshold determined using the OSHA. 2003 standard
-
-
-
-
- {this.state.temperatureTimeseries.length > 0 &&
-
-
- {this.props.sensors.loading && !this.props.sensors.error && }
-
-
-
-
-
-
-
-
- {this.renderLineGraph(this.state.temperatureTimeseries)}
-
-
-
-
-
-
-
-
-
-
-
- Humidity
- {' '}
-
-
-
-
- Threshold determined using the OSHA. 2003 standard
-
-
-
-
- {this.state.humidityTimeseries.length > 0 &&
-
-
- {this.props.sensors.loading && !this.props.sensors.error && }
-
-
-
-
-
-
-
-
- {this.renderLineGraph(this.state.humidityTimeseries)}
-
-
-
-
-
-
-
-
-
-
-
- CO2
- {' '}
-
-
-
-
- Threshold determined using the ASHRAE 2010 standard
-
-
-
-
- {this.state.co2Timeseries.length > 0 &&
-
-
- {this.props.sensors.loading && !this.props.sensors.error && }
-
-
-
-
-
-
-
-
- {this.renderLineGraph(this.state.co2Timeseries)}
-
-
-
-
-
-
-
-
-
-
- Chemicals
- {' '}
-
-
-
-
- Threshold determined using the EPA 2014 standard
-
-
-
-
- {this.state.chemicalsTimeseries.length > 0 &&
-
-
- {this.props.sensors.loading && !this.props.sensors.error && }
-
-
-
-
-
-
-
-
- {this.renderLineGraph(this.state.chemicalsTimeseries)}
-
-
-
-
-
-
-
-
-
-
- Dust
- {' '}
-
-
-
-
- Threshold determined using the EPA 2014 standard
-
-
-
-
- {this.state.dustTimeseries.length > 0 &&
-
-
- {this.props.sensors.loading && !this.props.sensors.error && }
-
-
-
-
-
-
-
-
- {this.renderLineGraph(this.state.dustTimeseries)}
-
-
-
-
-
-
-
-
- );
- }
-}
-
-SensorGraphIAQ.propTypes = {
- buildingId: PropTypes.string,
- className: PropTypes.string,
- loadAllNodeData: PropTypes.func,
- loadWeather: PropTypes.func,
- sensors: PropTypes.object, // eslint-disable-line
- buildingArea: PropTypes.object, // eslint-disable-line
- weather: PropTypes.object, // eslint-disable-line
-};
-
-SensorGraphIAQ.defaultProps = {
- className: '',
-};
-
-const mapStateToProps = state => (
- {
- sensors: state.sensors,
- buildingArea: state.buildingArea,
- weather: state.weather,
- }
-);
-
-const mapDispatchToProps = dispatch => (
- bindActionCreators({
- loadAllNodeData,
- loadWeather,
- }, dispatch)
-);
-
-export default connect(mapStateToProps, mapDispatchToProps)(SensorGraphIAQ);
diff --git a/src/containers/Sensors/Sensors.js b/src/containers/Sensors/Sensors.js
index b11cec5d5ca59cbd3b14da353128ce8002f66351..fd18a1cddf55f20608fafdaeb8e575c445ca6336 100644
--- a/src/containers/Sensors/Sensors.js
+++ b/src/containers/Sensors/Sensors.js
@@ -4,14 +4,11 @@ import { connect } from 'react-redux';
import LinkBarDetail from '../../components/LinkBarDetail';
import SensorGraphCon from './SensorGraph';
import BuildingAreaTable from '../BuildingArea/BuildingAreaTable';
+import BuildingEventsTable from '../BuildingEvents/BuildingEventsTable';
class Sensors extends Component {
- state = { renderIaq: false }
-
- setRenderIaq = (renderIaq) => {
- this.setState({ renderIaq });
- }
+ state = { }
render() {
return (
@@ -23,13 +20,6 @@ class Sensors extends Component {
{ name: 'Sensors', url: 'null' },
]}
links={[
- ...this.state.renderIaq ?
- [{
- name: 'IAQ',
- url: `/buildings/${this.props.buildingId}/sensors/iaq`,
- tags: '',
- internalLink: true,
- }] : [],
{
name: 'Install',
url: `/buildings/${this.props.buildingId}/sensors/install`,
@@ -41,7 +31,9 @@ class Sensors extends Component {
+
-
-
-
-
- );
- }
-}
-
-SensorsIAQ.propTypes = {
- buildingId: PropTypes.string,
-};
-
-export default connect(null, null)(SensorsIAQ);
diff --git a/src/containers/Weather/reducer.js b/src/containers/Weather/reducer.js
index bda12282ab838e2f7900f24b6dad2f3a8bad722f..1143bfc24b4848893620974a587fbfa7cc7f00d5 100644
--- a/src/containers/Weather/reducer.js
+++ b/src/containers/Weather/reducer.js
@@ -11,16 +11,34 @@ export const initState = {
};
export default (state = initState, action) => {
+ let weatherData = null;
+ let location = null;
+ let interval = null;
switch (action.type) {
case WEATHER_REQUESTED:
return { ...state, weatherLoading: true, weatherError: false };
case WEATHER_SUCCEEDED:
+ weatherData = action.weatherData.data.reduce((acc, weatherPoint) => {
+ location = weatherPoint.tags.location;
+ if (!(location in acc)) {
+ acc[location] = {};
+ }
+ interval = weatherPoint.tags.interval;
+ if (!(interval in acc[location])) {
+ acc[location][interval] = [];
+ }
+ acc[location][interval].push({
+ ts: weatherPoint.time,
+ val: weatherPoint.fields.value,
+ });
+ return acc;
+ }, {});
return {
...state,
weatherLoading: false,
weatherError: false,
- weatherData: action.weatherData.data,
+ weatherData,
};
case WEATHER_FAILED:
diff --git a/src/containers/Weather/sagas.js b/src/containers/Weather/sagas.js
index c2e7c5c3f411e05236041bd1fcff523d9a7f8885..86b4ffefa8dd36b038828a2a3d4362d8b61f8e2d 100644
--- a/src/containers/Weather/sagas.js
+++ b/src/containers/Weather/sagas.js
@@ -12,7 +12,8 @@ import {
} from './actions';
function* getWeather(action) {
- yield SagaRequests.get(action, weatherURL, weatherLoaded, weatherFailed);
+ const temp = SagaRequests.get(action, weatherURL, weatherLoaded, weatherFailed);
+ yield temp;
}
diff --git a/src/reducers.js b/src/reducers.js
index 01448c9569ae32d43bef5ea58b1fe81fd87d4d53..a6b710cfccb473f998eb3378f70b6372d23a1d55 100644
--- a/src/reducers.js
+++ b/src/reducers.js
@@ -15,6 +15,7 @@ import user from './containers/User/reducer';
import sensors from './containers/Sensors/reducer';
import buildingArea from './containers/BuildingArea/reducer';
import weather from './containers/Weather/reducer';
+import events from './containers/Event/reducer';
export default combineReducers({
@@ -33,4 +34,5 @@ export default combineReducers({
sensors,
buildingArea,
weather,
+ events,
});
diff --git a/src/routes.js b/src/routes.js
index ca9a6d86b597064597c2cbd31f1183d358cd5cab..381b8699d32f795b53981ae64b23c82a131b7e24 100644
--- a/src/routes.js
+++ b/src/routes.js
@@ -20,7 +20,6 @@ import ReportsHome from './screens/ReportsHome';
import Utilities from './components/Utilities';
import Envelope from './containers/Envelope';
import Sensors from './containers/Sensors/Sensors';
-import SensorsIAQ from './containers/Sensors/SensorsIAQ';
import SensorInstall from './containers/Sensors/SensorInstall';
import GatewayList from './components/SensorInstall/GatewayList';
import BuildingReports from './containers/BuildingReports';
@@ -56,7 +55,6 @@ export default (
diff --git a/src/sagas.js b/src/sagas.js
index 7ef7849ac909f44fa203273ad145f1153bb9778f..7f33bf76b92293a53360514ddf6728f329287a39 100644
--- a/src/sagas.js
+++ b/src/sagas.js
@@ -12,6 +12,7 @@ import userSaga from './containers/User/sagas';
import sensorsSaga from './containers/Sensors/sagas';
import buildingAreaSaga from './containers/BuildingArea/sagas';
import weatherSaga from './containers/Weather/sagas';
+import eventsSaga from './containers/Event/sagas';
export default function* rootSaga() {
@@ -30,5 +31,6 @@ export default function* rootSaga() {
sensorsSaga(),
buildingAreaSaga(),
weatherSaga(),
+ eventsSaga(),
];
}
diff --git a/src/utils/restServices.js b/src/utils/restServices.js
index 18aad52a20a5f45fa7f8336898e4aa37577159f0..c7158fbf8d8dac5c91cdd97bf28b300c6421d4a7 100644
--- a/src/utils/restServices.js
+++ b/src/utils/restServices.js
@@ -47,6 +47,7 @@ export const customImpactReportURL = `${reportService}/customimpact/`;
export const gatewayURL = `${iotService}/gateway/`;
export const sensewareNodeURL = `${iotService}/sensewarenode/`;
export const sensorImageURL = `${iotService}/sensorimage/`;
+export const eventsURL = `${iotService}/event/`;
export const userURL = `${userService}/user/`;
export const userGroupsURL = `${userService}/usergroup/`;