diff --git a/.setup/gatewaysetup.sh b/.setup/gatewaysetup.sh
index 33c7485..c43ec4a 100644
--- a/.setup/gatewaysetup.sh
+++ b/.setup/gatewaysetup.sh
@@ -96,6 +96,11 @@ sudo npm install --unsafe-perm --build-from-source
sudo npm cache clean #clear any caches/incomplete installs
sudo mkdir $APPSRVDIR/logs -p
+#create db and empty placeholders so chown pi will override root permissions
+sudo mkdir $APPSRVDIR/data/db -p
+touch $APPSRVDIR/data/db/gateway.db
+touch $APPSRVDIR/data/db/gateway_nonmatches.db
+
#create self signed certificate
#WARNING: must do this *AFTER* the gateway app was git-cloned
echo -e "${CYAN}************* STEP: Create self signed HTTPS certificate (5 year) *************${NC}"
@@ -158,7 +163,8 @@ sudo systemctl enable gateway.service
sudo systemctl start gateway.service
echo -e "${RED}Make sure: ${YLW}to edit your gateway settings from the UI or from settings.json5 (and restart to apply changes)${NC}"
-echo -e "${RED}By default ${YLW}the gateway app uses the GPIO serial port. If you use MoteinoUSB or another serial port you must edit the serial port setting or else the app will not receive messages from your Moteino nodes.${NC}"
+echo -e "${RED}By default ${YLW}the gateway app uses the GPIO serial port. Run ${GRN}raspi-config${NC} and ensure the GPIO serial is enabled and GPIO console is disabled.${NC}"
+echo -e "${YLW}If you use MoteinoUSB or another serial port you must edit the serial port setting or the app will not receive messages from your Moteino nodes.${NC}"
echo -e "${RED}App restarts ${YLW}can be requested from the Gateway UI (power symbol button on settings page, or from the terminal via ${RED}sudo systemctl restart gateway.service${NC}"
echo -e "${RED}Don't forget: ${YLW}install proftpd (choose standalone mode) if you plan to FTP transfer files to your Pi (very useful!) with ${GRN}sudo apt-get install proftpd${NC}"
echo -e "${RED}Don't forget: ${YLW}install minicom - useful for serial port debugging with ${GRN}sudo apt-get install minicom${NC}"
diff --git a/README.md b/README.md
index 80e5612..5d0c3ca 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ RaspberryPi Gateway Web Interface for the Moteino IoT Framework
By Felix Rusu (lowpowerlab.com/contact)
-###Features:
+### Features:
- SSL Encrypted with self signed certificate
- `auth_basic` authenticated
- realtime websocket driven using node.js and socket.io
@@ -13,15 +13,15 @@ By Felix Rusu (lowpowerlab.com/contact)
- [nodemailer](https://github.com/andris9/Nodemailer) for sending email (and SMS relayed via email)
- [Font-awesome](http://htmlpreview.github.io/?https://github.com/dotcastle/jquery-mobile-font-awesome/blob/master/index.html) icons for jQuery-Mobile
-###License
+### License
This source code is released under GPL 3.0 with the following ammendment:
You are free to use, copy, distribute and transmit this Software for non-commercial purposes.
For more details see [LICENSE](https://github.com/LowPowerLab/RaspberryPi-Gateway/blob/master/LICENSE)
-###Details & Setup Guide
+### Details & Setup Guide
The full details of how to install this stack along with supporting webserver are published [here](http://lowpowerlab.com/gateway). There you will also find a pre-compiled Pi image that has this stack installed and ready to go. In addition there are hardware guidelines and requirements.
-###Quick reference:
+### Quick reference:
- Copy the contents of this directory in `/home/pi/gateway`
- run `npm install` in the `/home/pi/gateway` directory to install all node dependencies
- Adjust any email/password/SMS settings in `settings.json5`
@@ -32,8 +32,8 @@ The full details of how to install this stack along with supporting webserver ar
- if you are using a wi-fi dongle, edit your wifi password in `/etc/wpa_supplicant/wpa_supplicant.conf`
- Ensure your `gateway.js` script runs at boot (see the [Pi Stack Setup guide for how to set that up with upstart](http://lowpowerlab.com/gateway/#pisetup) and the [Gateway app setup](http://lowpowerlab.com/gateway/#sourcecode)). You can always use the pre-compiled Pi image that has all these things ready to go (except the settings which you should revisit anyway); this image also has upstart already configured to run the `gateway.js` app at startup. Otherwise if you want to manually start the `gateway.js` app or see the output it generates to the console start it with `node gateway.js &`. If you want to manually start it and ensure it persists after you logout use `nohup node gateway.js &`
-###Video Overview & Demo
+### Video Overview & Demo
https://www.youtube.com/watch?v=F15dEqZ4pMM
-###3rd party custom gateway setup overview
+### 3rd party custom gateway setup overview
https://www.youtube.com/watch?v=DP83RJeTpUY
\ No newline at end of file
diff --git a/gateway.js b/gateway.js
index 50a94d4..19c754c 100644
--- a/gateway.js
+++ b/gateway.js
@@ -125,7 +125,7 @@ global.sendEmail = function(SUBJECT, BODY) {
global.sendSMS = function(SUBJECT, BODY) {
var mailOptions = {
- from: 'Moteino Gateway ',
+ from: 'Gateway ',
to: settings.credentials.smsAlertsTo.value, //your mobile carrier should have an email address that will generate a SMS to your phone
subject: SUBJECT,
text: BODY
@@ -297,7 +297,7 @@ io.sockets.on('connection', function (socket) {
if (entries.length == 1)
{
var dbNode = entries[0];
- Object.keys(dbNode.metrics).forEach(function(mKey,index) {
+ Object.keys(dbNode.metrics).forEach(function(mKey,index) { //syncronous/blocking call
if (dbNode.metrics[mKey].graph == 1)
dbLog.removeMetricLog(path.join(__dirname, dbDir, dbLog.getLogName(dbNode._id, mKey)));
});
@@ -405,7 +405,29 @@ io.sockets.on('connection', function (socket) {
else
socket.emit('GRAPHDATAREADY', { graphData:graphData, options : graphOptions });
});
-
+
+ socket.on('EXPORTNODELOGSCSV', function (nodeId, start, end, howManyPoints) {
+ var sts = Math.floor(start / 1000); //get timestamp in whole seconds
+ var ets = Math.floor(end / 1000); //get timestamp in whole seconds
+ var sets = [];
+
+ db.find({ _id : nodeId }, function (err, entries) {
+ if (entries.length == 1)
+ {
+ var dbNode = entries[0];
+ Object.keys(dbNode.metrics).forEach(function(mKey,index) { //syncronous/blocking call
+ if (dbNode.metrics[mKey].graph == 1) {
+ var logfile = path.join(__dirname, dbDir, dbLog.getLogName(dbNode._id, mKey));
+ var theData = dbLog.getData(logfile, sts, ets, howManyPoints /*settings.general.graphMaxPoints.value*/);
+ theData.label = dbNode.metrics[mKey].label || mKey;
+ sets.push(theData); //100k points when exporting, more points is really pointless
+ }
+ });
+ socket.emit('EXPORTNODELOGSCSVREADY', { sets:sets });
+ }
+ });
+ });
+
socket.on('UPDATESETTINGSDEF', function (newSettings) {
var settings = nconf.get('settings');
diff --git a/logUtil.js b/logUtil.js
index 6d9662b..248bcbe 100644
--- a/logUtil.js
+++ b/logUtil.js
@@ -33,6 +33,18 @@ exports.getData = function(filename, start, end, dpcount) {
filesize = exports.fileSize(filename);
if (filesize == -1) return {data:data, queryTime:0, msg:'no log data'};
fd = fs.openSync(filename, 'r');
+
+ //truncate start/end to log time limits if necessary - this ensures good data resolution when time limits are out of bounds
+ var buff = new Buffer(9);
+ fs.readSync(fd, buff, 0, 9, 0);
+ var firstLogTimestamp = buff.readUInt32BE(1);
+ fs.readSync(fd, buff, 0, 9, filesize-9);
+ var lastLogTimestamp = buff.readUInt32BE(1); //read timestamp (bytes 0-3 in buffer)
+ if (start < firstLogTimestamp) start = firstLogTimestamp;
+ if (end > lastLogTimestamp) end = lastLogTimestamp;
+
+ //console.info('getData() [start,end] = ' + start + ', ' + end);
+
interval = (end - start) / dpcount;
// Ensure that interval request is less than 1, adjust number of datapoints to request if interval = 1
@@ -42,7 +54,6 @@ exports.getData = function(filename, start, end, dpcount) {
}
timetmp = 0;
- buff = new Buffer(9);
//first check if sequential reads (much faster) make sense
posStart = exports.binarySearch(fd,start-interval,filesize);
diff --git a/metrics.js b/metrics.js
index 14c2758..4eeda12 100644
--- a/metrics.js
+++ b/metrics.js
@@ -124,10 +124,10 @@ exports.metrics = {
exports.events = {
motionAlert : { label:'Motion : Alert', icon:'audio', descr:'Alert sound when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { io.sockets.emit('PLAYSOUND', 'sounds/alert.wav'); }; } },
mailboxAlert : { label:'Mailbox Open Alert!', icon:'audio', descr:'Message sound when mailbox is opened', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { io.sockets.emit('PLAYSOUND', 'sounds/incomingmessage.wav'); }; } },
- motionEmail : { label:'Motion : Email', icon:'mail', descr:'Send email when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendEmail('MOTION DETECTED', 'MOTION WAS DETECTED ON NODE: [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
- motionSMS : { label:'Motion : SMS', icon:'comment', descr:'Send SMS when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendSMS('MOTION DETECTED', 'MOTION WAS DETECTED ON NODE: [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
+ motionEmail : { label:'Motion : Email', icon:'mail', descr:'Send email when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendEmail('MOTION DETECTED', 'MOTION WAS DETECTED ON NODE: [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString()); }; } },
+ motionSMS : { label:'Motion : SMS', icon:'comment', descr:'Send SMS when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendSMS('MOTION DETECTED', 'MOTION WAS DETECTED ON NODE: [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString()); }; } },
- motionSMSLimiter : { label:'Motion : SMS Limited', icon:'comment', descr:'Send SMS when MOTION is detected, once per hour',
+ motionSMSLimiter : { label:'Motion : SMS Limited 1/hr', icon:'comment', descr:'Send SMS when MOTION is detected, once per hour',
serverExecute:function(node) {
if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - node.metrics['M'].updated < 2000)) /*check if M metric exists and value is MOTION, received less than 2s ago*/
{
@@ -147,7 +147,7 @@ exports.events = {
if (approveSMS)
{
node.metrics['M'].lastSMS = Date.now();
- sendSMS('MOTION DETECTED', 'MOTION WAS DETECTED ON NODE: [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM')));
+ sendSMS('MOTION!', 'MOTION DETECTED ON NODE [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString());
db.update({ _id: node._id }, { $set : node}, {}, function (err, numReplaced) { console.log(' ['+node._id+'] DB-Updates:' + numReplaced);}); /*save lastSMS timestamp to DB*/
}
else console.log(' ['+node._id+'] MOTION SMS skipped.');
@@ -175,7 +175,7 @@ exports.events = {
if (approveSMS)
{
node.metrics['F'].lastSMS = Date.now();
- sendSMS('Temperature > 75° !', 'Temperature alert (>75°F!): [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM')));
+ sendSMS('Temperature > 75° !', 'Temperature alert (>75°F!): [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString());
db.update({ _id: node._id }, { $set : node}, {}, function (err, numReplaced) { console.log(' ['+node._id+'] DB-Updates:' + numReplaced);}); /*save lastSMS timestamp to DB*/
}
else console.log(' ['+node._id+'] THAlert SMS skipped.');
@@ -183,21 +183,21 @@ exports.events = {
}
},
- mailboxSMS : { label:'Mailbox open : SMS', icon:'comment', descr:'Send SMS when mailbox is opened', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendSMS('MAILBOX OPENED', 'Mailbox opened [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
+ mailboxSMS : { label:'Mailbox open : SMS', icon:'comment', descr:'Send SMS when mailbox is opened', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendSMS('MAILBOX OPENED', 'Mailbox opened [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString()); }; } },
motionLightON23 : { label:'Motion: SM23 ON!', icon:'action', descr:'Turn SwitchMote:23 ON when MOTION is detected', serverExecute:function(node) { if (node.metrics['M'] && node.metrics['M'].value == 'MOTION' && (Date.now() - new Date(node.metrics['M'].updated).getTime() < 2000)) { sendMessageToNode({nodeId:23, action:'MOT:1'}); }; } },
doorbellSound : { label:'Doorbell : Sound', icon:'audio', descr:'Play sound when doorbell rings', serverExecute:function(node) { if (node.metrics['RING'] && node.metrics['RING'].value == 'RING' && (Date.now() - new Date(node.metrics['RING'].updated).getTime() < 2000)) { io.sockets.emit('PLAYSOUND', 'sounds/doorbell.wav'); }; } },
- doorbellSMS : { label:'Doorbell : SMS', icon:'comment', descr:'Send SMS when Doorbell button is pressed', serverExecute:function(node) { if (node.metrics['RING'] && node.metrics['RING'].value == 'RING' && (Date.now() - new Date(node.metrics['RING'].updated).getTime() < 2000)) { sendSMS('DOORBELL', 'DOORBELL WAS RINGED: [' + node._id + '] ' + node.label + ' @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
- sumpSMS : { label:'SumpPump : SMS (below 20cm)', icon:'comment', descr:'Send SMS if water < 20cm below surface', serverExecute:function(node) { if (node.metrics['CM'] && node.metrics['CM'].value < 20 && (Date.now() - new Date(node.metrics['CM'].updated).getTime() < 2000)) { sendSMS('SUMP PUMP ALERT', 'Water is only 20cm below surface and rising - [' + node._id + '] ' + node.label + ' @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
+ doorbellSMS : { label:'Doorbell : SMS', icon:'comment', descr:'Send SMS when Doorbell button is pressed', serverExecute:function(node) { if (node.metrics['RING'] && node.metrics['RING'].value == 'RING' && (Date.now() - new Date(node.metrics['RING'].updated).getTime() < 2000)) { sendSMS('DOORBELL', 'DOORBELL WAS RINGED: [' + node._id + '] ' + node.label + ' @ ' + new Date().toLocaleTimeString()); }; } },
+ sumpSMS : { label:'SumpPump : SMS (below 20cm)', icon:'comment', descr:'Send SMS if water < 20cm below surface', serverExecute:function(node) { if (node.metrics['CM'] && node.metrics['CM'].value < 20 && (Date.now() - new Date(node.metrics['CM'].updated).getTime() < 2000)) { sendSMS('SUMP PUMP ALERT', 'Water is only 20cm below surface and rising - [' + node._id + '] ' + node.label + ' @ ' + new Date().toLocaleTimeString()); }; } },
- garageSMS : { label:'Garage : SMS', icon:'comment', descr:'Send SMS when garage is OPENING', serverExecute:function(node) { if (node.metrics['Status'] && (node.metrics['Status'].value.indexOf('OPENING')>-1) && (Date.now() - new Date(node.metrics['Status'].updated).getTime() < 2000)) { sendSMS('Garage event', 'Garage was opening on node : [' + node._id + ':' + node.label + '] @ ' + (new Date().toLocaleTimeString() + (new Date().getHours() > 12 ? 'PM':'AM'))); }; } },
+ garageSMS : { label:'Garage : SMS', icon:'comment', descr:'Send SMS when garage is OPENING', serverExecute:function(node) { if (node.metrics['Status'] && (node.metrics['Status'].value.indexOf('OPENING')>-1) && (Date.now() - new Date(node.metrics['Status'].updated).getTime() < 2000)) { sendSMS('Garage event', 'Garage was opening on node : [' + node._id + ':' + node.label + '] @ ' + new Date().toLocaleTimeString()); }; } },
garagePoll: { label:'Garage : POLL', icon:'comment', descr:'Poll Garage Status', nextSchedule:function(nodeAtScheduleTime) { return 30000; }, scheduledExecute:function(nodeAtScheduleTime) { db.findOne({ _id : nodeAtScheduleTime._id }, function (err, nodeRightNow) { if (nodeRightNow) { /*just emit a log the status to client(s)*/ io.sockets.emit('LOG', 'GARAGE POLL STATUS: ' + nodeRightNow.metrics['Status'].value ); } }); } },
switchMoteON_PM : { label:'SwitchMote ON at 6:30PM!', icon:'clock', descr:'Turn this switch ON every evening', nextSchedule:function(node) { return exports.timeoutOffset(18,30); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:1'}); } },
switchMoteOFF_AM : { label:'SwitchMote OFF at 8:00AM!', icon:'clock', descr:'Turn this switch OFF every morning', nextSchedule:function(node) { return exports.timeoutOffset(8,00); }, scheduledExecute:function(node) { sendMessageToNode({nodeId:node._id, action:'BTN1:0'}); } },
switchMoteONBUZZ : { label:'SwitchMote ON Buzzer beep!', icon:'clock', descr:'Buzz gateway when switchmote is ON', serverExecute:function(node) { if (node.metrics['B1'] && node.metrics['B1'].value == 'ON' && (Date.now() - new Date(node.metrics['B1'].updated).getTime() < 2000)) { setTimeout(function() { sendMessageToGateway('BEEP'); }, 5); } }},
- //for the sprinkler events, rather than scheduling with offsets, its much easir we run them every day, and check the odd/even/weekend condition in the event itself
+ //for the sprinkler events, rather than scheduling with offsets, its much easier to run them every day, and check the odd/even/weekend condition in the event itself
sprinklersOddDays : { label:'Odd days @ 2:30AM', icon:'clock', descr:'Run this sprinkler program on odd days at 2:30AM', nextSchedule:function(node) { return exports.timeoutOffset(2,30); }, scheduledExecute:function(node) { if ((new Date().getDate()%2)==1) sendMessageToNode({nodeId:node._id, action:'PRG 1:300 2:300 3:300 4:300 5:300' /*runs stations 1-5 (300sec each))*/}); } },
sprinklersEvenDays : { label:'Even days @ 2:30AM', icon:'clock', descr:'Run this sprinkler program on even days at 2:30AM', nextSchedule:function(node) { return exports.timeoutOffset(2,30); }, scheduledExecute:function(node) { if ((new Date().getDate()%2)==0) sendMessageToNode({nodeId:node._id, action:'PRG 1:300 2:300 3:300 4:300 5:300' /*runs stations 1-5 (300sec each)*/}); } },
sprinklersWeekends : { label:'Weekends @ 2:30AM)', icon:'clock', descr:'Run this sprinkler program on weekend days at 2:30AM', nextSchedule:function(node) { return exports.timeoutOffset(2,30); }, scheduledExecute:function(node) { if ([0,6].indexOf(new Date().getDay())>-1 /*Saturday=6,Sunday=0,*/) sendMessageToNode({nodeId:node._id, action:'PRG 1:180 2:180 3:180 4:180 5:180' /*runs stations 1-5 (180sec each)*/}); } },
@@ -566,17 +566,17 @@ exports.determineGraphValue = function(matchingMetric, matchingToken) {
if (exports.isNumeric(result))
return Number(result);
else return result;
-};
+}
//calculates the milliseconds timeout remaining until a given time of the day (if it's 8AM now and time given was 3AM, it will calculate to the next day 3AM)
//offset can be used to add more time to the calculated timeout, for instance to delay by one day: pass offset=86400000
-exports.timeoutOffset = function(hour, minute, second, millisecond, offset) {
- var result = new Date().setHours(hour,minute,second || 0, millisecond || 0);
+exports.timeoutOffset = function(hour, minute, second, offset) {
+ var result = new Date().setHours(hour,minute,second || 0, 0);
result = result < new Date().getTime() ? (result + exports.ONEDAY) : result;
result -= new Date().getTime();
if (exports.isNumeric(offset)) result += offset;
return result;
-};
+}
// ******************************************************************************************************************************************
// RADIO THERMOSTAT SPECIFIC HELPER FUNCTIONS
diff --git a/settings.json5 b/settings.json5
index a9cca98..5144cab 100644
--- a/settings.json5
+++ b/settings.json5
@@ -4,43 +4,53 @@
editable: true,
exposed: true,
emailservice: {
- value: "gmail" //default is gmail, see nodemailer reference for other clients - https://github.com/andris9/Nodemailer
+ value: "gmail",
+ description: "default is gmail, see nodemailer reference for other clients - https://github.com/andris9/Nodemailer"
},
email: {
- value: "________@gmail.com" //put your gmail address here
+ value: "________@gmail.com",
+ description: "put your gmail address here - this will also be used to send SMS (SMS via email)"
},
emailpass: {
- value: "________", //put your gmail password or app access code here
- password: true
+ value: "________",
+ password: true,
+ description: "put your gmail password or app access code here"
},
emailAlertsTo: {
- value: "________@gmail.com" //put your alert/notification email here (can be the same as above)
+ value: "________@gmail.com",
+ description: "put your alert/notification email here (can be the same as above)"
},
smsAlertsTo: {
- value: "________@txt.att.net" //if you want SMS notifications, fill this in with your phone number (it's your cell#, domain differs for each carrier, ex: 5551234567@vtext.com for verizon)
+ value: "________@txt.att.net",
+ description: "if you want SMS notifications, fill this in with your phone number (it's your cell#, domain differs for each carrier, ex: 5551234567@vtext.com for verizon, or 5551234567@txt.att.net for at&t)"
}
},
serial: {
editable: true,
exposed: true,
port: {
- value: "/dev/ttyAMA0" //replace this with whatever serial port you have on your Pi/gateway (ttyAMA0 is the default GPIO serial port)
- }, //if you use a MoteinoUSB then your Pi will emulate a serial port like /dev/ttyUSB0
+ value: "/dev/ttyAMA0",
+ description: "replace this with whatever serial port you have on your Pi/gateway (ttyAMA0 is the default GPIO serial port). If you use a MoteinoUSB then your Pi will emulate a serial port like /dev/ttyUSB0"
+ },
baud: {
- value: 19200
+ value: 19200,
+ description: "needs to match the Serial baud speed in the sketch running on the Moteino or MightyHat that is attached to this Pi"
}
},
database: {
editable: false,
exposed: false,
name: {
- value: "gateway.db"
+ value: "gateway.db",
+ description: "where your node information is stored, should not really be changed"
},
nonMatchesName: {
- value: "gateway_nonmatches.db"
+ value: "gateway_nonmatches.db",
+ description: "where non match node information is dumped, should not really be changed"
},
compactDBInterval: {
- value: 86400000
+ value: 86400000,
+ description: "the gateway.db database is compacted every this many milliseconds, default = once every day"
}
},
general: {
@@ -52,16 +62,24 @@
},
socketPort: {
value: 8080,
- editable: false
+ editable: false,
+ description: "the port at which the gateway.js socket app is listening"
},
genNodeIfNoMatch: {
- value: "false"
+ value: "false",
+ description: "generate a new node even if the data received does not match any metric definition, default = false"
},
- keepMetricLogsOnDelete: {
- value: "false"
+ keepMetricLogsOnDelete: {
+ value: "false",
+ description: "keep the metric binary logs when metric is deleted, default = false"
},
graphMaxPoints: {
- value: 800
+ value: 800,
+ description: "display up to this many points in the UI, should not be more than your monitor resolution"
+ },
+ smsRepeatLimit: {
+ value: 180000,
+ description: "an SMS will only be repeated after this many milliseconds. Helps to limit repeats for things like repeated motion or alerts"
}
},
radiothermostat: {
diff --git a/userMetrics/_example.js b/userMetrics/_example.js
index b6dd4d7..ad7413f 100644
--- a/userMetrics/_example.js
+++ b/userMetrics/_example.js
@@ -58,6 +58,10 @@ exports.metrics = {
}
};
+exports.events = {
+ sprinklersOddDays : { label:'Odd days @ 6:31AM', icon:'clock', descr:'Run this sprinkler program on odd days at 6:30AM', nextSchedule:function(node) { return exports.timeoutOffset(6,30); }, scheduledExecute:function(node) { if ((new Date().getDate()%2)==1) sendMessageToNode({nodeId:node._id, action:'PRG 2:300 3:300 1:300 4:300 5:300' /*runs stations 1-5 (300sec each))*/}); } },
+};
+
/*example of defining a property to use anywhere in the app/events or in other custom functions*/
exports.ONEDAYHOURS = 24;
diff --git a/www/index.html b/www/index.html
index 227e2de..a112b24 100644
--- a/www/index.html
+++ b/www/index.html
@@ -64,6 +64,10 @@
+
+
+
+
@@ -220,7 +226,7 @@
- NOTE: This command will actually execute a process.exit() which stops the gateway app. If upstart is running it should automatically detect the app has stopped and restart it.
+ NOTE: This command will actually execute a process.exit() which stops the gateway app. If the gateway service (systemctl) is running it should automatically restart it.
Note: exporting very large data sets can lock your browser.
+ If Max Points Per Metric is less than points in the given interval, the data is aggregated.
+ The total combined points in CSV will likely be larger than Max Points Per Metric value.