Skip to content

Commit

Permalink
feat: Support for SunSpec inverter readings
Browse files Browse the repository at this point in the history
Fixes #7
  • Loading branch information
Stephan van Rooij committed May 30, 2020
1 parent 71cf818 commit 9804754
Show file tree
Hide file tree
Showing 11 changed files with 166 additions and 16 deletions.
32 changes: 32 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"homepage": "https://github.com/svrooij/smartmeter2mqtt#readme",
"dependencies": {
"@serialport/parser-readline": "^8.0.6",
"@svrooij/sunspec": "^0.9.0",
"@svrooij/tcp-server": "^1.0.1",
"crc": "^3.8.0",
"express": "^4.17.1",
Expand Down
18 changes: 17 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export interface MqttConfig {
url: string;
}

export interface SunspecConfig {
host: string;
port: number;
}

export interface OutputConfig {
debug: boolean;
jsonSocket?: number;
Expand All @@ -32,7 +37,7 @@ export interface Config {
socket?: string;

outputs: OutputConfig;

solar?: SunspecConfig;
}

export class ConfigLoader {
Expand Down Expand Up @@ -64,6 +69,9 @@ export class ConfigLoader {
.conflicts('port', 'socket')
.describe('debug', 'Enable debug output')
.boolean('debug')
.describe('sunspec-modbus', 'IP of solar inverter with modbus TCP enabled')
.describe('sunspec-modbus-port', 'modbus TCP port')
.number('sunspec-modbus-port')
.number('web-server')
.number('tcp-server')
.number('raw-tcp-server')
Expand All @@ -75,6 +83,7 @@ export class ConfigLoader {
'post-interval': 300,
'mqtt-topic': 'smartmeter',
'mqtt-discovery-prefix': 'homeassistant',
'sunspec-modbus-port': 502,
})
.wrap(80)
.version()
Expand Down Expand Up @@ -121,6 +130,13 @@ export class ConfigLoader {
config.outputs.webserver = args['web-server'];
}

if (args['sunspec-modbus']) {
config.solar = {
host: args['sunspec-modbus'],
port: args['sunspec-modbus-port'],
} as SunspecConfig;
}

return config;
}
}
16 changes: 16 additions & 0 deletions src/dsmr-message.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import GasValue from './gas-value';


/**
* Properties in this base class are used by some outputs.
* By defining them here we set the type instead of all possible types.
Expand Down Expand Up @@ -91,6 +92,21 @@ interface DsmrMessageBase {
* @memberof DsmrMessageBase
*/
totalT2Use?: number;

/**
* Number of watts your solar panels are producing.
*
* @type {number}
* @memberof DsmrMessageBase
*/
solarProduction?: number;
/**
* This is the solar production - calculated usage. Should show how much you are actually using.
*
* @type {number}
* @memberof DsmrMessageBase
*/
houseUsage?: number;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Smartmeter {
console.log('----------------------------------------');
}

start(): void {
public async start(): Promise<void> {
if (this.config.serialPort && this.config.serialPort.length > 0) {
console.log('- Read serial port %s', this.config.serialPort);
this.reader.startWithSerialPort(this.config.serialPort);
Expand All @@ -41,6 +41,9 @@ class Smartmeter {
console.warn('Port or socket required');
process.exit(2);
}
if (this.config.solar) {
await this.reader.enableSubspec(this.config.solar.host, this.config.solar.port);
}
this.startOutputs();
}

Expand Down
12 changes: 12 additions & 0 deletions src/output/mqtt-output.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import mqtt, { MqttClient, IClientOptions } from 'mqtt';
import { SunspecResult } from '@svrooij/sunspec/lib/sunspec-result';
import Output from './output';
import P1ReaderEvents from '../p1-reader-events';
import { MqttConfig } from '../config';
import P1Reader from '../p1-reader';
import DsmrMessage from '../dsmr-message';


export default class MqttOutput extends Output {
private mqtt?: MqttClient;

Expand All @@ -19,16 +21,22 @@ export default class MqttOutput extends Output {
this.mqtt.on('connect', () => {
this.mqtt?.publish(`${this.config.prefix}/connected`, '2', { qos: 0, retain: true });
});

p1Reader.on(P1ReaderEvents.ParsedResult, (data) => {
this.publishData(data);
if (this.config.discovery && !this.discoverySend) {
this.publishAutoDiscovery(data);
this.discoverySend = true;
}
});

p1Reader.on(P1ReaderEvents.UsageChanged, (data) => {
this.publishUsage(data);
});

p1Reader.on(P1ReaderEvents.SolarResult, (data) => {
this.publishSolar(data);
});
}

async close(): Promise<void> {
Expand Down Expand Up @@ -73,6 +81,10 @@ export default class MqttOutput extends Output {
}
}

private publishSolar(data: SunspecResult): void {
this.sendToMqtt('solar', data);
}

private sendToMqtt(topicSuffix: string, data: any): void {
this.mqtt?.publish(this.getTopic(topicSuffix), JSON.stringify(data), { qos: 0, retain: true });
}
Expand Down
17 changes: 17 additions & 0 deletions src/output/web-server.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import http, { Server } from 'http';
import WebSocket from 'ws';
import path from 'path';
import { SunspecResult } from '@svrooij/sunspec/lib/sunspec-result';
import Output from './output';
import P1ReaderEvents from '../p1-reader-events';
import P1Reader from '../p1-reader';
import DsmrMessage from '../dsmr-message';

import express = require('express');


export default class WebServer extends Output {
private server?: Server;

private wsServer?: WebSocket.Server;

private lastReading?: DsmrMessage;
private lastSolarReading?: SunspecResult;

private checkTimeout?: NodeJS.Timeout;

Expand All @@ -27,6 +30,11 @@ export default class WebServer extends Output {
p1Reader.on(P1ReaderEvents.ParsedResult, (data) => {
this.setReading(data);
});

p1Reader.on(P1ReaderEvents.SolarResult, (data) => {
this.lastSolarReading = data;
});

if (this.startServer === true) {
this.startWebserver();
}
Expand All @@ -51,6 +59,7 @@ export default class WebServer extends Output {
});
}
app.get('/api/reading', (req, res) => this.getReading(req, res));
app.get('/api/solar', (req, res) => this.getSolarReading(req, res));
app.use(express.static(path.join(__dirname, 'wwwroot'), { index: 'index.html' }));
this.server.listen(this.port);
this.checkTimeout = setInterval(() => { this.checkSockets(); }, 10000);
Expand All @@ -73,6 +82,14 @@ export default class WebServer extends Output {
}
}

private getSolarReading(req: any, res: any): void {
if (this.lastSolarReading) {
res.json(this.lastSolarReading);
} else {
res.status(400).json({ err: 'No reading just yet!' });
}
}

private setReading(newReading: DsmrMessage): void {
this.lastReading = newReading;
this.broadcastMessage(newReading);
Expand Down
37 changes: 30 additions & 7 deletions src/output/wwwroot/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,38 @@
<a href="#" data-target="mobile-demo" class="sidenav-trigger"><i class="material-icons">menu</i></a>
<ul class="right hide-on-med-and-down">
<li><a href="https://github.com/svrooij/smartmeter2mqtt">Github</a></li>
<li><a href="/api/reading">Show data</a></li>
<li><a href="/api/reading">Energy data</a></li>
<li><a href="/api/solar">Solar data</a></li>
</ul>
</div>
</nav>

<ul class="sidenav" id="mobile-demo">
<li><a href="https://github.com/svrooij/smartmeter2mqtt">Github</a></li>
<li><a href="/api/reading">Show data</a></li>
<li><a href="/api/reading">Energy data</a></li>
<li><a href="/api/solar">Solar data</a></li>
</ul>
<div class="container">
<div class="row solar hide">
<div class="col s12 m6">
<div class="card">
<div class="card-content center-align">
<h3><i class="material-icons">wb_sunny</i> Solar production</h3>
<h4><span class="solarProduction"></span> watt</h4>
</div>
</div>
</div>
<div class="col s12 m6">
<div class="card">
<div class="card-content center-align">
<h3><i class="material-icons">house</i> House usage</h3>
<h4><span class="houseUsage"></span> watt</h4>
</div>
</div>
</div>

</div>

<div class="row">
<div class="col s12 m6">
<div class="card">
Expand Down Expand Up @@ -70,14 +92,15 @@ <h3><i class="material-icons delivery">wb_sunny</i> <i class="material-icons usa
</div>
</div>
<div class="col s12 m6">
<div class="card">
<div class="card-content">
<span class="card-title"><i class="material-icons">filter_drama</i> Gas</span>
<p><b><span class="totalGas"></span> m3</b><br/><small class="gasTs"></small></p>
</div>
<div class="card">
<div class="card-content">
<span class="card-title"><i class="material-icons">filter_drama</i> Gas</span>
<p><b><span class="totalGas"></span> m3</b><br/><small class="gasTs"></small></p>
</div>
</div>
</div>
</div>

</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="loader.js"></script>
Expand Down
7 changes: 7 additions & 0 deletions src/output/wwwroot/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,11 @@ function updateData(data) {
let gas = data.gas.totalUse;
gas = Math.round(gas * 100.0) / 100.0;
$('.totalGas').text(gas);

if(data.houseUsage) {
// Load solar
$('.houseUsage').text(data.houseUsage);
$('.solarProduction').text(Math.round(data.solarProduction));
$('.solar').removeClass('hide');
}
}
2 changes: 2 additions & 0 deletions src/p1-reader-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ export default class P1ReaderEvents {

/** Usage change is emitted after the parsed result. It keeps the last result to compare. */
static get UsageChanged(): string { return 'usageChanged'; }

static get SolarResult(): string { return 'solar'; }
}
Loading

0 comments on commit 9804754

Please sign in to comment.