Move project to GitHub and hide Telegram config

This commit is contained in:
2018-03-26 15:39:27 -04:00
commit 74c384a652
26 changed files with 15047 additions and 0 deletions

13
app/config.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
export interface IConfig {
port: number;
refreshInterval: number;
deviceDebounceInterval: number;
botToken: string;
chatId: string;
debugChatId: string;
debug: boolean;
enableTelegram: boolean;
}

17
app/config.ts Normal file
View File

@@ -0,0 +1,17 @@
import { IConfig } from './config.d';
class Config implements IConfig {
port = 80;
refreshInterval = 500;
deviceDebounceInterval = 10000;
botToken = 'BOT-TOKEN';
chatId = 'CHAT-ID';
debugChatId = 'DEBUG-CHAT-ID';
debug = false;
enableTelegram = true;
}
export const config = new Config();

100
app/device.ts Normal file
View File

@@ -0,0 +1,100 @@
import _ = require('underscore');
import bunyan = require('bunyan');
import gpio = require('onoff');
export var log: bunyan.Logger;
export class DeviceConfiguration {
key: string;
pin: number;
}
export class DeviceListConfiguration {
pollInterval: number;
deviceConfiguration: Array<DeviceConfiguration>;
deviceUpdateCallback: (device: Device, initialUpdate: boolean) => void;
}
export class DeviceList {
private deviceList: { [key: string]: Device };
constructor(private configuration: DeviceListConfiguration) {
this.deviceList = {};
configuration.deviceConfiguration.forEach((deviceConfiguration: DeviceConfiguration) => {
this.createDevice(deviceConfiguration.key, deviceConfiguration.pin);
});
this.update(true);
setInterval(this.update.bind(this), configuration.pollInterval);
}
private createDevice(key: string, pin: number): Device {
const device = new Device(key, pin);
this.deviceList[key] = device;
return device;
}
getDevices(): Array<Device> {
return _.toArray<Device>(this.deviceList);
}
getStatus(): any {
var status: { [key: string]: boolean } = {};
_.each(this.deviceList, (device: Device) => {
status[device.key] = device.value;
});
return status;
}
private update(initialUpdate: boolean = false) {
_.each(this.deviceList, (device: Device) => {
var deviceChanged = device.update();
if (deviceChanged) {
this.configuration.deviceUpdateCallback(device, initialUpdate);
if (log) {
log.info(`DeviceList.Update - ${device.toString()}`);
}
}
});
}
}
export class Device {
private gpioPin: gpio.Gpio;
value: boolean;
constructor(public key: string, pin: number) {
this.gpioPin = new gpio.Gpio(pin, 'in', 'both');
}
toString(): string {
return `Device: ${this.key} = ${this.value}`;
}
getStatus(): any {
const status: { [key: string]: boolean } = {};
status[this.key] = this.value;
return status;
}
update(): boolean {
const newValue = this.gpioPin.readSync() === 0;
const changed = (this.value !== newValue);
this.value = newValue;
return changed;
}
}

164
app/main.ts Normal file
View File

@@ -0,0 +1,164 @@
/// <reference path="../typings/express/express.d.ts" />
/// <reference path="../typings/onoff/onoff.d.ts" />
/// <reference path="../typings/request/request.d.ts" />
/// <reference path="../typings/tsd.d.ts" />
// ---
import os = require('os');
import bunyan = require('bunyan');
import express = require('express');
import http = require('http');
import socketio = require('socket.io');
import request = require('request');
import device = require('./device');
import Device = device.Device;
import config = require('./config');
import Config = config.config;
var app = express();
var server = http.createServer(app);
var io = socketio(server);
// ---
var log = bunyan.createLogger({
name: 'laundry_monitor',
streams: [
{
type: 'rotating-file',
path: '/var/log/laundry_monitor.log',
period: '1d',
count: 3
},
{
stream: process.stdout
}
]
});
device.log = log;
// ---
log.info('Setting up devices');
var deviceListConfiguration: device.DeviceListConfiguration = {
pollInterval: Config.refreshInterval,
deviceConfiguration: [{ key: 'washer', pin: 413 }, { key: 'dryer', pin: 415 }],
deviceUpdateCallback: (device: Device, initialUpdate: boolean) => {
if (initialUpdate) {
return;
}
queueDeviceUpdate(device, undefined, 'The {name} is now {status}.');
}
};
var devices = new device.DeviceList(deviceListConfiguration);
// ---
const deviceLastState: { [key: string]: boolean } = {};
const deviceUpdateQueue: { [key: string]: number } = {};
function queueDeviceUpdate(device: Device, header: string, deviceTemplate: string) {
if (deviceUpdateQueue[device.key]) {
log.info(`queueDeviceUpdate - Stopping old timer for device: ${device.key}`);
clearTimeout(deviceUpdateQueue[device.key]);
}
log.info(`queueDeviceUpdate - Starting new timer for device: ${device.key}`);
deviceUpdateQueue[device.key] = setTimeout(() => {
if (deviceLastState[device.key] !== device.value) {
sendTelegramDeviceUpdate([device], header, deviceTemplate);
emitStatus(device.getStatus());
} else {
log.info(`queueDeviceUpdate - No change for device: ${device.key}`);
}
delete deviceUpdateQueue[device.key];
}, Config.deviceDebounceInterval) as any;
}
function sendTelegramDeviceUpdate(devices: Array<Device>, header: string, deviceTemplate: string) {
let telegramMessage = header || '';
devices.forEach((device: Device) => {
deviceLastState[device.key] = device.value;
const deviceLine = deviceTemplate.replace('{name}', device.key).replace('{status}', device.value ? 'ON' : 'OFF');
telegramMessage += `${deviceLine}\n`;
});
if (telegramMessage.length > 0) {
sendTelegramMessage(telegramMessage);
}
}
function sendTelegramMessage(message: string) {
log.info(`Telegram message: ${message}`);
if (!Config.enableTelegram) {
return;
}
const sendChatId = Config.debug ? Config.debugChatId : Config.chatId;
const url = `https://api.telegram.org/bot${Config.botToken}/sendMessage?chat_id=${sendChatId}&text=${encodeURIComponent(message)}`;
request(url);
}
// ---
log.info('Starting web server');
app.get('/getStatus', (request, response) => {
const data = devices.getStatus();
response.end(JSON.stringify(data));
});
server.listen(Config.port, () => {
const host = os.hostname();
log.info(`Listening at http://${host}:${Config.port}`);
});
// ---
log.info('Starting socket.io server');
function emitStatus(status: any) {
const socketMessage = JSON.stringify(status);
log.info(`emitStatus: ${socketMessage}`);
io.emit('status', socketMessage);
}
io.on('connection', (socket: SocketIO.Socket) => {
log.info('socket.io: connection');
socket.on('getStatus', () => {
log.info('socket.io: getStatus');
emitStatus(devices.getStatus());
});
socket.on('disconnect', () => {
log.info('socket.io: disconnect');
});
});
// ---
log.info('Ready');
emitStatus(devices.getStatus());
sendTelegramDeviceUpdate(devices.getDevices(), 'Powering up!\n\n', 'The {name} is currently {status}.');