Support for pulse meter

This commit is contained in:
Gunnar Skjold 2024-01-18 07:57:56 +01:00
parent e7ca408baa
commit 87ddf00afa
8 changed files with 244 additions and 52 deletions

File diff suppressed because one or more lines are too long

View File

@ -368,30 +368,37 @@
Communication<br/>
<select name="ma" bind:value={configuration.m.a} class="in-s">
<option value={0}>Passive (Push)</option>
<option value={9}>Kamstrup (Pull)</option>
<option value={2}>Pulse</option>
</select>
</div>
<div class="my-1">
<span class="float-right">Buffer size</span>
<span>Serial conf.</span>
<label class="mt-2 ml-3 whitespace-nowrap"><input name="mi" value="true" bind:checked={configuration.m.i} type="checkbox" class="rounded mb-1"/> inverted</label>
<div class="flex w-full">
<select name="mb" bind:value={configuration.m.b} class="in-f tr w-1/2">
<option value={0} disabled={configuration.m.b != 0}>Autodetect</option>
{#each [24,48,96,192,384,576,1152] as b}
<option value={b*100}>{b*100}</option>
{/each}
</select>
<select name="mp" bind:value={configuration.m.p} class="in-m" disabled={configuration.m.b == 0}>
<option value={0} disabled={configuration.m.b != 0}>-</option>
<option value={2}>7N1</option>
<option value={3}>8N1</option>
<option value={10}>7E1</option>
<option value={11}>8E1</option>
</select>
<input name="ms" type="number" bind:value={configuration.m.s} min={64} max={sysinfo.chip == 'esp8266' ? configuration.i.h.p == 3 || configuration.i.h.p == 113 ? 512 : 128 : 4096} step={64} class="in-l tr w-1/2">
{#if configuration.m.a === 2}
<div class="my-1">
<span>Pulses per kWh</span>
<input name="mb" bind:value={configuration.m.b} class="in-s tr" type="number" min={1} max={3600}/>
</div>
</div>
{:else}
<div class="my-1">
<span class="float-right">Buffer size</span>
<span>Serial conf.</span>
<label class="mt-2 ml-3 whitespace-nowrap"><input name="mi" value="true" bind:checked={configuration.m.i} type="checkbox" class="rounded mb-1"/> inverted</label>
<div class="flex w-full">
<select name="mb" bind:value={configuration.m.b} class="in-f tr w-1/2">
<option value={0} disabled={configuration.m.b != 0}>Autodetect</option>
{#each [24,48,96,192,384,576,1152] as b}
<option value={b*100}>{b*100}</option>
{/each}
</select>
<select name="mp" bind:value={configuration.m.p} class="in-m" disabled={configuration.m.b == 0}>
<option value={0} disabled={configuration.m.b != 0}>-</option>
<option value={2}>7N1</option>
<option value={3}>8N1</option>
<option value={10}>7E1</option>
<option value={11}>8E1</option>
</select>
<input name="ms" type="number" bind:value={configuration.m.s} min={64} max={sysinfo.chip == 'esp8266' ? configuration.i.h.p == 3 || configuration.i.h.p == 113 ? 512 : 128 : 4096} step={64} class="in-l tr w-1/2">
</div>
</div>
{/if}
<div class="my-1">
Voltage<br/>
<select name="md" bind:value={configuration.m.d} class="in-s">

View File

@ -17,23 +17,23 @@ export default defineConfig({
plugins: [svelte()],
server: {
proxy: {
"/data.json": "http://192.168.21.87",
"/energyprice.json": "http://192.168.21.87",
"/dayplot.json": "http://192.168.21.87",
"/monthplot.json": "http://192.168.21.87",
"/temperature.json": "http://192.168.21.87",
"/sysinfo.json": "http://192.168.21.87",
"/configuration.json": "http://192.168.21.87",
"/tariff.json": "http://192.168.21.87",
"/realtime.json": "http://192.168.21.87",
"/priceconfig.json": "http://192.168.21.87",
"/save": "http://192.168.21.87",
"/reboot": "http://192.168.21.87",
"/configfile": "http://192.168.21.87",
"/upgrade": "http://192.168.21.87",
"/mqtt-ca": "http://192.168.21.87",
"/mqtt-cert": "http://192.168.21.87",
"/mqtt-key": "http://192.168.21.87",
"/data.json": "http://192.168.233.110",
"/energyprice.json": "http://192.168.233.110",
"/dayplot.json": "http://192.168.233.110",
"/monthplot.json": "http://192.168.233.110",
"/temperature.json": "http://192.168.233.110",
"/sysinfo.json": "http://192.168.233.110",
"/configuration.json": "http://192.168.233.110",
"/tariff.json": "http://192.168.233.110",
"/realtime.json": "http://192.168.233.110",
"/priceconfig.json": "http://192.168.233.110",
"/save": "http://192.168.233.110",
"/reboot": "http://192.168.233.110",
"/configfile": "http://192.168.233.110",
"/upgrade": "http://192.168.233.110",
"/mqtt-ca": "http://192.168.233.110",
"/mqtt-cert": "http://192.168.233.110",
"/mqtt-key": "http://192.168.233.110",
}
}
})

View File

@ -30,11 +30,12 @@ ADC_MODE(ADC_VCC);
#define WDT_TIMEOUT 60
#define METER_SOURCE_NONE 0
#define METER_SOURCE_SERIAL 1
#define METER_SOURCE_GPIO 1
#define METER_SOURCE_MQTT 2
#define METER_SOURCE_ESPNOW 3
#define METER_PARSER_PASSIVE 0
#define METER_PARSER_PULSE 2
#define METER_PARSER_KAMSTRUP 9
#define METER_ERROR_NO_DATA 90
@ -79,6 +80,7 @@ ADC_MODE(ADC_VCC);
#include "MeterCommunicator.h"
#include "PassiveMeterCommunicator.h"
#include "KamstrupPullCommunicator.h"
#include "PulseMeterCommunicator.h"
#include "Uptime.h"
@ -172,6 +174,7 @@ RealtimePlot rtp;
MeterCommunicator* mc = NULL;
PassiveMeterCommunicator* passiveMc = NULL;
KamstrupPullCommunicator* kamstrupMc = NULL;
PulseMeterCommunicator* pulseMc = NULL;
bool networkConnected = false;
bool setupMode = false;
@ -193,6 +196,9 @@ bool handleVoltageCheck();
bool readHanPort();
void errorBlink();
uint8_t pulses = 0;
void onPulse();
#if defined(ESP32)
uint8_t dnsState = 0;
ip_addr_t dns0;
@ -682,9 +688,13 @@ void loop() {
if(config.isMeterChanged()) {
config.getMeterConfig(meterConfig);
if(meterConfig.source = METER_SOURCE_SERIAL) {
if(meterConfig.source = METER_SOURCE_GPIO) {
switch(meterConfig.parser) {
case METER_PARSER_PASSIVE:
if(pulseMc != NULL) {
delete pulseMc;
pulseMc = NULL;
}
if(kamstrupMc != NULL) {
delete(kamstrupMc);
kamstrupMc = NULL;
@ -697,6 +707,10 @@ void loop() {
mc = passiveMc;
break;
case METER_PARSER_KAMSTRUP:
if(pulseMc != NULL) {
delete pulseMc;
pulseMc = NULL;
}
if(passiveMc != NULL) {
delete(passiveMc);
passiveMc = NULL;
@ -708,6 +722,23 @@ void loop() {
hwSerial = kamstrupMc->getHwSerial();
mc = kamstrupMc;
break;
case METER_PARSER_PULSE:
if(kamstrupMc != NULL) {
delete(kamstrupMc);
kamstrupMc = NULL;
}
if(passiveMc != NULL) {
delete(passiveMc);
passiveMc = NULL;
}
if(pulseMc == NULL) {
pulseMc = new PulseMeterCommunicator(&Debug);
}
pulseMc->configure(meterConfig, tz);
attachInterrupt(digitalPinToInterrupt(meterConfig.rxPin), onPulse, meterConfig.rxPinPullup ? RISING : FALLING);
mc = pulseMc;
break;
default:
debugE_P(PSTR("Unknown meter parser selected: %d"), meterConfig.parser);
}
@ -1092,6 +1123,10 @@ void toggleSetupMode() {
bool readHanPort() {
if(mc == NULL) return false;
if(pulseMc != NULL) {
pulseMc->onPulse(pulses);
pulses = 0;
}
if(!mc->loop()) {
meterState.setLastError(mc->getLastError());
return false;
@ -1818,3 +1853,7 @@ void configFileParse() {
config.save();
LittleFS.end();
}
void IRAM_ATTR onPulse() {
pulses++;
}

16
src/ImpulseAmsData.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "ImpulseAmsData.h"
#include "Uptime.h"
ImpulseAmsData::ImpulseAmsData(AmsData& state, uint16_t pulsePerKwh, uint8_t pulses) {
listType = 1;
if(pulses > 0) {
lastUpdateMillis = millis64();
uint64_t lastStateMillis = state.getLastUpdateMillis();
if(lastStateMillis > 0) {
uint64_t ms = (lastUpdateMillis - lastStateMillis) / pulses;
activeImportPower = (1000.0 / pulsePerKwh) / (((float) ms) / 3600000.0);
}
} else {
lastUpdateMillis = state.getLastUpdateMillis();
}
}

11
src/ImpulseAmsData.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef _IMPULSEAMSDATA_H
#define _IMPULSEAMSDATA_H
#include "AmsData.h"
class ImpulseAmsData : public AmsData {
public:
ImpulseAmsData(AmsData &state, uint16_t pulsePerKwh, uint8_t pulses);
};
#endif

View File

@ -0,0 +1,78 @@
#include "PulseMeterCommunicator.h"
#include "Uptime.h"
PulseMeterCommunicator::PulseMeterCommunicator(RemoteDebug* debugger) {
this->debugger = debugger;
}
void PulseMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
this->meterConfig = meterConfig;
this->configChanged = false;
this->tz = tz;
setupGpio();
}
bool PulseMeterCommunicator::loop() {
return updated || !initialized;
}
AmsData* PulseMeterCommunicator::getData(AmsData& meterState) {
if(!initialized) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Initializing pulse meter state\n"));
state.apply(meterState);
initialized = true;
return NULL;
}
updated = false;
AmsData* ret = new AmsData();
ret->apply(state);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Returning AMS data, list type: %d\n"), ret->getListType());
return ret;
}
int PulseMeterCommunicator::getLastError() {
return 0;
}
bool PulseMeterCommunicator::isConfigChanged() {
return this->configChanged;
}
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
void PulseMeterCommunicator::setupGpio() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
if(meterConfig.rxPin != NOT_A_PIN) {
pinMode(meterConfig.rxPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT_PULLDOWN);
}
if(meterConfig.txPin != NOT_A_PIN) {
pinMode(meterConfig.txPin, OUTPUT);
digitalWrite(meterConfig.txPin, HIGH);
}
}
void PulseMeterCommunicator::onPulse(uint8_t pulses) {
uint64_t now = millis64();
if(initialized && pulses == 0) {
if(now - lastUpdate > 10000) {
ImpulseAmsData update(state, meterConfig.baud, pulses);
state.apply(update);
updated = true;
lastUpdate = now;
}
return;
}
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("PULSE\n"));
if(!initialized) {
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Pulse communicator not initialized\n"));
return;
}
ImpulseAmsData update(state, meterConfig.baud, pulses);
state.apply(update);
updated = true;
lastUpdate = now;
}

View File

@ -0,0 +1,41 @@
/**
* @copyright Utilitech AS 2024
* License: Fair Source
*
*/
#ifndef _PULSEMETERCOMMUNICATOR_H
#define _PULSEMETERCOMMUNICATOR_H
#include "MeterCommunicator.h"
#include "RemoteDebug.h"
#include "AmsConfiguration.h"
#include "Timezone.h"
#include "ImpulseAmsData.h"
class PulseMeterCommunicator : public MeterCommunicator {
public:
PulseMeterCommunicator(RemoteDebug* debugger);
void configure(MeterConfig&, Timezone*);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void onPulse(uint8_t pulses);
protected:
RemoteDebug* debugger = NULL;
MeterConfig meterConfig;
bool configChanged = false;
Timezone* tz;
bool updated = false;
bool initialized = false;
AmsData state;
uint64_t lastUpdate = 0;
void setupGpio();
};
#endif