73 Commits

Author SHA1 Message Date
ba7af1addf Use early night mode instead of evening, lights were triggering early
home_automation/HA-NerdFlows#32
2025-05-29 17:30:44 -04:00
054b498f51 Rework main processing node for sunset lighting flow
home_automation/HA-NerdFlows#32
2025-05-27 21:25:56 -04:00
c99f125289 Add sunset lights triggered boolean to the booleanOff variable
home_automation/HA-NerdFlows#32
2025-05-27 21:25:42 -04:00
9f415eb232 Rework light level filter node
home_automation/HA-NerdFlows#32
2025-05-27 21:25:18 -04:00
3a97da1b6d Function to convert station pressure to SLP 2025-05-24 20:16:07 -04:00
d764f99977 Function to set variables for rain window alert in Node-RED
home_automation/Home-Assistant-Configs#247
2025-05-23 21:35:42 -04:00
0980c3b1ec I'm a moron who should not be allowed near mathematical operations 2025-05-16 04:09:24 -04:00
86730c48ef Use outdoor light level to determine whether porch light is needed 2025-05-15 04:06:29 -04:00
4029a7992f Kallen bedroom motion lighting fix, for when master bedroom sleeping on 2025-04-30 15:42:25 -04:00
d4bf2e3f23 Facilitate on-the-fly temperature changes in climate watchdog 2025-04-30 15:38:31 -04:00
5d8f69e4f4 Emma bedroom climate watchdog
home_automation/HA-NerdFlows#31
2025-04-24 01:28:39 -04:00
0b64c61cd1 Change name of eco variable in flow context 2025-04-24 01:27:17 -04:00
b6c0ac0713 Add defaults for some variables 2025-04-24 01:27:16 -04:00
bff29b48e9 Handle display screens where needed 2025-04-24 01:27:15 -04:00
bc1ed66b56 Use peopleIDs constant in Emma's climate processing function 2025-04-24 01:27:14 -04:00
fefc52bd01 Standardize some entity constants naming 2025-04-24 01:27:13 -04:00
b626b5b5e8 Master bedroom meltdown center rate-limiting
home_automation/Home-Assistant-Configs#237
2025-04-24 01:27:12 -04:00
408b85bf6a Emma climate flow rate-limiting
home_automation/Home-Assistant-Configs#237
2025-04-24 01:26:59 -04:00
ba56bd08f4 Shower mode nodes rewritten to handle rate-limiting
home_automation/Home-Assistant-Configs#237
2025-04-23 21:54:35 -04:00
b0870578ef Change to switch for eco mode in master bedroom aircon
home_automation/Home-Assistant-Configs#237
2025-04-23 21:18:54 -04:00
da63c34b3d Beginnings of Emma's climate watchdog project
home_automation/HA-NerdFlows#31
2025-04-19 04:03:47 -04:00
ae17ad6969 Remember kids, ONE fucked up dot can ruin everything
home_automation/Home-Assistant-Configs#237
2025-04-19 01:18:32 -04:00
7a957572c8 Change entity ID constants from strings to arrays
home_automation/Home-Assistant-Configs#237
2025-04-18 18:20:10 -04:00
f15aec8e2d Fix log spam when wife's work schedule is not in the calendar 2025-04-16 00:59:27 -04:00
6207e38806 Possible prevention of Kallen's white noise being turned off at 11pm
home_automation/Home-Assistant-Configs#233
2025-04-02 15:54:19 -04:00
8a1f727595 Add date/time to general weather alerts function node status 2025-03-29 23:25:38 -04:00
31b543be65 Rewrite general weather alerts filter 2025-03-29 22:42:27 -04:00
d93d5bd1df Forgot to include topic for all alerts in an area 2025-03-29 22:42:15 -04:00
39c8ed5c9d Rewrite user conversion function 2025-03-29 22:07:06 -04:00
15f1d4970c Updated .gitignore 2025-03-29 21:34:01 -04:00
69099d1d0e Play Emma's door alert on all Echo Dots unless told otherwise 2025-03-29 15:50:12 -04:00
bac4c67530 Add "Alexa Everywhere" to speech engine subflow 2025-03-29 15:45:35 -04:00
114d5884c8 Make Emma's climate flow compatible with white noise 2025-03-29 02:05:39 -04:00
8c1d10f3ef Master bedroom climate flow entity constants 2025-03-29 01:33:27 -04:00
e2c609fc3b Move master bedroom last mode context to object 2025-03-29 01:28:52 -04:00
a585d45606 Move master bedroom aircon installed context to object 2025-03-29 01:22:36 -04:00
c7471fd45b Turn aircon entity ID into a constant 2025-03-29 01:10:04 -04:00
02ce030fe7 Functions code for Emma's bedroom climate flow 2025-03-28 21:35:05 -04:00
71db65fca2 Idiotic typo 2025-03-28 20:21:45 -04:00
3ef08eebf9 Rename last mode context to be room specific 2025-03-28 20:16:25 -04:00
e87275baee Use better templating for node status in master bedroom climate flow 2025-03-26 15:12:01 -04:00
ad6a3db18c Maybe I mean it this time? Heh... 2025-03-26 15:01:06 -04:00
9620f4b4f7 Sleep switch off will cancel Emma's door alert 2025-03-26 14:52:34 -04:00
3574445be0 Fix Emma's door alert not checking if she's sleeping
Also added logging to try and nail down any future issues
2025-03-24 18:34:29 -04:00
1b1048aecd Function code for Emma's door alert 2025-03-24 05:17:02 -04:00
456ec217f2 Rename floor-based folders 2025-03-24 03:09:52 -04:00
6b29b8622f Change temp source to global context for basement heat logic 2025-03-23 14:35:57 -04:00
9d5252c7e4 Improve node status on Tina Work node in presence flow 2025-03-23 13:44:51 -04:00
8a1e0558e8 Small tweak to Work Tomorrow sensor 2025-03-23 04:56:46 -04:00
61c79ca0c0 Improve templating in basement climate flows 2025-03-23 04:42:41 -04:00
09988333f7 Small adjustment in Kallen's timer flow 2025-03-23 04:36:57 -04:00
0fc7fdb31b Improve templating in Kallen's climate flows 2025-03-23 04:32:32 -04:00
10be0213dc Improve templating in master bedroom climate flows 2025-03-23 04:30:10 -04:00
013b482f18 Relocated outdoor temperatures in global context 2025-03-23 01:06:15 -04:00
8dd450940d Cleanup speech engine subflow, and also add it here 2025-03-22 23:44:33 -04:00
bb6dd3cf3e Send notifications to my phone from issue reporter 2025-03-22 23:16:59 -04:00
b6bd149610 User ID conversion subflow 2025-03-22 23:16:16 -04:00
dcb075ed48 Improve status message on local weather alerts node 2025-03-22 23:15:29 -04:00
a1c1e23935 Fix report system to work with Gitea
home_automation/HA-NerdFlows#30
2025-03-22 19:40:34 -04:00
1a4ecc6888 Some cleanup on master bedroom climate processing function 2025-03-22 16:40:49 -04:00
b154903c4d Function to turn off first floor lights when Kallen leaves for school
home_automation/HA-NerdFlows#29
2025-03-22 16:01:26 -04:00
0e13102cde Make folder for goodnight flow because it needs multiple functions 2025-03-22 15:42:40 -04:00
6a6b5c2ae2 Let's try this instead
home_automation/HA-NerdFlows#29
2025-03-22 15:24:24 -04:00
f8b528a9d2 Goodnight should leave first floor alone if Kallen is waiting for school
home_automation/HA-NerdFlows#29
2025-03-22 07:19:37 -04:00
8dd7771eee Remove deprecated service calls
home_automation/HA-NerdFlows#28
2025-03-21 01:28:23 -04:00
c63adaa68d Make area weather alerts reusable as a subflow 2025-03-20 00:33:31 -04:00
3c092bf519 Rename Defiance file for use as local alerts template 2025-03-20 00:04:59 -04:00
19e0e35bf4 Add watches for Defiance 2025-03-19 18:18:35 -04:00
b9a296d312 Improve filter phrases for warnings 2025-03-19 17:56:47 -04:00
503776de4a Add stats for considerable destructive t-storm, confirmed tornado 2025-03-19 17:11:17 -04:00
e37e4d7718 Clean-up severe thunderstorm and tornado alert presentation 2025-03-18 01:13:27 -04:00
2a5aa26e5c Functions for general alerts and Defiance-specific alerts 2025-03-17 01:35:05 -04:00
96c4918d76 Function for filtering weather alerts 2025-03-16 03:59:03 -04:00
54 changed files with 1730 additions and 311 deletions

3
.gitignore vendored
View File

@ -1 +1,2 @@
**.vscode
**.vscode
testing/

View File

