json part 1
[{"id": "40211f9928c67e8d","type": "catch","z": "237432cea239de8b","name": "","scope": null,"uncaught": false,"x": 200,"y": 40,"wires": [["af03db4d701e4904"]]},{"id": "af03db4d701e4904","type": "debug","z": "237432cea239de8b","name": "","active": true,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 295,"y": 40,"wires": [],"l": false},{"id": "12135eef93ef97af","type": "comment","z": "237432cea239de8b","name": "Stephen Gordinier, Mar. 2026","info": "","x": 480,"y": 40,"wires": []},{"id": "211e8f87b320af41","type": "group","z": "237432cea239de8b","name": "DAQ from Analog Input Modules","style": {"label": true},"nodes": ["7a7c333c85a5f5df","78eed138404245f3","5ec2edf91401996a","71ad442ea0fa6363","b5ad4d4eebcec615","ad50188e126d7485","bf79086287db5be0","924b6fc0a1c26ee2","325e9dda97913335","deaf68c4121b276b","235b4fce058cb8d5","24caa6c2b24b721d","ad3f5464606415e0","311f0bcde7d0da65","7ebdeaf256124911","869b696705f10ed9","1669f03e1a34333a","50446d328630d38b","ea5e9ee5c4d60c44","56f58928da4dbf5b","74d4d54ba06ed4ba","93c99afc2b6b504b","d4a255351cac9a81"],"x": 134,"y": 359,"w": 1612,"h": 502},{"id": "7a7c333c85a5f5df","type": "comment","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Arbitrary # of Input Modules","info": "","x": 620,"y": 600,"wires": []},{"id": "78eed138404245f3","type": "groov-io-read","z": "237432cea239de8b","g": "211e8f87b320af41","device": "d918290744a0a7b4","dataType": "module-analog","moduleIndex": "1","channelIndex": "","mmpAddress": "0xF0D81000","mmpType": "int32","mmpLength": "1","mmpEncoding": "ascii","value": "","valueType": "msg.payload","itemName": "","name": "Module 1 Read","x": 620,"y": 480,"wires": [["bf79086287db5be0","24caa6c2b24b721d","93c99afc2b6b504b"]]},{"id": "5ec2edf91401996a","type": "csv","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Convert Message to CSV","spec": "rfc","sep": ",","hdrin": "","hdrout": "none","multi": "mult","ret": "\\r\\n","temp": "","skip": "0","strings": true,"include_empty_strings": true,"include_null_values": true,"x": 1270,"y": 780,"wires": [["deaf68c4121b276b","71ad442ea0fa6363"]]},{"id": "71ad442ea0fa6363","type": "file","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Append Data to CSV","filename": "daqfilename","filenameType": "msg","appendNewline": false,"createDir": false,"overwriteFile": "false","encoding": "none","x": 1540,"y": 800,"wires": [[]]},{"id": "b5ad4d4eebcec615","type": "function","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Build Payload for CSV","func": "var csv = {};\n\n// Set up headers with first column containing timestamp in local 24h time.\nconst time = new Date(msg.timestamp);\nconst timestamp = time.getFullYear() + '-' +\n ('00' + (time.getMonth()+1)).slice(-2) + '-' +\n ('00' + time.getDate()).slice(-2) + ' ' +\n ('00' + time.getHours()).slice(-2) + ':' +\n ('00' + time.getMinutes()).slice(-2) + ':' +\n ('00' + time.getSeconds()).slice(-2);\n\nconst headers = [\"timestamp\"];\nconst pad = n => String(n).padStart(2, '0');\n\nfor (let i = 1; i <= 2; i++) {\n for (let j = 1; j <= 12; j++) {\n headers.push(TC_${i}${pad(j)});\n }\n}\n\n// Ensure payload is an array of length 24\n// Normalize to exactly 24 entries (pad with empty if needed)\nconst values = Array.isArray(msg.payload)\n ? msg.payload\n : (typeof msg.payload === \"string\" ? msg.payload.split(\",\") : []);\nconst normalized = Array.from({ length: 24 }, (_, i) => {\n const v = values[i];\n if (v === \"\" || v === null || v === undefined) return \"\";\n const num = Number(v);\n return isNaN(num) ? \"\" : num;\n});\n\n// Build key:value object\nconst payloadObj = {};\npayloadObj[\"timestamp\"] = timestamp;\n\nheaders.slice(1).forEach((key, idx) => {\n payloadObj[key] = normalized[idx];\n});\n\n// Assign to csv.payload\ncsv.payload = {};\ncsv.payload = payloadObj;\ncsv.daqfilename = global.get(\"daqfilename\");\n\nreturn csv;\n","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 1280,"y": 660,"wires": [["924b6fc0a1c26ee2","311f0bcde7d0da65","74d4d54ba06ed4ba"]]},{"id": "ad50188e126d7485","type": "join","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Combine Module Readings","mode": "custom","build": "string","property": "payload","propertyType": "msg","key": "payload","joiner": ",","joinerType": "str","useparts": true,"accumulate": false,"timeout": "1.5","count": "24","reduceRight": false,"reduceExp": "","reduceInit": "","reduceInitType": "num","reduceFixup": "","x": 1250,"y": 600,"wires": [["b5ad4d4eebcec615","ad3f5464606415e0"]]},{"id": "bf79086287db5be0","type": "function","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Separate TCs Per Module","func": "// Discussion of setting up batch module read and splitting method:\n// https://forums.opto22.com/t/groov-epic-performance-issue/5759/9\n\n// (Since we care about logging every data point, we don't want the deadband filter he mentions.)\n// Reading the entire module is why we use the \"groov i/o read\" node instead of the \"groov i/o input\"\n// node, which is only for watching a single point for changes.\n\nvar returnobj = [];\nconst timestamp = msg.timestamp;\n\n// The module index comes in as a string in the msg from the groov node\nconst tcmodule = parseInt(msg.body.moduleIndex);\n\n// Each analog input module has 12 inputs\nfor (let i = 0; i <= 11; i++) {\n returnobj[i] = {\n // If the reading is null, leave it null, otherwise trim to 3 decimal places\n payload: (msg.payload[i].value === null) ? null : parseFloat(msg.payload[i].value.toFixed(3)),\n timestamp: msg.timestamp,\n // Add msg.parts so the join node can combine readings from all modules into one big message\n // 'count' should be 12 * number of analog input modules we're aggregating\n parts: {\n id: timestamp,\n index: 12*(tcmodule-1)+i,\n count: 24 \n }\n };\n}\n\nreturn returnobj;\n","outputs": 12,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 870,"y": 520,"wires": [["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"],["ad50188e126d7485","7ebdeaf256124911"]]},{"id": "924b6fc0a1c26ee2","type": "debug","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1455,"y": 640,"wires": [],"l": false},{"id": "325e9dda97913335","type": "inject","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Manual Acq Ping","props": [{"p": "timestamp","v": "","vt": "date"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","x": 400,"y": 500,"wires": [["78eed138404245f3"]]},{"id": "deaf68c4121b276b","type": "debug","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1455,"y": 760,"wires": [],"l": false},{"id": "235b4fce058cb8d5","type": "switch","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Acq Pause Gate","property": "pauseacq","propertyType": "global","rules": [{"t": "false"}],"checkall": "true","repair": false,"outputs": 1,"x": 400,"y": 460,"wires": [["78eed138404245f3"]]},{"id": "24caa6c2b24b721d","type": "debug","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 775,"y": 400,"wires": [],"l": false},{"id": "ad3f5464606415e0","type": "debug","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1455,"y": 580,"wires": [],"l": false},{"id": "311f0bcde7d0da65","type": "link out","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Broadcast Array of TC Readings","mode": "link","links": ["8615ae63dc9a4e71"],"x": 1570,"y": 680,"wires": [],"l": true},{"id": "7ebdeaf256124911","type": "function","z": "237432cea239de8b","d": true,"g": "211e8f87b320af41","name": "Individual Reading [Unused]","func": "const idx = msg.parts.index;\n\nconst tcmodule = Math.floor(idx / 12) + 1;\nconst channel = (idx % 12) + 1;\nconst pad = n => String(n).padStart(2, '0');\nmsg.topic = TC${tcmodule}_${pad(channel)};\ndelete msg.parts;\n\nreturn msg;\n\n// Works, just disabling because not currently needed","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 1240,"y": 440,"wires": [["869b696705f10ed9","1669f03e1a34333a"]]},{"id": "869b696705f10ed9","type": "debug","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1455,"y": 420,"wires": [],"l": false},{"id": "1669f03e1a34333a","type": "link out","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Broadcast Individual TC Reading","mode": "link","links": [],"x": 1580,"y": 460,"wires": [],"l": true},{"id": "50446d328630d38b","type": "link in","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Rcv Heartbeat","links": ["c677f7b715ec076a"],"x": 230,"y": 460,"wires": [["235b4fce058cb8d5"]],"outputLabels": ["Heartbeat"],"icon": "font-awesome/fa-heartbeat","l": true},{"id": "ea5e9ee5c4d60c44","type": "link in","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Rcv CSV Headers on UI Click ","links": ["53d73dd6b46821f7"],"x": 1260,"y": 820,"wires": [["71ad442ea0fa6363"]],"l": true},{"id": "56f58928da4dbf5b","type": "groov-io-read","z": "237432cea239de8b","g": "211e8f87b320af41","device": "d918290744a0a7b4","dataType": "module-analog","moduleIndex": "2","channelIndex": "","mmpAddress": "0xF0D81000","mmpType": "int32","mmpLength": "1","mmpEncoding": "ascii","value": "","valueType": "msg.payload","itemName": "","name": "Module 2 Read","x": 620,"y": 560,"wires": [["bf79086287db5be0"]]},{"id": "74d4d54ba06ed4ba","type": "switch","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Recording Gate","property": "recording","propertyType": "global","rules": [{"t": "true"}],"checkall": "true","repair": false,"outputs": 1,"x": 1300,"y": 720,"wires": [["5ec2edf91401996a"]]},{"id": "93c99afc2b6b504b","type": "delay","z": "237432cea239de8b","g": "211e8f87b320af41","name": "","pauseType": "delay","timeout": ".5","timeoutUnits": "seconds","rate": "1","nbRateUnits": "1","rateUnits": "second","randomFirst": ".2","randomLast": ".5","randomUnits": "seconds","drop": false,"allowrate": false,"outputs": 1,"x": 600,"y": 520,"wires": [["56f58928da4dbf5b"]]},{"id": "d4a255351cac9a81","type": "comment","z": "237432cea239de8b","g": "211e8f87b320af41","name": "Configure count for # of Modules","info": "","x": 1250,"y": 560,"wires": []},{"id": "d918290744a0a7b4","type": "groov-io-device","address": "localhost","msgQueueFullBehavior": "DROP_OLD"},{"id": "d92a097fa9a08dee","type": "group","z": "237432cea239de8b","name": "Initialization and Heartbeat","style": {"label": true},"nodes": ["c677f7b715ec076a","c9ac1706b803376e","963bb44a150ddec0","926b6a6b736adc27","d958ae42891cf0a2","bbf7509a2d55d2fa","f897bc90879b242d","81835d50fa278961","1875004269cced91","87fdb57618e4d702","5ea8951783f48d68","6ae6740aab8630a6","328a4f3d030440ba","27658fc32aca95ec"],"x": 134,"y": 79,"w": 1212,"h": 262},{"id": "c677f7b715ec076a","type": "link out","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Broadcast Heartbeat","mode": "link","links": ["23388e7b952fcd18","50446d328630d38b","108d7208f3f696a9","34c7b80131f2a75a"],"x": 1220,"y": 220,"wires": [],"inputLabels": ["Heartbeat"],"icon": "font-awesome/fa-heartbeat","l": true},{"id": "c9ac1706b803376e","type": "comment","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Polling/Refresh Rate for all Flows","info": "","x": 910,"y": 260,"wires": []},{"id": "963bb44a150ddec0","type": "debug","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "","active": false,"tosidebar": true,"console": false,"tostatus": false,"complete": "true","targetType": "full","statusVal": "","statusType": "auto","x": 1135,"y": 260,"wires": [],"l": false},{"id": "926b6a6b736adc27","type": "link out","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Broadcast Wakeup","mode": "link","links": [],"x": 1230,"y": 160,"wires": [],"icon": "font-awesome/fa-coffee","l": true},{"id": "d958ae42891cf0a2","type": "function","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Initialize Global Setpoints and Mappings","func": "// This function runs once on boot, and can be re-triggered by the reset inject button.\n\n// Output message will have a timestamp from the boot signal (first \"heartbeat\")\n// as well as the msg.delay property that sets the heartbeat rate (in milliseconds).\n// This node is followed by a brief delay node to allow initial startup before heartbeat continues.\nlet obj = {\n \"boot\": true,\n \"timestamp\": msg.payload,\n \"delay\" : 2000\n};\n\n// Timestamp of boot signal is first \"heartbeat\" set in global context\n// TODO: Heartbeat being a global variable was necessary for TPO workaround.\n// with timeprop v2.0 release (thanks to me raising this issue!), consider removal.\n// Default state is to NOT be recording to .csv\n// TODO: Consider persistence so no data is skipped on an unintentional reboot?\nglobal.set(\"heartbeat\", obj.timestamp); \nglobal.set(\"heartbeatinterval\", obj.delay);\nglobal.set(\"recording\", false); \n\n// Path in groov EPIC to store/receive DAQ recording .CSV files.\nglobal.set(\"daqpath\", \"/home/dev/unsecured/\");\n\n// Since the filename is displayed in dashboard UI, need a placeholder if not yet set.\n// Filename is populated with timestamp at instant DAQ button is pressed in UI.\n// (See Dashboard tab)\nif (global.get(\"daqfilename\") === undefined || global.get(\"daqfilename\") === null) {\n global.set(\"daqfilename\", \"[No filename set in recent memory.]\");\n}\n\n// Default state is to not simulate TCs\nglobal.set(\"simdaq\", false); \n\n// Default state is to not poll analog inputs\nglobal.set(\"pauseacq\", true); \n\n// Default state is to not drive real digital outputs\nglobal.set(\"W1\", { \"sim\": true }); \nglobal.set(\"W2\", { \"sim\": true });\nglobal.set(\"W3\", { \"sim\": true });\nglobal.set(\"W4\", { \"sim\": true });\nglobal.set(\"W5\", { \"sim\": true });\nglobal.set(\"W6\", { \"sim\": true });\n\n// Global mapping of thermocouples to input channels and red/yellow limits.\n// Between the numbers, channels, and labels, this is three names for each data point. That's too much.\nglobal.set(\"tcmap\", {\n \"TC1\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 0, \"label\": \"TC_1_01\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC2\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 1, \"label\": \"TC_1_02\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC3\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 2, \"label\": \"TC_1_03\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC4\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 3, \"label\": \"TC_1_04\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC5\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 4, \"label\": \"TC_1_05\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC6\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 5, \"label\": \"TC_1_06\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC7\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 6, \"label\": \"TC_1_07\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC8\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 7, \"label\": \"TC_1_08\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC9\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 8, \"label\": \"TC_1_09\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC10\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 9, \"label\": \"TC_1_10\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC11\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 10, \"label\": \"TC_1_11\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC12\": { \"description\": \"Example TC Description\", \"module\": 1, \"channel\": 11, \"label\": \"TC_1_12\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC13\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 4, \"label\": \"TC_2_05\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC14\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 5, \"label\": \"TC_2_06\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC15\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 6, \"label\": \"TC_2_07\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC16\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 7, \"label\": \"TC_2_08\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC17\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 8, \"label\": \"TC_2_09\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC18\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 9, \"label\": \"TC_2_10\", \"redmin\": null, \"yelmin\": null, \"yelmax\": null, \"redmax\": null},\n \"TC19\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 10, \"label\": \"TC_2_11\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99},\n \"TC20\": { \"description\": \"Example TC Description\", \"module\": 2, \"channel\": 11, \"label\": \"TC_2_12\", \"redmin\": -99, \"yelmin\": -50, \"yelmax\": 50, \"redmax\": 99}\n});\n\nconst alarms = {\n \"TC2\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC3\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC4\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC5\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC15\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC16\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC17\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC19\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false },\n \"TC20\": { \"redminflag\": false, \"yelminflag\": false, \"yelmaxflag\": false, \"redmaxflag\": false }\n};\n\nglobal.set(\"alarms\", alarms);\n\n// Global mapping of which heater circuit (WX) is controlled by which thermocouple (TCY), as well as setpoints and power levels per operating regime.\n// For zone-based control, this (and the downstream) will need a refactor.\n// These are strings on purpose, even though it could be an indexed array.\nconst heatermap = {\n \"W1\": { \"module\": 0, \"channel\": 0, \"tc\": \"TC3\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": -30, \"nom-hi\": -25, \"bal-lo\": -30, \"bal-hi\": -25 },\n \"W2\": { \"module\": 0, \"channel\": 1, \"tc\": \"TC5\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": -30, \"nom-hi\": -25, \"bal-lo\": -30, \"bal-hi\": -25 },\n \"W3\": { \"module\": 0, \"channel\": 2, \"tc\": \"TC19\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": -30, \"nom-hi\": -25, \"bal-lo\": -30, \"bal-hi\": -25 },\n \"W4\": { \"module\": 0, \"channel\": 3, \"tc\": \"TC11\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": false, \"nom-hi\": false, \"bal-lo\": false, \"bal-hi\": false, \"nom-pwr\": 1.00, \"hbl-pwr\": 1.00, \"cbl-pwr\": 0.80, \"srv-pwr\": false },\n \"W5\": { \"module\": 0, \"channel\": 4, \"tc\": \"TC12\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": false, \"nom-hi\": false, \"bal-lo\": false, \"bal-hi\": false, \"nom-pwr\": 0.90, \"hbl-pwr\": 1.00, \"cbl-pwr\": 0.80, \"srv-pwr\": false },\n \"W6\": { \"module\": 0, \"channel\": 5, \"tc\": \"TC10\", \"srv-lo\": -48, \"srv-hi\": -40, \"nom-lo\": false, \"nom-hi\": false, \"bal-lo\": false, \"bal-hi\": false, \"nom-pwr\": 0.80, \"hbl-pwr\": 0.80, \"cbl-pwr\": 0.80, \"srv-pwr\": false }\n};\n\nglobal.set(\"heatermap\", heatermap);\n\n// Default cycle at boot \nglobal.set(\"cycle\", \"Survival\");\n\nreturn obj;","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 540,"y": 160,"wires": [["926b6a6b736adc27","81835d50fa278961","87fdb57618e4d702"]]},{"id": "bbf7509a2d55d2fa","type": "trigger","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Heartbeat","op1": "","op2": "0","op1type": "pay","op2type": "str","duration": "-10","extend": false,"overrideDelay": true,"units": "s","reset": "","bytopic": "all","topic": "topic","outputs": 1,"x": 840,"y": 220,"wires": [["f897bc90879b242d"]]},{"id": "f897bc90879b242d","type": "function","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Set Timestamp","func": "// This is triggered every heartbeat.\n\nlet obj = {\n \"timestamp\": new Date()\n};\n\nglobal.set(\"heartbeat\", obj.timestamp);\n\nreturn obj;","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 1000,"y": 220,"wires": [["963bb44a150ddec0","c677f7b715ec076a"]]},{"id": "81835d50fa278961","type": "debug","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Booted","active": true,"tosidebar": true,"console": false,"tostatus": false,"complete": "\"WOKE UP AT \" & $now()","targetType": "jsonata","statusVal": "","statusType": "auto","x": 755,"y": 120,"wires": [],"l": false},{"id": "1875004269cced91","type": "inject","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Wakeup, Reset","props": [{"p": "payload"}],"repeat": "","crontab": "","once": true,"onceDelay": 0.1,"topic": "","payload": "","payloadType": "date","x": 260,"y": 160,"wires": [["d958ae42891cf0a2"]],"info": "This inject node runs once on deploy/boot \r\nand triggers the OnInitialization function."},{"id": "87fdb57618e4d702","type": "delay","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Delay after wake-up before heartbeat starts","pauseType": "delay","timeout": "2","timeoutUnits": "seconds","rate": "1","nbRateUnits": "1","rateUnits": "second","randomFirst": "1","randomLast": "5","randomUnits": "seconds","drop": false,"allowrate": false,"outputs": 1,"x": 685,"y": 220,"wires": [["bbf7509a2d55d2fa"]],"l": false},{"id": "5ea8951783f48d68","type": "function","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "1/2","func": "\nlet interval = global.get(\"heartbeatinterval\");\n\nlet newinterval = Math.max(250, 0.5 * interval);\n\nglobal.set(\"heartbeatinterval\", newinterval);\n\nmsg.delay = newinterval;\n\nreturn msg;","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 650,"y": 260,"wires": [["bbf7509a2d55d2fa"]]},{"id": "6ae6740aab8630a6","type": "inject","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Decrease Sample Interval (Min: 250ms)","props": [{"p": "payload"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "true","payloadType": "bool","x": 330,"y": 260,"wires": [["5ea8951783f48d68"]]},{"id": "328a4f3d030440ba","type": "inject","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "Increase Sample Interval (Max: 60s)","props": [{"p": "payload"}],"repeat": "","crontab": "","once": false,"onceDelay": 0.1,"topic": "","payload": "true","payloadType": "bool","x": 320,"y": 300,"wires": [["27658fc32aca95ec"]]},{"id": "27658fc32aca95ec","type": "function","z": "237432cea239de8b","g": "d92a097fa9a08dee","name": "2","func": "\nlet interval = global.get(\"heartbeatinterval\");\n\nlet newinterval = Math.min(64000,2*interval);\n\nglobal.set(\"heartbeatinterval\",newinterval);\n\nmsg.delay = newinterval;\n\nreturn msg;","outputs": 1,"timeout": 0,"noerr": 0,"initialize": "","finalize": "","libs": [],"x": 650,"y": 300,"wires": [["bbf7509a2d55d2fa"]]}]