UNPKG

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
<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&deg;</option> <option value="9">+9&deg;</option> <option value="8">+8&deg;</option> <option value="7">+7&deg;</option> <option value="6">+6&deg; - Golden Hour</option> <option value="5">+5&deg;</option> <option value="4">+4&deg;</option> <option value="3">+3&deg;</option> <option value="2">+2&deg;</option> <option value="1">+1&deg;</option> <option value="0">+0&deg; - Sunset / Sunrise</option> <option value="-1">-1&deg;</option> <option value="-2">-2&deg;</option> <option value="-3">-3&deg;</option> <option value="-4">-4&deg;</option> <option value="-5">-5&deg;</option> <option value="-6">-6&deg; - Civil dawn</option> <option value="-7">-7&deg;</option> <option value="-8">-8&deg;</option> <option value="-9">-9&deg;</option> <option value="-10">-10&deg;</option> <option value="-11">-11&deg;</option> <option value="-12">-12&deg; - Nautical dawn</option> <option value="-13">-13&deg;</option> <option value="-14">-14&deg;</option> <option value="-15">-15&deg;</option> <option value="-16">-16&deg;</option> <option value="-17">-17&deg;</option> <option value="-18">-18&deg; - Astronomical dawn / Nigh</option> </select> <div class="form-row"> <label>&nbsp;</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&deg; corresponds to the time when the sun is at the horizon.</p> </script>