@ -1,6 +1,6 @@
const states = global.get('homeassistant.homeAssistant.states')
const allowed = states['input_boolean.basement_studio_heat_allowed'].state
const outsideTemp = states['weather.iron_nerd_weather_station'].attributes.temperature
const outsideTemp = global.get('outdoorTemp.tempInt')
const nightMode = states['input_boolean.night_mode'].state
let power = {}
@ -20,7 +20,7 @@ if (outsideTemp > 32) {
if (allowed === 'on') {
if (power === 'on') {
msg.preset = preset
node.status({fill:'green',shape:'dot',text:'Preset ' + preset})
node.status({fill:'green',shape:'dot',text:`Preset ${preset}`})
node.send(msg,null)
} else {
node.status({fill:'red',shape:'ring',text:'Heater Shutoff'})

View File

@ -1,6 +1,6 @@
const states = global.get('homeassistant.homeAssistant.states')
const allowed = states['input_boolean.basement_studio_heat_allowed'].state
const outsideTemp = states['weather.iron_nerd_weather_station'].attributes.temperature
const outsideTemp = global.get('outdoorTemp.tempInt')
const todaysHighTemp = states['sensor.todays_high_temp'].state
const basementTemp = states['sensor.basement_studio_temperature'].state
@ -22,7 +22,7 @@ if (todaysHighTemp < 55) {
if (allowed === 'on') {
if (power === 'on') {
node.status({fill:'green',shape:'dot',text:'Heater Needed: ' + reason})
node.status({fill:'green',shape:'dot',text:`Heater Needed: ${reason}`})
node.send(msg,null)
} else {
node.status({fill:'red',shape:'ring',text:'Heater Not Needed'})

View File

@ -0,0 +1,72 @@
const states = global.get('homeassistant.homeAssistant.states')
const toggle = msg.payload
const ac = global.get('emmaBedroom.aircon.installed', "diskCon")
const lastMode = flow.get("emmaBedroom.lastMode", "diskCon")
const bedTemp = states['input_number.emma_bedroom_bedtime_temp'].state
const airconEntity = ["climate.emma_bedroom_aircon"]
let setTemp = bedTemp
let setEco = 'turn_off'
let setHvac = 'cool'
let setAcFan = 'High'
let sendHvac = {
"payload": {
"action": "climate.set_hvac_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
}
}
}
let sendTemp = {
"payload": {
"action": "climate.set_temperature",
"target": {
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
}
}
}
let sendEco = {
"payload": {
"action": `switch.${setEco}`,
"target": {
"entity_id": ["switch.emma_bedroom_aircon_eco_mode"]
},
"data": {}
}
}
let sendAcFan = {
"payload": {
"action": "climate.set_fan_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"fan_mode": setAcFan
}
}
}
let reset = {
"topic": "Reset"
}
if (toggle === 'on') {
node.status({fill:"red",shape:"dot",text:"DANGER MODE ACTIVE"})
if (ac === 'on') {
node.send([[sendHvac,sendTemp,sendAcFan,sendEco],null])
}
} else {
node.status({fill:"green",shape:"dot",text:"Danger Mode Off"})
node.send([null,reset])
}

View File

@ -0,0 +1,25 @@
const topic = msg.topic
const nightTemp = msg.nighttemp
const acMode = msg.acmode
let coolMsg = {
"payload": `Emma bedroom AC temp has been set to ${nightTemp} °F as scheduled`,
"topic": 'Emma AC Mode: Cooling'
}
let fanOnlyMsg = {
"payload": 'Mildly chilly outside, AC running fan only.',
"topic": "Emma AC Mode: Fan Only"
}
if (topic === 'emmabedroom-cooling') {
if (acMode === 'AC') {
node.status({fill:"green",shape:"dot",text:"AC Cooling"})
return coolMsg
} else if (acMode === 'Fan') {
node.status({fill:"green",shape:"dot",text:"AC Fan Only"})
return fanOnlyMsg
}
} else {
node.status({fill:"red",shape:"ring",text:"Notification not sent"})
return null
}

View File

@ -0,0 +1,423 @@
node.log("Emma Bedroom Climate: Processing Started");
// pull in the necessary information
const states = global.get('homeassistant.homeAssistant.states')
const allowed = states['input_boolean.emma_bedroom_climate_protocol'].state
const ac = global.get('emmaBedroom.aircon.installed', "diskCon")
const temp = global.get("outdoorTemp.tempStr")
const payload = msg.payload
const vacation = states['input_boolean.vacation_mode'].state
const dayTemp = states['input_number.emma_bedroom_daytime_temp'].state
const nightTemp = states['input_number.emma_bedroom_night_temp'].state
const bedTemp = states['input_number.emma_bedroom_bedtime_temp'].state
const schedMode = states['input_select.scheduled_climate_mode_emma_bedroom'].state
const sleeping = states['input_boolean.emma_sleeping'].state
const hotDay = states['input_boolean.hot_day'].state
const heatWarning = states["binary_sensor.heat_warning"].state
const earlyNight = states["binary_sensor.early_night_mode"].state
const danger = states['binary_sensor.heat_warning'].attributes.danger
const meltdown = states['input_boolean.meltdown_protocol'].state
const coolingActive = states['input_boolean.emma_bedroom_cooling_on'].state
node.log("Emma Bedroom Climate: Constants Set")
// Define reusable constants
const airconEntity = ["climate.emma_bedroom_aircon"]
const whiteNoiseEntity = ["input_boolean.white_noise_emma_bedroom"]
const validSchedModesAC = ['AC', 'Fan']
const peopleIDs = ["input_boolean.emma_awake"]
// Helper function to convert a string to title case
function convertToTitleCase(str) {
if (!str) {
return "";
}
return str.toLowerCase().replace(/\b\w/g, (s) => s.toUpperCase());
}
// init variables
let setTemp = dayTemp
let setAcFan = 'auto'
let setEco = 'turn_on'
let setHvac = 'cool'
let setCool = []
let setSleep = []
let setPeople = []
let setDisplay = 'turn_on'
let setWhiteNoise = "turn_off"
let time = []
let type = msg.type
let topic = msg.topic
let isWakeup = context.get("isWakeup")
node.log("Emma Bedroom Climate: Variables Defined")
// Sleep Switch Handling
if (type === 'sleep' && payload === 'off') {
setDisplay = 'turn_on'
setPeople = 'turn_on'
if (coolingActive === 'on') {
time = 'night'
} else {
time = 'day'
}
} else if (type === 'sleep' && payload === 'on') {
setDisplay = 'turn_off'
time = 'bedtime'
} else {
time = msg.time
}
if (topic === 'emmabedroom-wakeup') {
setSleep = 'turn_off'
}
// Day Time
if (time === 'day') {
if (type === 'auto') {
setCool = 'turn_off'
}
if (earlyNight === 'off') {
if (ac === 'on') {
if (danger === 'Extreme') {
setTemp = nightTemp
setEco = "turn_on"
setHvac = "cool"
} else if (hotDay === 'on' || heatWarning === 'on') {
setTemp = dayTemp
setEco = "turn_on"
setHvac = "cool"
} else {
setTemp = nightTemp
setEco = "turn_on"
setHvac = "off"
}
}
} else if (earlyNight === 'on') {
if (ac === 'on') {
if (danger === 'Extreme') {
setTemp === bedTemp
} else {
setTemp = nightTemp
}
if (schedMode === 'AC') {
setHvac = 'cool'
} else if (schedMode === 'fan') {
setHvac = 'fan_only'
} else {
setHvac = 'off'
}
if (hotDay === 'on') {
setEco = 'turn_off'
} else {
setEco = 'turn_on'
}
}
}
// Night Time
} else if (time === 'night') {
// If this is being run at scheduled time, turn on input_boolean.emma_bedroom_cooling_on
if (type === 'auto') {
setCool = 'turn_on'
}
// Decide temperature
if (type === 'sleep' && payload === 'off') {
setTemp = dayTemp
} else if (danger === 'Extreme') {
setTemp = bedTemp
} else {
setTemp = nightTemp
}
// Decide eco mode
if (sleeping === 'on') {
setEco = 'turn_off'
} else {
setEco = 'turn_on'
}
// Decide HVAC mode
if (schedMode === 'AC') {
setHvac = 'cool'
} else if (schedMode === 'Fan') {
setHvac = 'fan_only'
} else {
setHvac = 'off'
}
// Bed Time
} else if (time === 'bedtime') {
setPeople = 'turn_off'
if (ac === 'on') {
setTemp = bedTemp
setEco = 'turn_off'
if (schedMode === 'AC') {
setHvac = 'cool'
setDisplay = 'turn_off'
} else if (schedMode === 'Fan') {
setHvac = "fan_only"
setDisplay = 'turn_off'
} else if (schedMode === 'White Noise') {
setWhiteNoise = 'turn_on'
if (ac === 'on') {
setHvac = 'off'
}
} else {
setHvac = "off"
}
}
}
node.log("Emma Bedroom Climate: Decision Logic Complete")
// Define message payloads
let sendCool = {
"payload": {
"action": `input_boolean.${setCool}`,
"target": {
"entity_id": ["input_boolean.emma_bedroom_cooling_on"]
},
"data": {}
}
}
let sendSleep = {
"payload": {
"action": `input_boolean.${setSleep}`,
"target": {
"entity_id": ["input_boolean.emma_sleeping"]
},
"data": {}
}
}
let sendPeople = {
"payload": {
"action": `input_boolean.${setPeople}`,
"target": {
"entity_id": peopleIDs
},
"data": {}
}
}
let sendDisplay = {
"payload": {
"action": `switch.${setDisplay}`,
"target": {
"entity_id": ["switch.emma_bedroom_aircon_display"]
},
"data": {}
}
}
let sendWhiteNoise = {
"payload": {
"action": `input_boolean.${setWhiteNoise}`,
"target": {
"entity_id": whiteNoiseEntity
},
"data": {}
}
}
let notify = {
"topic": topic,
"nighttemp": nightTemp,
"schedMode": schedMode
}
let sendHvac = {
"payload": {
"action": "climate.set_hvac_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
}
}
}
let sendTemp = {
"payload": {
"action": "climate.set_temperature",
"target": {
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
}
}
}
let sendEco = {
"payload": {
"action": `switch.${setEco}`,
"target": {
"entity_id": ["switch.emma_bedroom_aircon_eco_mode"]
},
"data": {}
}
}
let sendAcFan = {
"payload": {
"action": "climate.set_fan_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"fan_mode": setAcFan
}
}
}
node.log("Emma Bedroom Climate: Message Payloads Defined")
// Send some parameters to flow context for use in other nodes
if (setHvac.length > 0) {
flow.set('emmaBedroom.airconHvacMode',setHvac,'diskCon')
}
if (setEco.length > 0) {
flow.set('emmaBedroom.airconEco',setEco,'diskCon')
}
if (setTemp.length > 0) {
flow.set('emmaBedroom.airconTargetTemp',setTemp,'diskCon')
}
if (setDisplay.length > 0) {
flow.set('emmaBedroom.airconDisplay',setDisplay,'diskCon')
}
flow.set('emmaBedroom.airconFanMode',setAcFan,'diskCon')
// Log the parameters that were chosen, for debugging purposes
node.log("----- Emma Bedroom Climate: Set Parameters -----")
node.log(`setTemp: ${setTemp}`)
node.log(`setAcFan: ${setAcFan}`)
node.log(`setEco: ${setEco}`)
node.log(`setHvac: ${setHvac}`)
node.log(`setCool: ${setCool}`)
node.log(`setSleep: ${setSleep}`)
node.log(`setPeople: ${setPeople}`)
node.log(`setDisplay: ${setDisplay}`)
node.log(`time: ${time}`)
node.log(`type: ${type}`)
node.log(`topic: ${topic}`)
node.log("----- Emma Bedroom Climate: End Parameters -----")
// If this was an automated trigger, set the cooling context for the bedroom accordingly.
if (type === 'auto' && time != 'bedtime') {
node.send([null, sendCool, null])
node.log("Emma Bedroom Climate: Cooling Context Set")
}
if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'off') {
node.log("Emma Bedroom Climate: Auto")
if (sleeping === 'on' && topic != 'emmabedroom-wakeup') {
node.status({ fill: "red", shape: "ring", text: "Blocked (sleep mode)" })
node.log("Emma Bedroom Climate: Blocked (sleep mode)")
} else {
if (topic === 'emmabedroom-cooling' && ac === 'on') {
node.status({ fill: "green", shape: "dot", text: "Cooling Schedule" })
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Auto/Cooling")
} else if (topic === 'emmabedroom-bedtime') {
node.send([null, sendPeople, null])
node.status({ fill: "green", shape: "dot", text: "Bedtime" })
node.log("Emma Bedroom Climate: Auto/Bedtime")
if (validSchedModesAC.includes(schedMode) && ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Auto/Bedtime/AC")
} else if (schedMode === 'White Noise') {
node.send([null, sendWhiteNoise, null])
node.log("Emma Bedroom Climate: Auto/Bedtime/White Noise")
}
} else if (topic === 'emmabedroom-wakeup') {
node.status({ fill: "green", shape: "dot", text: "Wakeup Schedule" })
node.log("Emma Bedroom Climate: Auto/Wakeup")
if (sleeping === 'off') {
context.set("isWakeup", false)
node.send([null, sendWhiteNoise, null])
node.log("Emma Bedroom Climate: Auto/Wakeup/Sleep Off")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Auto/Wakeup/AC On")
}
} else if (sleeping === 'on') {
context.set("isWakeup", true)
node.send([null, sendSleep, null])
node.log("Emma Bedroom Climate: Auto/Wakeup/Sleep On")
}
}
}
// Manual Responses
} else if (type === 'manual') {
node.log("Emma Bedroom Climate: Manual")
if (time === 'night') {
node.status({ fill: "blue", shape: "dot", text: "Manual Night" })
node.log("Emma Bedroom Climate: Manual/Night")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Manual/Night/AC")
}
} else if (time === 'day') {
node.status({ fill: "blue", shape: "dot", text: "Manual Day" })
node.send([null, sendWhiteNoise, null])
node.log("Emma Bedroom Climate: Manual/Day")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Manual/Day/AC")
}
} else if (time === 'bedtime') {
node.status({ fill: "blue", shape: "dot", text: "Manual Bedtime" })
node.log("Emma Bedroom Climate: Manual/Bedtime")
if (validSchedModesAC.includes(schedMode) && ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Manual/Bedtime/AC")
} else if (schedMode === 'White Noise') {
node.send([null, sendWhiteNoise, null])
node.log("Emma Bedroom Climate: Manual/Bedtime/White Noise")
}
}
context.set("isWakeup", false)
// Sleep Switch Responses
} else if (type === 'sleep') {
if (payload === 'off') {
node.send([null, [sendPeople, sendWhiteNoise], null])
}
node.log("Emma Bedroom Climate: Sleep")
if (time === 'bedtime') {
node.status({ fill: "blue", shape: "dot", text: "Sleep" })
node.send([null, [sendPeople, sendWhiteNoise], null])
node.log("Emma Bedroom Climate: Sleep/Bedtime")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log("Emma Bedroom Climate: Sleep/Bedtime/AC")
}
} else {
node.status({ fill: "blue", shape: "dot", text: "Wakeup" })
node.send([null, sendWhiteNoise, null])
node.log("Emma Bedroom Climate: Sleep/Day")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null])
node.log(`Emma Bedroom Climate: Sleep/${convertToTitleCase(time)}/AC`)
}
}
context.set("isWakeup", false)
} else if (meltdown === 'on') {
node.status({ fill: "red", shape: "ring", text: "Blocked (Meltdown Protocol)" })
node.log("Emma Bedroom Climate: Blocked (Meltdown Protocol)")
} else if (vacation === 'on') {
node.status({ fill: "red", shape: "ring", text: "Blocked (Vacation Mode)" })
node.log("Emma Bedroom Climate: Blocked (Vacation Mode)")
} else {
node.status({ fill: "red", shape: "ring", text: "Blocked (Automation Disabled)" })
node.log("Emma Bedroom Climate: Blocked (Automation Disabled)")
}
node.log("Emma Bedroom Climate: Processing Complete")

