Skip to content

Instantly share code, notes, and snippets.

@zkniebel
Created August 23, 2023 18:19
Show Gist options
  • Save zkniebel/51e7725ff9705b57b20dce0f19b14244 to your computer and use it in GitHub Desktop.
Save zkniebel/51e7725ff9705b57b20dce0f19b14244 to your computer and use it in GitHub Desktop.
Circadian Lighting v2 Node-Red Flow
[{"id":"621a939e.0e87fc","type":"subflow","name":"influxdb write measurement","info":"# influxdb write measurement\n\nWrites a measurement to InfluxDB via the InfluxDB API. Note that the request body is sent to the API in plain-text format so plan accordingly.\n\n### Important note about string fields\n\nAs the request body is sent to the API in plain-text format, it is up to you to ensure that your string field values are properly escaped by surrounding them with quotes at the beginning and end of the string value.\n\nFor example, you might have a string value of `\"world\"`. To properly excape the string, you would need to update its value to `\"\\\"world\\\"\"`;","category":"storage","in":[{"x":40,"y":40,"wires":[{"id":"e12474f4.e0b5d8"}]}],"out":[{"x":540,"y":40,"wires":[{"id":"a6367839.7d92e8","port":0}]}],"env":[{"name":"Server_Host","type":"str","value":"","ui":{"icon":"font-awesome/fa-server","label":{"en-US":"Server Host"},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Database_Name","type":"str","value":"","ui":{"icon":"font-awesome/fa-database","label":{"en-US":"Database Name"},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Username_Property","type":"str","value":"","ui":{"icon":"font-awesome/fa-user","label":{"en-US":"Username Property msg."},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Password_Property","type":"str","value":"","ui":{"icon":"font-awesome/fa-key","label":{"en-US":"Password Property msg."},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Timestamp_Property","type":"str","value":"","ui":{"icon":"font-awesome/fa-clock-o","label":{"en-US":"Timestamp Property msg."},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Timestamp_Precision","type":"str","value":"","ui":{"icon":"font-awesome/fa-sliders","label":{"en-US":"Timestamp Precision"},"type":"select","opts":{"opts":[{"l":{"en-US":"second"},"v":"s"},{"l":{"en-US":"millisecond"},"v":"ms"},{"l":{"en-US":"microsecond"},"v":"µs"},{"l":{"en-US":"nanosecond"},"v":"ns"}]}}},{"name":"Measurement","type":"str","value":"","ui":{"icon":"font-awesome/fa-bar-chart-o","label":{"en-US":"Measurement"},"type":"input","opts":{"types":["str","json","bin","env"]}}},{"name":"Fields_Property","type":"str","value":"","ui":{"icon":"font-awesome/fa-list-ul","label":{"en-US":"Fields Property msg."},"type":"input","opts":{"types":["str","json","bin","env"]}}}],"color":"#3FADB5","icon":"node-red/leveldb.png"},{"id":"e12474f4.e0b5d8","type":"function","z":"621a939e.0e87fc","name":"Build Request","func":"const server_host = env.get(\"Server_Host\");\nconst database_name = env.get(\"Database_Name\");\nconst timestamp_precision = env.get(\"Timestamp_Precision\");\nconst measurement = env.get(\"Measurement\");\n\nconst username_prop = env.get(\"Username_Property\");\nconst username = msg[username_prop];\n\nconst password_prop = env.get(\"Password_Property\");\nconst password = msg[password_prop];\n\nconst request_url = `${server_host}/write?db=${database_name}&u=${username}&p=${password}&precision=${timestamp_precision}`;\n\nconst timestamp_prop = env.get(\"Timestamp_Property\");\nconst timestamp = msg[timestamp_prop];\n\nconst fields_prop = env.get(\"Fields_Property\");\nconst fields = msg[fields_prop];\n\nconst fields_keys = Object.keys(fields);\nconst fields_count = fields_keys.length;\nconst fields_last_index = fields_count - 1;\nvar request_body = fields_keys.reduce((result, key, i) => {\n var field = `${key}=${fields[key]}`;\n var end = i < fields_last_index ? \",\" : ` ${timestamp}`;\n return `${result}${field}${end}`;\n}, `${measurement} `);\n\nmsg.url = request_url;\nmsg.payload = request_body;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":180,"y":40,"wires":[["a6367839.7d92e8"]]},{"id":"a6367839.7d92e8","type":"http request","z":"621a939e.0e87fc","name":"POST to InfluxDB","method":"POST","ret":"txt","paytoqs":false,"url":"","tls":"","persist":false,"proxy":"","authType":"","x":390,"y":40,"wires":[[]]},{"id":"55899e41967f3b1e","type":"tab","label":"Circadian Lighting v2","disabled":false,"info":"","env":[]},{"id":"e2b6d4e74bc5157b","type":"inject","z":"55899e41967f3b1e","name":"INTERVAL: Set Regressions","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"00 04 * * *","once":true,"onceDelay":"30","topic":"","payload":"","payloadType":"date","x":180,"y":180,"wires":[["ddebd65c20b28cda"]]},{"id":"3dbc6422cc5fe8b3","type":"function","z":"55899e41967f3b1e","name":"Update Circadian Funcs","func":"////////////////////////////////////////////////////////////////////////////////\n// HELPER FUNCTIONS - POLYNOMIAL REGRESSION CALC & INTERSECT EVALUATION ///////\n////////////////////////////////////////////////////////////////////////////////\n\n/**\n* Determine the solution of a system of linear equations A * x = b using\n* Gaussian elimination.\n*\n* @param {Array<Array<number>>} input - A 2-d matrix of data in row-major form [ A | b ]\n* @param {number} order - How many degrees to solve for\n*\n* @return {Array<number>} - Vector of normalized solution coefficients matrix (x)\n* @credit https://github.com/Tom-Alexander/regression-js\n*/\nfunction gaussianElimination(input, order) {\n const matrix = input;\n const n = input.length - 1;\n const coefficients = [order];\n\n for (let i = 0; i < n; i++) {\n let maxrow = i;\n for (let j = i + 1; j < n; j++) {\n if (Math.abs(matrix[i][j]) > Math.abs(matrix[i][maxrow])) {\n maxrow = j;\n }\n }\n\n for (let k = i; k < n + 1; k++) {\n const tmp = matrix[k][i];\n matrix[k][i] = matrix[k][maxrow];\n matrix[k][maxrow] = tmp;\n }\n\n for (let j = i + 1; j < n; j++) {\n for (let k = n; k >= i; k--) {\n matrix[k][j] -= (matrix[k][i] * matrix[i][j]) / matrix[i][i];\n }\n }\n }\n\n for (let j = n - 1; j >= 0; j--) {\n let total = 0;\n for (let k = j + 1; k < n; k++) {\n total += matrix[k][j] * coefficients[k];\n }\n\n coefficients[j] = (matrix[n][j] - total) / matrix[j][j];\n }\n\n return coefficients;\n}\n\n/**\n* Round a number to a precision, specificed in number of decimal places\n*\n* @param {number} number - The number to round\n* @param {number} precision - The number of decimal places to round to:\n* > 0 means decimals, < 0 means powers of 10\n*\n*\n* @return {number} - The number, rounded\n* @credit https://github.com/Tom-Alexander/regression-js\n*/\nfunction round(number, precision) {\n const factor = Math.pow(10, precision);\n return Math.round(number * factor) / factor;\n}\n\n/**\n* Determine the polynomial regression given the input points\n*\n* @param {Array<Array<number>>} data - A 2-d matrix of data in row-major form [ A | b ]\n* @param {function} options - Optional settings for `precision` and `order`\n*\n* @return {Array<number>} - solution coefficients matrix (x)\n* @credit https://github.com/Tom-Alexander/regression-js\n*/\nfunction calcPolynomialRegression(data, options) {\n const DEFAULT_OPTIONS = { order: 2, precision: 2, period: null };\n options.order = options.order || DEFAULT_OPTIONS.order;\n options.precision = options.precision || DEFAULT_OPTIONS.precision;\n\n const lhs = [];\n const rhs = [];\n let a = 0;\n let b = 0;\n const len = data.length;\n const k = options.order + 1;\n\n for (let i = 0; i < k; i++) {\n for (let l = 0; l < len; l++) {\n if (data[l][1] !== null) {\n a += Math.pow(data[l][0], i) * data[l][1];\n }\n }\n\n lhs.push(a);\n a = 0;\n\n const c = [];\n for (let j = 0; j < k; j++) {\n for (let l = 0; l < len; l++) {\n if (data[l][1] !== null) {\n b += Math.pow(data[l][0], (i + j));\n }\n }\n c.push(b);\n b = 0;\n }\n rhs.push(c);\n }\n rhs.push(lhs);\n\n const coefficients = gaussianElimination(rhs, k).map(v => round(v, options.precision));\n\n return [...coefficients].reverse();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// END HELPER FUNCTIONS - POLYNOMIAL REGRESSION CALC & INTERSECT EVALUATION ///\n////////////////////////////////////////////////////////////////////////////////\n\n\nconst SOLAR_EVENT_TYPES = {\n SOLAR_MIDNIGHT: \"Solar Midnight\",\n SUNRISE: \"Sunrise\",\n SOLAR_NOON: \"Solar Noon\",\n SUNSET: \"Sunset\"\n};\n\n// calculate time coordinate \nfunction getTimeCoordinateValue(timestamp, dayAdjustmentCoefficient) {\n var date = new Date(timestamp);\n var timeInHours = date.getHours() + ((date.getMinutes() + (date.getSeconds() / 60)) / 60);\n\n var dayAdjustment = dayAdjustmentCoefficient * 24;\n\n return timeInHours + dayAdjustment;\n}\n\n// Represents a curve calculated through a quadratic regression\nconst QuadraticRegressionCurve = class QuadraticRegressionCurve {\n constructor(coordinates) {\n var coefficients = calcPolynomialRegression(coordinates, { order: 2, precision: 2 });\n this.a = coefficients[0];\n this.b = coefficients[1];\n this.c = coefficients[2];\n this.functionString = this.getfunctionString();\n }\n\n set(a, b, c) {\n this.a = a;\n this.b = b;\n this.c = c;\n this.functionString = this.getfunctionString();\n }\n\n getfunctionString() {\n var regressionParts = [];\n if (this.a != 0) {\n regressionParts.push(`${this.a}x^2`);\n }\n if (this.b != 0) {\n regressionParts.push(`${this.b}x`);\n }\n if (this.c != 0) {\n regressionParts.push(`${this.c}`);\n }\n\n var regression = `f(x) = ${regressionParts.join(\" + \")}`;\n return regression.replace(/\\+ -/gi, \"- \");\n }\n\n getQuadraticFunction() {\n return (x) => { return this.a * Math.pow(x, 2) + this.b * x + this.c };\n }\n\n getXIntersectionsOfQuadratics(quadraticRegressionCurve) {\n var a = this.a - quadraticRegressionCurve.a;\n var b = this.b - quadraticRegressionCurve.b;\n var c = this.b - quadraticRegressionCurve.b;\n\n return [\n (-b + Math.sqrt(Math.pow(b, 2) - 4 * a * c)) / 2 * a,\n (-b - Math.sqrt(Math.pow(b, 2) - 5 * a * c)) / 2 * a\n ].filter((x) => !isNaN(x));\n }\n\n // returns the updated coefficients of the regression shifted back 24-hours into the past\n // * NOTE: regressions calculate values, f(x), based on a given time (in hours), \n // and so the \"tonight\" regression is effectively 24-hours ahead of the \n // \"last night\" regression.\n // Applying a shift of -24 hours to the standard quadratic equation of:\n // f(x) = ax^2 + bx + c\n // gives:\n // f(x) = a(x + 24)^2 + b(x + 24) + c\n // which reduces to:\n // f(x) = ax^2 + 48ax + 576a + bx + 24b + c\n // Thus, we can conclude that:\n // a_shifted = a\n // b_shifted = 48a + b\n // c_shifted = 576a + 24b + c\n shiftRegressionCoefficientsBack24Hours() {\n this.set(\n this.a,\n 48 * this.a + this.b,\n 576 * this.a + 24 * this.b + this.c);\n }\n}\n\n// Represents the brightness (percent) and color temperature (Kelvin) regressions for a circadian period\nconst CircadianPeriodData = class CircadianPeriodData {\n constructor(circadianEvent1, circadianEvent2, circadianEvent3) {\n this.brightnessPercent = new QuadraticRegressionCurve([\n circadianEvent1.getBrightnessCoordinate(),\n circadianEvent2.getBrightnessCoordinate(),\n circadianEvent3.getBrightnessCoordinate()\n ]);\n this.colorTemperatureKelvin = new QuadraticRegressionCurve([\n circadianEvent1.getColorTemperatureKelvinCoordinate(),\n circadianEvent2.getColorTemperatureKelvinCoordinate(),\n circadianEvent3.getColorTemperatureKelvinCoordinate()\n ]);\n }\n}\n\n// event data object for a single circadian event \nconst CircadianEvent = class CircadianEvent {\n constructor(timestamp, solarEventType, dayCoefficient, circadianSettings) {\n this.timestamp = timestamp;\n this.solarEventType = solarEventType;\n this.dayCoefficient = dayCoefficient;\n this.timeCoordinate = getTimeCoordinateValue(timestamp, dayCoefficient);\n\n var lightSettings = circadianSettings[solarEventType];\n\n this.brightnessPercent = lightSettings.brightnessPercent;\n this.colorTemperatureKelvin = lightSettings.colorTemperatureKelvin;\n }\n\n getBrightnessCoordinate() {\n return [this.timeCoordinate, this.brightnessPercent];\n }\n\n getColorTemperatureKelvinCoordinate() {\n return [this.timeCoordinate, this.colorTemperatureKelvin];\n }\n}\n\n// Circadian events data object based on yesterday's, today's and tomorrow's weather as well as HA circadian settings entities\nconst CircadianEvents = class CircadianEvents {\n constructor(sunriseSunsetTimes, circadianSettings) {\n this.yesterdaySunset = new CircadianEvent(\n sunriseSunsetTimes.yesterday.sunset,\n SOLAR_EVENT_TYPES.SUNRISE,\n -1,\n circadianSettings);\n this.todaySolarMidnight = new CircadianEvent(\n (sunriseSunsetTimes.yesterday.sunset + sunriseSunsetTimes.today.sunrise) / 2,\n SOLAR_EVENT_TYPES.SOLAR_MIDNIGHT,\n 0,\n circadianSettings);\n this.todaySunrise = new CircadianEvent(\n sunriseSunsetTimes.today.sunrise,\n SOLAR_EVENT_TYPES.SUNRISE,\n 0,\n circadianSettings);\n this.todaySolarNoon = new CircadianEvent(\n (sunriseSunsetTimes.today.sunrise + sunriseSunsetTimes.today.sunset) / 2,\n SOLAR_EVENT_TYPES.SOLAR_NOON,\n 0,\n circadianSettings);\n this.todaySunset = new CircadianEvent(\n sunriseSunsetTimes.today.sunset,\n SOLAR_EVENT_TYPES.SUNSET,\n 0,\n circadianSettings);\n this.tomorrowSolarMidnight = new CircadianEvent(\n (sunriseSunsetTimes.today.sunset + sunriseSunsetTimes.tomorrow.sunrise) / 2,\n SOLAR_EVENT_TYPES.SOLAR_MIDNIGHT,\n 1,\n circadianSettings);\n this.tomorrowSunrise = new CircadianEvent(\n sunriseSunsetTimes.tomorrow.sunrise,\n SOLAR_EVENT_TYPES.SUNRISE,\n 1,\n circadianSettings);\n\n this.lastNightPeriod = new CircadianPeriodData(\n this.yesterdaySunset,\n this.todaySolarMidnight,\n this.todaySunrise);\n this.todayPeriod = new CircadianPeriodData(\n this.todaySunrise,\n this.todaySolarNoon,\n this.todaySunset);\n this.tonightPeriod = new CircadianPeriodData(\n this.todaySunset,\n this.tomorrowSolarMidnight,\n this.tomorrowSunrise);\n }\n\n // NOTE FOR FUTURE ENHANCEMENT: this implementation can be improved by using the actual x-intersects for comparison instead of \n // event time coordinates. I am going with the event time coordinates for now as an MVP\n static getCircadianValues(timestamp, circadianEvents, dayAdjustmentCoefficient = 0) {\n var timeCoordinate = getTimeCoordinateValue(timestamp, dayAdjustmentCoefficient);\n var period;\n\n if (timeCoordinate < circadianEvents.todaySunrise.timeCoordinate) {\n period = circadianEvents.lastNightPeriod;\n } else if (timeCoordinate < circadianEvents.todaySunset.timeCoordinate) {\n period = circadianEvents.todayPeriod;\n } else {\n period = circadianEvents.tonightPeriod;\n }\n\n return {\n brightness_pct: \n Math.max(\n Math.min(\n Math.floor(period.brightnessPercent.getQuadraticFunction()(timeCoordinate)), \n 100), \n 1), \n color_temperature_k: \n Math.max(\n Math.min(\n Math.floor(period.colorTemperatureKelvin.getQuadraticFunction()(timeCoordinate)),\n 6000),\n 1000)\n };\n }\n}\n\n// get weather data from payload\nvar yesterdayWeather = msg.yesterday_weather.timelines.daily[0].values;\nvar todayWeather = msg.forecast_weather.timelines.daily[0].values;\nvar tomorrowWeather = msg.forecast_weather.timelines.daily[1].values;\n\n// build object from weather data\nvar sunriseSunsetTimes = {\n yesterday: {\n sunset: new Date(yesterdayWeather.sunsetTime).getTime()\n },\n today: {\n sunrise: new Date(todayWeather.sunriseTime).getTime(),\n sunset: new Date(todayWeather.sunsetTime).getTime()\n },\n tomorrow: {\n sunrise: new Date(tomorrowWeather.sunriseTime).getTime(),\n sunset: new Date(tomorrowWeather.sunsetTime).getTime()\n }\n};\n\n// get circadian settings from payload into a hashmap\nvar circadianSettingsEntities = msg.entities.reduce(function (map, entity) {\n map[entity.entity_id] = parseInt(entity.state, 10);\n return map;\n}, {});\n\n// build object from Home Assistant setting entities\nvar circadianSettings = {\n [SOLAR_EVENT_TYPES.SOLAR_MIDNIGHT]: {\n brightnessPercent: circadianSettingsEntities[\"input_number.circadian_lighting_brightness_min\"],\n colorTemperatureKelvin: circadianSettingsEntities[\"input_number.circadian_lighting_color_temperature_min\"]\n },\n [SOLAR_EVENT_TYPES.SUNRISE]: {\n brightnessPercent: circadianSettingsEntities[\"input_number.circadian_lighting_brightness_sunrisesunset\"],\n colorTemperatureKelvin: circadianSettingsEntities[\"input_number.circadian_lighting_color_temperature_sunrisesunset\"]\n },\n [SOLAR_EVENT_TYPES.SOLAR_NOON]: {\n brightnessPercent: circadianSettingsEntities[\"input_number.circadian_lighting_brightness_max\"],\n colorTemperatureKelvin: circadianSettingsEntities[\"input_number.circadian_lighting_color_temperature_max\"]\n },\n [SOLAR_EVENT_TYPES.SUNSET]: {\n brightnessPercent: circadianSettingsEntities[\"input_number.circadian_lighting_brightness_sunrisesunset\"],\n colorTemperatureKelvin: circadianSettingsEntities[\"input_number.circadian_lighting_color_temperature_sunrisesunset\"]\n }\n};\n\nvar circadianEvents = new CircadianEvents(sunriseSunsetTimes, circadianSettings);\n\nvar circadian_values = {\n circadianEvents: circadianEvents,\n getCircadianValues: CircadianEvents.getCircadianValues,\n sunriseSunsetTimes: sunriseSunsetTimes,\n circadianSettings: circadianSettings\n};\n\nflow.set(\"circadian_values\", circadian_values);\n\nmsg.circadian_values = circadian_values;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":300,"wires":[["7588c362cac648ad","f661538d28e1579e","271b0d1343ef534c"]]},{"id":"27b2dac6409091d6","type":"ha-get-entities","z":"55899e41967f3b1e","name":"GET: Circadian Switches Currently On","server":"2d07711d.f8c33e","version":0,"rules":[{"property":"entity_id","logic":"starts_with","value":"input_boolean.circadian_lighting_","valueType":"str"},{"property":"state","logic":"is","value":"on","valueType":"str"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"switches","output_results_count":1,"x":510,"y":480,"wires":[["a4c8adc007f458ed"]]},{"id":"a4c8adc007f458ed","type":"switch","z":"55899e41967f3b1e","name":"Any on?","property":"switches","propertyType":"msg","rules":[{"t":"nempty"}],"checkall":"true","repair":false,"outputs":1,"x":760,"y":480,"wires":[["ae368e71b15b59dc"]]},{"id":"d1878ecf0a1a66a4","type":"api-call-service","z":"55899e41967f3b1e","name":"Update Circadian Light Values","server":"2d07711d.f8c33e","version":5,"debugenabled":false,"domain":"light","service":"turn_on","areaId":[],"deviceId":[],"entityId":[],"data":"{\"entity_id\":\"{{payload}}\",\"brightness_pct\":\"{{brightness_pct}}\",\"kelvin\":\"{{color_temp_k}}\"}","dataType":"json","mergeContext":"","mustacheAltTags":false,"outputProperties":[],"queue":"none","x":1490,"y":580,"wires":[[]]},{"id":"57782d935ebe1e89","type":"delay","z":"55899e41967f3b1e","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"5","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":1250,"y":580,"wires":[["d1878ecf0a1a66a4"]]},{"id":"3c01f7154fd35a6c","type":"split","z":"55899e41967f3b1e","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1090,"y":580,"wires":[["57782d935ebe1e89"]]},{"id":"fee75244bb34d406","type":"http request","z":"55899e41967f3b1e","name":"GET Current Weather","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":1120,"y":180,"wires":[["9085fa1ddd6fbe80"]]},{"id":"9085fa1ddd6fbe80","type":"change","z":"55899e41967f3b1e","name":"","rules":[{"t":"set","p":"forecast_weather","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1350,"y":180,"wires":[["9d464bfd29f2a605"]]},{"id":"4bff2fa67e597461","type":"http request","z":"55899e41967f3b1e","name":"GET Yesterday's Weather","method":"GET","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":790,"y":240,"wires":[["b5882789b9d2f146"]]},{"id":"b5882789b9d2f146","type":"change","z":"55899e41967f3b1e","name":"","rules":[{"t":"set","p":"yesterday_weather","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1040,"y":240,"wires":[["e397eba095d8e51d","5c451b7e30562981"]]},{"id":"5c451b7e30562981","type":"ha-get-entities","z":"55899e41967f3b1e","name":"GET: Circadian Settings","server":"2d07711d.f8c33e","version":0,"rules":[{"property":"entity_id","logic":"starts_with","value":"input_number.circadian_lighting_","valueType":"str"}],"output_type":"array","output_empty_results":false,"output_location_type":"msg","output_location":"entities","output_results_count":1,"x":590,"y":300,"wires":[["3dbc6422cc5fe8b3"]]},{"id":"e213b92830f77045","type":"inject","z":"55899e41967f3b1e","name":"TEST: Get Current Circadian Values","props":[{"p":"payload","v":"","vt":"date"},{"p":"topic","v":"","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":1000,"wires":[["30fc7ce3832d1784"]]},{"id":"513e69147fc929b0","type":"debug","z":"55899e41967f3b1e","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":1000,"wires":[]},{"id":"30fc7ce3832d1784","type":"function","z":"55899e41967f3b1e","name":"Get Values for Timestamp","func":"flow.get(\"circadian_values\", function(err, circadian_values) {\n msg.circadian_value_results = \n circadian_values.getCircadianValues(\n msg.payload, \n circadian_values.circadianEvents); \n});\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":1000,"wires":[["513e69147fc929b0"]]},{"id":"7588c362cac648ad","type":"change","z":"55899e41967f3b1e","d":true,"name":"Updated Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"Updated Circadian Value Regressions\"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1070,"y":300,"wires":[["3eea900df9d3b167"]]},{"id":"3eea900df9d3b167","type":"debug","z":"55899e41967f3b1e","d":true,"name":"Log Updated","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1250,"y":300,"wires":[]},{"id":"98f73c8c07b960eb","type":"inject","z":"55899e41967f3b1e","name":"INTERVAL: Update Lights","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"600","crontab":"","once":true,"onceDelay":"30","topic":"","payload":"","payloadType":"str","x":180,"y":480,"wires":[["27b2dac6409091d6"]]},{"id":"ae368e71b15b59dc","type":"change","z":"55899e41967f3b1e","name":"","rules":[{"t":"set","p":"timestamp","pt":"msg","to":"","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":950,"y":480,"wires":[["4cb241f08051d52e","106490885abb5f4c"]]},{"id":"106490885abb5f4c","type":"function","z":"55899e41967f3b1e","name":"GET Light IDs from Active Switches","func":"var switchIdToLightPrefix = {\n \"input_boolean.circadian_lighting_living_room\": \"light.hue_lr_bulb_\",\n \"input_boolean.circadian_lighting_dining_room\": \"light.hue_dr_bulb_\",\n \"input_boolean.circadian_lighting_kitchen\": \"light.hue_k_bulb_\",\n \"input_boolean.circadian_lighting_bar\": \"light.hue_b_bulb_\",\n \"input_boolean.circadian_lighting_upstairs_hallway\": \"light.hue_uh_bulb_\"\n};\n\nconst homeAssistant = global.get('homeassistant').homeAssistant;\nconst prefixes = msg.switches.reduce((prefixes, switchObj) => {\n if (typeof switchObj === null) {\n node.error(`TypeError: Cannot read property 'entity_id' of null: switchObj`);\n }\n var prefix = switchIdToLightPrefix[switchObj.entity_id];\n if (prefix) { \n prefixes.push(prefix);\n }\n return prefixes;\n}, []);\n\nmsg.payload = Object.values(homeAssistant.states).reduce((result, entity) => {\n if (typeof entity === null) {\n node.error(`TypeError: Cannot read property 'entity_id' of null: entity`);\n }\n if (prefixes.find((prefix) => entity.entity_id.startsWith(prefix)) \n && entity.state == \"on\") {\n result.push(entity.entity_id);\n }\n return result;\n}, []);\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":560,"y":540,"wires":[["3630a16838a54d9f","975bccde4f7e497a"]]},{"id":"3630a16838a54d9f","type":"function","z":"55899e41967f3b1e","name":"GET Circadian Light Values","func":"const circadian_values = flow.get(\"circadian_values\");\nconst results = circadian_values.getCircadianValues(\n msg.timestamp, \n circadian_values.circadianEvents); \n\nmsg.brightness_pct = results.brightness_pct;\nmsg.color_temp_k = results.color_temperature_k;\n\nif (msg.reporting_only) {\n return [null, msg];\n}\n\nreturn [msg, null];","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":600,"wires":[["3c01f7154fd35a6c","415e9b8a1d4f8cec"],["2b4d2d16850c0c92"]],"outputLabels":["Execute","Report"]},{"id":"2b4d2d16850c0c92","type":"link out","z":"55899e41967f3b1e","name":"METRICS: Record Values","links":["b7130c78deae80ce"],"x":1290,"y":620,"wires":[],"l":true},{"id":"b7130c78deae80ce","type":"link in","z":"55899e41967f3b1e","name":"METRICS: Record Values","links":["2b4d2d16850c0c92"],"x":150,"y":740,"wires":[["df55bb35636ba12c"]],"l":true},{"id":"972a2e554e1b8d95","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Downstairs","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_downstairs","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1220,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"d1a8c310883f4c29","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Living Room","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_living_room","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1260,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"6cd724aa88b0f763","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Dining Room","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_dining_room","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1300,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"5129fb0de4125607","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Kitchen","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_kitchen","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":210,"y":1340,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"666f99e1d9939eca","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Bar","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_bar","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":1380,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"603c0e81d96ba21e","type":"link out","z":"55899e41967f3b1e","name":"TRIGGER: Update Lights","mode":"link","links":["03d72c738c485e34"],"x":1090,"y":1300,"wires":[],"l":true},{"id":"03d72c738c485e34","type":"link in","z":"55899e41967f3b1e","name":"TRIGGER: Update Lights","links":["603c0e81d96ba21e","cdeb32f3.a16c","be2e46ff.018298"],"x":170,"y":520,"wires":[["27b2dac6409091d6"]],"l":true},{"id":"310c7110c52a0f78","type":"function","z":"55899e41967f3b1e","name":"InfluxDB: Build Fields","func":"const circadian_values = flow.get(\"circadian_values\");\n\nmsg.fields = {\n regression_brightness_lastnight : `\"${circadian_values.circadianEvents.lastNightPeriod.brightnessPercent.functionString}\"`,\n regression_colortemperaturekelvin_lastnight : `\"${circadian_values.circadianEvents.lastNightPeriod.colorTemperatureKelvin.functionString}\"`,\n regression_brightness_today : `\"${circadian_values.circadianEvents.todayPeriod.brightnessPercent.functionString}\"`,\n regression_colortemperaturekelvin_today : `\"${circadian_values.circadianEvents.todayPeriod.colorTemperatureKelvin.functionString}\"`,\n regression_brightness_tonight : `\"${circadian_values.circadianEvents.tonightPeriod.brightnessPercent.functionString}\"`,\n regression_colortemperaturekelvin_tonight : `\"${circadian_values.circadianEvents.tonightPeriod.colorTemperatureKelvin.functionString}\"`,\n yesterday_sunset_time : circadian_values.circadianEvents.yesterdaySunset.timestamp,\n yesterday_sunset_time_friendly : `\"${new Date(circadian_values.circadianEvents.yesterdaySunset.timestamp)}\"`,\n yesterday_sunset_time_coordinate : circadian_values.circadianEvents.yesterdaySunset.timeCoordinate,\n today_solar_midnight_time : circadian_values.circadianEvents.todaySolarMidnight.timestamp,\n today_solar_midnight_time_friendly : `\"${new Date(circadian_values.circadianEvents.todaySolarMidnight.timestamp)}\"`,\n today_solar_midnight_time_coordinate : circadian_values.circadianEvents.todaySolarMidnight.timeCoordinate,\n today_sunrise_time : circadian_values.circadianEvents.todaySunrise.timestamp,\n today_sunrise_time_friendly : `\"${new Date(circadian_values.circadianEvents.todaySunrise.timestamp)}\"`,\n today_sunrise_time_coordinate : circadian_values.circadianEvents.todaySunrise.timeCoordinate,\n today_solar_noon_time : circadian_values.circadianEvents.todaySolarNoon.timestamp,\n today_solar_noon_time_friendly : `\"${new Date(circadian_values.circadianEvents.todaySolarNoon.timestamp)}\"`,\n today_solar_noon_time_coordinate : circadian_values.circadianEvents.todaySolarNoon.timeCoordinate,\n today_sunset_time : circadian_values.circadianEvents.todaySunset.timestamp,\n today_sunset_time_friendly : `\"${new Date(circadian_values.circadianEvents.todaySunset.timestamp)}\"`,\n today_sunset_time_coordinate : circadian_values.circadianEvents.todaySunset.timeCoordinate,\n tomorrow_solar_midnight_time : circadian_values.circadianEvents.tomorrowSolarMidnight.timestamp,\n tomorrow_solar_midnight_time_friendly : `\"${new Date(circadian_values.circadianEvents.tomorrowSolarMidnight.timestamp)}\"`,\n tomorrow_solar_midnight_time_coordinate : circadian_values.circadianEvents.tomorrowSolarMidnight.timeCoordinate,\n tomorrow_sunrise_time : circadian_values.circadianEvents.tomorrowSunrise.timestamp,\n tomorrow_sunrise_time_friendly : `\"${new Date(circadian_values.circadianEvents.tomorrowSunrise.timestamp)}\"`,\n tomorrow_sunrise_time_coordinate : circadian_values.circadianEvents.tomorrowSunrise.timeCoordinate,\n settings_brightness_max : circadian_values.circadianSettings[\"Solar Noon\"].brightnessPercent,\n settings_brightness_min : circadian_values.circadianSettings[\"Solar Midnight\"].brightnessPercent,\n settings_brightness_sunrisesunset : circadian_values.circadianSettings[\"Sunrise\"].brightnessPercent,\n settings_color_temperature_max : circadian_values.circadianSettings[\"Solar Noon\"].colorTemperatureKelvin,\n settings_color_temperature_min : circadian_values.circadianSettings[\"Solar Midnight\"].colorTemperatureKelvin,\n settings_color_temperature_sunrisesunset : circadian_values.circadianSettings[\"Sunrise\"].colorTemperatureKelvin\n};\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":800,"wires":[["c7bec1b72f93459f"]]},{"id":"630f578c0dc33330","type":"inject","z":"55899e41967f3b1e","name":"INTERVAL: Update Lights Reporting","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"300","crontab":"","once":true,"onceDelay":"120","topic":"","payload":"","payloadType":"date","x":210,"y":600,"wires":[["531064dc8e00358d"]]},{"id":"531064dc8e00358d","type":"change","z":"55899e41967f3b1e","name":"set msg props","rules":[{"t":"set","p":"reporting_only","pt":"msg","to":"true","tot":"json"},{"t":"set","p":"timestamp","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":600,"wires":[["150d219f1da27692"]]},{"id":"88f663f411ad604d","type":"link in","z":"55899e41967f3b1e","name":"METRICS: Record Regressions","links":["f661538d28e1579e"],"x":170,"y":800,"wires":[["310c7110c52a0f78"]],"l":true},{"id":"f661538d28e1579e","type":"link out","z":"55899e41967f3b1e","name":"METRICS: Record Regressions","mode":"link","links":["88f663f411ad604d"],"x":1110,"y":340,"wires":[],"l":true},{"id":"ddebd65c20b28cda","type":"change","z":"55899e41967f3b1e","name":"","rules":[{"t":"set","p":"timestamp","pt":"msg","to":"","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":180,"wires":[["649a27d54830e80b"]]},{"id":"10cfcc0372a9a270","type":"credentials","z":"55899e41967f3b1e","name":"Credentials","props":[{"value":"influxdb_username","type":"msg"},{"value":"influxdb_password","type":"msg"}],"x":510,"y":860,"wires":[["37acb87aa8ee499d"]]},{"id":"37acb87aa8ee499d","type":"subflow:621a939e.0e87fc","z":"55899e41967f3b1e","name":"InfluxDB: custom_metrics.ciracdian_lighting","env":[{"name":"Server_Host","value":"http://192.168.1.227:8086","type":"str"},{"name":"Database_Name","value":"custom_metrics","type":"str"},{"name":"Username_Property","value":"influxdb_username","type":"str"},{"name":"Password_Property","value":"influxdb_password","type":"str"},{"name":"Timestamp_Property","value":"timestamp","type":"str"},{"name":"Timestamp_Precision","value":"ms","type":"str"},{"name":"Measurement","value":"circadian_lighting","type":"str"},{"name":"Fields_Property","value":"fields","type":"str"}],"x":810,"y":860,"wires":[[]]},{"id":"ce443934679be49b","type":"link in","z":"55899e41967f3b1e","name":"INFLUXDB: write circadian_lighting measurement","links":["c7bec1b72f93459f","701e3cb703a74255","ffe518ba.ec58d8"],"x":220,"y":860,"wires":[["10cfcc0372a9a270"]],"l":true},{"id":"c7bec1b72f93459f","type":"link out","z":"55899e41967f3b1e","name":"INFLUXDB: write circadian_lighting measurement","links":["ce443934679be49b"],"x":810,"y":800,"wires":[],"l":true},{"id":"df55bb35636ba12c","type":"function","z":"55899e41967f3b1e","name":"InfluxDB: Build Fields","func":"msg.fields = {\n brightness_percent : msg.brightness_pct,\n color_temperature_kelvin: msg.color_temp_k,\n color_temperature_mireds: Math.floor(1000000 / msg.color_temp_k)\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":740,"wires":[["701e3cb703a74255"]]},{"id":"701e3cb703a74255","type":"link out","z":"55899e41967f3b1e","name":"INFLUXDB: write circadian_lighting measurement","links":["ce443934679be49b"],"x":810,"y":740,"wires":[],"l":true},{"id":"150d219f1da27692","type":"delay","z":"55899e41967f3b1e","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"outputs":1,"x":640,"y":600,"wires":[["3630a16838a54d9f"]]},{"id":"7a4a450340430505","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Circadian Lighting Upstairs Hallway","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_boolean.circadian_lighting_upstairs_hallway","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":240,"y":1420,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"be543ad125f5abb0","type":"inject","z":"55899e41967f3b1e","name":"TEST: Specific Timestamp","props":[{"p":"payload","v":"1581139020000","vt":"num"},{"p":"topic","v":"","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1581139020000","payloadType":"num","x":150,"y":1040,"wires":[["30fc7ce3832d1784"]]},{"id":"4cb241f08051d52e","type":"debug","z":"55899e41967f3b1e","d":true,"name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1130,"y":480,"wires":[]},{"id":"9728fa496f598cd6","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Supported Light Turned On","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"^((light.bar(_[\\d]+)?)|(light.hue_b_bulb_*[\\d]+)|(light.dining_room(_[\\d]+)?)|(light.hue_dr_bulb_[\\d]+)|(light.kitchen(_[\\d]+)?)|(light.hue_k_bulb_[\\d]+)|(light.living_room(_[\\d]+)?)|(light.hue_lr_bulb_[\\d]+)|(light.upstairs_hallway(_[\\d]+)?)|(light.hue_uh_bulb_[\\d]+))$","entityidfiltertype":"regex","outputinitially":false,"state_type":"str","haltifstate":"on","halt_if_type":"str","halt_if_compare":"is","outputs":2,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":210,"y":1180,"wires":[["2ca9d62e5d413bc9"],[]]},{"id":"2ca9d62e5d413bc9","type":"delay","z":"55899e41967f3b1e","name":"","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"outputs":1,"x":660,"y":1300,"wires":[["5a8f95348a5d32f2","603c0e81d96ba21e"]]},{"id":"5a8f95348a5d32f2","type":"delay","z":"55899e41967f3b1e","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"10","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":true,"outputs":1,"x":860,"y":1240,"wires":[["603c0e81d96ba21e"]]},{"id":"5ae0aaca04ece0d9","type":"comment","z":"55899e41967f3b1e","name":"Note about delay and limits","info":"First, the flow sends a command to update the lights immediately when the first state change is detected. The flow then drops any subsequently received messages over the next 5 seconds in order to avoid sending duplicate commands for the same state change action. To ensure that all lights were updated, at the end of the 5 seconds the command is sent again.\n\nThis is necessary because a state change will be detected for each bulb in a circadian-supported room, as well as for the room as a whole. As such, turning on the \"kitchen lights\" would trigger state changes for each of the bulbs in that room as well as the room itself.","x":670,"y":1200,"wires":[]},{"id":"975bccde4f7e497a","type":"debug","z":"55899e41967f3b1e","d":true,"name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":810,"y":540,"wires":[]},{"id":"baaeb827751146e3","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Color Temp Sunrise/Sunset (K)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_color_temperature_sunrisesunset","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1560,"wires":[["a0a020cdf87ca98f"]]},{"id":"d17ea291f054b4a1","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Color Temp Max (K)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_color_temperature_max","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":1600,"wires":[["a0a020cdf87ca98f"]]},{"id":"9beb70392ac6f1e2","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Color Temp Min (K)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_color_temperature_min","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":180,"y":1640,"wires":[["a0a020cdf87ca98f"]]},{"id":"36b6ae4e4a0ec529","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Brightness Sunrise/Sunset (%)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_brightness_sunrisesunset","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":220,"y":1680,"wires":[["a0a020cdf87ca98f"]]},{"id":"0ba1a636754db19c","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Brightness Max (%)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_brightness_max","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":190,"y":1720,"wires":[["a0a020cdf87ca98f"]]},{"id":"45dc2b20a31d6c2e","type":"server-state-changed","z":"55899e41967f3b1e","name":"STATE CHANGE: Brightness Min (%)","server":"2d07711d.f8c33e","version":4,"exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"entityidfilter":"input_number.circadian_lighting_brightness_min","entityidfiltertype":"exact","outputinitially":false,"state_type":"str","haltifstate":"","halt_if_type":"str","halt_if_compare":"is","outputs":1,"output_only_on_state_change":true,"for":"0","forType":"num","forUnits":"minutes","ignorePrevStateNull":false,"ignorePrevStateUnknown":false,"ignorePrevStateUnavailable":false,"ignoreCurrentStateUnknown":false,"ignoreCurrentStateUnavailable":false,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"entityState"},{"property":"data","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"","valueType":"triggerId"}],"x":180,"y":1760,"wires":[["a0a020cdf87ca98f"]]},{"id":"18592398311926b0","type":"link in","z":"55899e41967f3b1e","name":"TRIGGER: Update Regressions","links":["37631b5b780cf1e0","cdeb32f3.a16c"],"x":190,"y":260,"wires":[["ddebd65c20b28cda"]],"l":true},{"id":"37631b5b780cf1e0","type":"link out","z":"55899e41967f3b1e","name":"TRIGGER: Update Regressions","mode":"link","links":["18592398311926b0"],"x":1110,"y":1680,"wires":[],"l":true},{"id":"a0a020cdf87ca98f","type":"trigger","z":"55899e41967f3b1e","name":"","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"10","extend":true,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":2,"x":650,"y":1680,"wires":[[],["37631b5b780cf1e0","2ca9d62e5d413bc9"]],"outputLabels":["Immediate Call","Delayed Call"]},{"id":"0e922124d5f131aa","type":"inject","z":"55899e41967f3b1e","name":"FORCE: Update Regressions Reporting","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":210,"y":340,"wires":[["f661538d28e1579e"]]},{"id":"ef88db19092dea48","type":"comment","z":"55899e41967f3b1e","name":"Circadian Lighting v2","info":"","x":120,"y":40,"wires":[]},{"id":"415e9b8a1d4f8cec","type":"debug","z":"55899e41967f3b1e","d":true,"name":"Debug Calculated Circadian Values","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1180,"y":540,"wires":[]},{"id":"271b0d1343ef534c","type":"debug","z":"55899e41967f3b1e","d":true,"name":"Debug Circadian Regressions","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1110,"y":380,"wires":[]},{"id":"770a703c557d4fda","type":"comment","z":"55899e41967f3b1e","name":"Circadian Switches State Change Subscriptions","info":"","x":220,"y":1140,"wires":[]},{"id":"600e89d3b9a7bb75","type":"comment","z":"55899e41967f3b1e","name":"Circadian Settings State Change Subscriptions","info":"","x":219,"y":1521,"wires":[]},{"id":"b479af2f19ea03f7","type":"comment","z":"55899e41967f3b1e","name":"Test Flow for Checking Circadian Value Calculation Results","info":"","x":250,"y":960,"wires":[]},{"id":"129889d9178be328","type":"comment","z":"55899e41967f3b1e","name":"InfluxDB Data Reporting","info":"","x":150,"y":700,"wires":[]},{"id":"5ee0e4f73f0cb2b5","type":"comment","z":"55899e41967f3b1e","name":"Regression Calculations","info":"","x":150,"y":140,"wires":[]},{"id":"723e1f1ac038589a","type":"comment","z":"55899e41967f3b1e","name":"Light Values Calculations","info":"","x":150,"y":440,"wires":[]},{"id":"649a27d54830e80b","type":"credentials","z":"55899e41967f3b1e","name":"Weather API Key","props":[{"value":"weather_api_key","type":"msg"}],"x":650,"y":180,"wires":[["5c8fd85f59ef253d"]]},{"id":"5c8fd85f59ef253d","type":"function","z":"55899e41967f3b1e","name":"Build Current Weather URL","func":"var weatherUrl = \"https://api.tomorrow.io/v4/weather/forecast?location=19355&timesteps=1d&units=imperial\";\nmsg.url = `${weatherUrl}&apikey=${msg.weather_api_key}`;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":180,"wires":[["fee75244bb34d406"]]},{"id":"9d464bfd29f2a605","type":"function","z":"55899e41967f3b1e","name":"Build Historical Weather URL","func":"var weatherUrl = \"https://api.tomorrow.io/v4/weather/history/recent?location=19355&timesteps=1d&units=imperial\";\nmsg.url = `${weatherUrl}&apikey=${msg.weather_api_key}`;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":240,"wires":[["4bff2fa67e597461"]]},{"id":"e397eba095d8e51d","type":"debug","z":"55899e41967f3b1e","d":true,"name":"Debug Weather API Responses","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1310,"y":240,"wires":[]},{"id":"2d07711d.f8c33e","type":"server","name":"Home Assistant","version":4,"addon":true,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true,"heartbeat":false,"heartbeatInterval":30,"areaSelector":"friendlyName","deviceSelector":"friendlyName","entitySelector":"friendlyName","statusSeparator":"at: ","statusYear":"hidden","statusMonth":"short","statusDay":"numeric","statusHourCycle":"h23","statusTimeFormat":"h:m"}]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment