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.

385 lines (348 loc) 15.1 kB
<script type="text/javascript"> RED.nodes.registerType('light-scheduler', { category: 'function', color:"#F8B1FF", defaults: { settings: {value: "", type: "light-scheduler-settings"}, events: {value:"[]"}, topic: {value:""}, name: {value:""}, onPayload: {value:"ON", validate: RED.validators.typedInput("onPayloadType")}, onPayloadType: {value:"str"}, offPayload: {value:"OFF", validate: RED.validators.typedInput("offPayloadType")}, offPayloadType: {value:"str"}, onlyWhenDark: {value: true}, scheduleRndMax: {value: 0}, sunElevationThreshold: {value: 6, required: true, validate: RED.validators.number()}, sunShowElevationInStatus: {value: false}, outputfreq: {value: "output.statechange.startup"} }, inputs: 1, outputs: 1, icon: 'cal.png', paletteLabel: 'Light Scheduler', label: function(){ return this.name?this.name:'Light Scheduler' }, inputLabels: ['Control Input'], outputLabels: ['Schedule Output'], 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-input-outputfreq").val(node.outputfreq ? node.outputfreq : 'output.statechange.startup'); 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); } }); }, }); if(node.onPayloadType == null) { node.onPayloadType = "str"; } $("#node-input-onPayloadType").val(node.onPayloadType); $("#node-input-onPayload").typedInput({ default: 'str', typeField: $("#node-input-onPayloadType"), types:['str','num','bool','json'] }); if(node.offPayloadType == null) { node.offPayloadType = "str"; } $("#node-input-offPayloadType").val(node.offPayloadType); $("#node-input-offPayload").typedInput({ default: 'str', typeField: $("#node-input-offPayloadType"), types:['str','num','bool','json'] }); } $.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"> <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 = "On Payload"</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>Output</h5> <div class="form-row"> <label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label> <input type="text" id="node-input-topic" placeholder="Topic"></input> </div> <div class="form-row"> <label for="node-input-onPayload"><i class="fa fa-envelope"></i> On Payload</label> <input type="text" id="node-input-onPayload" placeholder="ON"></input> <input type="hidden" id="node-input-onPayloadType"></input> </div> <div class="form-row"> <label for="node-input-offPayload"><i class="fa fa-envelope"></i> Off Payload</label> <input type="text" id="node-input-offPayload" placeholder="OFF"></input> <input type="hidden" id="node-input-offPayloadType"></input> </div> <div class="form-row"> <label for="node-input-outputfreq"><i class="fa fa-play"></i> Output </label> <select id="node-input-outputfreq"> <option value="output.statechange">when state changes</option> <option value="output.statechange.startup">when state changes + startup</option> <option value="output.minutely">minutely</option> </select> </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"> <p>A node to control light based on a schedule and the sun position.</p> <p>The marked area in the weekly schedule correlates to the "On Payload" and the non marked area to the "Off Payload". The Dusk / Dawn settings overrides the schedule so that the sun position will force the schedule "Off" during daylight.</p> <p>For more information on planned features and changes please <a href="https://github.com/niklaswall/node-red-contrib-light-scheduler#planned-features--changes">read here</a>.</p> <h3>Inputs</h3> <dl class="message-properties"> <dt>payload <span class="property-type">string</span></dt> <dd> <p>The <code>msg.payload</code> will be used to set the override-mode of the light scheduler.</p> <p>Valid overrides: <ul> <li>auto - Removes the override and act according to configuration.</li> <li>stop - Postpone all output.</li> <li>on / 1 - Forces output to ON state.</li> <li>off / 0 - Forces output to OFF state.</li> <li>light-only - Ignores the schedule and turns ON if it is considered dark.</li> <li>schedule-only - Ignores the light level and turns ON according to schedule.</li> <li>trigger - Force output according to the ongoing settings.</li> </ul> <i>Please Note:</i> The override is runtime only and will revert to auto during deploy / restart. </p> </dd> </dl> <h3>Output</h3> <dl class="message-properties"> <dt>payload</dt> <dd>The payload will be set according to the settings and will output either the ON or OFF payload depending on the set parameters.</dd> </dl> The output setting can be either of: <ul> <li>"when state changes" - Will only output a <code>msg</code> when the state changes. The state is evaluated on a minutely basis, so it can take up to a minute after deploy of a new configuration before the output it triggered.</li> <li>"when state changes + startup" - Will only output a <code>msg</code> when the state changes and directly after a deploy and a restart of node-red.</li> <li>"minutely" - Will only output a <code>msg</code> on a minutely basis even if the state have not changed.</li> </ul> <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 go "ON".</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>