View File

@ -0,0 +1,30 @@
const lastMode = flow.get("emmaBedroom.lastMode", "diskCon")
const linkSource = msg._linkSource
let setTime = []
let setType = []
let setTopic = []
if (lastMode === 'day') {
setTime = 'day'
setType = 'manual'
setTopic = 'manual-day'
} else if (lastMode === 'night') {
setTime = 'night'
setType = 'manual'
setTopic = 'manual-night'
} else if (lastMode === 'bedtime') {
setTime = 'bedtime'
setType = 'manual'
setTopic = 'manual-bedtime'
}
let reset = {
"type": setType,
"time": setTime,
"topic": setTopic,
"_linkSource": linkSource
}
node.send(reset)
node.status({ fill: "green", shape: "dot", text: `Mode reset to ${setTopic}`})

View File

@ -0,0 +1,20 @@
const states = global.get("homeassistant.homeAssistant.states")
const sleeping = states["input_boolean.emma_sleeping"].state
const earlyNight = states["binary_sensor.early_night_mode"].state
const giveMeDarkness = states["input_boolean.give_me_darkness"].state
const nightMode = states["input_boolean.night_mode"].state
const goodnight = states["input_boolean.goodnight"].state
const coolingActive = states['input_boolean.emma_bedroom_cooling_on'].state
let lastMode = []
if (sleeping === 'on' || nightMode === 'on' || goodnight === 'on') {
lastMode = 'bedtime'
} else if (earlyNight === 'on' || giveMeDarkness === 'on' || coolingActive === 'on') {
lastMode = 'night'
} else {
lastMode = 'day'
}
flow.set("emmaBedroom.lastMode", lastMode, "diskCon")
node.status({ fill: "green", shape: "dot", text: `Last Mode Set: ${lastMode}`})

View File

@ -0,0 +1,21 @@
const topic = msg.topic
const payload = msg.payload
const lastMode = flow.get('emmaBedroom.lastMode', 'diskCon')
if (topic === 'daytemp') {
flow.set('emmaBedroom.dayTemp', payload, 'diskCon')
} else if (topic === 'nighttemp') {
flow.set("emmaBedroom.nightTemp", payload, "diskCon")
} else if (topic === 'bedtemp') {
flow.set("emmaBedroom.bedTemp", payload, "diskCon")
}
if (lastMode === 'day') {
flow.set('emmaBedroom.airconTargetTemp', flow.get('emmaBedroom.dayTemp', 'diskCon'), 'diskCon')
} else if (lastMode === 'night') {
flow.set('emmaBedroom.airconTargetTemp', flow.get('emmaBedroom.nightTemp', 'diskCon'), 'diskCon')
} else if (lastMode === 'bedtime') {
flow.set('emmaBedroom.airconTargetTemp', flow.get('emmaBedroom.bedTemp', 'diskCon'), 'diskCon')
}
node.status({fill:'green',shape:'dot',text:`Set ${topic} to ${payload}`})

View File

@ -0,0 +1,151 @@
const states = global.get('homeassistant.homeAssistant.states')
const sleeping = states['input_boolean.emma_sleeping'].state
if (sleeping !== 'on') {
node.status({fill:'red',shape:'ring',text:'Emma not sleeping, watchdog disabled'})
return null
}
const airconEntity = ['climate.emma_bedroom_aircon']
// Gather relevant attributes from the aircon entity
const airconState = msg.payload.state
const airconAttributes = msg.payload.attributes
const airconEco = airconAttributes['eco_mode']
const airconTargetTemp = airconAttributes['temperature']
const airconFanMode = airconAttributes['fan_mode']
const airconDisplay = airconAttributes['screen_display']
// Gather the last explicitly sent configuration from the context
const airconHvacCfg = flow.get('emmaBedroom.airconHvacMode', 'diskCon')
const airconEcoCfg = flow.get('emmaBedroom.airconEco', 'diskCon')
const airconTargetTempCfg = parseFloat(flow.get('emmaBedroom.airconTargetTemp', 'diskCon'))
const airconFanModeCfg = flow.get('emmaBedroom.airconFanMode', 'diskCon')
const airconDisplayCfg = flow.get('emmaBedroom.airconDisplay', 'diskCon')
// Compare the current state with the last sent configuration
let ecoMatch = {}
let displayMatch = {}
let counter = 0
let sendDisplay = {
"payload": {
"action": `switch.${airconDisplayCfg}`,
"target": {
"entity_id": ["switch.emma_bedroom_aircon_display"]
},
"data": {}
}
}
let sendHvac = {
"payload": {
"action": "climate.set_hvac_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"hvac_mode": airconHvacCfg
}
}
}
let sendTemp = {
"payload": {
"action": "climate.set_temperature",
"target": {
"entity_id": airconEntity
},
"data": {
"temperature": airconTargetTempCfg
}
}
}
let sendEco = {
"payload": {
"action": `switch.${airconEcoCfg}`,
"target": {
"entity_id": ["switch.emma_bedroom_aircon_eco_mode"]
},
"data": {}
}
}
let sendAcFan = {
"payload": {
"action": "climate.set_fan_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"fan_mode": airconFanModeCfg
}
}
}
// Fix hvac mode if mismatch
if (airconState != airconHvacCfg) {
node.log(`Emma Bedroom Aircon HVAC mode mismatch: ${airconState} vs ${airconHvacCfg}`)
node.send(sendHvac)
counter += 1
}
// Fix eco mode if mismatch
if (airconEco === false && airconEcoCfg === 'turn_off') {
ecoMatch = true
} else if (airconEco === true && airconEcoCfg === 'turn_on') {
ecoMatch = true
} else {
ecoMatch = false
}
if (ecoMatch === false) {
node.log(`Emma Bedroom Aircon Eco mode mismatch: ${airconEco} vs ${airconEcoCfg}`)
node.send(sendEco)
counter += 1
}
// Fix target temperature if mismatch
if (airconTargetTemp != airconTargetTempCfg) {
node.log(`Target temperature mismatch: ${airconTargetTemp} vs ${airconTargetTempCfg}`)
node.send(sendTemp)
counter += 1
}
// Fix fan mode if mismatch
if (airconFanMode != airconFanModeCfg) {
node.log(`Fan mode mismatch: ${airconFanMode} vs ${airconFanModeCfg}`)
node.send(sendAcFan)
counter += 1
}
// Fix display mode if mismatch
if (airconDisplay === false && airconDisplayCfg === 'turn_off') {
displayMatch = true
} else if (airconDisplay === true && airconDisplayCfg === 'turn_on') {
displayMatch = true
} else {
displayMatch = false
}
if (displayMatch === false) {
node.log(`Emma Bedroom Aircon Display mode mismatch: ${airconDisplay} vs ${airconDisplayCfg}`)
node.send(sendDisplay)
counter += 1
}
if (counter > 0) {
node.log(`Emma Bedroom Aircon: ${counter} configuration(s) fixed`)
node.status({fill:'yellow',shape:'dot',text:`${counter} config(s) fixed`})
} else {
node.status({fill:'green',shape:'dot',text:`No config changes`})
}

View File

