mirror of
https://github.com/ckaczor/LaundryMonitor.git
synced 2026-02-05 01:25:43 -05:00
Move project to GitHub and hide Telegram config
This commit is contained in:
13
app/config.d.ts
vendored
Normal file
13
app/config.d.ts
vendored
Normal 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
17
app/config.ts
Normal 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
100
app/device.ts
Normal 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
164
app/main.ts
Normal 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}.');
|
||||
Reference in New Issue
Block a user