170 lines
4.1 KiB
JavaScript
170 lines
4.1 KiB
JavaScript
var express = require('express');
|
|
var router = express.Router();
|
|
var mqtt = require('mqtt');
|
|
var moment = require('moment');
|
|
var fs = require('fs/promises');
|
|
var crypto = require('crypto');
|
|
|
|
var DEFAULT_BRENNDAUER = 300;
|
|
var brenndauer = DEFAULT_BRENNDAUER;
|
|
|
|
var MQTTHOST = process.env.MQTTHOST || 'localhost';
|
|
var MQTTPORT = process.env.MQTTPORT || 1883;
|
|
var MQTTUSR = process.env.MQTTUSR || '';
|
|
var MQTTPWD = process.env.MQTTPWD || '';
|
|
var TOPIC = process.env.TOPIC || 'sonoff';
|
|
var SWITCH_API_TOKEN = process.env.SWITCH_API_TOKEN || '';
|
|
|
|
var switchOffTimer;
|
|
var state = {
|
|
connect: 'disconnected',
|
|
relais: 'UNKNOWN',
|
|
offtime: undefined,
|
|
lastError: undefined
|
|
};
|
|
|
|
var client = mqtt.connect('mqtt://' + MQTTHOST + ':' + MQTTPORT, {
|
|
username: MQTTUSR,
|
|
password: MQTTPWD
|
|
});
|
|
|
|
console.log('Start: ', moment().format('YYYY-MM-DD HH:mm'));
|
|
|
|
client.on('connect', function() {
|
|
state.connect = 'connected';
|
|
state.lastError = undefined;
|
|
client.subscribe('stat/' + TOPIC + '/POWER', function(err) {
|
|
if (err) {
|
|
state.lastError = 'subscribe failed: ' + err.message;
|
|
}
|
|
});
|
|
});
|
|
|
|
client.on('reconnect', function() {
|
|
state.connect = 'reconnect';
|
|
});
|
|
|
|
client.on('offline', function() {
|
|
state.connect = 'offline';
|
|
});
|
|
|
|
client.on('error', function(err) {
|
|
state.lastError = err.message;
|
|
});
|
|
|
|
client.on('message', function(_topic, message) {
|
|
state.relais = message.toString();
|
|
if (state.relais === 'OFF') {
|
|
state.offtime = undefined;
|
|
}
|
|
});
|
|
|
|
function getResponse() {
|
|
return {
|
|
connect: state.connect,
|
|
relais: state.relais,
|
|
offtime: state.offtime,
|
|
lastError: state.lastError
|
|
};
|
|
}
|
|
|
|
function doPublish(payload) {
|
|
client.publish('cmnd/' + TOPIC + '/Power', payload, function(err) {
|
|
if (err) {
|
|
state.lastError = err.message;
|
|
}
|
|
});
|
|
|
|
if (payload === 'On') {
|
|
clearTimeout(switchOffTimer);
|
|
state.offtime = moment().add(brenndauer, 's').format('HH.mm');
|
|
switchOffTimer = setTimeout(doPublish, brenndauer * 1000, 'Off');
|
|
} else if (payload === 'Off') {
|
|
clearTimeout(switchOffTimer);
|
|
state.offtime = undefined;
|
|
}
|
|
}
|
|
|
|
// Read optional runtime config.
|
|
(async function loadConfig() {
|
|
try {
|
|
var json = await fs.readFile('config/config.json', 'utf8');
|
|
var cfg = JSON.parse(json);
|
|
if (cfg.brenndauer !== undefined) {
|
|
brenndauer = Number(cfg.brenndauer) || DEFAULT_BRENNDAUER;
|
|
}
|
|
} catch (err) {
|
|
state.lastError = 'config load failed: ' + err.message;
|
|
}
|
|
})();
|
|
|
|
// Query initial state once at startup.
|
|
doPublish('');
|
|
|
|
function timingSafeEqualString(a, b) {
|
|
var aBuf = Buffer.from(String(a));
|
|
var bBuf = Buffer.from(String(b));
|
|
if (aBuf.length !== bBuf.length) {
|
|
return false;
|
|
}
|
|
return crypto.timingSafeEqual(aBuf, bBuf);
|
|
}
|
|
|
|
function extractRequestToken(req) {
|
|
var authHeader = req.get('authorization') || '';
|
|
if (authHeader.startsWith('Bearer ')) {
|
|
return authHeader.slice(7).trim();
|
|
}
|
|
|
|
var apiKeyHeader = req.get('x-api-key');
|
|
if (apiKeyHeader) {
|
|
return apiKeyHeader;
|
|
}
|
|
|
|
if (typeof req.query.token === 'string') {
|
|
return req.query.token;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
router.use(function(req, res, next) {
|
|
if (!SWITCH_API_TOKEN) {
|
|
return next();
|
|
}
|
|
|
|
var providedToken = extractRequestToken(req);
|
|
if (!providedToken || !timingSafeEqualString(providedToken, SWITCH_API_TOKEN)) {
|
|
return res.status(401).json({ error: 'unauthorized' });
|
|
}
|
|
|
|
return next();
|
|
});
|
|
|
|
router.get('/:cmd', function(req, res) {
|
|
var cmd = req.params.cmd;
|
|
|
|
if (cmd === 'get_status') {
|
|
doPublish('');
|
|
return res.json(getResponse());
|
|
}
|
|
|
|
if (cmd === 'switch_on') {
|
|
doPublish('On');
|
|
return res.json(getResponse());
|
|
}
|
|
|
|
if (cmd === 'switch_off') {
|
|
doPublish('Off');
|
|
return res.json(getResponse());
|
|
}
|
|
|
|
if (cmd === 'check') {
|
|
return res.json(getResponse());
|
|
}
|
|
|
|
return res.status(400).json({ error: 'invalid command' });
|
|
});
|
|
|
|
module.exports = router;
|