@ -8,10 +8,15 @@ const brightness = states['switch.adaptive_lighting_kallen_bedroom'].attributes.
const fadeNight = states['input_number.wakeup_lights_fade_night'].state
const hotDay = states['input_boolean.hot_day'].state
const heatWarning = states['binary_sensor.heat_warning'].state
const fanState = states['fan.kallen_bedroom_fan'].state
const fanAvailable = fanState !== 'unavailable'
const fanSeparate = states['binary_sensor.kallen_fan_separate_schedule'].state
const schedMode = states['input_select.scheduled_climate_mode_kallen_bedroom'].state
const topic = msg.topic
const toggle = msg.toggle
const toggle = msg.toggle || ''
const roomName = 'Kallen Bedroom'
node.log("Kallen Bedroom: Constants Set")
let setFan = []
@ -19,27 +24,40 @@ let setWhiteNoise = []
let setVolume = []
let setLights = []
let setWake = []
node.log("Kallen Bedroom: Variables Defined")
node.log(`${roomName}: Variables Defined`)
if (toggle === 'off' && (hotDay === 'on' || heatWarning === 'on')) {
setFan = 'turn_on'
} else if ((toggle === 'on' || topic === 'kallen-fan') && schedMode === 'Fan') {
setFan = 'turn_on'
} else {
if (schedMode === 'White Noise') {
setFan = 'turn_off'
} else {
if (fanAvailable) {
if (toggle === 'off' && (hotDay === 'on' || heatWarning === 'on')) {
setFan = 'turn_on'
} else if ((toggle === 'on' || topic === 'kallen-fan') && schedMode === 'Fan') {
setFan = 'turn_on'
} else {
setFan = 'turn_off'
}
} else {
if (schedMode === 'Fan') {
node.log(`${roomName}: Fan is unavailable, forcing White Noise mode`)
}
setFan = 'turn_off'
}
}
if (schedMode === 'White Noise' && toggle === 'on') {
if ((schedMode === 'White Noise' && toggle === 'on') || (schedMode === 'Fan' && !fanAvailable)) {
setWhiteNoise = 'turn_on'
} else if (topic === 'kallen-fan' && !fanAvailable && schedMode !== 'N/A') {
setWhiteNoise = 'turn_on'
} else {
setWhiteNoise = 'turn_off'
}
if (toggle === 'on') {
if (topic === 'boolean' && toggle === 'on') {
setVolume = parseFloat(nightVolume)
setLights = 'turn_on'
setWake = 'turn_off'
} else {
} else if (topic === 'boolean' && toggle === 'off') {
setVolume = parseFloat(dayVolume)
setWake = 'turn_on'
}
@ -49,12 +67,11 @@ let fadeFinal = Math.round(fadeMult)
let brtFinal = Math.round(brightness)
node.log("Kallen Bedroom: Decision Logic Complete")
node.log(`${roomName}: Decision Logic Complete`)
let sendFan = {
"payload": {
"domain": "fan",
"service": setFan,
"action": `fan.${setFan}`,
"target": {
"entity_id": ["fan.kallen_bedroom_fan"]
},
@ -64,8 +81,7 @@ let sendFan = {
let sendWhiteNoise = {
"payload": {
"domain": "input_boolean",
"service": setWhiteNoise,
"action": `input_boolean.${setWhiteNoise}`,
"target": {
"entity_id": ["input_boolean.white_noise_kallen_bedroom"]
},
@ -75,8 +91,7 @@ let sendWhiteNoise = {
let sendVolume = {
"payload": {
"domain": "media_player",
"service": "volume_set",
"action": "media_player.volume_set",
"target": {
"entity_id": ["media_player.kallen_bedroom_google_speaker"]
},
@ -88,8 +103,7 @@ let sendVolume = {
let sendLights = {
"payload": {
"domain": "light",
"service": setLights,
"action": `light.${setLights}`,
"target": {
"entity_id": ["light.kallen_bedroom_lights"]
},
@ -99,8 +113,7 @@ let sendLights = {
let sendWake = {
"payload": {
"domain": "input_boolean",
"service": setWake,
"action": `input_boolean.${setWake}`,
"target": {
"entity_id": ["input_boolean.kallen_awake"]
},
@ -117,33 +130,34 @@ let sleepMsg = {
"payload": "sleep"
}
node.log("Kallen Bedroom: Message Payloads Defined")
node.log(`${roomName}: Message Payloads Defined`)
node.log("----- Kallen Bedroom: Set Parameters -----")
node.log("setFan: " + setFan)
node.log("setWhiteNoise: " + setWhiteNoise)
node.log("setVolume: " + setVolume)
node.log("setLights: " + setLights)
node.log("setWake: " + setWake)
node.log("----- Kallen Bedroom: End Parameters -----")
node.log(`----- ${roomName}: Set Parameters -----`)
node.log(`${roomName}: fanAvailable: ${fanAvailable}`)
node.log(`${roomName}: setFan: ${setFan}`)
node.log(`${roomName}: setWhiteNoise: ${setWhiteNoise}`)
node.log(`${roomName}: setVolume: ${setVolume}`)
node.log(`${roomName}: setLights: ${setLights}`)
node.log(`${roomName}: setWake: ${setWake}`)
node.log(`----- ${roomName}: End Parameters -----`)
if (vacationMode === 'off' && overnight === 'off' && kallenLoc === 'home') {
if (topic === 'kallen-fan' && fanSeparate === 'on') {
node.status({fill:"green",shape:"dot",text:"Fan"})
node.log("Kallen Bedroom: Early Fan")
node.log(`${roomName}: Early Fan`)
node.send([null,[sendFan,sendWhiteNoise],null])
} else if (toggle === 'off') {
} else if (topic === 'boolean' && toggle === 'off') {
node.status({fill:"green",shape:"dot",text:"Wakeup"})
node.log("Kallen Bedroom: Wake")
node.log(`${roomName}: Wake`)
node.send([wakeMsg,[sendFan,sendWhiteNoise,sendVolume,sendWake],null])
} else if (toggle === 'on') {
} else if (topic === 'boolean' && toggle === 'on') {
node.status({fill:"green",shape:"dot",text:"Sleep"})
node.log("Kallen Bedroom: Sleep")
node.log(`${roomName}: Sleep`)
node.send([null,[sendFan,sendWhiteNoise,sendVolume,sendLights,sendWake],sleepMsg])
}
} else {
node.status({fill:"red",shape:"ring",text:"Blocked"})
node.log("Kallen Bedroom: Flow Blocked")
node.log(`${roomName}: Flow Blocked`)
}
node.log("Kallen Bedroom: Processing Complete")
node.log(`${roomName}: Processing Complete`)

View File

@ -1,21 +1,23 @@
const states = global.get('homeassistant.homeAssistant.states')
const toggle = msg.payload
const ac = global.get('mb_aircon_installed', "diskCon")
const lastMode = flow.get("lastMode", "diskCon")
const ac = global.get('masterBedroom.aircon.installed', "diskCon")
const lastMode = flow.get("masterBedroom.lastMode", "diskCon")
const bedTemp = states['input_number.master_bedroom_bedtime_temp'].state
const airconEntity = ["climate.master_bedroom_aircon"]
const fanEntity = ["fan.master_bedroom_fan"]
let setTemp = bedTemp
let setEco = 'none'
let setEco = 'turn_off'
let setHvac = 'cool'
let setAcFan = 'High'
let setFan = 'turn_off'
let sendFan = {
"payload": {
"domain": "fan",
"service": setFan,
"action": `fan.${setFan}`,
"target": {
"entity_id": ["fan.master_bedroom_fan"]
"entity_id": fanEntity
},
"data": {}
}
@ -23,10 +25,9 @@ let sendFan = {
let sendHvac = {
"payload": {
"domain": "climate",
"service": "set_hvac_mode",
"action": "climate.set_hvac_mode",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
@ -36,10 +37,9 @@ let sendHvac = {
let sendTemp = {
"payload": {
"domain": "climate",
"service": "set_temperature",
"action": "climate.set_temperature",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
@ -49,23 +49,19 @@ let sendTemp = {
let sendEco = {
"payload": {
"domain": "climate",
"service": "set_preset_mode",
"action": `switch.${setEco}`,
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": ["switch.master_bedroom_aircon_eco_mode"]
},
"data": {
"preset_mode": setEco
}
"data": {}
}
}
let sendAcFan = {
"payload": {
"domain": "climate",
"service": "set_fan_mode",
"action": "climate.set_fan_mode",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"fan_mode": setAcFan
@ -81,7 +77,7 @@ if (toggle === 'on') {
node.status({fill:"red",shape:"dot",text:"DANGER MODE ACTIVE"})
node.send([null,sendFan,null])
if (ac === 'on') {
node.send([[sendHvac,sendTemp,sendEco,sendTemp],null,null])
node.send([[sendHvac,sendTemp,sendAcFan,sendEco],null,null])
}
} else {
node.status({fill:"green",shape:"dot",text:"Danger Mode Off"})

View File

@ -4,7 +4,7 @@ const acMode = msg.acmode
const fanMode = msg.fanmode
let coolMsg = {
"payload": 'Master bedroom AC temp has been set to ' + nightTemp + '°F as scheduled',
"payload": `Master bedroom AC temp has been set to ${nightTemp} °F as scheduled`,
"topic": 'AC Mode: Cooling'
}
let fanOnlyMsg = {

View File

@ -3,8 +3,8 @@ node.log("Master Bedroom Climate: Processing Started")
const states = global.get('homeassistant.homeAssistant.states')
const allowed = states['input_boolean.master_bedroom_climate_protocol'].state
const ac = global.get('mb_aircon_installed', "diskCon")
const temp = global.get("tempStr")
const ac = global.get('masterBedroom.aircon.installed', "diskCon")
const temp = global.get("outdoorTemp.tempStr")
const payload = msg.payload
const vacation = states['input_boolean.vacation_mode'].state
const dayTemp = states['input_number.master_bedroom_daytime_temp'].state
@ -25,16 +25,32 @@ const coolingActive = states['input_boolean.master_bedroom_cooling_on'].state
const echoDotDND = 'switch.basement_echo_dot_do_not_disturb_switch'
node.log("Master Bedroom Climate: Constants Set")
// Define some entity IDs
const airconEntity = ["climate.master_bedroom_aircon"]
const fanEntity = ["fan.master_bedroom_fan"]
const peopleIDs = ["input_boolean.tony_awake", "input_boolean.tina_awake"]
// Helper function to convert a string to title case
function convertToTitleCase(str) {
if (!str) {
return "";
}
return str.toLowerCase().replace(/\b\w/g, (s) => s.toUpperCase());
}
// init variables
let setTemp = []
let setEco = []
let setHvac = []
let setTemp = dayTemp
let setAcFan = 'auto'
let setEco = 'auto'
let setHvac = 'cool'
let setFan = []
let setCool = []
let setSleep = []
let setPeople = []
let setDisplay = []
let setDisplay = 'turn_on'
let time = []
let echoDotService = []
let setBriefing = []
@ -85,15 +101,15 @@ if (time === 'day') {
if (ac === 'on') {
if (danger === 'Extreme') {
setTemp = nightTemp
setEco = "eco"
setEco = "turn_on"
setHvac = "cool"
} else if (hotDay === 'on' || heatWarning === 'on') {
setTemp = dayTemp
setEco = "eco"
setEco = "turn_on"
setHvac = "cool"
} else {
setTemp = nightTemp
setEco = "eco"
setEco = "turn_on"
setHvac = "off"
}
}
@ -117,9 +133,9 @@ if (time === 'day') {
setHvac = 'off'
}
if (hotDay === 'on') {
setEco = 'none'
setEco = 'turn_off'
} else {
setEco = 'eco'
setEco = 'turn_on'
}
}
}
@ -139,9 +155,9 @@ if (time === 'day') {
}
// Decide eco mode
if (sleeping === 'on') {
setEco = 'none'
setEco = 'turn_off'
} else {
setEco = 'eco'
setEco = 'turn_on'
}
// Decide HVAC mode
if (acMode === 'AC') {
@ -162,11 +178,13 @@ if (time === 'day') {
setPeople = 'turn_off'
if (ac === 'on') {
setTemp = bedTemp
setEco = 'none'
setEco = 'turn_off'
if (acMode === 'AC') {
setHvac = 'cool'
setDisplay = 'turn_off'
} else if (acMode === 'Fan') {
setHvac = "fan_only"
setDisplay = 'turn_off'
} else {
setHvac = "off"
}
@ -183,10 +201,9 @@ node.log("Master Bedroom Climate: Decision Logic Complete")
let sendFan = {
"payload": {
"domain": "fan",
"service": setFan,
"action": `fan.${setFan}`,
"target": {
"entity_id": ["fan.master_bedroom_fan"]
"entity_id": fanEntity
},
"data": {}
}
@ -194,8 +211,7 @@ let sendFan = {
let sendCool = {
"payload": {
"domain": "input_boolean",
"service": setCool,
"action": `input_boolean.${setCool}`,
"target": {
"entity_id": ["input_boolean.master_bedroom_cooling_on"]
},
@ -205,8 +221,7 @@ let sendCool = {
let sendSleep = {
"payload": {
"domain": "input_boolean",
"service": setSleep,
"action": `input_boolean.${setSleep}`,
"target": {
"entity_id": ["input_boolean.master_bedroom_sleeping"]
},
@ -216,10 +231,9 @@ let sendSleep = {
let sendPeople = {
"payload": {
"domain": "input_boolean",
"service": setPeople,
"action": `input_boolean.${setPeople}`,
"target": {
"entity_id": ["input_boolean.tony_awake","input_boolean.tina_awake"]
"entity_id": peopleIDs
},
"data": {}
}
@ -227,8 +241,7 @@ let sendPeople = {
let sendDisplay = {
"payload": {
"domain": "switch",
"service": setDisplay,
"action": `switch.${setDisplay}`,
"target": {
"entity_id": ["switch.master_bedroom_aircon_display"]
},
@ -245,18 +258,16 @@ let notify = {
let sendBriefing = {
"payload": {
"domain": "script",
"service": setBriefing
"action": `script.${setBriefing}`,
},
"delay": setBriefingDelay
}
let sendHvac = {
"payload": {
"domain": "climate",
"service": "set_hvac_mode",
"action": "climate.set_hvac_mode",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
@ -266,10 +277,9 @@ let sendHvac = {
let sendTemp = {
"payload": {
"domain": "climate",
"service": "set_temperature",
"action": "climate.set_temperature",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
@ -279,34 +289,29 @@ let sendTemp = {
let sendEco = {
"payload": {
"domain": "climate",
"service": "set_preset_mode",
"action": `switch.${setEco}`,
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": ["switch.master_bedroom_aircon_eco_mode"]
},
"data": {
"preset_mode": setEco
}
"data": {}
}
}
let sendAcFan = {
"payload": {
"domain": "climate",
"service": "set_fan_mode",
"action": "climate.set_fan_mode",
"target": {
"entity_id": ["climate.master_bedroom_aircon"]
"entity_id": airconEntity
},
"data": {
"fan_mode": "auto"
"fan_mode": setAcFan
}
}
}
let sendEchoDotDND = {
"payload": {
"domain": "switch",
"service": echoDotService,
"action": `switch.${echoDotService}`,
"target": {
"entity_id": ["switch.basement_echo_dot_do_not_disturb_switch"]
},
@ -319,19 +324,20 @@ node.log("Master Bedroom Climate: Message Payloads Defined")
// Log the parameters that were chosen, for debugging purposes
node.log("----- Master Bedroom Climate: Set Parameters -----")
node.log("setTemp: " + setTemp)
node.log("setEco: " + setEco)
node.log("setHvac: " + setHvac)
node.log("setFan: " + setFan)
node.log("setCool: " + setCool)
node.log("setSleep: " + setSleep)
node.log("setPeople: " + setPeople)
node.log("setDisplay: " + setDisplay)
node.log("setBriefing: " + setBriefing)
node.log("setBriefingDelay: " + setBriefingDelay)
node.log("time: " + time)
node.log("type: " + type)
node.log("topic: " + topic)
node.log(`setTemp: ${setTemp}`)
node.log(`setAcFan: ${setAcFan}`)
node.log(`setEco: ${setEco}`)
node.log(`setHvac: ${setHvac}`)
node.log(`setFan: ${setFan}`)
node.log(`setCool: ${setCool}`)
node.log(`setSleep: ${setSleep}`)
node.log(`setPeople: ${setPeople}`)
node.log(`setDisplay: ${setDisplay}`)
node.log(`setBriefing: ${setBriefing}`)
node.log(`setBriefingDelay: ${setBriefingDelay}`)
node.log(`time: ${time}`)
node.log(`type: ${type}`)
node.log(`topic: ${topic}`)
node.log("----- Master Bedroom Climate: End Parameters -----")
// If this was an automated trigger, set the cooling context for the bedroom accordingly.
@ -350,14 +356,14 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
} else {
if (topic === 'mrbedroom-cooling' && ac === 'on') {
node.status({ fill: "green", shape: "dot", text: "Cooling Schedule" })
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null])
node.log("Master Bedroom Climate: Auto/Cooling")
} else if (topic === 'mrbedroom-bedtime') {
node.send([null, null, sendPeople, null, null])
node.status({ fill: "green", shape: "dot", text: "Bedtime" })
node.log("Master Bedroom Climate: Auto/Bedtime")
if (ac === 'on') {
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Auto/Bedtime/AC")
}
if (fanMode === 'fan') {
@ -377,7 +383,7 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
node.send([null, sendFan, null, null, null])
node.log("Master Bedroom Climate: Auto/Wakeup/Sleep Off")
if (ac === 'on') {
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Auto/Wakeup/AC On")
}
} else if (sleeping === 'on') {
@ -395,7 +401,7 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
node.send([null, sendFan, null, null, null])
node.log("Master Bedroom Climate: Manual/Night")
if (ac === 'on') {
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Manual/Night/AC")
}
} else if (time === 'day') {
@ -403,7 +409,7 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
node.send([null, sendFan, null, null, null])
node.log("Master Bedroom Climate: Manual/Day")
if (ac === 'on') {
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Manual/Day/AC")
}
} else if (time === 'bedtime') {
@ -411,7 +417,7 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
node.send([null, sendFan, null, null, null])
node.log("Master Bedroom Climate: Manual/Bedtime")
if (ac === 'on') {
node.send([[sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Manual/Bedtime/AC")
}
}
@ -422,33 +428,25 @@ if (type === 'auto' && allowed === 'on' && meltdown === 'off' && vacation === 'o
node.send([null, null, sendPeople, null, null])
}
node.log("Master Bedroom Climate: Sleep")
if (time === 'night') {
node.status({ fill: "blue", shape: "dot", text: "Wakeup (Hot Day)" })
node.send([null, sendFan, sendEchoDotDND, null, null])
node.log("Master Bedroom Climate: Sleep/Night")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendEco, sendAcFan], null, null, null])
node.log("Master Bedroom Climate: Sleep/Night/AC")
}
} else if (time === 'day') {
node.status({ fill: "blue", shape: "dot", text: "Wakeup" })
node.send([null, sendFan, sendEchoDotDND, null, null])
node.log("Master Bedroom Climate: Sleep/Day")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.log("Master Bedroom Climate: Sleep/Day/AC")
}
if (isWakeup === false) {
node.send([null, null, null, null, sendBriefing])
}
} else if (time === 'bedtime') {
if (time === 'bedtime') {
node.status({ fill: "blue", shape: "dot", text: "Sleep" })
node.send([null, sendFan, [sendEchoDotDND,sendPeople], null, null])
node.log("Master Bedroom Climate: Sleep/Bedtime")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendEco, sendAcFan], null, null, null, null])
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log("Master Bedroom Climate: Sleep/Bedtime/AC")
}
} else {
node.status({ fill: "blue", shape: "dot", text: "Wakeup" })
node.send([null, sendFan, sendEchoDotDND, null, null])
node.log("Master Bedroom Climate: Sleep/Day")
if (ac === 'on') {
node.send([[sendDisplay, sendHvac, sendTemp, sendAcFan, sendEco], null, null, null, null])
node.log(`Master Bedroom Climate: Sleep/${convertToTitleCase(time)}/AC`)
}
if (time === 'day' && isWakeup === false) {
node.send([null, null, null, null, sendBriefing])
}
}
context.set("isWakeup", false)
} else if (meltdown === 'on') {

View File

@ -1,4 +1,4 @@
const lastMode = flow.get('lastMode', "diskCon")
const lastMode = flow.get('masterBedroom.lastMode', "diskCon")
const linkSource = msg._linkSource
let setTime = []
@ -27,4 +27,4 @@ let reset = {
}
node.send(reset)
node.status({ fill: "green", shape: "dot", text: "Mode reset to " + setTopic})
node.status({ fill: "green", shape: "dot", text: `Mode reset to ${setTopic}`})

View File

@ -16,5 +16,5 @@ if (sleeping === 'on' || nightMode === 'on' || goodnight === 'on') {
lastMode = 'day'
}
flow.set("lastMode", lastMode, "diskCon")
node.status({ fill: "green", shape: "dot", text: "Last Mode Set: " + lastMode})
flow.set("masterBedroom.lastMode", lastMode, "diskCon")
node.status({ fill: "green", shape: "dot", text: `Last Mode Set: ${lastMode}`})

View File

@ -1,6 +1,6 @@
const states = global.get('homeassistant.homeAssistant.states')
const ac = states['input_boolean.master_bedroom_aircon_installed'].state
const temp = global.get('tempStr')
const ac = global.get('masterBedroom.aircon.installed', "diskCon")
const temp = global.get('outdoorTemp.tempStr')
const threshold = states['input_number.master_bedroom_aircon_run_threshold'].state
const dayTemp = states['input_number.master_bedroom_daytime_temp'].state
const nightTemp = states['input_number.master_bedroom_night_temp'].state
@ -11,31 +11,33 @@ const sleeping = states['input_boolean.master_bedroom_sleeping'].state
const hotDay = states['input_boolean.hot_day'].state
const topic = msg.topic
const airconEntity = ["climate.master_bedroom_aircon"]
let setTemp = {}
let ecoMode = {}
let setEco = {}
let duration = {}
let end = {}
let hvac = {}
let setHvac = {}
if (topic === 'toggle') {
if (hotDay === 'on' || sleeping === 'on') {
setTemp = bedTemp
ecoMode = 'none'
setEco = 'turn_off'
} else {
setTemp = nightTemp
ecoMode = 'eco'
setEco = 'turn_on'
}
} else if (topic === 'timer') {
if (currentSetTemp == bedTemp) {
setTemp = nightTemp
if (hotDay === 'on') {
ecoMode = 'none'
setEco = 'turn_off'
} else {
ecoMode = 'eco'
setEco = 'turn_on'
}
} else {
setTemp = dayTemp
ecoMode = 'eco'
setEco = 'turn_on'
}
}
@ -50,36 +52,75 @@ if (setTemp == bedTemp) {
if (setTemp == dayTemp) {
end = true
if (hotDay === 'off') {
hvac = 'off'
setHvac = 'off'
} else {
hvac = 'cool'
setHvac = 'cool'
}
} else if (setTemp != bedTemp && coolingTime === 'on') {
end = true
hvac = 'cool'
setHvac = 'cool'
} else {
end = false
hvac = 'cool'
}
let setMsg = {
"set": setTemp,
"eco": ecoMode,
"hvac": hvac,
"end": end
setHvac = 'cool'
}
let timerStart = {
"duration": duration
}
let sendHvac = {
"payload": {
"action": "climate.set_hvac_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
}
}
}
let sendTemp = {
"payload": {
"action": "climate.set_temperature",
"target": {
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
}
}
}
let sendEco = {
"payload": {
"action": `switch.${setEco}`,
"target": {
"entity_id": ["switch.master_bedroom_aircon_eco_mode"]
},
"data": {}
}
}
let sendAcFan = {
"payload": {
"action": "climate.set_fan_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"fan_mode": "auto"
}
}
}
if (ac === 'on' && temp >= threshold) {
if (sleeping === 'on' || coolingTime === 'on' || end === true) {
node.status({fill:"green",shape:"dot",text:"Finished"})
return [setMsg, null]
return [[sendHvac, sendTemp, sendAcFan, sendEco], null]
} else {
node.status({fill:"blue",shape:"dot",text:"Next Cycle"})
return [setMsg, timerStart]
return [[sendHvac, sendTemp, sendAcFan, sendEco], timerStart]
}
} else if (ac === 'off') {
node.status({fill:"red",shape:"ring",text:"A/C Not Installed"})

View File

@ -1,22 +1,61 @@
const states = global.get('homeassistant.homeAssistant.states')
const ac = states['input_boolean.master_bedroom_aircon_installed'].state
const temp = global.get('tempStr')
const ac = global.get('masterBedroom.aircon.installed', "diskCon")
const temp = global.get('outdoorTemp.tempStr')
const threshold = states['input_number.master_bedroom_aircon_run_threshold'].state
const nightTemp = states['input_number.master_bedroom_night_temp'].state
const bedTemp = states['input_number.master_bedroom_bedtime_temp'].state
const sleeping = states['input_boolean.master_bedroom_sleeping'].state
const airconEntity = ["climate.master_bedroom_aircon"]
let setTemp = []
let setHvac = "dry"
let setEco = "turn_off"
if (sleeping === 'on') {
setTemp = bedTemp
} else {
setTemp = nightTemp
}
let sendHvac = {
"payload": {
"action": "climate.set_hvac_mode",
"target": {
"entity_id": airconEntity
},
"data": {
"hvac_mode": setHvac
}
}
}
let sendTemp = {
"payload": {
"action": "climate.set_temperature",
"target": {
"entity_id": airconEntity
},
"data": {
"temperature": setTemp
}
}
}
let sendEco = {
"payload": {
"action": `switch.${setEco}`,
"target": {
"entity_id": ["switch.master_bedroom_aircon_eco_mode"]
},
"data": {}
}
}
if (ac === 'on') {
if (temp >= threshold) {
if (sleeping === 'on') {
msg.set = bedTemp
node.status({fill:"green",shape:"dot",text:"Shower Mode On: " + bedTemp})
return msg
} else {
msg.set = nightTemp
node.status({fill:"green",shape:"dot",text:"Shower Mode On: " + nightTemp})
return msg
}
node.status({fill:"green",shape:"dot",text:`Shower Mode On: ${setTemp}°F`})
node.send([[sendHvac, sendTemp, sendEco]])
} else {
node.status({fill:"red",shape:"ring",text:"Too Cold"})
return null

View File

@ -1,5 +1,6 @@
const states = global.get('homeassistant.homeAssistant.states')
const earlyNightMode = states['binary_sensor.early_night_mode'].state
const outdoorLux = parseInt(states['sensor.home_tempest_illuminance'].state)
const threshold = parseInt(states['input_number.front_porch_lux_threshold'].state)
const frontPorchToggle = states['input_boolean.front_porch_light_on'].state
const holidaymode = states['input_boolean.holiday_mode'].state
const currentholiday = states['input_select.holiday_animation'].state
@ -9,7 +10,7 @@ let holidayMsg = {
"holiday": holiday
}
if (earlyNightMode === 'on' && frontPorchToggle === 'on') {
if ((outdoorLux <= threshold) && frontPorchToggle === 'on') {
if (holidaymode === 'on') {
node.status({fill:"blue",shape:"dot",text:"Holiday On"})
node.send([holidayMsg,msg])
@ -20,7 +21,7 @@ if (earlyNightMode === 'on' && frontPorchToggle === 'on') {
} else {
if (frontPorchToggle === 'off') {
node.status({ fill: "red", shape: "ring", text: "Disabled" })
} else if (earlyNightMode === 'off') {
} else if (outdoorLux > threshold) {
node.status({ fill: "red", shape: "ring", text: "Daytime" })
}
}

View File

@ -0,0 +1,30 @@
const states = global.get('homeassistant.homeAssistant.states')
const schoolToday = states['input_boolean.kallen_school_today'].state
const kallen = states['person.kallen_stork'].state
const morning = states['binary_sensor.morning'].state
let basement = {
"payload": {
"action": "script.goodnight_in_basement"
}
}
let firstFloor = {
"payload": {
"action": "script.goodnight_on_first_floor"
}
}
let secondFloor = {
"payload": {
"action": "script.goodnight_on_second_floor"
}
}
if (schoolToday === 'on' && kallen === 'home' && morning === 'on') {
node.send([[basement,secondFloor]])
node.status({fill:'yellow',shape:'dot',text:'Kallen waiting for school bus'})
} else {
node.send([[basement,firstFloor,secondFloor]])
node.status({fill:'green',shape:'dot',text:'All scripts sent'})
}

View File

@ -0,0 +1,25 @@
const states = global.get('homeassistant.homeAssistant.states')
const schoolToday = states['input_boolean.kallen_school_today'].state
const kallen = states['person.kallen_stork'].state
const morning = states['binary_sensor.morning'].state
const masterBedroomSleeping = states['input_boolean.master_bedroom_sleeping'].state
const payload = msg.payload
let presence = {}
if (payload === 'home') {
presence = 'on'
} else if (payload != 'unavailable' && payload != 'unknown') {
presence = 'off'
}
if (schoolToday === 'on' && kallen != 'home' && morning === 'on') {
if (presence === 'off' && masterBedroomSleeping === 'on') {
node.send(msg)
node.status({fill:'green',shape:'dot',text:'Kallen left, first floor lights off'})
} else {
return null
}
} else {
return null
}

View File

@ -0,0 +1,20 @@
const statusCode = msg.statusCode
const issueNumber = msg.payload.number
const issueTitle = msg.payload.title
const url = msg.payload.html_url
const user = msg.user
let notification = {
"title": "Issue Submitted",
"message": `${user.firstName} ${user.lastName} submitted a new issue: #${issueNumber} - ${issueTitle}`,
"actions": [
{
"action": "URI",
"uri": url,
"title": "View Issue"
}
]
}
node.send(notification)
node.status({fill:'green',shape:'dot',text:`Issue #${issueNumber} notification sent`})

View File

@ -0,0 +1,33 @@
const needsReponseLabel = flow.get("needsReponseLabel", "diskCon")
const typeBugLabel = flow.get("typeBugLabel", "diskCon")
const typeEnhancementLabel = flow.get("typeEnhancementLabel", "diskCon")
const typeAdjustmentLabel = flow.get("typeAdjustmentLabel", "diskCon")
const title = msg.text
const type = msg.type
const user = msg.user
let labels = [needsReponseLabel]
let formatType = {}
if (type === 'Bug Report') {
labels.push(typeBugLabel)
formatType = "[Bug]"
} else if (type === 'New Idea') {
labels.push(typeEnhancementLabel)
formatType = "[Feature Request]"
} else if (type === 'Adjustment') {
labels.push(typeAdjustmentLabel)
formatType = "[Adjustment Request]"
}
let newTitle = `${formatType}: ${title}`
msg.payload = {
"title": newTitle,
"body": `This issue was submitted by ${user.firstName} ${user.lastName}, using the automated form on the main Home Assistant dashboard`,
"assignees":["tm24fan8"],
"labels": labels
}
node.status({fill:'green',shape:'dot',text:'API data prepped'})
return msg

View File

@ -0,0 +1,21 @@
const states = global.get('homeassistant.homeAssistant.states')
const text = states['input_text.report_system'].state
const type = states['input_select.report_type'].state
const resetText = 'Type your report here'
const resetType = 'Bug Report'
msg.text = text
msg.type = type
msg.reset_text = resetText
msg.reset_type = resetType
if (type === "New Idea") {
node.status({fill:"green",shape:"dot",text:"New Idea"})
} else if (type === "Bug Report") {
node.status({fill:"green",shape:"dot",text:"Bug Report"})
} else if (type === "Adjustment") {
node.status({fill:"green",shape:"dot",text:"Adjustment"})
}
return msg

View File

@ -0,0 +1,9 @@
const needsReponseLabel = 213
const typeBugLabel = 196
const typeEnhancementLabel = 198
const typeAdjustmentLabel = 208
flow.set("needsReponseLabel", needsReponseLabel, "diskCon")
flow.set("typeBugLabel", typeBugLabel, "diskCon")
flow.set("typeEnhancementLabel", typeEnhancementLabel, "diskCon")
flow.set("typeAdjustmentLabel", typeAdjustmentLabel, "diskCon")

View File

@ -6,7 +6,7 @@ if (msg.lights === 'on') {
node.status({fill:"green",shape:"dot",text:"Lights Controlled"})
return[null,msg]
} else {
node.status({fill:"red",shape:"ring",text:"N/A in " + msg.room})
node.status({fill:"red",shape:"ring",text:`N/A in ${msg.room}`})
return null
}
} else {

View File

@ -68,8 +68,7 @@ if (payload === 'start') {
// Prepare message payloads
let sendTimerStart = {
"payload": {
"domain": "timer",
"service": "start",
"action": "timer.start",
"target": {
"entity_id": timerEntity
},
@ -81,8 +80,7 @@ let sendTimerStart = {
let sendTimerCancel = {
"payload": {
"domain": "timer",
"service": "cancel",
"action": "timer.cancel",
"target": {
"entity_id": timerEntity
},
@ -92,8 +90,7 @@ let sendTimerCancel = {
let sendBoolFinished = {
"payload": {
"domain": "input_boolean",
"service": servFinished,
"action": "input_boolean." + servFinished,
"target": {
"entity_id": boolFinished
},
@ -103,8 +100,7 @@ let sendBoolFinished = {
let sendDateTimeFinished = {
"payload": {
"domain": "input_datetime",
"service": "set_datetime",
"action": "input_datetime.set_datetime",
"target": {
"entity_id": dateTimeEntity
},

View File

@ -5,6 +5,9 @@ const tinawork = msg.tinawork
if (tinawork === 'on' && workEndZone === 'on') {
node.status({fill:"green",shape:"dot",text:"Activated"})
return[msg,null]
} else if (tinawork === 'on' && workEndZone === 'off') {
node.status({fill:"yellow",shape:"dot",text:"Not time to leave yet"})
return null
} else if (tinawork === 'off') {
node.status({fill:"yellow",shape:"dot",text:"No Work Today"})
return null

View File

@ -0,0 +1,94 @@
const states = global.get('homeassistant.homeAssistant.states')
const masterBedroomSleeping = states['input_boolean.master_bedroom_sleeping'].state
const emmaSleep = states['input_boolean.emma_sleeping'].state
const tonyAwake = states['input_boolean.tony_awake'].state
const xiaDesktopUser = states['sensor.xia_desktop_current_username'].state
const xiaDesktopIdle = states['binary_sensor.tina_desktop_idle'].state
const tonyLocation = states['person.tony_stork'].state
const tinaLocation = states['person.christina_stork'].state
const overnight = states['binary_sensor.overnight'].state
const payload = msg.payload
const topic = msg.topic
// Set some default values
let who = 'Alexa Everywhere' // Default to playing on all Echo Dots unless told otherwise
let tinaAvailable = false
let tonyAvailable = false
let enable = false
let reason = {}
// Determine who is available to respond
if (tonyAwake === 'on' && tonyLocation === 'home') {
tonyAvailable = true
}
if (xiaDesktopUser === 'irish' && xiaDesktopIdle === 'off' && tinaLocation === 'home') {
tinaAvailable = true
}
// If both of us are still asleep, or I am asleep and she is at work, play the alert only in the master bedroom
if (tinaAvailable === false && tonyAvailable === false) {
who = 'Master Bedroom'
}
// Determine if the alert should be sent
if (emmaSleep === 'on') {
if (overnight === 'on' && masterBedroomSleeping === 'on') {
enable = true
reason = 'Overnight is on, and master bedroom is sleeping'
} else if (overnight === 'off') {
enable = true
reason = 'Overnight is off'
}
}
// Define the alert message
let alertTTS = {
"topic": who,
"payload": "Emma has opened her door, please check on her. I repeat, Emma has opened her door, please check on her. This is urgent, Emma is awake. PDS toddler warning, a large and extremely dangerous toddler is on the ground in your area.",
"type": "critical"
}
// Add some logs for debugging
node.log(`--------------- Emma Door Alert --------------`)
node.log(`Emma Door Alert - topic: ${topic}`)
node.log(`Emma Door Alert - payload: ${payload}`)
node.log(`Emma Door Alert - masterBedroomSleeping: ${masterBedroomSleeping}`)
node.log(`Emma Door Alert - emmaSleep: ${emmaSleep}`)
node.log(`Emma Door Alert - tonyAwake: ${tonyAwake}`)
node.log(`Emma Door Alert - xiaDesktopUser: ${xiaDesktopUser}`)
node.log(`Emma Door Alert - xiaDesktopIdle: ${xiaDesktopIdle}`)
node.log(`Emma Door Alert - tonyLocation: ${tonyLocation}`)
node.log(`Emma Door Alert - tinaLocation: ${tinaLocation}`)
node.log(`Emma Door Alert - overnight: ${overnight}`)
node.log(`Emma Door Alert - tonyAvailable: ${tonyAvailable}`)
node.log(`Emma Door Alert - tinaAvailable: ${tinaAvailable}`)
node.log(`Emma Door Alert - who: ${who}`)
node.log(`Emma Door Alert - enable: ${enable}`)
node.log(`Emma Door Alert - reason: ${reason}`)
node.log(`----------------------------------------------`)
// Send the alert if conditions are met
if (topic === 'door' && payload === 'on') {
if (enable === true) {
node.status({fill:'green',shape:'dot',text:`Sending alert to ${who}`})
node.send([alertTTS,msg,null])
} else {
node.status({fill:'red',shape:'ring',text:'Door Opened, but no alert sent'})
}
} else if (topic === 'timer') {
node.status({fill:'green',shape:'dot',text:'Timer Finished'})
node.send([alertTTS,msg,null])
} else if (topic === 'cancel') {
node.status({fill:'green',shape:'dot',text:'Cancel Button Pressed'})
node.send([null,null,msg])
} else if (topic === 'door' && payload === 'off') {
node.status({fill:'green',shape:'dot',text:'Door Closed'})
}

View File

@ -3,11 +3,13 @@ const lights = states['light.kallen_bedroom_lights'].state
const duration = states['input_number.kallen_bedroom_lights_off_delay'].state
const lux = parseInt(states['sensor.kallen_bedroom_illuminance'].state)
const threshold = parseInt(states['input_number.kallen_bedroom_lux_threshold'].state)
const sleeping = states['input_boolean.kallen_sleeping'].state
const kallenBedroomSleep = states['input_boolean.kallen_sleeping'].state
const masterBedroomSleep = states['input_boolean.master_bedroom_sleeping'].state
const peopleSleeping = (masterBedroomSleep === 'on' || kallenBedroomSleep === 'on')
const payload = msg.payload
const newDuration = duration * 60
if (sleeping === 'off') {
if (peopleSleeping === false) {
if (payload === 'on') {
if (lux <= threshold && lights === 'off') {
node.status({fill:'green',shape:'dot',text:'Turning lights on'})

50
subflows/speech-engine.js Normal file
View File

@ -0,0 +1,50 @@
const livingRoom = ["Living Room", "Living Room Echo Dot"]
const basementAlexa = ["Basement", "Basement Echo Dot"]
const basementGoogle = ["Basement Google", "basement_google"]
const kallenBedroom = ["Kallen Bedroom", "Kallen Bedroom Speaker"]
const emmaBedroom = ["Emma Bedroom", "Emma Bedroom Speaker"]
const kidsBedrooms = ["Kids Bedrooms"]
const allBedrooms = ["All Bedrooms"]
const masterBedroom = ["Master Bedroom", "Master Bedroom Echo Dot"]
const commonAreas = ["Common Areas"]
const alexaEverywhere = ["alexa_everywhere", "Alexa Everywhere"]
const room = msg.topic
const voice = msg.voice || global.get("jarvis_voice", "diskCon")
const type = msg.type || "normal"
if (livingRoom.includes(room)) {
msg.topic = "living_room"
} else if (basementAlexa.includes(room)) {
msg.topic = "basement"
} else if (basementGoogle.includes(room)) {
msg.topic = "basement_google"
} else if (kallenBedroom.includes(room)) {
msg.topic = "kallen_bedroom"
} else if (emmaBedroom.includes(room)) {
msg.topic = "emma_bedroom"
} else if (kidsBedrooms.includes(room)) {
msg.topic = "kids_bedrooms"
} else if (allBedrooms.includes(room)) {
msg.topic = "all_bedrooms"
} else if (masterBedroom.includes(room)) {
msg.topic = "master_bedroom"
} else if (commonAreas.includes(room)) {
msg.topic = "common"
} else if (alexaEverywhere.includes(room)) {
msg.topic = "alexa_everywhere"
} else if (room === "Everywhere") {
msg.topic = "everywhere"
}
msg.voice = voice
msg.type = type
let statusMsg = {
"status": {
"fill": "green",
"shape": "dot",
"text": `${msg.voice} speaking in ${room}`
}
}
node.send([msg,statusMsg])

View File

@ -0,0 +1,36 @@
// Get the user ID list from global context
const users = global.get("userIDList", "diskCon") || {}
// Initialize variables
let userTrigger = "none"
let statusTxt = ""
// Extract user ID from the message
if (msg.payload?.context?.user_id) {
userTrigger = msg.payload.context.user_id
} else if (msg.data?.context?.user_id) {
userTrigger = msg.data.context.user_id
}
// Validate userTrigger and set status message
if (userTrigger === "none" || !users[userTrigger]) {
statusTxt = "User not found"
node.error(`User ID '${userTrigger}' not found in user list.`)
return null // Stop execution if user is not found
} else {
const user = users[userTrigger]
statusTxt = `User: ${user.firstName}`
msg.user = { ...user } // Avoid mutating the original msg
}
// Create the status message
const statusMsg = {
status: {
fill: "green",
shape: "dot",
text: `${statusTxt} at ${new Date().toLocaleString()}`
}
}
// Send the message and status message to the output nodes
node.send([msg, statusMsg])

22
subflows/user-id-list.js Normal file
View File

@ -0,0 +1,22 @@
let userIDList = {
"c3909d27048140729f002aaef0391775": {
"shortName": "tony",
"firstName": "Tony",
"lastName": "Stork",
"hassUser": "tonystork"
},
"f387a983651a4321a7411ff8cf36f949": {
"shortName": "tina",
"firstName": "Christina",
"lastName": "Stork",
"hassUser": "christinastork"
},
"879b4c04e32841b3ad1d2763a11b4e70": {
"shortName": "kallen",
"firstName": "Kallen",
"lastName": "Stork",
"hassUser": "kallenstork"
}
}
global.set("userIDList",userIDList,"diskCon")

View File

@ -1,18 +1,27 @@
const states = global.get('homeassistant.homeAssistant.states')
const luxThresholdLivingRoom = states['input_number.living_room_lux_threshold'].state
const luxThresholdStairwell = states['input_number.stairwell_lux_threshold'].state
const timer = states['timer.sunset_lighting_timer'].state
const luxThresholdOutdoor = states['input_number.sunset_lights_outdoor_lux_threshold'].state
const earlyNight = states['binary_sensor.early_night_mode'].state
const triggered = states['input_boolean.sunset_lights_triggered'].state
const level = msg.level
const room = msg.room
if (timer === 'active') {
if (earlyNight === 'on' && triggered === 'off') {
if (room === 'living-room' && level <= luxThresholdLivingRoom) {
node.status({fill:'green',shape:'dot',text:'Sent (Living Room)'})
node.send(msg)
node.send([msg,msg])
} else if (room === 'stairwell-bottom' && level <= luxThresholdStairwell) {
node.status({fill:'green',shape:'dot',text:'Sent (Stairwell Bottom)'})
node.send(msg)
node.send([msg,msg])
} else if (room === 'outdoor' && level <= luxThresholdOutdoor) {
node.status({fill:'green',shape:'dot',text:'Sent (Outdoor)'})
node.send([msg,msg])
}
} else if (earlyNight === 'off') {
node.status({fill:'red',shape:'ring',text:'Not Evening'})
} else if (triggered === 'on') {
node.status({fill:'red',shape:'ring',text:'Already Triggered'})
} else {
node.status({fill:'red',shape:'ring',text:'Blocked'})
node.status({fill:'red',shape:'ring',text:'No Action'})
}

View File

@ -64,7 +64,8 @@ const booleanOff = [
"input_boolean.give_me_darkness",
"input_boolean.goodnight",
"input_boolean.kallen_computer_updates",
"input_boolean.night_mode"
"input_boolean.night_mode",
"input_boolean.sunset_lights_triggered"
]
//! ---------- NIGHT MODE ----------

View File

@ -11,22 +11,18 @@ const sunsetLights = states['input_boolean.sunset_lights_on'].state
const vacation = states['input_boolean.vacation_mode'].state
const upBathOcc = states['binary_sensor.upstairs_bathroom_occupied'].state
const deskLights = states['light.tina_desk_lights'].state
const luxLivingRoom = states['sensor.living_room_front_illuminance'].state
const luxThresholdLivingRoom = states['input_number.living_room_lux_threshold'].state
const luxStairwell = states['sensor.stairwell_bottom_illuminance'].state
const luxThresholdStairwell = states['input_number.stairwell_lux_threshold'].state
const adaptiveSleep = flow.get("adaptiveSleep", "diskCon")
const selScenesMain = flow.get("selScenesMain", "diskCon")
const selScenesTinaDesk = flow.get("selScenesTinaDesk", "diskCon")
const booleanOff = flow.get("booleanOff", "diskCon")
const triggered = states['input_boolean.sunset_lights_triggered'].state
const payload = msg.payload
const topic = msg.topic
node.log("Time-based Automations: Constants Set")
// Set a few important variables
let time = {}
let topic = {}
let delay = {}
let eventCall = {}
if (payload == 0) {
@ -37,17 +33,6 @@ if (payload == 0) {
eventCall = "off"
}
if (msg.topic === 'timer-finished' || msg.topic === 'manual-trigger' || msg.topic === 'lux') {
topic = msg.topic
}
if (topic === 'timer-finished' || topic === 'manual-trigger' || topic === 'lux') {
delay = 'off'
} else if (luxLivingRoom > luxThresholdLivingRoom && luxStairwell > luxThresholdStairwell) {
delay = 'on'
} else {
delay = 'off'
}
node.log("Time-based Automations: Main variables defined")
// ---------- Configuration ----------
@ -96,19 +81,8 @@ if (sunsetLights === 'off') {
sunsetStatus = "Skipped"
} else {
notifyTitle = "Sunset Lights On"
if (topic === 'timer-finished') {
notifyMsg = "Lights on because timer was finished manually"
sunsetStatus = "Manual Trigger"
} else if (topic === 'lux') {
notifyMsg = "Lights on because light level dropped below threshold"
sunsetStatus = "Light Level Trigger"
} else if (delay === 'on') {
notifyMsg = "Lights on after delay due to sufficient light"
sunsetStatus = "Delayed"
} else if (delay === 'off') {
notifyMsg = "Lights on early due to low light"
sunsetStatus = "Early"
}
notifyMsg = "It's getting dark, evening lighting is now active"
sunsetStatus = "Light Level Trigger"
}
// Holiday Settings
@ -125,10 +99,20 @@ if (holidayMode === 'on') {
node.log("Time-based Automations: Decision Logic Complete")
// ---------- Service Calls ----------
let sendTriggered = {
"payload": {
"action": "input_boolean.turn_on",
"target": {
"entity_id": ["input_boolean.sunset_lights_triggered"]
},
"data": {}
}
}
let sendLights = {
"payload": {
"domain": "light",
"service": "turn_off",
"action": "light.turn_off",
"target": {
"entity_id": lightsOff
},
@ -138,8 +122,7 @@ let sendLights = {
let sendSleepOff = {
"payload": {
"domain": "switch",
"service": "turn_off",
"action": "switch.turn_off",
"target": {
"entity_id": adaptiveSleep
},
@ -149,8 +132,7 @@ let sendSleepOff = {
let sendBooleanOff = {
"payload": {
"domain": "input_boolean",
"service": "turn_off",
"action": "input_boolean.turn_off",
"target": {
"entity_id": booleanOff
},
@ -160,8 +142,7 @@ let sendBooleanOff = {
let sendAdaptive = {
"payload": {
"domain": "switch",
"service": "turn_" + setAdaptive,
"action": "switch.turn_" + setAdaptive,
"target": {
"entity_id": switchAdaptive
},
@ -171,8 +152,7 @@ let sendAdaptive = {
let sendSceneResetMain = {
"payload": {
"domain": "input_text",
"service": "set_value",
"action": "input_text.set_value",
"target": {
"entity_id": selScenesMain
},
@ -184,8 +164,7 @@ let sendSceneResetMain = {
let sendSceneResetDesk = {
"payload": {
"domain": "input_text",
"service": "set_value",
"action": "input_text.set_value",
"target": {
"entity_id": selScenesTinaDesk
},
@ -197,8 +176,7 @@ let sendSceneResetDesk = {
let sendHoliday = {
"payload": {
"domain": "switch",
"service": "turn_" + setHoliday,
"action": "switch.turn_" + setHoliday,
"target": {
"entity_id": switchHoliday
},
@ -206,34 +184,9 @@ let sendHoliday = {
}
}
let sendTimer = {
"payload": {
"domain": "timer",
"service": "start",
"target": {
"entity_id": timerEntity
},
"data": {
"duration": timerDuration
}
}
}
let sendTimerCancel = {
"payload": {
"domain": "timer",
"service": "cancel",
"target": {
"entity_id": timerEntity
},
"data": {}
}
}
let sendNotifyPhone = {
"payload": {
"domain": "script",
"service": "text_notify",
"action": "script.text_notify",
"data": {
"who": "all",
"title": notifyTitle,
@ -246,8 +199,7 @@ let sendNotifyPhone = {
let sendNotifyTV = {
"payload": {
"domain": "script",
"service": "tv_notify",
"action": "script.tv_notify",
"data": {
"who": "all",
"title": notifyTitle,
@ -263,8 +215,7 @@ let sendNotifyTV = {
let sendFirstFloorScene = {
"payload": {
"domain": "script",
"service": "evening_on_first_floor",
"action": "script.evening_on_first_floor",
"data": {
"sunset_lights": 1
}
@ -273,8 +224,7 @@ let sendFirstFloorScene = {
let sendSecondFloorScene = {
"payload": {
"domain": "script",
"service": "evening_on_second_floor",
"action": "script.evening_on_second_floor",
"data": {
"sunset_lights": 1
}
@ -283,8 +233,7 @@ let sendSecondFloorScene = {
let sendDeskScene = {
"payload": {
"domain": "input_select",
"service": "select_option",
"action": "input_select.select_option",
"target": {
"entity_id": ["input_select.tina_desk_scenes"]
},
@ -312,11 +261,6 @@ node.log("lightsOff: " + lightsOff)
node.log("holidayMode: " + holidayMode)
node.log("eventCall: " + eventCall)
if (time === 'night') {
node.log("luxLivingRoom: " + luxLivingRoom)
node.log("luxThresholdLivingRoom: " + luxThresholdLivingRoom)
node.log("luxStairwell: " + luxStairwell)
node.log("luxThresholdStairwell: " + luxThresholdStairwell)
node.log("delay: " + delay)
node.log("sunsetStatus: " + sunsetStatus)
}
if (holidayMode === 'on') {
@ -338,18 +282,13 @@ if (vacation === 'off') {
}
}, 5000)
} else if (time === "night") {
node.status({fill:"green",shape:"dot",text:"Sunset Flow"})
node.send([null,sendAdaptive,null,null])
if (holidayMode === 'on') {
setTimeout(() => {
node.send([null,sendHoliday,null,null])
}, 1000)
}
if (delay === 'on') {
node.send([null,sendTimer,null,null])
} else {
if (topic === 'lux') {
node.send([null,sendTimerCancel,null,null])
if (triggered === 'off') {
node.status({fill:"green",shape:"dot",text:"Sunset Flow"})
node.send([null,[sendAdaptive,sendTriggered],null,null])
if (holidayMode === 'on') {
setTimeout(() => {
node.send([null,sendHoliday,null,null])
}, 1000)
}
node.send([null,null,[sendFirstFloorScene,sendSecondFloorScene],sendExtraLights])
if (deskLights === 'on') {
@ -358,6 +297,8 @@ if (vacation === 'off') {
if (topic != 'manual-trigger') {
node.send([[sendNotifyPhone,sendNotifyTV],null,null,null])
}
} else {
node.status({fill:'green',shape:'dot',text:'Sunset Flow (Already Triggered)'})
}
}
}

View File

@ -3,21 +3,39 @@ let today = msg.today
let number = {}
let work_tomorrow = {}
if (tomorrow > 0) {
work_tomorrow = "true"
if (today == 0) {
number = 0
} else {
number = 1
}
} else {
// Check if calendar data exists
if (!msg.payload || msg.payload.length === 0) {
msg.payload = [{
date: "No Data",
eventStart: "No Data",
eventEnd: "No Data",
summary: "No Data",
location: "No Data",
isRecurring: "No Data",
allDay: "No Data",
calendarName: "No Data",
countdown: "No Data",
uid: { date: "No Data" }
}]
work_tomorrow = "false"
number = 0
} else {
if (tomorrow > 0) {
work_tomorrow = "true"
if (today == 0) {
number = 0
} else {
number = 1
}
} else {
work_tomorrow = "false"
number = 0
}
}
msg.work_tomorrow = work_tomorrow
node.status({fill:"green",shape:"dot",text:"Number " + number})
node.status({fill:"green",shape:"dot",text:`Number ${number}`})
if (number == 0) {
node.send([msg,null])

69
weather/alerts_filter.js Normal file
View File

@ -0,0 +1,69 @@
const severeWarningEvents = ["Severe Thunderstorm Warning", "Destructive Severe Thunderstorm Warning", "Considerable Destructive Severe Thunderstorm Warning"]
const tornadoWarningEvents = ["Tornado Warning", "Radar Indicated Tornado Warning", "Confirmed Tornado Warning", "Tornado Emergency"]
let alerts = msg.payload
// Helper function to safely access nested properties
const getNestedProperty = (obj, path, defaultValue = undefined) => {
return path.reduce((acc, key) => (acc && acc[key] !== undefined ? acc[key] : defaultValue), obj)
}
// Filter function
const filterAlerts = (alerts, condition) => {
return alerts.filter(alert => {
try {
return condition(alert)
} catch (e) {
node.warn(`Error processing alert: ${e.message}`)
return false
}
})
}
// Filtering logic
const ts = filterAlerts(alerts, alert => {
const event = getNestedProperty(alert, ['raw', 'properties', 'event'])
return severeWarningEvents.includes(event)
})
const tstp = filterAlerts(alerts, alert => {
const event = getNestedProperty(alert, ['raw', 'properties', 'event'])
const tornadoDetection = getNestedProperty(alert, ['raw', 'properties', 'parameters', 'tornadoDetection'], [])
return severeWarningEvents.includes(event) && tornadoDetection.includes("POSSIBLE")
})
const cdst = filterAlerts(alerts, alert => {
const event = getNestedProperty(alert, ['raw', 'properties', 'event'])
return event === "Considerable Destructive Severe Thunderstorm Warning"
})
const tornado = filterAlerts(alerts, alert => {
const event = getNestedProperty(alert, ['raw', 'properties', 'event'])
return tornadoWarningEvents.includes(event)
})
const confirmed_tornado = filterAlerts(alerts, alert => {
const event = getNestedProperty(alert, ['raw', 'properties', 'event'])
return event === "Confirmed Tornado Warning"
})
// Output messages
let tstormMsg = {
"payload": {
"alerts": ts,
"count": ts.length,
"tornado_possible": tstp.length,
"considerable_destructive": cdst.length
}
}
let tornadoWarnMsg = {
"payload": {
"alerts": tornado,
"count": tornado.length,
"confirmed": confirmed_tornado.length
}
}
node.send([tstormMsg, tornadoWarnMsg])
node.status({ fill: 'green', shape: 'dot', text: `${alerts.length} alerts processed at ${new Date().toLocaleString()}`})

150
weather/local_alerts.js Normal file
View File

@ -0,0 +1,150 @@
const severeWarningEvents = ["Severe Thunderstorm Warning","Destructive Severe Thunderstorm Warning","Considerable Destructive Severe Thunderstorm Warning"]
const tornadoWarningEvents = ["Tornado Warning","Radar Indicated Tornado Warning","Confirmed Tornado Warning","Tornado Emergency"]
const tornadoWatchEvents = ["Tornado Watch"]
const severeThunderstormWatchEvents = ["Severe Thunderstorm Watch"]
const area = msg.area
const mqttTopic = "weather/alerts/" + area
const allTopic = mqttTopic + "/alerts"
const tstormTopic = mqttTopic + "/severe_thunderstorm_warning"
const tornadoWarnTopic = mqttTopic + "/tornado_warning"
const tornadoWatchTopic = mqttTopic + "/tornado_watch"
const severeThunderstormWatchTopic = mqttTopic + "/severe_thunderstorm_watch"
let alerts = msg.payload.features
let tornado_possible = false
let considerable_destructive = false
let confirmed = false
// Filter for Severe Thunderstorm Warnings
let ts = alerts.filter(function(alert) {
if (alert.properties &&
severeWarningEvents.includes(alert.properties.event)) {
return true
}
})
// Filter for Severe Thunderstorm Warnings with tornado detection
// that have a tornado possible parameter
let tstp = alerts.filter(function(alert) {
if (alert.properties &&
severeWarningEvents.includes(alert.properties.event) &&
alert.properties.parameters &&
alert.properties.parameters.tornadoDetection &&
alert.properties.parameters.tornadoDetection.length > 0) {
let tornadoPossible = alert.properties.parameters.tornadoDetection[0]
return tornadoPossible === "POSSIBLE"
}
})
if (tstp.length > 0) {
tornado_possible = true
}
// Filter for considerable destructive severe thunderstorm warnings
let cdst = alerts.filter(function(alert) {
if (alert.properties &&
alert.properties.event === "Considerable Destructive Severe Thunderstorm Warning") {
return true
}
})
if (cdst.length > 0) {
considerable_destructive = true
}
// Filter for Tornado Warnings
let tornado = alerts.filter(function(alert) {
if (alert.properties &&
tornadoWarningEvents.includes(alert.properties.event)) {
return true
}
})
let confirmed_tornado = alerts.filter(function(alert) {
if (alert.properties &&
alert.properties.event === "Confirmed Tornado Warning") {
return true
}
})
if (confirmed_tornado.length > 0) {
confirmed = true
}
// Filter for Tornado Watches
let tornado_watch = alerts.filter(function(alert) {
if (alert.properties &&
tornadoWatchEvents.includes(alert.properties.event)) {
return true
}
})
// Filter for Severe Thunderstorm Watches
let severe_thunderstorm_watch = alerts.filter(function(alert) {
if (alert.properties &&
severeThunderstormWatchEvents.includes(alert.properties.event)) {
return true
}
})
// If there are any alerts, return them
let allAlerts = {
"payload": msg.payload.features,
"topic": allTopic
}
let tstormMsg = {
"payload": {
"alerts": ts,
"count": ts.length,
"tornado_possible": tornado_possible,
"considerable_destructive": considerable_destructive
},
"topic": tstormTopic
}
let tornadoWarnMsg = {
"payload": {
"alerts": tornado,
"count": tornado.length,
"confirmed": confirmed
},
"topic": tornadoWarnTopic
}
let tornadoWatchMsg = {
"payload": {
"alerts": tornado_watch,
"count": tornado_watch.length
},
"topic": tornadoWatchTopic
}
let severeThunderstormWatchMsg = {
"payload": {
"alerts": severe_thunderstorm_watch,
"count": severe_thunderstorm_watch.length
},
"topic": severeThunderstormWatchTopic
}
// Create status message for node
let statusMsg = {
"status": {
"fill": "green",
"shape": "dot",
"text": `${alerts.length} alerts processed at ${new Date().toLocaleString()}`
}
}
// Send messages to output nodes
node.send([allAlerts,tstormMsg,tornadoWarnMsg,tornadoWatchMsg,severeThunderstormWatchMsg,statusMsg])

View File

@ -0,0 +1,17 @@
const states = global.get('homeassistant.homeAssistant.states')
const precipType = states['sensor.home_tempest_precipitation_type'].state
const precipIntensity = states['sensor.home_tempest_cloud_sensors_precipitation_intensity'].state
const typeStatesRaining = ['rain','hail','rain_hail']
const intensityStatesRaining = ['very_light','light','moderate','heavy','very_heavy','extreme']
const isTypeRaining = typeStatesRaining.includes(precipType)
const isIntensityRaining = intensityStatesRaining.includes(precipIntensity)
const isRaining = isTypeRaining || isIntensityRaining
flow.set('typeStatesRaining', typeStatesRaining)
flow.set('intensityStatesRaining', intensityStatesRaining)
flow.set('isRaining', isRaining)
return null

41
weather/station_slp.js Normal file
View File

@ -0,0 +1,41 @@
const states = global.get('homeassistant.homeAssistant.states')
const temp_F = states['sensor.home_tempest_temperature'].state
const elevation_ft = 693
// Get the pressure from the payload
let pressure_inHg = msg.payload
if (isNaN(pressure_inHg)) {
node.error("Invalid pressure value in msg.payload: " + msg.payload)
msg.payload = null
return null
}
// Convert pressure from inHg to hPa
let pressure_hPa = pressure_inHg * 33.8639
// Convert temperature to °C
let temp_C = (temp_F - 32) * (5 / 9)
// Convert elevation from feet to meters
let elevation_m = elevation_ft / 3.28084
// Apply the barometric formula
let slp = pressure_hPa * Math.pow(
1 - ((0.0065 * elevation_m) / (temp_C + (0.0065 * elevation_m) + 273.15)),
-5.257
)
// Optional: Convert back to inHg
let slp_inHg = slp / 33.8639
// Round both values
slp = Math.round(slp * 100) / 100
slp_inHg = Math.round(slp_inHg * 100) / 100
// Return the SLP value
msg.payload = slp_inHg
node.status({fill:'Success',shape:'dot',text:'green'})
return msg