node-red-contrib-light-scheduler
Version:
Light Scheduler is a node-red node that provides a weekly schedule, and is mainly focused on controlling light in home automation scenarios.
306 lines (276 loc) • 11.3 kB
HTML
<script type="text/javascript">
RED.nodes.registerType('light-scheduler-filter', {
category: 'function',
color: '#F8B1FF',
defaults: {
settings: {value: '', type: 'light-scheduler-settings'},
events: {value: '[]'},
name: {value: ''},
onlyWhenDark: {value: true},
scheduleRndMax: {value: 0},
sunElevationThreshold: {value: 6, required: true, validate: RED.validators.number()},
sunShowElevationInStatus: {value: false},
},
inputs: 1,
outputs: 2,
icon: 'cal.png',
paletteLabel: 'LS Filter',
label: function() {
return this.name ? this.name : 'Light Scheduler Filter'
},
inputLabels: ['Input'],
outputLabels: ['Match', 'No Match'],
oneditprepare: function() {
var node = this
node.fromMoment = function(m) {
m = moment(m)
return {
dow: m.day(),
mod: m.hours() * 60 + m.minutes(),
}
}
node.toMoment = function(o) {
var day = o.dow == 0 ? 7 : o.dow
var m = moment('2018-01-0' + day + ' 00:00:00')
m.hours(Math.floor(o.mod / 60))
m.minutes(o.mod % 60)
return m
}
node.nextId = (function() {
var id = 0
return function() {
return id++
}
})()
var setup = function(node) {
$('#node-input-duskdawn').val(node.onlyWhenDark ? 'duskdawn.goldenhour' : 'duskdawn.none')
node.configuredEvents = []
JSON.parse(node.events).forEach(function(e) {
if (!(e.start != undefined && e.start.dow != undefined && e.start.mod != undefined)) return
if (!(e.end != undefined && e.end.dow != undefined && e.end.mod != undefined)) return
// Workaround for issue #64, for events that cross over to the next day.
// This should be solved by creating a new event the next day.
// https://github.com/niklaswall/node-red-contrib-light-scheduler/issues/64
if(e.start.mod > e.end.mod)
e.end.mod = 0;
eventData = {
id: node.nextId(),
title: '',
start: node.toMoment(e.start),
end: node.toMoment(e.end),
stick: true,
}
if (eventData.start.isAfter(eventData.end)) {
// start is a sunday (which is moved from date 0 to 7)
eventData.end.add(7, 'days')
}
node.configuredEvents.push(eventData)
})
$('#calendar').fullCalendar({
timezone: false,
defaultDate: moment('2018-01-01 00:00:00'),
header: false,
footer: false,
firstDay: 1,
allDaySlot: false,
defaultView: 'agendaWeek',
timeFormat: 'H:mm',
slotLabelFormat: 'H:mm',
columnFormat: 'ddd',
duration: '00:05:00',
snapDuration: '00:05:00',
displayEventTime: false,
selectOverlap: false,
eventOverlap: false,
selectable: true,
editable: true,
events: node.configuredEvents,
select: function(start, end) {
// Remove timezone
start = node.toMoment(node.fromMoment(start))
end = node.toMoment(node.fromMoment(end))
var eventData = {
id: node.nextId(),
title: '',
start: start,
end: end,
stick: true,
}
$('#calendar').fullCalendar('renderEvent', eventData, true)
$('#calendar').fullCalendar('unselect')
node.configuredEvents.push(eventData)
},
selectAllow: function(selectInfo) {
var start = moment(selectInfo.start)
var end = moment(selectInfo.end)
if (start.isSame(end, 'day')) {
return true
}
if (
start
.add(1, 'day')
.startOf('day')
.isSame(end, 'minute')
) {
return true // Next day but midnight
}
return false
},
eventClick: function(event, jsEvent, view) {
$('#calendar').fullCalendar('removeEvents', event._id)
node.configuredEvents = node.configuredEvents.filter(function(e) {
return event.id != e.id
})
},
eventResize: function(event, delta, revertFunc) {
// Prevent resize outside of the current day
start = moment(event.start);
end = moment(event.end);
if(!start.isSame(end, 'day'))
if(!start.add(1, 'day').startOf('day').isSame(end, 'minute'))
revertFunc();
node.configuredEvents.forEach(function(e) {
if (event.id == e.id) {
e.end = moment(e.end).add(delta)
}
})
},
eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
// Prevent drag outside of the current day
start = moment(event.start);
end = moment(event.end);
if(!start.isSame(end, 'day'))
if(!start.add(1, 'day').startOf('day').isSame(end, 'minute'))
revertFunc();
node.configuredEvents.forEach(function(e) {
if (event.id == e.id) {
e.start = moment(e.start).add(delta)
e.end = moment(e.end).add(delta)
}
})
},
})
}
$.getScript('light-scheduler/js/moment.min.js').done(function(data, textStatus, jqxhr) {
$.getScript('light-scheduler/js/fullcalendar.min.js')
.done(function(data, textStatus, jqxhr) {
setup(node)
})
.fail(function(jqxhr, settings, exception) {
console.log('failed to load fullcalendar.min.js')
console.log(exception)
console.log(exception.stack)
})
})
},
oneditsave: function() {
var node = this
// Transform and store data
var events = node.configuredEvents.map(function(e) {
return {
start: node.fromMoment(e.start),
end: node.fromMoment(e.end),
}
})
node.events = JSON.stringify(events)
var duskdawn = $('#node-input-duskdawn').val()
node.onlyWhenDark = duskdawn == 'duskdawn.goldenhour'
delete window.calendar
},
oneditresize: function() {},
})
</script>
<script type="text/x-red" data-template-name="light-scheduler-filter">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name"></input>
</div>
<div class="form-row">
<label for="node-input-settings"><i class="fa fa-globe"></i> Globals</label>
<input type="text" id="node-input-settings"></input>
</div>
<div class="form-row">
<link rel='stylesheet' href='light-scheduler/js/fullcalendar.min.css' />
<style type="text/css">
.wc-business-hours {
font-size: 1.0em;
}
</style>
<div id="calendar" style="width: 100%; border: 1px solid grey"></div>
<div class="form-row">
<p style="padding-top: 10px"><i class="fa fa-info-circle"></i> Note: Marked schedule = Forward to first output</p>
</div>
<div class="form-row">
<label for="node-input-scheduleRndMax"><i class="fa fa-tasks"></i> Randomness (in minutes)</label>
<input type="text" id="node-input-scheduleRndMax" placeholder="0"></input>
</div>
</div>
<div style="padding-top: 15px">
<h5>Dusk / Dawn</h5>
<div class="form-row">
<label for="node-input-duskdawn"><i class="fa fa-moon-o"></i> Dusk / Dawn</label>
<select id="node-input-duskdawn">
<option value="duskdawn.goldenhour">Only when dark</option>
<option value="duskdawn.none">Schedule only</option>
</select>
</div>
<div class="form-row">
<label for="node-input-sunElevationThreshold"><i class="fa fa-sun-o"></i> Threshold </label>
<select id="node-input-sunElevationThreshold">
<option value="10">+10°</option>
<option value="9">+9°</option>
<option value="8">+8°</option>
<option value="7">+7°</option>
<option value="6">+6° - Golden Hour</option>
<option value="5">+5°</option>
<option value="4">+4°</option>
<option value="3">+3°</option>
<option value="2">+2°</option>
<option value="1">+1°</option>
<option value="0">+0° - Sunset / Sunrise</option>
<option value="-1">-1°</option>
<option value="-2">-2°</option>
<option value="-3">-3°</option>
<option value="-4">-4°</option>
<option value="-5">-5°</option>
<option value="-6">-6° - Civil dawn</option>
<option value="-7">-7°</option>
<option value="-8">-8°</option>
<option value="-9">-9°</option>
<option value="-10">-10°</option>
<option value="-11">-11°</option>
<option value="-12">-12° - Nautical dawn</option>
<option value="-13">-13°</option>
<option value="-14">-14°</option>
<option value="-15">-15°</option>
<option value="-16">-16°</option>
<option value="-17">-17°</option>
<option value="-18">-18° - Astronomical dawn / Nigh</option>
</select>
<div class="form-row">
<label> </label>
<input type="checkbox" id="node-input-sunShowElevationInStatus" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-sunShowElevationInStatus" style="width: 70%;">Show sun elevation in status.</label>
</div>
</div>
</div>
</script>
<script type="text/x-red" data-help-name="light-scheduler-filter">
<p>A node to control messages based on a schedule and the sun position.</p>
<p>The marked area in the weekly schedule correlates to the first output and the non marked area to the second output. The Dusk / Dawn settings overrides the schedule so that the sun position will force the schedule to the second output during daylight.</p>
<p>For more information on planned features and changes please <a href="https://faulty.cloud/projects/node-red-contrib-light-scheduler">read here</a>.</p>
<h3>Outputs</h3>
<ol class="node-ports">
<li>Match - Input will be forwarded to the first output when the schedule matches.
</li>
<li>No Match - Input will be forwarded to the second output when the schedule does NOT match.
</li>
</ol>
<h3>Dusk / Dawn</h3>
The Dusk / Dawn setting can be either of:
<ul>
<li>"Only when dark" - Require it "to be dark" and that the schedule allows it, to match.</li>
<li>"Schedule only" - Only control the output based on the schedule.</li>
</ul>
<p>The <i>Threshold</i> adjusts the sun angle that should be used for determining if is is dark or not. A higher value will make the output turn on when it's brighter and a lower when it's darker. 0° corresponds to the time when the sun is at the horizon.</p>
</script>