diff --git a/README.md b/README.md index 72f141f..a24ef0f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # powerinterface -This is a simple web interface that shows statistics about your Nedap PowerRouter. It aims to serve as a self-hosted replacement for the discontinued mypowerrouter.com interface. +This is a simple web interface that shows statistics about your Nedap PowerRouters. It aims to serve as a self-hosted replacement for the discontinued mypowerrouter.com interface. ## Features -- Show current PowerRouter stats of your PowerRouter +- Show current PowerRouter stats of multiple PowerRouters - Forward data to an InfluxDB ## Documentation @@ -50,7 +50,7 @@ Enter `http://[IP of your devie running Powerinterface]` in your browser. You sh #### Install without Docker / run from source -Make sure to have NodeJS and Git installed. +Make sure to have NodeJS (>=12) and Git installed. ```bash git clone https://github.com/ngrie/powerinterface.git @@ -121,8 +121,8 @@ actions: port: 8086 ``` -Of course, adjust host, database, username, password and port as needed (if you run InfluxDB on the same device without changing any configs the above values should work). +Of course, adjust host, database, username, password and port as needed (if you run InfluxDB on the same device without changing any configs the above values should work). If you're using Powerinterface with Docker localhost (127.0.0.1) doesn't work here because the Docker container runs in its own separated environment. Because of that you have to give the local ip address of the device. -If you installed Powerinterface using Docker, edit `docker-compose.yml` and uncomment (remove the `#`) the last four lines. +If you installed Powerinterface using Docker, edit `docker-compose.yml` and uncomment (remove the `#`) the last four lines. Then you need to update your Docker container with `docker-compose up -d` Afterwards, restart Powerinterface (Docker: `docker-compose restart`, systemd: `systemctl restart powerinterface`). diff --git a/server.js b/server.js index 9ba1830..28f6da2 100644 --- a/server.js +++ b/server.js @@ -70,44 +70,117 @@ app.use(express.json()) app.use(morgan('combined')) let updateAvailable = false -let currentData = {} -let currentStatuses = {} -let events = [] -let isWinterMode = false -let isMaintenanceCharge = false -let lastUpdate = null -let stats = initStats() +let powerRouters = new Map() + +// creates and initializes new powerrouter object and stores it in a map +const addNewPowerRouter = (powerRouterId) => { + let powerRouter = {} + powerRouter.powerRouterId = powerRouterId + powerRouter.currentData = {} + powerRouter.currentStatuses = {} + powerRouter.lastUpdate = null + powerRouter.stats = initStats() + powerRouter.isWinterMode = false + powerRouter.isMaintenanceCharge = false + powerRouter.events = [] + powerRouters.set(powerRouterId, powerRouter) +} runUpdateCheck(CURRENT_VERSION, () => updateAvailable = true) app.get('/', (req, res) => { - res.send(buildWebinterface(currentData, stats, { isWinterMode, isMaintenanceCharge }, { webReload }, lastUpdate, updateAvailable)) + // get the powerrouter ID from url query + // change the powerrouter ID to the first one which sent data already when no query is given + let powerRouterId = req.query.powerRouterId + if (powerRouters.size > 0 && powerRouterId === undefined) { + powerRouterId = powerRouters.keys().next().value + } + + if (powerRouters.has(powerRouterId)) { + // show webinterface with data of the correct powerrouter + let powerRouter = powerRouters.get(powerRouterId) + res.send(buildWebinterface(powerRouterId, powerRouters.keys(), powerRouter.currentData, powerRouter.stats, { + isWinterMode:powerRouter.isWinterMode, + isMaintenanceCharge:powerRouter.isMaintenanceCharge + }, {webReload}, powerRouter.lastUpdate, updateAvailable)) + } else { + // show no data page and selected, available IDs when available if no powerrouter sent data or this ID isn't known + let powerRouterIds = powerRouters.size > 0 ? powerRouters.keys() : null + res.send(buildWebinterface(powerRouterId, powerRouterIds, {}, initStats(), { + isWinterMode:false, + isMaintenanceCharge:false + }, {webReload}, null, updateAvailable)) + } }) app.get('/values.json', (req, res) => { - res.type('json').send(currentData) + // get the powerrouter ID from url query + // change the powerrouter ID to the first one which sent data already when no query is given + let powerRouterId = req.query.powerRouterId + if (powerRouters.size > 0 && powerRouterId === undefined) { + powerRouterId = powerRouters.keys().next().value + } + + if (powerRouters.has(powerRouterId)) { + // send current data of the correct ID in json format + res.type('json').send(powerRouters.get(powerRouterId).currentData) + } else { + // send empty object if no powerrouter sent data or this ID isn't known + res.type('json').send({}) + } }) app.get('/status.json', (req, res) => { - res.type('json').send(currentStatuses) + // get the powerrouter ID from url query + // change the powerrouter ID to the first one which sent data already when no query is given + let powerRouterId = req.query.powerRouterId + if (powerRouters.size > 0 && powerRouterId === undefined) { + powerRouterId = powerRouters.keys().next().value + } + + if (powerRouters.has(powerRouterId)) { + // send current status of the correct ID in json format + res.type('json').send(powerRouters.get(powerRouterId).currentStatuses) + } else { + // send empty object if no powerrouter sent data or this ID isn't known + res.type('json').send({}) + } }) app.get('/events.json', (req, res) => { - res.type('json').send([...events].reverse()) + // get the powerrouter ID from url query + // change the powerrouter ID to the first one which sent data already when no query is given + let powerRouterId = req.query.powerRouterId + if (powerRouters.size > 0 && powerRouterId === undefined) { + powerRouterId = powerRouters.keys().next().value + } + + if (powerRouters.has(powerRouterId)) { + // send events list of the correct ID in json format + res.type('json').send([...powerRouters.get(powerRouterId).events].reverse()) + } else { + // send empty list if no powerrouter sent data or this ID isn't known + res.type('json').send([]) + } }) app.post('/logs.json', (req, res) => { try { const { data, statuses } = paramConverter(req.body, paramDefinition) - currentData = data - currentStatuses = statuses - lastUpdate = new Date() - updateStats(data, stats) + const powerRouterId = req.body.header.powerrouter_id + if (!powerRouters.has(powerRouterId)) { + addNewPowerRouter(powerRouterId) + } + // update the correct powerrouter with the newly received data + let powerRouter = powerRouters.get(powerRouterId) + powerRouter.currentData = data + powerRouter.currentStatuses = statuses + powerRouter.lastUpdate = new Date() + updateStats(data, powerRouter.stats) if (logRequests) { logUnknownRequest(req) } - const powerRouterId = req.body.header.powerrouter_id actions.forEach((action, index) => { try { action.update({ data, powerRouterId }) @@ -134,14 +207,25 @@ app.post('/logs.json', (req, res) => { app.post('/events.json', (req, res) => { try { const event = parseEvent(req.body.event) + let isWinterMode = false + let isMaintenanceCharge = false if (isWinterModeStartedEvent(event)) isWinterMode = true if (isWinterModeEndedEvent(event)) isWinterMode = false if (isMaintenanceChargeStartedEvent(event)) isMaintenanceCharge = true if (isMaintenanceChargeEndedEvent(event)) isMaintenanceCharge = false - events.push(event) - if (events.length > 300) { - events.shift() + const powerRouterId = req.body.header.powerrouter_id + if (!powerRouters.has(powerRouterId)) { + addNewPowerRouter(powerRouterId) + } + // update the correct powerrouter with the newly received data + let powerRouter = powerRouters.get(powerRouterId) + powerRouter.isWinterMode = isWinterMode + powerRouter.isMaintenanceCharge = isMaintenanceCharge + + powerRouter.events.push(event) + if (powerRouter.events.length > 300) { + powerRouter.events.shift() } } catch (e) { console.error(e) diff --git a/src/webinterface.js b/src/webinterface.js index 53c0d01..01edd92 100644 --- a/src/webinterface.js +++ b/src/webinterface.js @@ -15,6 +15,16 @@ const buildStatsMonthlyLabel = (stats) => `seit ${stats.dataSince.monthly.toLoca { day: '2-digit', month:'2-digit' } )}` +// build a string with links to all given powerrouter IDs +const buildPowerRouterIds = (powerRouterIds) => { + let powerRouterIdsString = '' + for (let powerRouterId of powerRouterIds) { + powerRouterIdsString = powerRouterIdsString + '' + powerRouterId + '' + ', ' + } + powerRouterIdsString = powerRouterIdsString.slice(0, -2) + return powerRouterIdsString +} + const buildHeader = () => `
@@ -161,8 +171,10 @@ const buildNoDataMessage = () => ` ` -const buildWebinterface = (data, stats, status, config, lastUpdate, updateAvailable = false) => ` +const buildWebinterface = (powerRouterId, powerRouterIds, data, stats, status, config, lastUpdate, updateAvailable = false) => ` ${buildHeader()} +${powerRouterId ? 'Aktuelle Powerrouter ID: ' + powerRouterId + '
' : ''} +${powerRouterIds ? 'Verfügbare Powerrouter IDs: ' + buildPowerRouterIds(powerRouterIds) + '
' : ''} ${lastUpdate ? buildTable(data, stats, status) : buildNoDataMessage()} ${buildFooter(lastUpdate, updateAvailable, config.webReload)} `