Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multiple PowerRouters to the webinterface #5

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`).
122 changes: 103 additions & 19 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand All @@ -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)
Expand Down
14 changes: 13 additions & 1 deletion src/webinterface.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 + '<a href="/?powerRouterId=' + powerRouterId + '" target="_blank">' + powerRouterId + '</a>' + ', '
}
powerRouterIdsString = powerRouterIdsString.slice(0, -2)
return powerRouterIdsString
}

const buildHeader = () => `<!DOCTYPE html>
<html lang="de">
<head>
Expand Down Expand Up @@ -161,8 +171,10 @@ const buildNoDataMessage = () => `
</div>
`

const buildWebinterface = (data, stats, status, config, lastUpdate, updateAvailable = false) => `
const buildWebinterface = (powerRouterId, powerRouterIds, data, stats, status, config, lastUpdate, updateAvailable = false) => `
${buildHeader()}
${powerRouterId ? '<p>Aktuelle Powerrouter ID: ' + powerRouterId + '</p>' : ''}
${powerRouterIds ? '<p>Verfügbare Powerrouter IDs: ' + buildPowerRouterIds(powerRouterIds) + '</p>' : ''}
${lastUpdate ? buildTable(data, stats, status) : buildNoDataMessage()}
${buildFooter(lastUpdate, updateAvailable, config.webReload)}
`
Expand Down