You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

482 lines
19 KiB

var mqtt_server = function (o_params) {
var o = {
MaxWaitTime: 4000,
port: 30031,
portSSL: 30032,
SECURE_CERT: '/home/yimian/ssl/star.yimian.xyz.ssl/star.yimian.xyz.crt',
SECURE_KEY: '/home/yimian/ssl/star.yimian.xyz.ssl/star.yimian.xyz.key',
sql: {
host: '192.168.0.90',
user: 'smartfarm',
password: null,
port: '3306',
database: 'smartfarm'
},
debug: false,
intervalTime: 50000,
CheckMinTime: 1000,
MaxTryTimes: 3,
};
/* merge paras */
Object.assign(o, o_params);
/* require packages */
const mosca = require('mosca');
const mysql = require('mysql');
const fs = require('fs');
const redis = require('redis');
/* tmp global var */
var timer = null;
var fStatus = {
node0: 'disconnect',
node1: 'disconnect',
station: 'disconnect',
waterSys: 'disconnect'
};
var cache = {
node0: {
status: null,
batteryLevel: null,
waterSwitch: null,
temperature: null,
humidity: null,
BeginTime: null,
EndTime: null,
qos: null,
},
node1: {
status: null,
batteryLevel: null,
waterSwitch: null,
temperature: null,
humidity: null,
BeginTime: null,
EndTime: null,
qos: null,
},
station: {
status: null,
batteryLevel: null,
light: null,
temperature: null,
humidity: null,
rainfall: null,
CO: null,
NH3: null,
airPressure: null,
BeginTime: null,
EndTime: null,
qos: null,
},
waterSys: {
status: null,
pump0: null,
pump1: null,
BeginTime: null,
EndTime: null,
qos: null,
}
}
/* mqtt ini */
var mqtt_broker = new mosca.Server({
port: o.port,
secure: {
port: o.portSSL,
keyPath: o.SECURE_KEY,
certPath: o.SECURE_CERT,
}
});
/* mysql ini */
var sqlCnt = mysql.createConnection({
host : o.sql.host,
user : o.sql.user,
password : o.sql.password || fs.readFileSync("/home/yimian/conf/dbKeys/smartfarm.db.key").toString().replace(/\s+/g,""),
port: o.sql.port,
database: o.sql.database
});
sqlCnt.connect();
/* redis ini */
var rc = redis.createClient();
rc.on("error", function (err) {
console.log("Redis Error " + err);
});
/* mqtt events */
mqtt_broker.on('ready', function(){
console.log((new Date((new Date()).getTime())).toLocaleString()+' - mqtt broker ready at port '+o.port);
console.log((new Date((new Date()).getTime())).toLocaleString()+' - mqtt broker ready at SSL port '+o.portSSL);
});
mqtt_broker.on('published', function (packet, client) {
switch (packet.topic) {
/* json */
case 'res/json':
try{
var obj = JSON.parse(packet.payload.toString());
if(obj.t == "station"){
tools.cache.push('qos', obj.q, 'station');
tools.cache.push('status', obj.s, 'station');
tools.cache.push('batteryLevel', obj.b, 'station');
tools.cache.push('light', obj.l, 'station');
tools.cache.push('temperature', obj.T, 'station');
tools.cache.push('humidity', obj.h, 'station');
tools.cache.push('rainfall', obj.r, 'station');
tools.cache.push('CO', obj.C, 'station');
tools.cache.push('NH3', obj.N, 'station');
tools.cache.push('airPressure', obj.a, 'station');
}
if(obj.t == "node0"){
tools.cache.push('qos', obj.q, 'node0');
tools.cache.push('status', obj.s, 'node0');
tools.cache.push('batteryLevel', obj.b, 'node0');
tools.cache.push('temperature', obj.T, 'node0');
tools.cache.push('humidity', obj.h, 'node0');
tools.cache.push('waterSwitch', obj.w, 'node0');
}
if(obj.t == "node1"){
tools.cache.push('qos', obj.q, 'node1');
tools.cache.push('status', obj.s, 'node1');
tools.cache.push('batteryLevel', obj.b, 'node1');
tools.cache.push('temperature', obj.T, 'node1');
tools.cache.push('humidity', obj.h, 'node1');
tools.cache.push('waterSwitch', obj.w, 'node1');
}
}catch(e){
console.log("Not JSON at res/json");
}
break;
/* node0 */
case 'res/node0/status':
tools.cache.push('status', packet.payload.toString(), 'node0');
break;
case 'res/node0/batteryLevel':
tools.cache.push('batteryLevel', packet.payload.toString(), 'node0');
break;
case 'res/node0/waterSwitch':
tools.cache.push('waterSwitch', packet.payload.toString(), 'node0');
break;
case 'res/node0/temperature':
tools.cache.push('temperature', packet.payload.toString(), 'node0');
break;
case 'res/node0/humidity':
tools.cache.push('humidity', packet.payload.toString(), 'node0');
break;
case 'qos/node0':
tools.cache.push('qos', packet.payload.toString(), 'node0');
break;
/* node 1*/
case 'res/node1/status':
tools.cache.push('status', packet.payload.toString(), 'node1');
break;
case 'res/node1/batteryLevel':
tools.cache.push('batteryLevel', packet.payload.toString(), 'node1');
break;
case 'res/node1/waterSwitch':
tools.cache.push('waterSwitch', packet.payload.toString(), 'node1');
break;
case 'res/node1/temperature':
tools.cache.push('temperature', packet.payload.toString(), 'node1');
break;
case 'res/node1/humidity':
tools.cache.push('humidity', packet.payload.toString(), 'node1');
break;
case 'qos/node1':
tools.cache.push('qos', packet.payload.toString(), 'node1');
break;
/* station*/
case 'res/station/status':
tools.cache.push('status', packet.payload.toString(), 'station');
break;
case 'res/station/batteryLevel':
tools.cache.push('batteryLevel', packet.payload.toString(), 'station');
break;
case 'res/station/light':
tools.cache.push('light', packet.payload.toString(), 'station');
break;
case 'res/station/temperature':
tools.cache.push('temperature', packet.payload.toString(), 'station');
break;
case 'res/station/humidity':
tools.cache.push('humidity', packet.payload.toString(), 'station');
break;
case 'res/station/rainfall':
tools.cache.push('rainfall', packet.payload.toString(), 'station');
break;
case 'res/station/CO':
tools.cache.push('CO', packet.payload.toString(), 'station');
break;
case 'res/station/NH3':
tools.cache.push('NH3', packet.payload.toString(), 'station');
break;
case 'res/station/airPressure':
tools.cache.push('airPressure', packet.payload.toString(), 'station');
break;
case 'qos/station':
tools.cache.push('qos', packet.payload.toString(), 'station');
break;
/* waterSys*/
case 'res/waterSys/status':
tools.cache.push('status', packet.payload.toString(), 'waterSys');
break;
case 'res/waterSys/pump0':
tools.cache.push('pump0', packet.payload.toString(), 'waterSys');
break;
case 'res/waterSys/pump1':
tools.cache.push('pump1', packet.payload.toString(), 'waterSys');
break;
case 'qos/waterSys':
tools.cache.push('qos', packet.payload.toString(), 'waterSys');
break;
}
});
/* tools */
var tools = {
cache:{
push: function(t, s, name){
var f = tools.sql[name].push;
if(t != 'qos') rc.hset('sf/'+name, t, s);
rc.hset('sf/LastConnectTime', name, (new Date()).valueOf());
if(cache[name].BeginTime && (new Date()).valueOf() - cache[name].BeginTime > o.MaxWaitTime) tools.obj.reset(cache[name]);
if(tools.obj.getNum(cache[name], null) == Object.keys(cache[name]).length){
cache[name].BeginTime = (new Date()).valueOf();
rc.hset('sf/'+name, 'BeginTime', cache[name].BeginTime);
}
if(t == 'qos'){
rc.hset('sf/clientStatus', name, 'connect');
if(s == -1){
rc.hset("sf/"+name, 'qos', '-1');
cache[name].qos = -1;
}else{
rc.get('sf/sync/'+s, function(err, res){
if(err){
rc.hset("sf/"+name, 'qos', '-1');
cache[name].qos = -1;
}else{
rc.hset("sf/"+name, 'qos', (new Date()).valueOf() - res);
cache[name].qos = (new Date()).valueOf() - res;
}
if(tools.obj.getNum(cache[name], null) == 1){
cache[name].EndTime = (new Date()).valueOf();
rc.hset('sf/'+name, 'EndTime', cache[name].EndTime);
rc.publish('sf/channel/'+name, 'ok');
if(o.debug) console.log(cache[name]);
if(o.debug && false){
rc.hkeys("sf/"+name, function (err, replies) {
console.log(replies.length + " replies:");
replies.forEach(function (reply, i) {
rc.hget("sf/"+name, reply, function(err, res){
console.log(reply+': '+res);
});
});
});
}
f(cache[name]);
tools.obj.reset(cache[name]);
}
});
}
}
else if(cache[name].hasOwnProperty(t)) cache[name][t] = s;
if(tools.obj.getNum(cache[name], null) == 1){
cache[name].EndTime = (new Date()).valueOf();
rc.hset('sf/'+name, 'EndTime', cache[name].EndTime);
rc.publish('sf/channel/'+name, 'ok');
if(o.debug) console.log(cache[name]);
if(o.debug && false){
rc.hkeys("sf/"+name, function (err, replies) {
console.log(replies.length + " replies:");
replies.forEach(function (reply, i) {
rc.hget("sf/"+name, reply, function(err, res){
console.log(reply+': '+res);
});
});
});
}
f(cache[name]);
tools.obj.reset(cache[name]);
}
},
},
obj: {
reset: function(obj){
for(i in obj){
obj[i] = null;
}
},
getNum: function(obj, t){
var c = 0;
for(i in obj){
if(obj[i] == t) c ++;
}
return c;
},
},
sql: {
node0:{
push: (obj)=>{tools.sql.node.push(0, obj)},
},
node1:{
push: (obj)=>{tools.sql.node.push(1, obj)},
},
node:{
push: function(id, obj){
var addSql = 'INSERT INTO node(timestamp,id,status,qos,batterylevel,waterswitch,temperature,humidity) VALUES(?,?,?,?,?,?,?,?)';
var addSqlParams = [new Date(obj.BeginTime).toISOString().slice(0, 19).replace('T', ' '), id, obj.status, obj.qos, obj.batteryLevel, obj.waterSwitch, obj.temperature, obj.humidity];
sqlCnt.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
return;
}
});
},
},
station:{
push: function(obj){
var addSql = 'INSERT INTO station(timestamp,status,qos,batterylevel,light,temperature,humidity,rainfall,co,nh3,airpressure) VALUES(?,?,?,?,?,?,?,?,?,?,?)';
var addSqlParams = [new Date(obj.BeginTime).toISOString().slice(0, 19).replace('T', ' '), obj.status, obj.qos, obj.batteryLevel, obj.light, obj.temperature, obj.humidity, obj.rainfall, obj.CO, obj.NH3, obj.airPressure];
sqlCnt.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
return;
}
});
},
},
waterSys:{
push: function(obj){
var addSql = 'INSERT INTO watersys(timestamp,status,qos,pump0,pump1) VALUES(?,?,?,?,?)';
var addSqlParams = [new Date(obj.BeginTime).toISOString().slice(0, 19).replace('T', ' '), obj.status, obj.qos, obj.pump0, obj.pump1];
sqlCnt.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
return;
}
});
},
},
mqttLog:{
push: function(obj){
var addSql = 'INSERT INTO mqtt_log(timestamp,client,event,delay) VALUES(?,?,?,?)';
var addSqlParams = [(new Date()).toISOString().slice(0, 19).replace('T', ' '), obj.client, obj.event, obj.delay];
sqlCnt.query(addSql,addSqlParams,function (err, result) {
if(err){
console.log('[INSERT ERROR] - ',err.message);
return;
}
});
},
},
},
}
/* timer */
timer = function(){
return setInterval(function(){
var key = Math.floor(Math.random()*100);
rc.set('sf/sync/'+key, (new Date()).valueOf());
mqtt_broker.publish({ topic:'qos/sync', payload: key.toString()});
setTimeout(function(key){
key += 100;
rc.set('sf/sync/'+key, (new Date()).valueOf());
mqtt_broker.publish({topic:'qos/sync', payload: key.toString()});
setTimeout(function(key){
key += 100;
rc.set('sf/sync/'+key, (new Date()).valueOf());
mqtt_broker.publish({topic:'qos/sync', payload: key.toString()});
setTimeout(function(key){
key += 100;
rc.set('sf/sync/'+key, (new Date()).valueOf());
mqtt_broker.publish({topic:'qos/sync', payload: key.toString()});
}, 10000, key);
}, 10000, key);
}, 10000, key);
}, o.intervalTime);
}();
setInterval(function(){
rc.hkeys('sf/LastConnectTime', function(err, keys){
if(!err){
keys.forEach(function(key, i){
rc.hget('sf/clientStatus', key, function(err3, clientStatus){
if(!err3){
if(clientStatus != 'disconnect'){
rc.hget('sf/LastConnectTime', key, function(err2, val){
if(!err2){
if((new Date()).valueOf() - val > o.MaxWaitTime + o.MaxTryTimes * o.intervalTime){
rc.hset('sf/clientStatus', key, 'disconnect');
}
}
});
}
}
});
});
}
});
rc.hkeys('sf/clientStatus', function(err, keys){
if(!err){
keys.forEach(function(key, i){
rc.hget('sf/clientStatus', key, function(err2, val){
if(!err2){
if(val != fStatus[key]){
rc.hget('sf/'+key, 'qos', function(err3, qos){
if(!err3){
tools.sql.mqttLog.push({
client: key,
event: val,
delay: qos
});
fStatus[key] = val;
console.log((new Date((new Date()).getTime())).toLocaleString()+' - '+key+': '+val);
}
});
}
}
});
});
}
})
}, o.CheckMinTime);
return o;
};
/* exports */
exports.broker = mqtt_server;