function setOutValveTo(controller_id, nextState) { var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'state.out_valve': nextState, 'time': new Date(), 'set_by': 'server' } }); if (nextState === "open") { ControllerState.update(state._id, { '$set': { 'significantEvents.lastOutValveOpen': new Date(), } }); } } function setInValveTo(controller_id, nextState) { var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'state.in_valve': nextState, 'time': new Date(), 'set_by': 'server' } }); if (nextState === "open") { ControllerState.update(state._id, { '$set': { 'significantEvents.lastInValveOpen': new Date(), } }); } } function requestNewPicture(controller_id) { var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'state.picture_requested': 'true', 'time': new Date(), 'set_by': 'server' } }); }; function openInValve(controller_id) { var state = Meteor.zoblak.server.controller_state(controller_id); var config = state.config; if (config.manualInflow) { setInValveTo(controller_id, 'opening'); } reactToSensorData(last_sensor_reading(controller_id)); } function closeInValve(controller_id) { var state = Meteor.zoblak.server.controller_state(controller_id); var config = state.config; if (config.manualInflow) { setInValveTo(controller_id, 'closing'); } reactToSensorData(last_sensor_reading(controller_id)); } function openOutValve(controller_id) { setOutValveTo(controller_id, 'opening'); setInValveTo(controller_id, 'closing'); var state = Meteor.zoblak.server.controller_state(controller_id); var config = state.config; var jobName = "Close out valve " + state.controller_id + " after draining"; console.log("Opening valve ", controller_id, jobName); SyncedCron.remove(jobName); SyncedCron.add({ name: jobName, schedule: function(parser) { var time = moment().add(config.draining_period_amount, config.draining_period_unit).toDate(); return parser.recur().on(time).fullDate(); }, job: function() { closeOutValve(controller_id); } }); console.log(Meteor.sharedFunctions); reactToSensorData(last_sensor_reading(controller_id)); } function closeOutValve(controller_id) { var state = Meteor.zoblak.server.controller_state(controller_id); var jobName = "Close out valve " + state.controller_id + " after draining"; console.log("Closing valve ", controller_id, jobName); SyncedCron.remove(jobName); setOutValveTo(controller_id, 'closing'); console.log("Finished clearing cron ", controller_id); console.log(Meteor.sharedFunctions); reactToSensorData(last_sensor_reading(controller_id)); } function clearLog() { console.log("Removing sensor data"); SensorData.remove({}); } function saveControllerConfig(controller_id, time, days, manualInflow) { var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'config.automaticTimeOfDay': time, 'config.automaticDaysOfWeek': days, 'config.manualInflow': manualInflow } }); var jobName = "automatic_" + controller_id; var times = time.split(":"); var hours = parseInt(times[0]); var minutes = parseInt(times[1]); SyncedCron.remove(jobName); SyncedCron.add({ name: jobName, schedule: function(parser) { var period = parser.recur(); for (var i = 0; i < days.length; i++) { period = period.and().on(parseInt(days[i])).dayOfWeek().on(hours).hour().on(minutes).minute(); } return period; }, job: function() { openOutValve(controller_id); } }); } function saveAlarmSettings(controller_id, minTemperature, maxTemperature, timeoutBox, timeoutPhone, smsNumbers, sensorsEnabled) { var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'config.minTemperature': parseFloat(minTemperature), 'config.maxTemperature': parseFloat(maxTemperature), 'config.timeoutBox': timeoutBox ? parseInt(timeoutBox) : null, 'config.timeoutPhone': timeoutPhone ? parseInt(timeoutPhone) : null, 'config.smsNumbers': smsNumbers, 'config.sms1': smsNumbers[0], 'config.sms2': smsNumbers[1], 'config.sms3': smsNumbers[2], 'config.sms4': smsNumbers[3], 'config.sensorsEnabled': sensorsEnabled } }); var jobName = "automatic_alarm_" + controller_id; SyncedCron.remove(jobName); SyncedCron.add({ name: jobName, schedule: function(parser) { return parser.text('every 10 seconds'); }, job: function() { reactToAlarmData(controller_id); } }); reactToAlarmData(controller_id); } // there are three states of alarm: // 1. normal ( state.alarmTriggered: false, state.alarmStopped: null ) // 2. triggered ( state.alarmTriggered: true, state.alarmStopped: null ) // 3. silenced ( state.alarmTriggered: false, state.alarmStopped: (sometime) ) reactToAlarmData = function(controller_id) { var reading = last_sensor_reading(controller_id); var state = Meteor.zoblak.server.controller_state(controller_id); var config = state.config; var minTemperature = function(temperatures) { // if it gets a lot colder than absolute zero // we will have more problems than the bug in this code if (temperatures.length <= 0) return -1000; var minimal = parseFloat(temperatures[0]); for (var i in temperatures) { if (parseFloat(temperatures[i]) < minimal) { minimal = parseFloat(temperatures[i]); } } return minimal; }; var maxTemperature = function(temperatures) { // obviously - hell is not supported in this version if (temperatures.length <= 0) return 1000; var maximal = parseFloat(temperatures[0]); for (var i in temperatures) { if (parseFloat(temperatures[i]) > maximal) { maximal = parseFloat(temperatures[i]); } } return maximal; }; var temperatures = (reading.temperatures || []).filter(function(temperature, index) { var is_on = (index in config.sensorsEnabled)?config.sensorsEnabled[index]: true; return Meteor.zoblak.shared.valid_temperature(temperature) && is_on; }); console.log("Konfiguracija: ", controller_id, config); var tooCold = config.minTemperature && (minTemperature(temperatures) < config.minTemperature); var tooHot = config.maxTemperature && (maxTemperature(temperatures) > config.maxTemperature); var minutesSinceLastBoxContact = reading.lastBoxContact ? moment(new Date()).diff(moment(reading.lastBoxContact), 'minutes') : -1; var boxSilent = config.timeoutBox && minutesSinceLastBoxContact > config.timeoutBox; var minutesSinceLastPhoneContact = state.lastPhoneContact ? moment(new Date()).diff(moment(state.lastPhoneContact), 'minutes') : -1; var phoneSilent = false; //config.timeoutPhone && minutesSinceLastPhoneContact > config.timeoutPhone; console.log("too ", tooCold, tooHot, boxSilent, phoneSilent); console.log("lpc", state.lastPhoneContact); console.log("mslpc", minutesSinceLastPhoneContact); console.log("phoneSilent", phoneSilent); if (tooCold || tooHot || boxSilent || phoneSilent) { var alarmSilenced = !!state.state.alarmStopped; if (!alarmSilenced) soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent); } else { stopTheAlarm(controller_id, true); } } function soundTheAlarm(controller_id, tooCold, tooHot, boxSilent, phoneSilent) { var state = Meteor.zoblak.server.controller_state(controller_id); var reason = { tooHot: tooHot, tooCold: tooCold, boxSilent: boxSilent, phoneSilent: phoneSilent }; console.log("Alarmiram", reason); var firstTime = {}; if (!state.state.alarmTriggered) { firstTime = { 'state.alarmStarted': new Date() } }; var smsSent = !!state.state.alarmSmsSent; var needsToSendSms = !smsSent // && phoneSilent; var sendSmsPart = needsToSendSms ? { 'state.alarmSmsSent': true } : {}; ControllerState.update(state._id, { '$set': Object.assign({ 'state.alarmTriggered': true, 'state.alarmStopped': null, 'state.alarmReasons': reason }, firstTime, sendSmsPart) }); if (needsToSendSms) { sendAlarmingSms(controller_id, reason, state.config.smsNumbers); callTheUser(controller_id, reason, state.config.smsNumbers); } } function sendAlarmingSms(controller_id, reason, numbers) { for (var i in numbers) { var number = numbers[i]; twilio = Twilio('AC10d7ed0bf54c1be4b1cd7133130e63f4', 'e133d3f02a69b79e93ad9ca1d73517d1'); twilio.sendSms({ to: number, // Any number Twilio can deliver to from: '+19282124174', // A number you bought from Twilio and can use for outbound communication body: 'Zoblak alarm! Pokrenite aplikaciju! HITNO! http://agrar.zoblak.com/alarm?controller_id=' + controller_id // body of the SMS message }, function(err, responseData) { //this function is executed when a response is received from Twilio if (!err) { // "err" is an error received during the request, if any // "responseData" is a JavaScript object containing data received from Twilio. // A sample response from sending an SMS message is here (click "JSON" to see how the data appears in JavaScript): // http://www.twilio.com/docs/api/rest/sending-sms#example-1 console.log(responseData.from); // outputs "+14506667788" console.log(responseData.body); // outputs "word to your mother." } }); } } function callTheUser(controller_id, reason, numbers) { for (var i in numbers) { var number = numbers[i]; twilio = Twilio('AC10d7ed0bf54c1be4b1cd7133130e63f4', 'e133d3f02a69b79e93ad9ca1d73517d1'); twilio.makeCall({ to: number, // Any number Twilio can call from: '+441143031932', // A number you bought from Twilio and can use for outbound communication url: 'https://handler.twilio.com/twiml/EH9491c24474db07ec52b598baa5724f1e' // A URL that produces an XML document (TwiML) which contains instructions for the call }, function(err, responseData) { //executed when the call has been initiated. console.log(err); // outputs "+14506667788" }); } } function stopTheAlarm(controller_id, everythingIsBackToNormal = false) { // time of alarm stopped is reset so that scheduled job can raise the alarm // again var timeOfStopping = (everythingIsBackToNormal) ? null : new Date(); var state = Meteor.zoblak.server.controller_state(controller_id); ControllerState.update(state._id, { '$set': { 'state.alarmTriggered': false, 'state.alarmStopped': timeOfStopping, 'state.alarmSmsSent': false } }); } function last_sensor_reading(controller_id) { var result = null; if (controller_id) { result = SensorData.find({ controllerId: controller_id }, { sort: { created_at: -1 }, limit: 1 }); } if (result && result.count() > 0) { return result.fetch()[0]; } else { return {} } } Meteor.methods({ openOutValve: openOutValve, closeOutValve: closeOutValve, openInValve: openInValve, closeInValve: closeInValve, clearLog: clearLog, saveControllerConfig: saveControllerConfig, requestNewPicture: requestNewPicture, saveAlarmSettings: saveAlarmSettings, stopTheAlarm: stopTheAlarm });