diff --git a/app/components/gui/mqttForm.js b/app/components/gui/mqttForm.js
index 9453c00..c66e30a 100644
--- a/app/components/gui/mqttForm.js
+++ b/app/components/gui/mqttForm.js
@@ -1,21 +1,23 @@
"use strict";
var React = require('react');
-
+var Grid = require('react-bootstrap').Grid;
var Col = require('react-bootstrap').Col;
var Row = require('react-bootstrap').Row;
var Modal = require('react-bootstrap').Modal;
var Input = require('react-bootstrap').Input;
var Button = require('react-bootstrap').Button;
-var LinkedStateMixin = require('react-addons-linked-state-mixin');
-
+var Form = require('react-bootstrap').Form;
+var FormControl = require('react-bootstrap').FormControl;
+var FormGroup = require('react-bootstrap').FormGroup;
+var ControlLabel = require('react-bootstrap').ControlLabel;
+var Radio = require('react-bootstrap').Radio;
var Switch = require('react-bootstrap-switch');
var Blink1SerialOption = require('./blink1SerialOption');
var MqttForm = React.createClass({
- mixins: [LinkedStateMixin],
propTypes: {
rule: React.PropTypes.object.isRequired,
allowMultiBlink1: React.PropTypes.bool,
@@ -26,20 +28,17 @@ var MqttForm = React.createClass({
onCopy: React.PropTypes.func
},
getInitialState: function() {
- return {
- // name: rule.name,
- // patternId: rule.patternId
- };
+ return {};
},
- // FIXME: why am I doing this?
+
componentWillReceiveProps: function(nextProps) {
var rule = nextProps.rule;
this.setState({
type: 'mqtt',
- enabled: rule.enabled,
- name: rule.name,
- actionType: 'play-pattern',
- patternId: rule.patternId || nextProps.patterns[0].id,
+ enabled: rule.enabled || false,
+ name: rule.name || 'new rule',
+ actionType: rule.actionType || 'parse-color',
+ //patternId: rule.patternId || nextProps.patterns[0].id,
blink1Id: rule.blink1Id || "0",
topic: rule.topic || "",
url: rule.url || "",
@@ -53,6 +52,16 @@ var MqttForm = React.createClass({
handleBlink1SerialChange: function(blink1Id) {
this.setState({blink1Id: blink1Id});
},
+ handleActionType: function(e) {
+ var actionType = e.target.value;
+ this.setState({actionType:actionType});
+ },
+ handleInputChange: function(event) {
+ var target = event.target;
+ var value = target.type === 'checkbox' ? target.checked : target.value;
+ var name = target.name;
+ this.setState({ [name]: value });
+ },
render: function() {
var self = this;
@@ -63,38 +72,85 @@ var MqttForm = React.createClass({
return (
-
+
MQTT Settings
{this.state.errormsg}
-
+
diff --git a/app/components/gui/toolTable.js b/app/components/gui/toolTable.js
index 861d1ac..e017d45 100644
--- a/app/components/gui/toolTable.js
+++ b/app/components/gui/toolTable.js
@@ -30,7 +30,7 @@ var MailForm = require('./mailForm');
var ScriptForm = require('./scriptForm');
var SkypeForm = require('./skypeForm');
var TimeForm = require('./timeForm');
-// var MqttForm = require('./mqttForm');
+var MqttForm = require('./mqttForm');
var ToolTableList = require('./toolTableList');
@@ -105,7 +105,7 @@ var ToolTable = React.createClass({
TimeService.reloadConfig();
}
else if( rule.type === 'mqtt' ) {
- // MqttService.reloadConfig();
+ MqttService.reloadConfig();
}
},
handleSaveForm: function(data) {
@@ -193,12 +193,6 @@ var ToolTable = React.createClass({
}
}
- //
-
return (
@@ -220,6 +214,12 @@ var ToolTable = React.createClass({
onSave={this.handleSaveForm} onCancel={this.handleCancelForm}
onDelete={this.handleDeleteRule} onCopy={this.handleCopyRule} />
+
+
Add Script
+
diff --git a/app/maingui.js b/app/maingui.js
index 27d9528..69ed79d 100644
--- a/app/maingui.js
+++ b/app/maingui.js
@@ -79,7 +79,7 @@ var MailService = require('./server/mailService');
var SkypeService = require('./server/skypeService');
var ScriptService = require('./server/scriptService');
var TimeService = require('./server/timeService');
-// var MqttService = require('./server/mqttService');
+var MqttService = require('./server/mqttService');
setTimeout( function() {
log.msg("services starting...");
@@ -88,7 +88,7 @@ setTimeout( function() {
SkypeService.start();
ScriptService.start();
TimeService.start();
- // MqttService.start();
+ MqttService.start();
log.msg("services started");
}, 2000);
diff --git a/app/package.json b/app/package.json
index 1a76ef3..2ca3f5b 100644
--- a/app/package.json
+++ b/app/package.json
@@ -22,6 +22,7 @@
"imap": "^0.8.19",
"is-electron-renderer": "^2.0.1",
"moment": "^2.17.1",
+ "mqtt": "^4.3.7",
"nconf": "^0.11.4",
"needle": "^1.5.1",
"node-blink1": "^0.5.1",
diff --git a/app/server/mqttService.js b/app/server/mqttService.js
index 2eb14da..62621fa 100644
--- a/app/server/mqttService.js
+++ b/app/server/mqttService.js
@@ -2,14 +2,15 @@
'use strict';
+var mqtt = require('mqtt');
+var tinycolor = require('tinycolor2');
var conf = require('../configuration');
+var utils = require('../utils');
var log = require('../logger');
var Eventer = require('../eventer');
-
-// var mqtt = require('mqtt');
-// var mqtt = require('../mqtt.min');
+var PatternsService = require('./patternsService');
var MqttService = {
config: {},
@@ -24,56 +25,194 @@ var MqttService = {
start: function() {
var self = this;
- this.config = conf.readSettings('eventServices:mqttService');
+ self.config = conf.readSettings('eventServices:mqttService');
if( !this.config ) {
log.msg("MqttService.reloadConfig: NO CONFIG");
- this.config = {
+ self.config = {
type: 'mqtt',
service: 'mqttService',
enabled: true,
+ reconnectPeriod: 10000,
};
- conf.saveSettings('eventServices:mqttService', this.config);
+ conf.saveSettings('eventServices:mqttService', self.config);
}
// var allrules = conf.readSettings('eventRules') || [];
var allrules = conf.readSettings('eventRules') || [];
- this.rules = allrules.filter( function(r){return r.type === 'mqtt';} );
+ self.rules = allrules.filter( function(r){return r.type === 'mqtt';} );
- self.rules.forEach( function(rule) {
+ self.rules.map( function(rule) {
log.msg("MqttService.start: rule:", rule);
+ if( !rule.enabled ) { return; }
+ var pass = '';
+ try {
+ if( rule.passwordHash !== '' ) { // allow password-less login
+ pass = utils.decrypt( rule.passwordHash );
+ }
+ } catch(err) {
+ log.msg('MqttService: ERROR bad password for username', rule.username);
+ }
// FIXME: impelement sanity checks
// if( !rule.url ) { }
// if !rule.topic ) { }
- var config = {
- // there will be more here?
+ var mqtt_config = {
+ reconnectPeriod: self.config.reconnectPeriod
};
- if( rule.username || rule.password ) {
- config.username = rule.username;
- config.password = rule.password;
- }
- var client = client = mqtt.connect( rule.url, config );
+ mqtt_config.username = rule.username;
+ mqtt_config.password = pass;
+ log.msg("MqttService.start: mqtt_config:", mqtt_config);
+ var client = mqtt.connect( rule.url, mqtt_config );
+ self.errorLogged = false; // reset
client.on('connect', function () {
+ log.msg("MqttService.connected");
+ Eventer.addStatus( {type:'info', source:rule.type, id:rule.name, text:"connected"} );
client.subscribe( rule.topic );
});
- client.on('error', (error) => {
- console.log('MqttService Errored', error);
+ client.on('disconnect', function() {
+ log.msg("MqttService.disconnect");
+ });
+ client.on('close', function() {
+ log.msg("MqttService.close");
+ if( !self.errorLogged ) {
+ Eventer.addStatus( {type:'info', source:rule.type, id:rule.name, text:"connection closed, bad auth?"} );
+ }
+ });
+ client.on('end', function() {
+ log.msg("MqttService.end");
+ });
+ client.on('error', function(error) {
+ console.log("bAKKBKBKB",error);
+ log.msg('MqttService.error: error json',JSON.stringify(error), error.toString());
+ Eventer.addStatus( {type:'info', source:rule.type, id:rule.name, text:error.toString() } );
+ self.errorLogged = true;
});
client.on('message', function (topic, message) {
- // message is Buffer
- Eventer.addStatus( {type:'info', source:'mqtt', id:rule.name, text:message.toString()} );
- console.log("topic:", topic, "message:",message.toString())
+ self.parse(rule, message.toString()); // message is Buffer, thus .toString()
+ // Eventer.addStatus( {type:'trigger', source:rule.type, id:rule.name, text:message.toString()} );
+ Eventer.addStatus( {type:'info', source:rule.type, id:rule.name, text:message.toString()} );
+ log.msg("MqttService: message: topic:", topic, "message:",message.toString());
});
rule.client = client;
});
},
stop: function() {
- this.rules.forEach( function(rule) {
- if( rule.client ) {
- rule.client.end();
- rule.client = null;
+ log.msg("MqttService.stop");
+ this.rules.forEach( function(rule) {
+ if( rule.client ) {
+ rule.client.end();
+ rule.client = null;
+ }
+ });
+ },
+
+ playPattern: function(pattid,ruleid,blink1id) {
+ if( PatternsService.playPatternFrom( ruleid, pattid, blink1id ) ) {
+ // this.lastPatterns[ruleid] = pattid;
+ return pattid;
+ }
+ return false;
+ },
+
+ /**
+ * Parse the output from a MQTT response.
+ * Plays patterns if match.
+ * Sends log messages with source & id of rule.
+ * Checks for the following content:
+ * if 'actionType == 'parse-json', treat content as JSON,
+ * and look for 'pattern' or 'color' keys
+ * 'pattern' can be meta-pattern like: '~off' and '~blink'
+ * if 'actionType == 'parse-pattern', attempt to find a pattern
+ * with the "pattern:" format, and play it.
+ * Can also use meta-patterns here.
+ * if 'actionType == 'parse-color', look in text for RGB hex color string
+ *
+ * @param {Rule} rule eventRules rule for this content
+ * @param {String} str the content to be parsed, potentially multiple lines
+ * @return {[type]} [description]
+ */
+ parse: function(rule, str) {
+ if( typeof str != "string" ) {
+ str = (str) ? str.toString() : ''; // convert to string
+ }
+ str = str.substring(0,this.config.maxStringLength);
+ var self = this;
+ //var patternre = /pattern:\s*(#*\w+)/; // orig
+ //var patternre = /pattern:\s*(\"([^"])*\"|#?\w+)/; // suggested by @slakichi in issue #101
+ var patternre = /pattern:\s*("*)(.+)\1/; // match everything either quoted or not
+ var colorre = /(#[0-9a-f]{6}|#[0-9a-f]{3}|color:\s*(.+?)\s)/i; // regex to match hex color codes or 'color:' names
+ var matches;
+
+ // if( self.lastEvents[rule.name] === str && rule.actOnNew ) {
+ // Eventer.addStatus( {type:'info', source:rule.type, id:rule.name, text:'not modified'});
+ // return;
+ // }
+ // self.lastEvents[rule.name] = str;
+ // Eventer.addStatus( { type:'trigger', text:data.substring(0,40), source:rule.type, id:rule.name} );
+
+ var actionType = rule.actionType;
+ if( actionType === 'parse-json' ) {
+ var json = null;
+ try {
+ json = JSON.parse(str);
+ if( json.pattern ) {
+ // returns true on found pattern // FIXME: go back to using 'findPattern'
+ if( this.playPattern( json.pattern, rule.name, rule.blink1Id ) ) {
+ Eventer.addStatus( {type:'trigger', source:rule.type, id:rule.name, text:json.pattern});
+ }
+ else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+json.pattern});
+ }
+ }
+ else if( json.color ) {
+ var c = tinycolor(json.color);
+ if( c.isValid() ) {
+ Eventer.addStatus( {type:'trigger', source:rule.type, id:rule.name, text:json.color});
+ this.playPattern( c.toHexString(), rule.name, rule.blink1Id );
+ } else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+json.color});
+ }
+ }
+ } catch(error) {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:error.message});
}
- });
+ }
+ else if( actionType === 'parse-pattern' ) {
+ matches = patternre.exec( str );
+ if( matches ) {
+ var patt_name = matches[2]; // it's always the 2nd match, either quoted or not
+
+ if( this.playPattern( patt_name, rule.name, rule.blink1Id ) ) {
+ Eventer.addStatus( {type:'trigger', source:rule.type, id:rule.name, text:patt_name});
+ }
+ else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
+ }
+ }
+ else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'no pattern '+str});
+ }
+ }
+ else { // parse-color
+ matches = colorre.exec(str);
+ if( matches && matches) {
+ var colormatch = matches[2];
+ if( !colormatch ) { colormatch = matches[1]; }
+
+ var color = tinycolor( colormatch );
+ if( color.isValid() ) {
+ Eventer.addStatus( {type:'trigger', source:rule.type, id:rule.name, text:colormatch});
+ this.playPattern( color.toHexString(), rule.name, rule.blink1Id );
+ }
+ else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'invalid color '+colormatch});
+ }
+ }
+ else {
+ Eventer.addStatus( {type:'error', source:rule.type, id:rule.name, text:'no color found in:'+str});
+ }
+ }
}
+
};
module.exports = MqttService;