import React, { Component } from "react"; import { API } from "aws-amplify"; import { Tabs, Tab, Button, Alert, ProgressBar, Grid, Row, Col, Table, Label, FormGroup, FormControl, ControlLabel, ListGroup, ListGroupItem, HelpBlock, Modal } from 'react-bootstrap'; import { Card } from "components/Card/Card.jsx"; class DeviceDetail extends Component { constructor(props) { super(props); // General this.goDeviceRegistration = this.goDeviceRegistration.bind(this); // Commands this.handleCommandStatusChange = this.handleCommandStatusChange.bind(this); this.handleCommandSubmit = this.handleCommandSubmit.bind(this); this.handleCommandDetailShow = this.handleCommandDetailShow.bind(this); this.handleCommandDetailClose = this.handleCommandDetailClose.bind(this); this.handleCreateCommand = this.handleCreateCommand.bind(this); this.handleCommandClose = this.handleCommandClose.bind(this); this.handleTargetTemperatureChange = this.handleTargetTemperatureChange.bind(this); // Logs this.handleEventLogsSubmit = this.handleEventLogsSubmit.bind(this); this.handleEventTypeChange = this.handleEventTypeChange.bind(this); // Common this.goBack = this.goBack.bind(this); this.handleTabSelect = this.handleTabSelect.bind(this); // Sets up initial state this.state = { // common statusInitial: true, page: 'general', // general loadingDevice: false, loadingStatus: false, device: false, deviceStatus: false, deviceError: false, statusError: false, title: '', isMinimized: false, // commands actualTemperature: '', targetTemperature: '', updatedTargetTemperature: '', targetTemperatureState: null, setCommand: '', setCommandValue: '', powerStatus: '', commandShow: false, commandMessage: '', showCommandHelpBlock: false, commadError: false, commandHasMore: true, loadingCommand: false, creatingCommand: false, commandDetail: false, commandDetailError: false, commandDetailLoading: false, commandLastevalkey: null, commands: [], commandDetailShow: false, commandInitial: true, commandStatus: '', updatedCommandStatus: '', // logs eventLogsError: false, loadingEventLogs: false, eventLogsHasMore: true, events: [], eventLogsLastevalkey: null, eventType: '', updatedEventType: '', eventDetailShow: false, eventDetail: false, eventDetailError: false, eventDetailLoading: false, eventLogsInitial: true, }; } componentDidMount() { this.handleResize(); this.getDevice(); this.getDeviceStatus(); this.timer = setInterval(async () => { await this.getDeviceStatus(); }, 60000); // Gets status every 1 minute window.addEventListener('scroll', this.handleScroll); window.addEventListener('resize', this.handleResize); } componentWillUnmount() { clearInterval(this.timer); window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('resize', this.handleResize); } // Handles tab select handleTabSelect(eventKey) { if (eventKey === 'commands' && this.state.commandInitial) { this.setState({ commandInitial: false }); this.getCommands(); } else if (eventKey === 'logs' && this.state.eventLogsInitial) { this.setState({ eventLogsInitial: false }); this.getEventLogs(); } this.setState({ page: eventKey }); } // Handles scroll down to load more handleScroll = (_event) => { let page = this.state.page; if (page === 'commands') { const {commandError, loadingCommand, commandHasMore} = this.state; if (commandError || loadingCommand || !commandHasMore) return; // Checks that the page has scrolled to the bottom if (this.props.isScrollBottom() && !this.state.commandInitial) { this.getCommands(); } } else if (page === 'logs') { const {eventLogsError, loadingEventLogs, eventLogsHasMore} = this.state; if (eventLogsError || loadingEventLogs || !eventLogsHasMore) return; // Checks that the page has scrolled to the bottom if (this.props.isScrollBottom() && !this.state.eventLogsInitial) { this.getEventLogs(); } } }; // Handles window resize handleResize = (_event) => { if (window.innerWidth < 993) { this.setState({ isMinimized: true, }); } else { this.setState({ isMinimized: false, }); } }; // Goes to the device registration instruction goDeviceRegistration = (deviceId) => { this.props.history.push({ pathname: '/devices/registration', state: { deviceId: deviceId } }); } // Handles command status change handleCommandStatusChange = (event) => { this.setState({ updatedCommandStatus: event.target.value}); } // Handles command submit handleCommandSubmit = async (event) => { event.preventDefault(); this.setState({ commands: [], commandLastevalkey: null, commandError: false, commandStatus: this.state.updatedCommandStatus, }); this.getCommands(); } // Handles command detail handleCommandDetailShow = async (deviceId, commandId) => { this.setState({ commandDetailError: false, commandDetail: false, commandDetailLoading: true }); let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/commands/${commandId}`; let params = { headers: { 'Authorization': token, }, response: true }; API.get(apiName, path, params) .then(response => { this.setState({ commandDetail: response.data }); }) .catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ commandDetailError: message }); }) .finally(() => { this.setState({ commandDetailLoading: false }); }) this.setState({ commandDetailShow: true }); } // Handles command detail close handleCommandDetailClose = () => { this.setState({ commandDetailShow: false }); } // Handles create command handleCreateCommand = (mode) => { let pass = true; let message = ''; let setCommand = ''; let setCommandValue = ''; switch (mode) { case 'HEAT': { message = 'turn on the heat'; setCommand = 'set-mode'; setCommandValue = 'HEAT'; break; } case 'AC': { message = 'turn on the AC'; setCommand = 'set-mode'; setCommandValue = 'AC'; break; } case 'OFF': { message = 'turn off the device'; setCommand = 'set-mode'; setCommandValue = 'OFF'; break; } case 'TEMPERATURE': { if (!this.targetTemperatureValidate()) { pass = false; message = 'Invalid target temperature. (50 <= temperature <= 110)'; } else if (this.state.targetTemperature === this.state.updatedTargetTemperature) { pass = false; message = 'Target temperature has not been changed.'; } else { message = `set the temperature to ${this.state.updatedTargetTemperature}`; setCommand = 'set-temp'; setCommandValue = this.state.updatedTargetTemperature; } break; } default: { break; } } if (pass) { this.setState({ setCommand: setCommand, setCommandValue: setCommandValue, commandShow: true, message: message, }, () => {}); } else { this.props.handleNotification(message, 'error', 'pe-7s-check', 5); } } // Handles command pop up close handleCommandClose = () => { this.setState({ commandShow: false }); } // Handles target temperature change handleTargetTemperatureChange = (event) => { this.setState({ updatedTargetTemperature: event.target.value }, () => { this.targetTemperatureValidate(); }); } // Handles event logs submit handleEventLogsSubmit = async (event) => { event.preventDefault(); this.setState({ events: [], eventLogsLastevalkey: null, eventLogsError: false, eventType: this.state.updatedEventType, }); this.getEventLogs(); } // Handles input changes handleEventTypeChange = (event) => { this.setState({ updatedEventType: event.target.value}); } // Handles event detail handleEventDetailShow = async (deviceId, eventId) => { this.setState({ eventDetailError: false, eventDetail: false, eventDetailLoading: true }); let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/events/${eventId}`; let params = { headers: { 'Authorization': token, }, response: true }; API.get(apiName, path, params) .then(response => { this.setState({ eventDetail: response.data }); if (!response.data.ack) { this.updateEvent(deviceId, eventId); } }) .catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ eventDetailError: message }); }) .finally(() => { this.setState({ eventDetailLoading: false }); }) this.setState({ eventDetailShow: true }); } // Handles detail close handleEventDetailClose = () => { this.setState({ eventDetailShow: false }); } // Gets device information getDevice = async () => { this.setState({ loadingDevice: true }); const { deviceId } = this.props.match.params; let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}`; let params = { headers: { 'Authorization': token, }, response: true, }; API.get(apiName, path, params) .then(response => { let device = response.data; this.setState({ device: device, title: `${device.deviceName}`, }); }) .catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ deviceError: message }) }) .finally(() => { this.setState({ loadingDevice: false }); }); } // Gets device status getDeviceStatus = async () => { if (this.state.statusInitial) { this.setState({ loadingStatus: true }); } const { deviceId } = this.props.match.params; let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/status`; let params = { headers: { 'Authorization': token, }, response: true, }; API.get(apiName, path, params) .then(response => { let deviceStatus = ''; if ( Object.keys(response.data).length === 0 || response.data.state.reported === undefined || !response.data.connected ) { deviceStatus = { state: {}, connected: false, } this.setState({ actualTemperature: 'N/A', targetTemperature: 'N/A', powerStatus: 'Disconnected', }, () => {}); } else { deviceStatus = response.data; let reported = response.data.state.reported; if (this.state.statusInitial) { this.setState({ actualTemperature: reported.actualTemperature, targetTemperature: reported.targetTemperature, updatedTargetTemperature: reported.targetTemperature, powerStatus: reported.powerStatus, }); } else { this.setState({ actualTemperature: reported.actualTemperature, }); } } this.setState({ deviceStatus: deviceStatus }); }) .catch(error => { let message = error.response; if (message !== undefined) { if (message.data.error === 'MissingRegistration') { // If getting the device status has an issue due to MissingRegistration, clear the timer. clearInterval(this.timer); } else { this.props.handleNotification('Failed to get device status', 'error', 'pe-7s-check', 5); } } this.setState({ actualTemperature: 'N/A', targetTemperature: 'N/A', powerStatus: 'FAIL', }); }) .finally(() => { if (this.state.statusInitial) { this.setState({ loadingStatus: false, statusInitial: false, }); } }); } // Gets device commands getCommands = async () => { this.setState({ loadingCommand: true}); const { deviceId } = this.props.match.params; let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/commands`; let params = { headers: { 'Authorization': token, }, response: true, queryStringParameters: { lastevalkey: JSON.stringify(this.state.commandLastevalkey), commandStatus: this.state.commandStatus, } }; API.get(apiName, path, params) .then(response => { /** * If response has no data and LastEvaluatedKey is null, there is no more data. * Otherwise, show the data. */ if (response.data.Items.length === 0 && (response.data.LastEvaluatedKey === null || response.data.LastEvaluatedKey === undefined)) { this.setState({ commandHasMore: false, loadingCommand: false }); } else { this.setState({ commandHasMore: response.data.LastEvaluatedKey !== undefined, loadingCommand: false, commandLastevalkey: response.data.LastEvaluatedKey, commandStatus: response.data.commandStatus, commands: [ ...this.state.commands, ...response.data.Items ] }); } }).catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ commandError: message, loadingCommand: false }); }); }; // Validates target temperature targetTemperatureValidate = () => { let temperature = this.state.updatedTargetTemperature; let pass = !isNaN(temperature) && temperature !== '' && temperature >= 50 && temperature <= 110; if (!pass) { this.setState({ showHelpBlock: true, targetTemperatureState: 'error', }); } else { this.setState({ showHelpBlock: false, targetTemperatureState: null, }); } return pass; } // Creates a command createCommand = async () => { if (!this.state.creatingCommand) { this.setState({ creatingCommand: true }); const { deviceId } = this.props.match.params; let {targetTemperature, setCommand, setCommandValue, powerStatus} = this.state; if (setCommand === 'set-temp') { targetTemperature = this.state.updatedTargetTemperature; } if (setCommand === 'set-mode') { powerStatus = setCommandValue; } let body = { deviceId: deviceId, commandDetails: { command: this.state.setCommand, value: this.state.setCommandValue, }, shadowDetails: { powerStatus: powerStatus, actualTemperature: this.state.actualTemperature, targetTemperature: targetTemperature, } } let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/commands`; let params = { headers: { 'Authorization': token, }, body: body, response: true, }; API.post(apiName, path, params) .then(response => { this.setState({ powerStatus: powerStatus, targetTemperature: targetTemperature, }, () => {}); this.props.handleNotification('Success to execute the command', 'success', 'pe-7s-check', 5); }) .catch(error => { this.props.handleNotification('Failed to execute the command', 'error', 'pe-7s-check', 5); }) .finally(() => { this.setState({ commandShow: false, creatingCommand: false, commands: [], commandLastevalkey: null, commandError: false, }); this.getDeviceStatus(); this.getCommands(); }); } else { this.props.handleNotification('Still in progress to execute the command', 'error', 'pe-7s-check', 5); } } // Gets device event log getEventLogs = async () => { this.setState({ loadingEventLogs: true}); const { deviceId } = this.props.match.params; let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/events`; let params = { headers: { 'Authorization': token, }, response: true, queryStringParameters: { lastevalkey: JSON.stringify(this.state.eventLogsLastevalkey), eventType: this.state.eventType } }; API.get(apiName, path, params) .then(response => { /** * If response has no data and LastEvaluatedKey is null, there is no more data. * Otherwise, show the data. */ if (response.data.Items.length === 0 && (response.data.LastEvaluatedKey === null || response.data.LastEvaluatedKey === undefined)) { this.setState({ eventLogsHasMore: false, loadingEventLogs: false }); } else { this.setState({ eventLogsHasMore: response.data.LastEvaluatedKey !== undefined, loadingEventLogs: false, eventLogsLastevalkey: response.data.LastEvaluatedKey, eventType: response.data.eventType, events: [ ...this.state.events, ...response.data.Items ] }); } }).catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ eventLogsError: message, loadingEventLogs: false }); }); } // Updates the event updateEvent = async (deviceId, eventId) => { let token = await this.props.getToken(); let apiName = 'smart-product-api'; let path = `devices/${deviceId}/events/${eventId}`; let params = { headers: { 'Authorization': token, }, body: { id: eventId, ack: true, suppress: true, }, }; API.put(apiName, path, params) .catch(error => { let message = error.response; if (message === undefined) { message = error.message; } else { message = error.response.data.message; } this.setState({ eventDetailError: message }); }); } // Goes back to the main landing page goBack() { this.props.history.push('/devices'); } render() { const { loadingDevice, loadingStatus, device, deviceStatus, loadingCommand, commandDetailLoading, creatingCommand, commands, commandStatus, powerStatus, commandDetail, loadingEventLogs, eventDetailLoading, events, eventType, eventDetail, commandHasMore, eventLogsHasMore, showCommandHelpBlock, deviceError, statusError, commandError, commandDetailError, eventLogsError, eventDetailError, title, message, isMinimized, } = this.state; const commandThArray = ['Command Detail', 'Command Status', 'Created At', 'Updated At']; const disabledConditions = ['FAIL', 'Disconnected']; const eventThArray = ['Event Message', 'Event Type', 'Created At']; return(
{ !loadingDevice &&

Device Information

{ device && this.handleTabSelect(k)} className={ isMinimized ? "mobile_tabs" : "" }> {/* General Tab */} { device && deviceStatus &&
Serial Number {device.deviceId}
Created At {device.createdAt}
Activated At {device.activatedAt}
Updated At {device.updatedAt}
Status { device.status === 'pending' &&

}
{ device.details && Object.keys(device.details).map(key => { return( ) }) } { (!device.details || Object.keys(device.details).length === 0) && }
Details
{key} {device.details[key]}
Device Details Not found
} /> } { device && deviceStatus &&
Connectivitiy {deviceStatus.connected ? "Connected" : "Disconnected"}
{ Object.keys(deviceStatus.state).length !== 0 && Object.keys(deviceStatus.state.reported).map(key => { return( ) }) } { Object.keys(deviceStatus.state).length === 0 && }
State
{key} {deviceStatus.state.reported[key]}
State Not found
} /> } {/* Commands Tab */}

Issue Remote Command

Mode

Current Mode
   

Temperature (℉)

Actual Temperature Target Temperature -1} onChange={this.handleTargetTemperatureChange} /> { showCommandHelpBlock && Invalid value }
} /> Search by Command Status
} /> { !isMinimized && { commandThArray.map((prop, key) => { return ( {prop} ); }) } {/* If there is no command, showing no command found */} { commands.length === 0 && !loadingCommand && !commandHasMore && No command found. } { commands.map(command => { return ( { command.status === 'success' && } { command.status === 'failed' && } { command.status === 'pending' && } {command.createdAt} {command.updatedAt} ); }) } } /> } { isMinimized && {/* If there is no command, showing no command found */} { commands.length === 0 && !loadingCommand && !commandHasMore &&
No command found.
} { commands.map(command => { return (
this.handleCommandDetailShow(command.deviceId, command.commandId)}> C: {this.props.handleDateSize(command.createdAt)} { command.status === 'success' && } { command.status === 'failed' && } { command.status === 'pending' && } U: {this.props.handleDateSize(command.updatedAt)} {command.details.command} / {command.details.value}
); }) }
} /> }
{ loadingCommand &&
} { commandError && {this.state.commandError} } {/* Event Log Tab */} Search by Event Type
} /> { !isMinimized && { eventThArray.map((prop, key) => { return ( {prop} ); }) } {/* If there is no event, showing no event found */} { events.length === 0 && !loadingEventLogs && !eventLogsHasMore && No event found. } { events.map(event => { return ( {event.type === 'info' && } {event.type === 'warning' && } {event.type === 'error' && } {event.type === 'diagnostic' && } {event.createdAt} ); }) } } /> } { isMinimized && {/* If there is no event, showing no event found */} { events.length === 0 && !loadingEventLogs && !eventLogsHasMore &&
No event found.
} { events.map(event => { return (
this.handleEventDetailShow(event.deviceId, event.id)}> {this.props.handleDateSize(event.createdAt)}   { event.type === 'info' && } { event.type === 'warning' && } { event.type === 'error' && } { event.type === 'diagnostic' && }
); }) }
} /> }
{ loadingEventLogs &&
} { eventLogsError && {this.state.eventLogsError} }
}
} { (loadingDevice || loadingStatus) &&
} { deviceError && Device Error: {this.state.deviceError} } { statusError && Status Error: {this.state.statusError} } {/* Modal dialog */} Command Detail { commandDetailLoading &&
} { commandDetailError && {this.state.commandDetailError} } { commandDetail &&
Command ID {commandDetail.commandId}
Device ID {commandDetail.deviceId}
Command Status {commandDetail.status}
Reason {commandDetail.reason}
Created At {commandDetail.createdAt}
Updated At {commandDetail.updatedAt}
Details { Object.keys(commandDetail.details).map(key => { return(
 {commandDetail.details[key]}
) }) }
}
Device Command Are you sure to {message}? { creatingCommand &&
}
Event Detail { eventDetailLoading &&
} { eventDetailError && {this.state.eventDetailError} } { eventDetail &&
ID {eventDetail.id}
Device ID {eventDetail.deviceId}
Message {eventDetail.message}
Details { Object.keys(eventDetail.details).map(key => { return(
 {eventDetail.details[key]}
) }) }
Type {eventDetail.type}
Created At {eventDetail.createdAt}
Sent At {eventDetail.sentAt}
Updated At {eventDetail.updatedAt}
Acknowledged {eventDetail.ack ? "Read" : "Unread"}
}
{ device && } ) } } export default DeviceDetail;