added thingsboard support

This commit is contained in:
Nico Melone
2022-10-12 10:14:10 -05:00
parent 69d91dfbce
commit 5051ba81dc
5 changed files with 34542 additions and 22963 deletions

38621
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,20 +3,20 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^14.4.3",
"aws-sdk": "^2.707.0", "aws-sdk": "^2.1226.0",
"axios": "^0.19.2", "axios": "^0.27.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.17.1", "express": "^4.18.1",
"fetch-retry": "^4.0.1", "fetch-retry": "^5.0.3",
"multer": "^1.4.2", "multer": "^1.4.2",
"nodemon": "^2.0.4", "nodemon": "^2.0.20",
"react": "^16.13.1", "react": "^18.2.0",
"react-dom": "^16.13.1", "react-dom": "^18.2.0",
"react-scripts": "3.4.1", "react-scripts": "5.0.1",
"react-toastify": "^6.0.6" "react-toastify": "^9.0.8"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@@ -7,6 +7,7 @@ label {
font-size: medium; font-size: medium;
} }
.App-header { .App-header {
background-color: #282c34; background-color: #282c34;
min-height: 5vh; min-height: 5vh;
@@ -20,6 +21,7 @@ label {
UpdateDeviceForm { UpdateDeviceForm {
padding: 10px; padding: 10px;
display: inline-block
} }
SyncForm { SyncForm {
padding: 10px; padding: 10px;
@@ -36,4 +38,6 @@ FileUpload {
.tool { .tool {
padding-bottom: 5em; padding-bottom: 5em;
display: inline-flex;
} }

View File

@@ -1,86 +1,111 @@
import React from 'react'; import React from "react";
import { ToastContainer, toast } from 'react-toastify'; import { ToastContainer, toast } from "react-toastify";
import axios from 'axios'; import axios from "axios";
import 'react-toastify/dist/ReactToastify.css'; import "react-toastify/dist/ReactToastify.css";
import './App.css'; import "./App.css";
class UpdateDeviceForm extends React.Component { class UpdateDeviceForm extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
mac: '', mac: "",
devicetype: '', devicetype: "",
m1: true m1: true,
tb: false,
}; };
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleTBChange = this.handleTBChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
} }
handleChange(event) { handleChange(event) {
const target = event.target; const target = event.target;
const value = target.name === 'm1' ? target.checked : target.value; const value = target.name === "m1" ? target.checked : target.value;
const name = target.name; const name = target.name;
this.setState({[name]: value}); this.setState({ [name]: value });
}
handleTBChange(event) {
const target = event.target;
const value = target.name === "tb" ? target.checked : target.value;
const name = target.name;
this.setState({ [name]: value });
} }
handleSubmit(event) { handleSubmit(event) {
const devUrl = { const devUrl = {
"abbflow": "abbflow", abbflow: "abbflow",
"advvfdipp": "advvfdipp/advvfdippv2", advvfdipp: "advvfdipp/advvfdippv2",
"dhsensor": "dhsensor", dhsensor: "dhsensor",
"dual_flowmeter": "dual_flowmeter", dual_flowmeter: "dual_flowmeter",
"flowmeterskid": "flowmeterskid", flowmeterskid: "flowmeterskid",
"flowmonitor": "flow-monitor/flow-monitorv2", flowmonitor: "flow-monitor/flow-monitorv2",
"PiFlow": "piflow", PiFlow: "piflow",
"ipp": "ipp", ipp: "ipp",
"plcpond": "plcpond", plcpond: "plcpond",
"multisensor": "multisensor", multisensor: "multisensor",
"dualactuator": "dualactuator", dualactuator: "dualactuator",
"dualactuatorpri": "dualactuator/alt", dualactuatorpri: "dualactuator/alt",
"plcfreshwater": "plcfreshwater", plcfreshwater: "plcfreshwater",
"pondlevel": "pondlevel", pondlevel: "pondlevel",
"promagmbs": "promagmbs", promagmbs: "promagmbs",
"poc": "poc", poc: "poc",
"recycle_train": "recycle_train", recycle_train: "recycle_train",
"rigpump": "rigpump", rigpump: "rigpump",
"submonitor": "submonitor", submonitor: "submonitor",
"swdcontroller": "swdcontroller", swdcontroller: "swdcontroller",
"tankalarms": "tankalarms", tankalarms: "tankalarms",
"tanktransfer": "tanktransfer", tanktransfer: "tanktransfer",
"tenflowmeterskid": "tenflowmeterskid", tenflowmeterskid: "tenflowmeterskid",
"transferlite": "transferlite" transferlite: "transferlite",
}
let config = ""
if (this.state.m1) {
if (this.state.devicetype === "plcfreshwater"){
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/plcfresh/'";
}else if(this.state.devicetype === "PiFlow"){
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/piflow/'";
}else{
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/','m1': 'https://hp-drivers.s3-us-west-2.amazonaws.com/M1/'"
}
}else {
if (this.state.devicetype === "plcfreshwater"){
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/plcfresh/'";
}else{
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/piflow/'"
}
}
const devArray = this.state.devicetype.split(',');
//console.log(devArray);
for(let index = 0; index < devArray.length; index ++){
if (devArray[index] === "dualactuatorpri"){
config = config + ", 'dualactuator': 'https://s3.amazonaws.com/pocloud-drivers/" + devUrl[devArray[index]] +"/'";
}else{
config = config + ",'" + devArray[index] +"': 'https://s3.amazonaws.com/pocloud-drivers/" + devUrl[devArray[index]] +"/'";
}
}; };
fetch('https://f5rrbd3r45.execute-api.us-east-1.amazonaws.com/device_config?mac='+this.state.mac + '&config={' + config + '}') let config = "";
.then(res => { if (this.state.tb) {
if (this.state.m1) {
if (this.state.devicetype === "plcfreshwater") {
config = "'mainHP': 'https://hp-thingsboard.s3.amazonaws.com/mainHP/'";
} else if (this.state.devicetype === "PiFlow") {
config = "'mainHP': 'https://hp-thingsboard.s3.amazonaws.com/mainHP/'";
} else {
config = "'mainHP': 'https://hp-thingsboard.s3.amazonaws.com/mainHP/','m1': 'https://hp-thingsboard.s3.amazonaws.com/M1/'";
}
} else {
if (this.state.devicetype === "plcfreshwater") {
config = "'mainHP': 'https://hp-thingsboard.s3.amazonaws.com/mainHPRPI/'";
} else {
config = "'mainHP': 'https://hp-thingsboard.s3.amazonaws.com/mainHPRPI/'";
}
}
} else {
if (this.state.m1) {
if (this.state.devicetype === "plcfreshwater") {
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/plcfresh/'";
} else if (this.state.devicetype === "PiFlow") {
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/piflow/'";
} else {
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/','m1': 'https://hp-drivers.s3-us-west-2.amazonaws.com/M1/'";
}
} else {
if (this.state.devicetype === "plcfreshwater") {
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/plcfresh/'";
} else {
config = "'mainHP': 'https://hp-drivers.s3-us-west-2.amazonaws.com/mainMeshify/piflow/'";
}
}
}
const devArray = this.state.devicetype.split(",");
//console.log(devArray);
for (let index = 0; index < devArray.length; index++) {
if (devArray[index] === "dualactuatorpri") {
config = config + ", 'dualactuator': 'https://s3.amazonaws.com/pocloud-drivers/" + devUrl[devArray[index]] + "/'";
} else {
config = config + ",'" + devArray[index] + "': 'https://s3.amazonaws.com/pocloud-drivers/" + devUrl[devArray[index]] + "/'";
}
}
fetch("https://f5rrbd3r45.execute-api.us-east-1.amazonaws.com/device_config?mac=" + this.state.mac + "&config={" + config + "}").then((res) => {
res.json(); res.json();
if (res["status"] === 200){ if (res["status"] === 200) {
toast.success("Added to Database", { toast.success("Added to Database", {
position: "bottom-center", position: "bottom-center",
autoClose: 5000, autoClose: 5000,
@@ -90,7 +115,7 @@ class UpdateDeviceForm extends React.Component {
draggable: true, draggable: true,
progress: undefined, progress: undefined,
}); });
}else { } else {
toast.error("Something went wrong try again", { toast.error("Something went wrong try again", {
position: "bottom-center", position: "bottom-center",
autoClose: 5000, autoClose: 5000,
@@ -102,22 +127,19 @@ class UpdateDeviceForm extends React.Component {
}); });
} }
console.log(res); console.log(res);
}) });
//console.log(config); //console.log(config);
event.preventDefault(); event.preventDefault();
} }
render() { render() {
return ( return (
<div className="tool"> <div className="tool">
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<label> <label>MAC: </label>
MAC: </label>
<input name="mac" type="text" value={this.state.value} onChange={this.handleChange} /> <input name="mac" type="text" value={this.state.value} onChange={this.handleChange} />
<label> <label>Device Type: </label>
Device Type: </label>
<select name="devicetype" value={this.state.devicetype} onChange={this.handleChange}> <select name="devicetype" value={this.state.devicetype} onChange={this.handleChange}>
<option value=""></option> <option value=""></option>
<option value="abbflow">ABB Flow Meter</option> <option value="abbflow">ABB Flow Meter</option>
@@ -148,9 +170,11 @@ class UpdateDeviceForm extends React.Component {
<option value="transferlite">Transfer Station Lite</option> <option value="transferlite">Transfer Station Lite</option>
</select> </select>
<label> <label>M1: </label>
M1: </label> <input name="m1" type="checkbox" checked={this.state.m1} onChange={this.handleChange} />
<input name="m1" type="checkbox" checked={this.state.m1} onChange={this.handleChange}/>
<label>ThingsBoard: </label>
<input name="tb" type="checkbox" checked={this.state.tb} onChange={this.handleTBChange} />
<input type="submit" value="Submit" /> <input type="submit" value="Submit" />
</form> </form>
@@ -162,8 +186,8 @@ class SyncForm extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
mac: '', mac: "",
devicetype: '' devicetype: "",
}; };
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
@@ -174,41 +198,40 @@ class SyncForm extends React.Component {
const target = event.target; const target = event.target;
const value = target.value; const value = target.value;
const name = target.name; const name = target.name;
this.setState({[name]: value}); this.setState({ [name]: value });
} }
handleSubmit(event) { handleSubmit(event) {
const tech = { const tech = {
"abbflow": "01:99", abbflow: "01:99",
"advvfdipp": "01:99", advvfdipp: "01:99",
"dhsensor": "01:99", dhsensor: "01:99",
"dual_flowmeter": "01:99", dual_flowmeter: "01:99",
"flowmeterskid": "01:99", flowmeterskid: "01:99",
"flowmonitor": "01:40", flowmonitor: "01:40",
"PiFlow": "01:99", PiFlow: "01:99",
"ipp": "01:99", ipp: "01:99",
"plcpond": "01:99", plcpond: "01:99",
"multisensor": "01:99", multisensor: "01:99",
"dualactuator": "01:99", dualactuator: "01:99",
"M1": "00:30", M1: "00:30",
"plcfreshwater": "01:99", plcfreshwater: "01:99",
"pondlevel": "01:30", pondlevel: "01:30",
"promagmbs": "01:99", promagmbs: "01:99",
"poc": "01:99", poc: "01:99",
"recycle_train": "01:99", recycle_train: "01:99",
"rigpump": "01:99", rigpump: "01:99",
"submonitor": "01:99", submonitor: "01:99",
"swdcontroller": "01:99", swdcontroller: "01:99",
"tankalarms": "01:99", tankalarms: "01:99",
"tanktransfer": "01:99", tanktransfer: "01:99",
"tenflowmeterskid": "01:99", tenflowmeterskid: "01:99",
"transferlite": "01:99", transferlite: "01:99",
} };
fetch('https://f5rrbd3r45.execute-api.us-east-1.amazonaws.com/sync?mac='+this.state.mac + '&devicetype='+ this.state.devicetype + '&technumber=' + tech[this.state.devicetype]) fetch("https://f5rrbd3r45.execute-api.us-east-1.amazonaws.com/sync?mac=" + this.state.mac + "&devicetype=" + this.state.devicetype + "&technumber=" + tech[this.state.devicetype]).then((res) => {
.then(res => {
res.json(); res.json();
console.log(res); console.log(res);
if (res["status"] === 200){ if (res["status"] === 200) {
toast.success("Sent to MQTT Broker", { toast.success("Sent to MQTT Broker", {
position: "bottom-center", position: "bottom-center",
autoClose: 5000, autoClose: 5000,
@@ -218,7 +241,7 @@ class SyncForm extends React.Component {
draggable: true, draggable: true,
progress: undefined, progress: undefined,
}); });
}else { } else {
toast.error("Something went wrong try again", { toast.error("Something went wrong try again", {
position: "bottom-center", position: "bottom-center",
autoClose: 5000, autoClose: 5000,
@@ -229,19 +252,17 @@ class SyncForm extends React.Component {
progress: undefined, progress: undefined,
}); });
} }
}) });
event.preventDefault(); event.preventDefault();
} }
render() { render() {
return ( return (
<div className="tool"> <div className="tool">
<form onSubmit={this.handleSubmit}> <form onSubmit={this.handleSubmit}>
<label> <label>MAC: </label>
MAC: </label>
<input name="mac" type="text" value={this.state.value} onChange={this.handleChange} /> <input name="mac" type="text" value={this.state.value} onChange={this.handleChange} />
<label> <label>Device Type: </label>
Device Type: </label>
<select name="devicetype" value={this.state.devicetype} onChange={this.handleChange}> <select name="devicetype" value={this.state.devicetype} onChange={this.handleChange}>
<option value=""></option> <option value=""></option>
<option value="abbflow">ABB Flow Meter</option> <option value="abbflow">ABB Flow Meter</option>
@@ -273,23 +294,21 @@ class SyncForm extends React.Component {
<input type="submit" value="Submit" /> <input type="submit" value="Submit" />
</form> </form>
</div> </div>
) );
} }
} }
class FileUpload extends React.Component { class FileUpload extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
selectedFile: null, selectedFile: null,
resample: '', resample: "",
timescale: 'S', timescale: "S",
file: null file: null,
}; };
this.onSampleChange = this.onSampleChange.bind(this); this.onSampleChange = this.onSampleChange.bind(this);
} }
/* state = { /* state = {
@@ -300,43 +319,40 @@ class FileUpload extends React.Component {
}; */ }; */
// On file select (from the pop up) // On file select (from the pop up)
onFileChange = event => { onFileChange = (event) => {
// Update the state // Update the state
this.setState({ selectedFile: event.target.files }); this.setState({ selectedFile: event.target.files });
}; };
onSampleChange(event) { onSampleChange(event) {
const target = event.target; const target = event.target;
const value = target.value; const value = target.value;
const name = target.name; const name = target.name;
this.setState({[name]: value}); this.setState({ [name]: value });
} }
// On file upload (click the upload button) // On file upload (click the upload button)
onFileUpload = () => { onFileUpload = () => {
let requests = []; let requests = [];
let names = []; let names = [];
let resample = this.state.resample; let resample = this.state.resample;
let timescale = this.state.timescale; let timescale = this.state.timescale;
// Update the formData object // Update the formData object
for(var x = 0; x<this.state.selectedFile.length; x++){ for (var x = 0; x < this.state.selectedFile.length; x++) {
// Request made to the backend api // Request made to the backend api
// Send formData object // Send formData object
names.push(this.state.selectedFile[x].name) names.push(this.state.selectedFile[x].name);
requests.push(axios.put("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/"+this.state.selectedFile[x].name, this.state.selectedFile[x])) requests.push(axios.put("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/" + this.state.selectedFile[x].name, this.state.selectedFile[x]));
} }
axios.all(requests).then(axios.spread((...respones) => { axios.all(requests).then(
axios.spread((...respones) => {
let sentData = true; let sentData = true;
for( var y = 0; y<respones.length; y++){ for (var y = 0; y < respones.length; y++) {
//console.log(respones[y]); //console.log(respones[y]);
if(respones[y]["data"] !== ""){ if (respones[y]["data"] !== "") {
sentData = false; sentData = false;
} }
} }
if (sentData){ if (sentData) {
toast.success("File(s) uploaded successfully", { toast.success("File(s) uploaded successfully", {
position: "bottom-center", position: "bottom-center",
autoClose: 2500, autoClose: 2500,
@@ -347,29 +363,30 @@ class FileUpload extends React.Component {
progress: undefined, progress: undefined,
}); });
//trigger lambda to process files //trigger lambda to process files
setTimeout(() => {fetch('https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/processing?filenames='+names + '&resample='+resample+timescale) setTimeout(() => {
.catch(console.log("API Gateway Timed Out"))},1000); fetch("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/processing?filenames=" + names + "&resample=" + resample + timescale).catch(console.log("API Gateway Timed Out"));
}, 1000);
//poll to get output csv every 30 secs until success because API Gateway will likely timeout //poll to get output csv every 30 secs until success because API Gateway will likely timeout
const fetch_retry = (n) => { const fetch_retry = (n) => {
fetch('https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/output.csv') fetch("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/output.csv")
.then(res => { .then((res) => {
res.text() res
.text()
.then((blob, error) => { .then((blob, error) => {
if (blob[0] === "<") throw error; if (blob[0] === "<") throw error;
const url = window.URL.createObjectURL(new Blob([blob])); const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a'); const link = document.createElement("a");
link.href = url; link.href = url;
link.setAttribute('download', 'output.csv'); link.setAttribute("download", "output.csv");
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
link.parentNode.removeChild(link); link.parentNode.removeChild(link);
axios.delete("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/output.csv") axios.delete("https://gvya48j9o5.execute-api.us-east-1.amazonaws.com/beta/channel-combiner/output.csv");
}).catch((error) => { })
.catch((error) => {
if (n === 1) throw error; if (n === 1) throw error;
toast.warning(`File not ready trying again in 30s, ${n-1} tries left`, { toast.warning(`File not ready trying again in 30s, ${n - 1} tries left`, {
position: "bottom-center", position: "bottom-center",
autoClose: 2500, autoClose: 2500,
hideProgressBar: false, hideProgressBar: false,
@@ -378,16 +395,21 @@ class FileUpload extends React.Component {
draggable: true, draggable: true,
progress: undefined, progress: undefined,
}); });
return setTimeout(() => {fetch_retry(n-1);}, 30000); return setTimeout(() => {
fetch_retry(n - 1);
}, 30000);
}); });
}).catch((error) => {
if (n === 1) throw error;
console.log("There are " + n-1 + " tries left")
return setTimeout(() => {fetch_retry(n-1);}, 30000);
}) })
} .catch((error) => {
if (n === 1) throw error;
console.log("There are " + n - 1 + " tries left");
return setTimeout(() => {
fetch_retry(n - 1);
}, 30000);
});
};
fetch_retry(6); fetch_retry(6);
}else{ } else {
toast.error("File(s) failed to upload try again", { toast.error("File(s) failed to upload try again", {
position: "bottom-center", position: "bottom-center",
autoClose: 2500, autoClose: 2500,
@@ -398,23 +420,19 @@ class FileUpload extends React.Component {
progress: undefined, progress: undefined,
}); });
} }
})
})) );
}; };
// File content to be displayed after // File content to be displayed after
// file upload is complete // file upload is complete
fileData = () => { fileData = () => {
if (this.state.selectedFile) { if (this.state.selectedFile) {
return ( return (
<div> <div>
<h2>File Details:</h2> <h2>File Details:</h2>
<p>File Name: {this.state.selectedFile.name}</p> <p>File Name: {this.state.selectedFile.name}</p>
<p>File Type: {this.state.selectedFile.type}</p> <p>File Type: {this.state.selectedFile.type}</p>
</div> </div>
); );
} else { } else {
@@ -428,7 +446,6 @@ class FileUpload extends React.Component {
}; };
render() { render() {
return ( return (
<div> <div>
<div className="tool"> <div className="tool">
@@ -442,11 +459,7 @@ class FileUpload extends React.Component {
<option value="H">Hours</option> <option value="H">Hours</option>
<option value="D">Days</option> <option value="D">Days</option>
</select> </select>
{<button onClick={this.onFileUpload}> {<button onClick={this.onFileUpload}>Upload</button>}
Upload
</button> }
</div> </div>
</div> </div>
); );
@@ -454,29 +467,17 @@ class FileUpload extends React.Component {
} }
function App() { function App() {
document.title = "HP Tools" document.title = "HP Tools";
return ( return (
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">Tools</header>
Tools
</header>
<header className="toolheader">Update/Create Device</header> <header className="toolheader">Update/Create Device</header>
<UpdateDeviceForm></UpdateDeviceForm> <UpdateDeviceForm></UpdateDeviceForm>
<header className="toolheader">Sync Device</header> <header className="toolheader">Sync Device</header>
<SyncForm></SyncForm> <SyncForm></SyncForm>
<header className="toolheader">Channel Combiner</header> <header className="toolheader">Channel Combiner</header>
<FileUpload></FileUpload> <FileUpload></FileUpload>
<ToastContainer <ToastContainer position="bottom-center" autoClose={5000} hideProgressBar={false} newestOnTop={false} closeOnClick rtl={false} pauseOnFocusLoss draggable pauseOnHover />
position="bottom-center"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
</div> </div>
); );
} }

18875
yarn.lock

File diff suppressed because it is too large Load Diff