-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStation AQI vs sensors.json
1 lines (1 loc) · 61.1 KB
/
Station AQI vs sensors.json
1
[{"id":"29a3e25b.3b2fee","type":"tab","label":"station AQI data input","disabled":true,"info":""},{"id":"83ca2f35.d0e7a","type":"http request","z":"29a3e25b.3b2fee","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://an_gov_data.s3.amazonaws.com/Sites/060850005.json","tls":"","persist":false,"proxy":"","authType":"","x":350,"y":300,"wires":[["882df8a4.f3d498"]]},{"id":"307b0ae0.195ef6","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":300,"wires":[["83ca2f35.d0e7a"]]},{"id":"882df8a4.f3d498","type":"function","z":"29a3e25b.3b2fee","name":"","func":"//just going to leave this here but it's useless because the \n//file's timestamp changes enough without the PM2.5 data \n//changing that it's not usable.\n/*\nlet LastFileTime = context.get('FileTime') || '0';\n\nif (LastFileTime == msg.payload.fileWrittenDateTime) {\n //node.warn (`process: no new data`);\n return null;\n}\ncontext.set('FileTime', msg.payload.fileWrittenDateTime);\n*/\nvar inputTimeStr = msg.payload.utcDateTimes[msg.payload.utcDateTimes.length - 1];\n\nlet PrevValData = context.get('previousValidDataTime') || '0';\n//node.warn(`process: Previous valid data ${PrevValData} UTC`);\n\nvar Timestamp = new Date(inputTimeStr + \" UTC\");\nvar CurrTime = new Date();\nlet [_, year, month, day, hour, min, sec] = msg.payload.fileWrittenDateTime.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\nvar RemoteFileTime = new Date(Date.UTC(year, month - 1, day, hour, min, sec));\n\n//var Pm2_5;\n//var AqiPm2_5;\n\nvar msgTimeOfData = {topic:'jackson/TimeOfData', payload:null};\nvar msgTimeOfProcess = {topic:'jackson/TimeOfProcess', payload:null};\nvar msgPm25Aqi = {topic:'jackson/PM2_5_AQI', payload:null};\nvar msgPm25Conc = {topic:'jackson/PM2_5_Conc', payload:null};\nvar msgLoggingArray = {topic:'jackson/AqiLogging', payload:null};\n\n/*\nmsgTimeOfData.topic = 'TimeOfData';\nmsgTimeOfProcess.topic = 'TimeOfProcess';\nmsgPm25Aqi.topic = 'PM2_5_AQI';\nmsgPm25Conc.topic = 'PM2_5_Conc';\nmsgLoggingArray.topic = 'AqiLogging';\n*/\nif (inputTimeStr == PrevValData) {\n //node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}.\\n(1)No new data.`);\n return null;\n}\n//node.warn(`process: setting Previous valid data ${inputTimeStr}`);\n\n//node.warn (`process: input time ${inputTimeStr}`);\n//node.warn (`process: Timestamp is ${Timestamp}`);\n//node.warn (`process: file time ${RemoteFileTime}, ${RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})}`);\n\n//node.warn(`at ${CurrTime} last data time ${Timestamp}.`);\n\nfor (var i=0 ; i < msg.payload.monitors.length ; i++) {\n if (msg.payload.monitors[i].parameterName == \"PM2.5 - Principal\"){\n //AqiPm2_5 = \n // msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 1];\n \n if (msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 1] >= 0) {\n msgPm25Conc.payload = msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 1];\n msgPm25Aqi.payload = \n msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 1];\n } else {\n inputTimeStr = msg.payload.utcDateTimes[msg.payload.utcDateTimes.length - 2];\n Timestamp = new Date(inputTimeStr + \" UTC\");\n if (inputTimeStr == PrevValData) {\n node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}\\n(hour rollback). (2)No new data.`);\n return null;\n }\n context.set('previousValidDataTime', inputTimeStr);\n //node.warn(`process: setting Previous valid data ${inputTimeStr}`);\n if (msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 2] >=0){\n msgPm25Conc.payload = msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 2];\n msgPm25Aqi.payload = \n msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 2];\n } else {\n //the jackson station is really fucking flaky. It can take 6+ hors in some cases for data to actually be available.\n //I swear somneone has to manually guess the missing data.\n //I'll just drop it.\n msgPm25Aqi.payload = \"----\";\n msgPm25Conc.payload = \"----\";\n }\n Timestamp = new Date(inputTimeStr + \" UTC\");\n }\n }\n}\n\n//node.warn (`process: Local time of reading: ${LocalTime}`);\n//node.warn (`process: time of reading: ${Timestamp}`);\n//node.warn (`process: AQI ${msgPm25Aqi.payload} PM2.5 ${msgPm25Conc.payload}`);\nmsgTimeOfData.payload = Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\nmsgTimeOfProcess.payload = CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}),\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\nmsgLoggingArray.payload=logging_array;\n\n//outputs, \n//time this func runs, \n//time of data,\n//PM2.5 AQI, \n//PM2.5 concentration, \n// array for logging\n// feedback message to try and prevent hammering server (may be unncessary)\ncontext.set('previousValidDataTime', inputTimeStr);\n//node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}.\\nAQI ${msgPm25Aqi.payload} PM2.5 ${msgPm25Conc.payload}`);\nreturn [msgTimeOfProcess, msgTimeOfData, msgPm25Aqi, msgPm25Conc, msgLoggingArray]","outputs":5,"noerr":0,"initialize":"","finalize":"","x":540,"y":300,"wires":[["48ed80ac.0681a"],["2abe22d3.53c49e"],["7475593e.193268","a8d2c88b.efc278"],["37f41b41.fc0704"],["52a90757.e6abe8","db69665a.b784e8","22905417.a412dc","733903c4.238b4c"]]},{"id":"7475593e.193268","type":"ui_artlessgauge","z":"29a3e25b.3b2fee","group":"afdd0a85.703988","order":3,"width":0,"height":0,"name":"AQI","icon":"","label":"Computed AQI","unit":"","layout":"linear","decimals":"2","differential":false,"minmax":false,"colorTrack":"#555555","style":"","colorFromTheme":true,"property":"payload","secondary":"secondary","inline":false,"animate":true,"sectors":[{"val":0,"col":"#009900","t":"min","dot":2},{"val":51,"col":"#e5f505","t":"sec","dot":2},{"val":101,"col":"#eb6600","t":"sec","dot":2},{"val":151,"col":"#fe0606","t":"sec","dot":2},{"val":201,"col":"#850000","t":"sec","dot":2},{"val":301,"col":"#a50382","t":"sec","dot":2},{"val":401,"col":"#53045d","t":"sec","dot":2},{"val":500,"col":"#53045d","t":"max","dot":2}],"lineWidth":"7","bgcolorFromTheme":true,"diffCenter":"","x":890,"y":260,"wires":[]},{"id":"37f41b41.fc0704","type":"ui_text","z":"29a3e25b.3b2fee","group":"8c787508.2a78a8","order":1,"width":6,"height":2,"name":"2.5ppm raw","label":"Jackson 2.5ppm raw","format":"{{msg.payload}}","layout":"row-spread","x":910,"y":300,"wires":[]},{"id":"2abe22d3.53c49e","type":"ui_text","z":"29a3e25b.3b2fee","group":"afdd0a85.703988","order":2,"width":3,"height":1,"name":"Jackson time of data","label":"Jackson time of data","format":"{{msg.payload}}","layout":"row-left","x":940,"y":220,"wires":[]},{"id":"48ed80ac.0681a","type":"ui_text","z":"29a3e25b.3b2fee","group":"afdd0a85.703988","order":2,"width":3,"height":1,"name":"Jackson Time locally processed","label":"Jackson Time locally processed","format":"{{msg.payload}}","layout":"row-left","x":970,"y":180,"wires":[]},{"id":"91a6ef8f.b120f","type":"link in","z":"29a3e25b.3b2fee","name":"","links":["1108cb7d.123aa5"],"x":135,"y":380,"wires":[["83ca2f35.d0e7a"]]},{"id":"22905417.a412dc","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"Jackson_data","flatten":false,"name":"","x":930,"y":340,"wires":[[]]},{"id":"52a90757.e6abe8","type":"function","z":"29a3e25b.3b2fee","name":"","func":"\n\n//var AqiList = {Sds011Aqi:-1, Pms3003Aqi:-1, PmsA003Aqi:-1, Pms5003_aAqi:-1, Pms5003_bAqi:-1, JacksonAqi:-1};\n//turn that into an array suitable for logging\n//jackson's timestamp, , jackson data, sds011 AQI, PMS3003, PMSa003, PMS5003_a, PMS5003_b\n//that's all we care about since this is just to compare things\n\nconst TIME_IDX = 0;\nconst JACKSON_IDX = 1;\nconst KNOX_IDX = 2;\nconst SDS011_IDX = 3;\nconst PMS3003_IDX = 4;\nconst PMSA003_IDX = 5;\nconst PMS5003_A_IDX = 6;\nconst PMS5003_B_IDX = 7;\nconst LOG_TIME_IDX = 8;\n\nvar Hrs = -1;\nvar dbgTime = 0;\n\n//let buffer = context.get('AqiArray') || [];\nlet AqiArray = context.get('AqiArray') || [];\n\n//let DataArray = new Array(7);\n\n\n\n\n/*\nvar msgLoggingArray = {topic:\"AqiLogging\", payload:null};\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), //Time of data\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\n*/\n//\n/*\ntopic list\nAqiLogging\nhome/SDS011/ppm25/1hrAvg\nhome/PMS3003/ppm25/1hrAvg\nhome/PMSa003/ppm25/1hrAvg\nhome/PMS5003_a/ppm25/1hrAvg\nhome/PMS5003_b/ppm25/1hrAvg\n\n\n*/\n\n//node.warn(`Message: ${msg.topic}`);\n//node.warn(`payload: ${msg.payload}`);\n\nvar Timestamp;\n\nif (typeof(msg.timestamp) != \"undefined\") {\n Hrs = msg.timestamp.getHours();\n dbgTime = msg.timestamp;\n /* looks like this is unnecessary for sync but I'm not 100% certain\n if (Hrs == 0) {\n Hrs = 23;\n } else {\n Hrs -= 1;\n }\n */\n if (AqiArray[Hrs] == null || AqiArray[Hrs] == \"undefined\"){\n //node.warn (`creating a new array at AqiArray[${Hrs}]`);\n AqiArray[Hrs] = new Array(8);\n }\n //node.warn (`for ${dbgTime} Hrs is ${Hrs}`);\n}\n\n\nif (msg.topic == 'jackson/AqiLogging') {\n Timestamp = new Date(msg.payload[1]);\n Hrs = Timestamp.getHours();\n //node.warn (`Knox Hrs is ${Hrs}`);\n if (AqiArray[Hrs] == null) {\n //probably recently deployed and the local sensor entries were lost, or not added as needed.\n node.warn(`Entry clear, either no data or already published. Dropping msg`);\n return null;\n }\n AqiArray[Hrs][TIME_IDX] = msg.payload[1];\n AqiArray[Hrs][JACKSON_IDX] = msg.payload[3];\n //assume the other array entries are already filled in because of the huge amount of lag in the station readings\n // but we now have 2 stations to look at so that is one other index that needs to be filled out before producing output\n if (AqiArray[Hrs][KNOX_IDX] == null) {\n //wait for knox data\n return null;\n }\n AqiArray[Hrs][LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n msg.payload = AqiArray[Hrs];\n msg.topic = 'pm2.51hr_avgs';\n //clear the entry\n AqiArray[Hrs] = null;\n context.set ('AqiArray', AqiArray);\n return msg;\n \n}\n\n\nif (msg.topic == 'knox/AqiLogging') {\n Timestamp = new Date(msg.payload[1]);\n Hrs = Timestamp.getHours();\n //node.warn (`Jackson Hrs is ${Hrs}`);\n if (AqiArray[Hrs] == null) {\n //probably recently deployed and the local sensor entries were lost, or not added as needed.\n node.warn(`Entry clear, either no data or already published. Dropping msg`);\n return null;\n }\n AqiArray[Hrs][TIME_IDX] = msg.payload[1];\n AqiArray[Hrs][KNOX_IDX] = msg.payload[3];\n //assume the other array entries are already filled in because of the huge amount of lag in the station readings\n // but we now have 2 stations to look at so that is one other index that needs to be filled out before producing output\n if (AqiArray[Hrs][JACKSON_IDX] == null) {\n //wait for knox data\n return null;\n }\n AqiArray[Hrs][LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n msg.payload = AqiArray[Hrs];\n msg.topic = 'pm2.51hr_avgs';\n //clear the entry\n AqiArray[Hrs] = null;\n context.set ('AqiArray', AqiArray);\n return msg;\n \n}\n\nif (msg.topic == 'home/SDS011/ppm25/1hrAvg') {\n AqiArray[Hrs][SDS011_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS3003/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS3003_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMSa003/ppm25/1hrAvg') {\n AqiArray[Hrs][PMSA003_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS5003_a/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS5003_A_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS5003_b/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS5003_B_IDX] = msg.payload;\n}\ncontext.set ('AqiArray', AqiArray);\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n//let buffer = context.get('buffer') || [];\n//let AqiArray = new Array(24);\n//context.set('AqiArray');\n//Object.seal(AqiArray);","finalize":"","x":1100,"y":920,"wires":[["732c9594.e70d5c"]]},{"id":"a1f6e88f.b7f5b8","type":"function","z":"29a3e25b.3b2fee","name":"1 hour, on the hour average","func":"//let LastFileTime = context.get('FileTime') || '0';\nlet LastAvg = context.get ('PrevAvg') || 0;\nlet LastCnt = context.get ('PrevCnt') || 0;\n//formula is PrevAvg + (currVal - PrevAvg)/currCnt\n\n\nif (msg.topic == 'home/ticker_1hr') {\n //emit the avarage, then reset it.\n msg.payload = LastAvg.toFixed(2);\n msg.topic = \"home/SDS011/ppm25/1hrAvg\";\n msg.timestamp = new Date();\n context.set('PrevAvg', 0);\n context.set ('PrevCnt', 0);\n msg.timestamp = new Date();\n return msg;\n}\n \n\nlet currVal = Number(msg.payload);\nLastCnt += 1;\nvar newAvg = LastAvg + ((currVal - LastAvg)/(LastCnt));\ncontext.set('PrevAvg', newAvg);\ncontext.set ('PrevCnt', LastCnt);\n//node.warn (`current average ${newAvg}, current count ${LastCnt}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":600,"wires":[["52a90757.e6abe8","dc91e652.23a908","d8632364.bf24b","733903c4.238b4c"]]},{"id":"778c1c78.034344","type":"link in","z":"29a3e25b.3b2fee","name":"","links":["1cd58c.ca207a74"],"x":295,"y":500,"wires":[["a1f6e88f.b7f5b8","782c5554.182e2c","6d4d1820.bb9f98","22b31153.019dee","1a66b09f.fe973f"]]},{"id":"1d5b3b97.25ed54","type":"mqtt in","z":"29a3e25b.3b2fee","name":"","topic":"home/SDS011/ppm25","qos":"1","datatype":"auto","broker":"83099161.44dac","x":300,"y":600,"wires":[["a1f6e88f.b7f5b8"]]},{"id":"d491157c.b77ec8","type":"mqtt in","z":"29a3e25b.3b2fee","name":"","topic":"home/PMS3003/ppm25","qos":"1","datatype":"auto","broker":"83099161.44dac","x":300,"y":660,"wires":[["782c5554.182e2c"]]},{"id":"575059a.792a3a8","type":"mqtt in","z":"29a3e25b.3b2fee","name":"","topic":"home/PMSa003/ppm25","qos":"1","datatype":"auto","broker":"83099161.44dac","x":300,"y":720,"wires":[["6d4d1820.bb9f98"]]},{"id":"782c5554.182e2c","type":"function","z":"29a3e25b.3b2fee","name":"1 hour, on the hour average","func":"//let LastFileTime = context.get('FileTime') || '0';\nlet LastAvg = context.get ('PrevAvg') || 0;\nlet LastCnt = context.get ('PrevCnt') || 0;\n//formula is PrevAvg + (currVal - PrevAvg)/currCnt\n\n\nif (msg.topic == 'home/ticker_1hr') {\n //emit the avarage, then reset it.\n msg.payload = LastAvg.toFixed(2);\n msg.topic = \"home/PMS3003/ppm25/1hrAvg\";\n msg.timestamp = new Date();\n context.set('PrevAvg', 0);\n context.set ('PrevCnt', 0);\n msg.timestamp = new Date();\n return msg;\n}\n \n\nlet currVal = Number(msg.payload);\nLastCnt += 1;\nvar newAvg = LastAvg + ((currVal - LastAvg)/(LastCnt));\ncontext.set('PrevAvg', newAvg);\ncontext.set ('PrevCnt', LastCnt);\n//node.warn (`current average ${newAvg}, current count ${LastCnt}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":660,"wires":[["52a90757.e6abe8","78092637.1e5028","d8632364.bf24b","733903c4.238b4c"]]},{"id":"6d4d1820.bb9f98","type":"function","z":"29a3e25b.3b2fee","name":"1 hour, on the hour average","func":"//let LastFileTime = context.get('FileTime') || '0';\nlet LastAvg = context.get ('PrevAvg') || 0;\nlet LastCnt = context.get ('PrevCnt') || 0;\n//formula is PrevAvg + (currVal - PrevAvg)/currCnt\n\n\nif (msg.topic == 'home/ticker_1hr') {\n //emit the avarage, then reset it.\n msg.payload = LastAvg.toFixed(2);\n msg.topic = \"home/PMSa003/ppm25/1hrAvg\";\n msg.timestamp = new Date();\n context.set('PrevAvg', 0);\n context.set ('PrevCnt', 0);\n msg.timestamp = new Date();\n return msg;\n}\n \n\nlet currVal = Number(msg.payload);\nLastCnt += 1;\nvar newAvg = LastAvg + ((currVal - LastAvg)/(LastCnt));\ncontext.set('PrevAvg', newAvg);\ncontext.set ('PrevCnt', LastCnt);\n//node.warn (`current average ${newAvg}, current count ${LastCnt}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":720,"wires":[["52a90757.e6abe8","86e4c8c5.8740d8","d8632364.bf24b","733903c4.238b4c"]]},{"id":"fcf889d3.36a388","type":"mqtt in","z":"29a3e25b.3b2fee","name":"","topic":"home/PMS5003_b/ppm25","qos":"1","datatype":"auto","broker":"83099161.44dac","x":290,"y":840,"wires":[["1a66b09f.fe973f"]]},{"id":"7759f5ce.60ffbc","type":"mqtt in","z":"29a3e25b.3b2fee","name":"","topic":"home/PMS5003_a/ppm25","qos":"1","datatype":"auto","broker":"83099161.44dac","x":290,"y":780,"wires":[["22b31153.019dee"]]},{"id":"22b31153.019dee","type":"function","z":"29a3e25b.3b2fee","name":"1 hour, on the hour average","func":"//let LastFileTime = context.get('FileTime') || '0';\nlet LastAvg = context.get ('PrevAvg') || 0;\nlet LastCnt = context.get ('PrevCnt') || 0;\n//formula is PrevAvg + (currVal - PrevAvg)/currCnt\n\n\nif (msg.topic == 'home/ticker_1hr') {\n //emit the avarage, then reset it.\n msg.payload = LastAvg.toFixed(2);\n msg.topic = \"home/PMS5003_a/ppm25/1hrAvg\";\n msg.timestamp = new Date();\n context.set('PrevAvg', 0);\n context.set ('PrevCnt', 0);\n msg.timestamp = new Date();\n return msg;\n}\n \n\nlet currVal = Number(msg.payload);\nLastCnt += 1;\nvar newAvg = LastAvg + ((currVal - LastAvg)/(LastCnt));\ncontext.set('PrevAvg', newAvg);\ncontext.set ('PrevCnt', LastCnt);\n//node.warn (`current average ${newAvg}, current count ${LastCnt}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":780,"wires":[["52a90757.e6abe8","ead87af1.296b68","d8632364.bf24b","733903c4.238b4c"]]},{"id":"1a66b09f.fe973f","type":"function","z":"29a3e25b.3b2fee","name":"1 hour, on the hour average","func":"//let LastFileTime = context.get('FileTime') || '0';\nlet LastAvg = context.get ('PrevAvg') || 0;\nlet LastCnt = context.get ('PrevCnt') || 0;\n//formula is PrevAvg + (currVal - PrevAvg)/currCnt\n\n\nif (msg.topic == 'home/ticker_1hr') {\n //emit the avarage, then reset it.\n msg.payload = LastAvg.toFixed(2);\n msg.topic = \"home/PMS5003_b/ppm25/1hrAvg\";\n msg.timestamp = new Date();\n context.set('PrevAvg', 0);\n context.set ('PrevCnt', 0);\n msg.timestamp = new Date();\n return msg;\n}\n \n\nlet currVal = Number(msg.payload);\nLastCnt += 1;\nvar newAvg = LastAvg + ((currVal - LastAvg)/(LastCnt));\ncontext.set('PrevAvg', newAvg);\ncontext.set ('PrevCnt', LastCnt);\n//node.warn (`current average ${newAvg}, current count ${LastCnt}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":600,"y":840,"wires":[["52a90757.e6abe8","6887f78f.3374d8","d8632364.bf24b","733903c4.238b4c"]]},{"id":"732c9594.e70d5c","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"pm2_5_hourly_jsync","flatten":false,"name":"","x":1390,"y":920,"wires":[[]]},{"id":"437844a8.42a0ac","type":"function","z":"29a3e25b.3b2fee","name":"2.5PPM data to AQI","func":"//Formula taken from https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI\nvar Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(ppm < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (ppm - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nmsg.payload = Math.trunc(AQI).toString();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":520,"wires":[["db69665a.b784e8","a20b6d9e.367eb"]]},{"id":"dcdf6ab2.955898","type":"function","z":"29a3e25b.3b2fee","name":"2.5PPM data to AQI","func":"//Formula taken from https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI\nvar Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(ppm < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (ppm - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nmsg.payload = Math.trunc(AQI).toString();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":560,"wires":[["db69665a.b784e8","a20b6d9e.367eb"]]},{"id":"1aab9441.6806ec","type":"function","z":"29a3e25b.3b2fee","name":"2.5PPM data to AQI","func":"//Formula taken from https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI\nvar Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(ppm < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (ppm - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nmsg.payload = Math.trunc(AQI).toString();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":600,"wires":[["db69665a.b784e8","a20b6d9e.367eb"]]},{"id":"4da9986.572df68","type":"function","z":"29a3e25b.3b2fee","name":"2.5PPM data to AQI","func":"//Formula taken from https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI\nvar Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(ppm < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (ppm - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nmsg.payload = Math.trunc(AQI).toString();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":640,"wires":[["db69665a.b784e8","a20b6d9e.367eb"]]},{"id":"f2faf3d9.24e75","type":"function","z":"29a3e25b.3b2fee","name":"2.5PPM data to AQI","func":"//Formula taken from https://en.wikipedia.org/wiki/Air_quality_index#Computing_the_AQI\nvar Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(ppm < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (ppm - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nmsg.payload = Math.trunc(AQI).toString();\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1220,"y":680,"wires":[["db69665a.b784e8","a20b6d9e.367eb"]]},{"id":"db69665a.b784e8","type":"function","z":"29a3e25b.3b2fee","name":"","func":"\n\n//var AqiList = {Sds011Aqi:-1, Pms3003Aqi:-1, PmsA003Aqi:-1, Pms5003_aAqi:-1, Pms5003_bAqi:-1, JacksonAqi:-1};\n//turn that into an array suitable for logging\n//jackson's timestamp, , jackson data, sds011 AQI, PMS3003, PMSa003, PMS5003_a, PMS5003_b\n//that's all we care about since this is just to compare things\n\nconst TIME_IDX = 0;\nconst JACKSON_IDX = 1;\nconst SDS011_IDX = 2;\nconst PMS3003_IDX = 3;\nconst PMSA003_IDX = 4;\nconst PMS5003_A_IDX = 5;\nconst PMS5003_B_IDX = 6;\nconst LOG_TIME_IDX = 7;\n\nvar Hrs = -1;\nvar dbgTime = 0;\n\n//let buffer = context.get('AqiArray') || [];\nlet AqiArray = context.get('AqiArray') || [];\n\n//let DataArray = new Array(7);\n\n\n\n\n/*\nvar msgLoggingArray = {topic:\"AqiLogging\", payload:null};\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), //Time of data\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\n*/\n//\n/*\ntopic list\nAqiLogging\nhome/SDS011/ppm25Aqi/1hrAvg\nhome/PMS3003/ppm25Aqi/1hrAvg\nhome/PMSa003/ppm25Aqi/1hrAvg\nhome/PMS5003_a/ppm25Aqi/1hrAvg\nhome/PMS5003_b/ppm25Aqi/1hrAvg\n\n\n*/\n\n//node.warn(`Message: ${msg.topic}`);\n//node.warn(`payload: ${msg.payload}`);\n\nif (typeof(msg.timestamp) != \"undefined\") {\n Hrs = msg.timestamp.getHours();\n dbgTime = msg.timestamp;\n if (Hrs == 0) {\n Hrs = 23;\n } else {\n Hrs -= 1;\n }\n if (AqiArray[Hrs] == null || AqiArray[Hrs] == \"undefined\"){\n node.warn (`creating a new array at AqiArray[${Hrs}]`);\n AqiArray[Hrs] = new Array(8);\n }\n //node.warn (`for ${dbgTime} Hrs is ${Hrs}`);\n}\n\n\nif (msg.topic == 'jackson/AqiLogging') {\n var Timestamp = new Date(msg.payload[1]);\n Hrs = Timestamp.getHours();\n if (AqiArray[Hrs] == null) {\n //probably recently deployed and the local sensor entries were lost, or not added as needed.\n node.warn(`Entry clear, either no data or already published. Dropping msg`);\n return null;\n }\n AqiArray[Hrs][TIME_IDX] = msg.payload[1];\n AqiArray[Hrs][JACKSON_IDX] = msg.payload[2];\n AqiArray[Hrs][LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n //assume the other array entries are already filled in because of the huge amount of lag in the station readings\n msg.payload = AqiArray[Hrs];\n msg.topic = 'pm2.51hr_avgs';\n //clear the entry\n AqiArray[Hrs] = null;\n context.set ('AqiArray', AqiArray);\n return msg;\n \n}\n\nif (msg.topic == 'home/SDS011/ppm25Aqi/1hrAvg') {\n AqiArray[Hrs][SDS011_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS3003/ppm25Aqi/1hrAvg') {\n AqiArray[Hrs][PMS3003_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMSa003/ppm25Aqi/1hrAvg') {\n AqiArray[Hrs][PMSA003_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS5003_a/ppm25Aqi/1hrAvg') {\n AqiArray[Hrs][PMS5003_A_IDX] = msg.payload;\n}\n\nif (msg.topic == 'home/PMS5003_b/ppm25Aqi/1hrAvg') {\n AqiArray[Hrs][PMS5003_B_IDX] = msg.payload;\n}\ncontext.set ('AqiArray', AqiArray);\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n//let buffer = context.get('buffer') || [];\n//let AqiArray = new Array(24);\n//context.set('AqiArray');\n//Object.seal(AqiArray);","finalize":"","x":1500,"y":600,"wires":[["4e0df4b8.0860ac"]]},{"id":"dc91e652.23a908","type":"change","z":"29a3e25b.3b2fee","name":"TC SDS011","rules":[{"t":"set","p":"topic","pt":"msg","to":"home/SDS011/ppm25Aqi/1hrAvg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1010,"y":520,"wires":[["437844a8.42a0ac"]]},{"id":"78092637.1e5028","type":"change","z":"29a3e25b.3b2fee","name":"TC PMS3003","rules":[{"t":"set","p":"topic","pt":"msg","to":"home/PMS3003/ppm25Aqi/1hrAvg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":560,"wires":[["dcdf6ab2.955898"]]},{"id":"86e4c8c5.8740d8","type":"change","z":"29a3e25b.3b2fee","name":"TC PMSA003","rules":[{"t":"set","p":"topic","pt":"msg","to":"home/PMSa003/ppm25Aqi/1hrAvg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":600,"wires":[["1aab9441.6806ec"]]},{"id":"ead87af1.296b68","type":"change","z":"29a3e25b.3b2fee","name":"TC PMS5003_a","rules":[{"t":"set","p":"topic","pt":"msg","to":"home/PMS5003_a/ppm25Aqi/1hrAvg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":640,"wires":[["4da9986.572df68"]]},{"id":"6887f78f.3374d8","type":"change","z":"29a3e25b.3b2fee","name":"TC PMS5003_b","rules":[{"t":"set","p":"topic","pt":"msg","to":"home/PMS5003_b/ppm25Aqi/1hrAvg","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1000,"y":680,"wires":[["f2faf3d9.24e75"]]},{"id":"4e0df4b8.0860ac","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"pm2_5_aqi_hourly_jsync","flatten":false,"name":"","x":1690,"y":600,"wires":[[]]},{"id":"d8632364.bf24b","type":"function","z":"29a3e25b.3b2fee","name":"","func":"\n\n//var AqiList = {Sds011Aqi:-1, Pms3003Aqi:-1, PmsA003Aqi:-1, Pms5003_aAqi:-1, Pms5003_bAqi:-1, JacksonAqi:-1};\n//turn that into an array suitable for logging\n//jackson's timestamp, , jackson data, sds011 AQI, PMS3003, PMSa003, PMS5003_a, PMS5003_b\n//that's all we care about since this is just to compare things\n\nconst LOG_TIME_IDX = 0;\nconst SDS011_IDX = 1;\nconst PMS3003_IDX = 2;\nconst PMSA003_IDX = 3;\nconst PMS5003_A_IDX = 4;\nconst PMS5003_B_IDX = 5;\n\n\nvar Hrs = -1;\nvar dbgTime = 0;\n\n//let buffer = context.get('AqiArray') || [];\nlet DataArray = context.get('AqiArray') || [6];\nlet DataArrayCount = context.get('ArrayCnt') || 0\n//node.warn (`MY_AVGS: ${msg.topic}`);\n\nif (msg.topic == 'home/SDS011/ppm25/1hrAvg') {\n DataArray[SDS011_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS3003/ppm25/1hrAvg') {\n DataArray[PMS3003_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMSa003/ppm25/1hrAvg') {\n DataArray[PMSA003_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS5003_a/ppm25/1hrAvg') {\n DataArray[PMS5003_A_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS5003_b/ppm25/1hrAvg') {\n DataArray[PMS5003_B_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (DataArrayCount == 5) {\n DataArray[LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n msg.payload = DataArray;\n context.set('ArrayCnt', 0);\n //node.warn(`MY_AVGS: publishing array`);\n return msg;\n}\ncontext.set('ArrayCnt', DataArrayCount);\ncontext.set('AqiArray', DataArray);\n//node.warn(`MY_AVGS: saving count ${DataArrayCount}`);\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n//let buffer = context.get('buffer') || [];\n//let AqiArray = new Array(24);\n//context.set('AqiArray');\n//Object.seal(AqiArray);","finalize":"","x":1100,"y":1040,"wires":[["83f62624.ae5728"]]},{"id":"83f62624.ae5728","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"my_pm_2_5_1hr_avg","flatten":false,"name":"","x":1350,"y":1040,"wires":[[]]},{"id":"a20b6d9e.367eb","type":"function","z":"29a3e25b.3b2fee","name":"","func":"\n\n//var AqiList = {Sds011Aqi:-1, Pms3003Aqi:-1, PmsA003Aqi:-1, Pms5003_aAqi:-1, Pms5003_bAqi:-1, JacksonAqi:-1};\n//turn that into an array suitable for logging\n//jackson's timestamp, , jackson data, sds011 AQI, PMS3003, PMSa003, PMS5003_a, PMS5003_b\n//that's all we care about since this is just to compare things\n\nconst LOG_TIME_IDX = 0;\nconst SDS011_IDX = 1;\nconst PMS3003_IDX = 2;\nconst PMSA003_IDX = 3;\nconst PMS5003_A_IDX = 4;\nconst PMS5003_B_IDX = 5;\n\n\nvar Hrs = -1;\nvar dbgTime = 0;\n\n//let buffer = context.get('AqiArray') || [];\nlet DataArray = context.get('AqiArray') || [6];\nlet DataArrayCount = context.get('ArrayCnt') || 0\n\n\nif (msg.topic == 'home/SDS011/ppm25Aqi/1hrAvg') {\n DataArray[SDS011_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS3003/ppm25Aqi/1hrAvg') {\n DataArray[PMS3003_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMSa003/ppm25Aqi/1hrAvg') {\n DataArray[PMSA003_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS5003_a/ppm25Aqi/1hrAvg') {\n DataArray[PMS5003_A_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (msg.topic == 'home/PMS5003_b/ppm25Aqi/1hrAvg') {\n DataArray[PMS5003_B_IDX] = msg.payload;\n DataArrayCount += 1;\n}\n\nif (DataArrayCount == 5) {\n DataArray[LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n msg.payload = DataArray;\n context.set('ArrayCnt', 0);\n return msg;\n}\ncontext.set('ArrayCnt', DataArrayCount);\ncontext.set('AqiArray', DataArray);\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n//let buffer = context.get('buffer') || [];\n//let AqiArray = new Array(24);\n//context.set('AqiArray');\n//Object.seal(AqiArray);","finalize":"","x":1500,"y":680,"wires":[["b2376208.749a6"]]},{"id":"b2376208.749a6","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"my_pm_25AQI_1hr_avg","flatten":false,"name":"","x":1690,"y":680,"wires":[[]]},{"id":"a8d2c88b.efc278","type":"ui_chart","z":"29a3e25b.3b2fee","name":"","group":"afdd0a85.703988","order":3,"width":0,"height":0,"label":"Jackson AQI","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"2","removeOlderPoints":"","removeOlderUnit":"86400","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1220,"y":260,"wires":[[]]},{"id":"419fe2a1.9bf23c","type":"function","z":"29a3e25b.3b2fee","name":"log first 12 hrs every 8 hrs","func":"var TwelveHrArray = new Array();\nvar Pm2_5;\nvar index;\n\nfor (var i=0 ; i < msg.payload.monitors.length ; i++) {\n if (msg.payload.monitors[i].parameterName == \"PM2.5 - Principal\"){\n for (index = 0; index < 14; index++){\n var inputTimeStr = msg.payload.utcDateTimes[index];\n var Timestamp = new Date(inputTimeStr + \" UTC\");\n \n Pm2_5 = msg.payload.monitors[i].conc[index];\n \n TwelveHrArray[index] = [Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Pm2_5];\n }\n }\n}\nTwelveHrArray[index] = [\"----\",];\nmsg.payload = TwelveHrArray;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":650,"y":60,"wires":[["5ea2ca33.7c91c4"]]},{"id":"9edfc0e5.ca9c7","type":"http request","z":"29a3e25b.3b2fee","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://an_gov_data.s3.amazonaws.com/Sites/060850005.json","tls":"","persist":false,"proxy":"","authType":"","x":410,"y":60,"wires":[["419fe2a1.9bf23c"]]},{"id":"e1f8a5f6.0621b8","type":"function","z":"29a3e25b.3b2fee","name":"0, 8, 16 reader","func":"var CurrTime = new Date();\nvar CurrHour = CurrTime.getHours();\n//node.warn(`curr hour ${CurrHour}`);\n\nif (CurrHour != 0 && CurrHour != 8 && CurrHour != 16) {\n return null;\n} else {\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":210,"y":60,"wires":[["9edfc0e5.ca9c7"]]},{"id":"f93db570.018e88","type":"link in","z":"29a3e25b.3b2fee","name":"","links":["1cd58c.ca207a74"],"x":35,"y":60,"wires":[["e1f8a5f6.0621b8"]]},{"id":"5ea2ca33.7c91c4","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"test_sheet","flatten":false,"name":"","x":910,"y":60,"wires":[[]]},{"id":"19bde840.ce5788","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":180,"wires":[["e1f8a5f6.0621b8"]]},{"id":"b5f94232.a6cd5","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":330,"y":200,"wires":[["9edfc0e5.ca9c7"]]},{"id":"74142de9.c067f4","type":"http request","z":"29a3e25b.3b2fee","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://an_gov_data.s3.amazonaws.com/Sites/060850006.json","tls":"","persist":false,"proxy":"","authType":"","x":390,"y":1260,"wires":[["daf2b62a.522848"]]},{"id":"bbf0719e.8c3e1","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":1260,"wires":[["74142de9.c067f4"]]},{"id":"daf2b62a.522848","type":"function","z":"29a3e25b.3b2fee","name":"","func":"//just going to leave this here but it's useless because the \n//file's timestamp changes enough without the PM2.5 data \n//changing that it's not usable.\n/*\nlet LastFileTime = context.get('FileTime') || '0';\n\nif (LastFileTime == msg.payload.fileWrittenDateTime) {\n //node.warn (`process: no new data`);\n return null;\n}\ncontext.set('FileTime', msg.payload.fileWrittenDateTime);\n*/\nvar inputTimeStr = msg.payload.utcDateTimes[msg.payload.utcDateTimes.length - 1];\n\nlet PrevValData = context.get('previousValidDataTime') || '0';\n//node.warn(`process: Previous valid data ${PrevValData} UTC`);\n\nvar Timestamp = new Date(inputTimeStr + \" UTC\");\nvar CurrTime = new Date();\nlet [_, year, month, day, hour, min, sec] = msg.payload.fileWrittenDateTime.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\nvar RemoteFileTime = new Date(Date.UTC(year, month - 1, day, hour, min, sec));\n\n//var Pm2_5;\n//var AqiPm2_5;\n\nvar msgTimeOfData = {topic:'knox/TimeOfData', payload:null};\nvar msgTimeOfProcess = {topic:'knox/TimeOfProcess', payload:null};\nvar msgPm25Aqi = {topic:'knox/PM2_5_AQI', payload:null};\nvar msgPm25Conc = {topic:'knox/PM2_5_Conc', payload:null};\nvar msgLoggingArray = {topic:'knox/AqiLogging', payload:null};\n\n/*\nmsgTimeOfData.topic = 'TimeOfData';\nmsgTimeOfProcess.topic = 'TimeOfProcess';\nmsgPm25Aqi.topic = 'PM2_5_AQI';\nmsgPm25Conc.topic = 'PM2_5_Conc';\nmsgLoggingArray.topic = 'AqiLogging';\n*/\nif (inputTimeStr == PrevValData) {\n //node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}.\\n(1)No new data.`);\n return null;\n}\n//node.warn(`process: setting Previous valid data ${inputTimeStr}`);\n\n//node.warn (`process: input time ${inputTimeStr}`);\n//node.warn (`process: Timestamp is ${Timestamp}`);\n//node.warn (`process: file time ${RemoteFileTime}, ${RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})}`);\n\n//node.warn(`at ${CurrTime} last data time ${Timestamp}.`);\n\nfor (var i=0 ; i < msg.payload.monitors.length ; i++) {\n if (msg.payload.monitors[i].parameterName == \"PM2.5 - Principal\"){\n //AqiPm2_5 = \n // msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 1];\n \n if (msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 1] >= 0) {\n msgPm25Conc.payload = msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 1];\n msgPm25Aqi.payload = \n msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 1];\n } else {\n inputTimeStr = msg.payload.utcDateTimes[msg.payload.utcDateTimes.length - 2];\n Timestamp = new Date(inputTimeStr + \" UTC\");\n if (inputTimeStr == PrevValData) {\n node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}\\n(hour rollback). (2)No new data.`);\n return null;\n }\n context.set('previousValidDataTime', inputTimeStr);\n //node.warn(`process: setting Previous valid data ${inputTimeStr}`);\n if (msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 2] >=0){\n msgPm25Conc.payload = msg.payload.monitors[i].conc[msg.payload.monitors[i].conc.length - 2];\n msgPm25Aqi.payload = \n msg.payload.monitors[i].aqi[msg.payload.monitors[i].aqi.length - 2];\n } else {\n //Flaky stations, drop the data and recover it from the history logging page later\n msgPm25Aqi.payload = \"----\";\n msgPm25Conc.payload = \"----\";\n }\n Timestamp = new Date(inputTimeStr + \" UTC\");\n }\n }\n}\n\n//node.warn (`process: Local time of reading: ${LocalTime}`);\n//node.warn (`process: time of reading: ${Timestamp}`);\n//node.warn (`process: AQI ${msgPm25Aqi.payload} PM2.5 ${msgPm25Conc.payload}`);\nmsgTimeOfData.payload = Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\nmsgTimeOfProcess.payload = CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}),\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\nmsgLoggingArray.payload=logging_array;\n\n//outputs, \n//time this func runs, \n//time of data,\n//PM2.5 AQI, \n//PM2.5 concentration, \n// array for logging\n// feedback message to try and prevent hammering server (may be unncessary)\ncontext.set('previousValidDataTime', inputTimeStr);\n//node.warn(`at ${CurrTime}\\nlast data time ${Timestamp}.\\nAQI ${msgPm25Aqi.payload} PM2.5 ${msgPm25Conc.payload}`);\nreturn [msgTimeOfProcess, msgTimeOfData, msgPm25Aqi, msgPm25Conc, msgLoggingArray]","outputs":5,"noerr":0,"initialize":"","finalize":"","x":580,"y":1260,"wires":[["596fbea.683984"],["4e4406cc.d64578"],["97323bec.ca6798","a5ba2465.7c8d28"],["bf646de5.8a295"],["dd0913be.2a4b5","52a90757.e6abe8","733903c4.238b4c"]]},{"id":"97323bec.ca6798","type":"ui_artlessgauge","z":"29a3e25b.3b2fee","group":"f47335f4.c0a778","order":3,"width":0,"height":0,"name":"Knox Ave AQI","icon":"","label":"Knox AQI","unit":"","layout":"linear","decimals":"2","differential":false,"minmax":false,"colorTrack":"#555555","style":"","colorFromTheme":true,"property":"payload","secondary":"secondary","inline":false,"animate":true,"sectors":[{"val":0,"col":"#009900","t":"min","dot":2},{"val":51,"col":"#e5f505","t":"sec","dot":2},{"val":101,"col":"#eb6600","t":"sec","dot":2},{"val":151,"col":"#fe0606","t":"sec","dot":2},{"val":201,"col":"#850000","t":"sec","dot":2},{"val":301,"col":"#a50382","t":"sec","dot":2},{"val":401,"col":"#53045d","t":"sec","dot":2},{"val":500,"col":"#53045d","t":"max","dot":2}],"lineWidth":"7","bgcolorFromTheme":true,"diffCenter":"","x":960,"y":1220,"wires":[]},{"id":"bf646de5.8a295","type":"ui_text","z":"29a3e25b.3b2fee","group":"619d513c.6ffcc","order":1,"width":6,"height":2,"name":"Knox Ave 2.5ppm raw","label":"Knox 2.5ppm raw","format":"{{msg.payload}}","layout":"row-spread","x":980,"y":1260,"wires":[]},{"id":"4e4406cc.d64578","type":"ui_text","z":"29a3e25b.3b2fee","group":"f47335f4.c0a778","order":2,"width":3,"height":1,"name":"knox ave time of data","label":"Knox time of data","format":"{{msg.payload}}","layout":"row-left","x":980,"y":1180,"wires":[]},{"id":"596fbea.683984","type":"ui_text","z":"29a3e25b.3b2fee","group":"f47335f4.c0a778","order":2,"width":3,"height":1,"name":"Knox Ave Time locally processed","label":"Knox time processed","format":"{{msg.payload}}","layout":"row-left","x":1020,"y":1140,"wires":[]},{"id":"7ac21862.a6bf38","type":"link in","z":"29a3e25b.3b2fee","name":"","links":["1108cb7d.123aa5"],"x":175,"y":1340,"wires":[["74142de9.c067f4"]]},{"id":"dd0913be.2a4b5","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"knox_ave_data","flatten":false,"name":"","x":970,"y":1300,"wires":[[]]},{"id":"a5ba2465.7c8d28","type":"ui_chart","z":"29a3e25b.3b2fee","name":"","group":"f47335f4.c0a778","order":3,"width":0,"height":0,"label":"Knox Ave AQI","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":"2","removeOlderPoints":"","removeOlderUnit":"86400","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1270,"y":1220,"wires":[[]]},{"id":"142fde37.b3df92","type":"function","z":"29a3e25b.3b2fee","name":"log first 12 hrs every 8 hrs","func":"var TwelveHrArray = new Array();\nvar Pm2_5;\n\n\nfor (var i=0 ; i < msg.payload.monitors.length ; i++) {\n if (msg.payload.monitors[i].parameterName == \"PM2.5 - Principal\"){\n for (var index = 0; index < 12; index++){\n var inputTimeStr = msg.payload.utcDateTimes[index];\n var Timestamp = new Date(inputTimeStr + \" UTC\");\n \n Pm2_5 = msg.payload.monitors[i].conc[index];\n \n TwelveHrArray[index] = [Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Pm2_5];\n }\n }\n}\nTwelveHrArray[index] = [\"----\",];\nmsg.payload = TwelveHrArray;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":710,"y":1920,"wires":[["6fdca0a8.d0dcc"]]},{"id":"e420f6b2.2a6158","type":"http request","z":"29a3e25b.3b2fee","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://an_gov_data.s3.amazonaws.com/Sites/060850006.json","tls":"","persist":false,"proxy":"","authType":"","x":470,"y":1920,"wires":[["142fde37.b3df92"]]},{"id":"c1d7a663.f01608","type":"function","z":"29a3e25b.3b2fee","name":"0, 8, 16 reader","func":"var CurrTime = new Date();\nvar CurrHour = CurrTime.getHours();\n//node.warn(`curr hour ${CurrHour}`);\n\nif (CurrHour != 0 && CurrHour != 8 && CurrHour != 16) {\n return null;\n} else {\n return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":270,"y":1920,"wires":[["e420f6b2.2a6158"]]},{"id":"fa2b47bf.bddff8","type":"link in","z":"29a3e25b.3b2fee","name":"","links":["1cd58c.ca207a74"],"x":95,"y":1920,"wires":[["c1d7a663.f01608"]]},{"id":"6fdca0a8.d0dcc","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"knox_hist","flatten":false,"name":"","x":970,"y":1920,"wires":[[]]},{"id":"17041d06.d05dc3","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":390,"y":2060,"wires":[["e420f6b2.2a6158"]]},{"id":"ed139f2b.a92a3","type":"inject","z":"29a3e25b.3b2fee","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":2040,"wires":[["c1d7a663.f01608"]]},{"id":"733903c4.238b4c","type":"function","z":"29a3e25b.3b2fee","name":"mega-logger","func":"/*\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}),\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\n*/\n\n//var AqiList = {Sds011Aqi:-1, Pms3003Aqi:-1, PmsA003Aqi:-1, Pms5003_aAqi:-1, Pms5003_bAqi:-1, JacksonAqi:-1};\n//turn that into an array suitable for logging\n//jackson's timestamp, , jackson data, sds011 AQI, PMS3003, PMSa003, PMS5003_a, PMS5003_b\n//that's all we care about since this is just to compare things\n\n\nconst JACK_TIME = 0;\nconst JACK_CONC = 1;\nconst JACK_AQI = 2;\n\nconst KNOX_TIME = 3;\nconst KNOX_CONC = 4;\nconst KNOX_AQI = 5;\n\nconst SDS011_TIME = 6;\nconst SDS011_CONC = 7;\nconst SDS011_AQI = 8;\n\nconst PMS3003_TIME = 9;\nconst PMS3003_CONC = 10;\nconst PMS3003_AQI = 11;\n\nconst PMSA003_TIME = 12;\nconst PMSA003_CONC = 13;\nconst PMSA003_AQI = 14;\n\nconst PMS5003_A_TIME = 15;\nconst PMS5003_A_CONC = 16;\nconst PMS5003_A_AQI = 17;\n\nconst PMS5003_B_TIME = 18;\nconst PMS5003_B_CONC = 19;\nconst PMS5003_B_AQI = 20;\n\n\nvar Hrs = -1;\nvar dbgTime = 0;\n\n//let buffer = context.get('AqiArray') || [];\nlet AqiArray = context.get('AqiArray') || [];\n\n//let DataArray = new Array(7);\n\n\n/*\nvar msgLoggingArray = {topic:\"AqiLogging\", payload:null};\nvar logging_array = [\n CurrTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), \n Timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'}), //Time of data\n msgPm25Aqi.payload, \n msgPm25Conc.payload,\n RemoteFileTime.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'})\n ];\n*/\n//\n/*\ntopic list\nAqiLogging\nhome/SDS011/ppm25/1hrAvg\nhome/PMS3003/ppm25/1hrAvg\nhome/PMSa003/ppm25/1hrAvg\nhome/PMS5003_a/ppm25/1hrAvg\nhome/PMS5003_b/ppm25/1hrAvg\n\n\n*/\n\n//node.warn(`Message: ${msg.topic}`);\n//node.warn(`payload: ${msg.payload}`);\n\nfunction calculateAQI(PM25Concentration) {\n var Aqi25mmBreakpoints = [0.0, 12.1, 35.5, 55.5, 150.5, 250.5, 350.5, 500.5];\nvar AqiIndexBreakpoints = [0, 51, 101, 151, 201, 301, 401, 500]; //this list is the bottom bound for each breakpoint\nvar ppm = parseFloat(msg.payload);\nvar AQI = 500; //set to max because if the reading doesn't fall in the range of the data, it's very bad AQI;\n\nfor (let i = 0; i < Aqi25mmBreakpoints.length; i++) {\n if(PM25Concentration < Aqi25mmBreakpoints[i]) {\n //These variable names correspond to the parameter names specefied in the link above.\n //They aren't great but the intent is to allow these to be looked up that way.\n // There is a single line version below if that's preferable\n var I_high = AqiIndexBreakpoints[i] - 1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var I_low = AqiIndexBreakpoints[i-1];\n var C_high = Aqi25mmBreakpoints[i] - 0.1; //the subtraction is to move the term back into the proper range specefied on Wikipedia\n var C_low = Aqi25mmBreakpoints[i - 1];\n \n AQI = ((I_high - I_low)/(C_high - C_low)) * (PM25Concentration - C_low) + I_low;\n \n //This is the single line version in case the verbode way using separate variables isn't suitable.\n //AQI = ((((AqiIndexBreakpoints[i] -1) - AqiIndexBreakpoints[i-1])/((Aqi25mmBreakpoints[i] - 0.1) - Aqi25mmBreakpoints[i-1]))*(ppm-Aqi25mmBreakpoints[i-1])) + AqiIndexBreakpoints[i-1]; \n break;\n }\n}\n\nreturn Math.trunc(AQI).toString();\n}\n\n\n\nvar Timestamp;\n\nif (typeof(msg.timestamp) != \"undefined\") {\n Hrs = msg.timestamp.getHours();\n dbgTime = msg.timestamp;\n /* looks like this is unnecessary for sync but I'm not 100% certain\n if (Hrs == 0) {\n Hrs = 23;\n } else {\n Hrs -= 1;\n }\n */\n if (AqiArray[Hrs] == null || AqiArray[Hrs] == \"undefined\"){\n //node.warn (`creating a new array at AqiArray[${Hrs}]`);\n AqiArray[Hrs] = new Array(8);\n }\n //node.warn (`for ${dbgTime} Hrs is ${Hrs}`);\n}\n\n\nif (msg.topic == 'jackson/AqiLogging') {\n Timestamp = new Date(msg.payload[1]);\n Hrs = Timestamp.getHours();\n //node.warn (`Jack Hrs is ${Hrs}`);\n if (AqiArray[Hrs] == null) {\n //probably recently deployed and the local sensor entries were lost, or not added as needed.\n node.warn(`Entry clear, either no data or already published. Dropping msg`);\n return null;\n }\n AqiArray[Hrs][JACK_TIME] = msg.payload[1];\n AqiArray[Hrs][JACK_CONC] = msg.payload[3];\n AqiArray[Hrs][JACK_AQI] = msg.payload[2];\n //assume the other array entries are already filled in because of the huge amount of lag in the station readings\n // but we now have 2 stations to look at so that is one other index that needs to be filled out before producing output\n if (AqiArray[Hrs][KNOX_TIME] == null) {\n //wait for knox data\n return null;\n }\n msg.payload = AqiArray[Hrs];\n msg.topic = 'mega_avgs';\n //clear the entry\n AqiArray[Hrs] = null;\n context.set ('AqiArray', AqiArray);\n return msg;\n \n}\n\n\nif (msg.topic == 'knox/AqiLogging') {\n Timestamp = new Date(msg.payload[1]);\n Hrs = Timestamp.getHours();\n //node.warn (`Jackson Hrs is ${Hrs}`);\n if (AqiArray[Hrs] == null) {\n //probably recently deployed and the local sensor entries were lost, or not added as needed.\n node.warn(`Entry clear, either no data or already published. Dropping msg`);\n return null;\n }\n AqiArray[Hrs][KNOX_TIME] = msg.payload[1];\n AqiArray[Hrs][KNOX_CONC] = msg.payload[3];\n AqiArray[Hrs][KNOX_AQI] = msg.payload[2];\n //assume the other array entries are already filled in because of the huge amount of lag in the station readings\n // but we now have 2 stations to look at so that is one other index that needs to be filled out before producing output\n if (AqiArray[Hrs][JACK_TIME] == null) {\n //wait for knox data\n return null;\n }\n //AqiArray[Hrs][LOG_TIME_IDX] = new Date().toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n msg.payload = AqiArray[Hrs];\n msg.topic = 'mega_avgs';\n //clear the entry\n AqiArray[Hrs] = null;\n context.set ('AqiArray', AqiArray);\n return msg;\n \n}\n\nif (msg.topic == 'home/SDS011/ppm25/1hrAvg') {\n AqiArray[Hrs][SDS011_TIME] = msg.timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n AqiArray[Hrs][SDS011_CONC] = msg.payload;\n AqiArray[Hrs][SDS011_AQI] = calculateAQI(msg.payload);\n}\n\nif (msg.topic == 'home/PMS3003/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS3003_TIME] = msg.timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n AqiArray[Hrs][PMS3003_CONC] = msg.payload;\n AqiArray[Hrs][PMS3003_AQI] = calculateAQI(msg.payload);\n}\n\nif (msg.topic == 'home/PMSa003/ppm25/1hrAvg') {\n AqiArray[Hrs][PMSA003_TIME] = msg.timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n AqiArray[Hrs][PMSA003_CONC] = msg.payload;\n AqiArray[Hrs][PMSA003_AQI] = calculateAQI(msg.payload);\n}\n\nif (msg.topic == 'home/PMS5003_a/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS5003_A_TIME] = msg.timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n AqiArray[Hrs][PMS5003_A_CONC] = msg.payload;\n AqiArray[Hrs][PMS5003_A_AQI] = calculateAQI(msg.payload);\n}\n\nif (msg.topic == 'home/PMS5003_b/ppm25/1hrAvg') {\n AqiArray[Hrs][PMS5003_B_TIME] = msg.timestamp.toLocaleString('en-US', {timeZone: 'America/Los_Angeles'});\n AqiArray[Hrs][PMS5003_B_CONC] = msg.payload;\n AqiArray[Hrs][PMS5003_B_AQI] = calculateAQI(msg.payload);\n}\ncontext.set ('AqiArray', AqiArray);\nreturn null;","outputs":1,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n//let buffer = context.get('buffer') || [];\n//let AqiArray = new Array(24);\n//context.set('AqiArray');\n//Object.seal(AqiArray);","finalize":"","x":1110,"y":800,"wires":[["17845b0d.ca5065"]]},{"id":"17845b0d.ca5065","type":"GSheet","z":"29a3e25b.3b2fee","creds":"e21f03ec.823c2","method":"append","action":"","sheet":"","cells":"mega_list","flatten":false,"name":"","x":1310,"y":800,"wires":[[]]},{"id":"afdd0a85.703988","type":"ui_group","name":"Jackson","tab":"410cb058.8dd51","order":6,"disp":true,"width":"6","collapse":false},{"id":"8c787508.2a78a8","type":"ui_group","name":"Jackson","tab":"18e0029b.fb41ed","order":6,"disp":true,"width":"6","collapse":false},{"id":"e21f03ec.823c2","type":"gauth"},{"id":"83099161.44dac","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"f47335f4.c0a778","type":"ui_group","name":"Knox Ave","tab":"410cb058.8dd51","order":7,"disp":true,"width":"6","collapse":false},{"id":"619d513c.6ffcc","type":"ui_group","name":"knox ave","tab":"18e0029b.fb41ed","order":7,"disp":true,"width":"6","collapse":false},{"id":"410cb058.8dd51","type":"ui_tab","name":"Air Quality","icon":"dashboard","order":4,"disabled":false,"hidden":false},{"id":"18e0029b.fb41ed","type":"ui_tab","name":"air quality extras","icon":"dashboard","order":5,"disabled":false,"hidden":false}]