Merge DIYglenn

This commit is contained in:
Gunnar Skjold
2019-02-14 07:48:46 +01:00
parent a954b838f3
commit 3cb433b27e
273 changed files with 84 additions and 11488 deletions

View File

@@ -0,0 +1,563 @@
/*
Name: AmsToMqttBridge.ino
Created: 3/13/2018 7:40:28 PM
Author: roarf
*/
#include <DallasTemperature.h>
#include <OneWire.h>
#include <ArduinoJson.h>
#include <MQTT.h>
#include <HanReader.h>
#include <Aidon.h>
#include <Kaifa.h>
#include <Kamstrup.h>
#include "configuration.h"
#include "accesspoint.h"
#define WIFI_CONNECTION_TIMEOUT 30000;
#define TEMP_SENSOR_PIN 5 // Temperature sensor connected to GPIO5
#define LED_PIN 2 // The blue on-board LED of the ESP
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature tempSensor(&oneWire);
long lastTempDebug = 0;
// Object used to boot as Access Point
accesspoint ap;
// WiFi client and MQTT client
WiFiClient *client;
MQTTClient mqtt(256);
// Object used for debugging
HardwareSerial* debugger = NULL;
// The HAN Port reader, used to read serial data and decode DLMS
HanReader hanReader;
// the setup function runs once when you press reset or power the board
void setup()
{
// Uncomment to debug over the same port as used for HAN communication
debugger = &Serial;
if (debugger) {
// Setup serial port for debugging
debugger->begin(2400, SERIAL_8E1);
while (!&debugger);
debugger->println("Started...");
}
// Assign pin for boot as AP
delay(1000);
pinMode(0, INPUT_PULLUP);
// Flash the blue LED, to indicate we can boot as AP now
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// Initialize the AP
ap.setup(0, Serial);
// Turn off the blue LED
digitalWrite(LED_PIN, HIGH);
if (!ap.isActivated)
{
setupWiFi();
hanReader.setup(&Serial, 2400, SERIAL_8E1, 0);
// Compensate for the known Kaifa bug
hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1);
}
}
// the loop function runs over and over again until power down or reset
void loop()
{
// Only do normal stuff if we're not booted as AP
if (!ap.loop())
{
// turn off the blue LED
digitalWrite(LED_PIN, HIGH);
// allow the MQTT client some resources
mqtt.loop();
delay(10); // <- fixes some issues with WiFi stability
// Reconnect to WiFi and MQTT as needed
if (!mqtt.connected()) {
MQTT_connect();
}
else
{
// Read data from the HAN port
readHanPort();
}
}
else
{
// Continously flash the blue LED when AP mode
if (millis() / 1000 % 2 == 0)
digitalWrite(LED_PIN, LOW);
else
digitalWrite(LED_PIN, HIGH);
}
}
void setupWiFi()
{
// Turn off AP
WiFi.enableAP(false);
// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
client = new WiFiClient();
mqtt.begin(ap.config.mqtt, *client);
// Direct incoming MQTT messages
if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0) {
mqtt.subscribe(ap.config.mqttSubscribeTopic);
mqtt.onMessage(mqttMessageReceived);
}
// Notify everyone we're here!
sendMqttData("Connected!");
}
void mqttMessageReceived(String &topic, String &payload)
{
if (debugger) {
debugger->println("Incoming MQTT message:");
debugger->print("[");
debugger->print(topic);
debugger->print("] ");
debugger->println(payload);
}
// Do whatever needed here...
// Ideas could be to query for values or to initiate OTA firmware update
}
void readHanPort()
{
if (hanReader.read())
{
// Flash LED on, this shows us that data is received
digitalWrite(LED_PIN, LOW);
// Get the list identifier
int listSize = hanReader.getListSize();
switch (ap.config.meterType)
{
case 1: // Kaifa
readHanPort_Kaifa(listSize);
break;
case 2: // Aidon
readHanPort_Aidon(listSize);
break;
case 3: // Kamstrup
readHanPort_Kamstrup(listSize);
break;
default:
debugger->print("Meter type ");
debugger->print(ap.config.meterType, HEX);
debugger->println(" is unknown");
delay(10000);
break;
}
// Flash LED off
digitalWrite(LED_PIN, HIGH);
}
}
void readHanPort_Aidon(int listSize)
{
if (listSize == (int)Aidon::List1 || listSize == (int)Aidon::List2)
{
if (listSize == (int)Aidon::List2)
{
String id = hanReader.getString((int)Aidon_List2::ListVersionIdentifier);
if (debugger) debugger->println(id);
}
// Get the timestamp (as unix time) from the package
time_t time = hanReader.getPackageTime();
if (debugger) debugger->print("Time of the package is: ");
if (debugger) debugger->println(time);
// Define a json object to keep the data
StaticJsonBuffer<500> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Any generic useful info here
root["id"] = WiFi.macAddress();
root["up"] = millis();
root["t"] = time;
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject& data = root.createNestedObject("data");
// Get the temperature too
tempSensor.requestTemperatures();
float temperature = tempSensor.getTempCByIndex(0);
data["temp"] = temperature;
// Based on the list number, get all details
// according to OBIS specifications for the meter
if (listSize == (int)Aidon::List1)
{
data["P"] = hanReader.getInt((int)Aidon_List1::ActiveImportPower);
}
else if (listSize == (int)Aidon::List2)
{
data["lv"] = hanReader.getString((int)Aidon_List2::ListVersionIdentifier);
data["id"] = hanReader.getString((int)Aidon_List2::MeterID);
data["type"] = hanReader.getString((int)Aidon_List2::MeterType);
data["P"] = hanReader.getInt((int)Aidon_List2::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Aidon_List2::ReactiveExportPower);
data["I1"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL1)) / 10;
data["I2"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL2)) / 10;
data["I3"] = ((double) hanReader.getInt((int)Aidon_List2::CurrentL3)) / 10;
data["U1"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL1)) / 10;
data["U2"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL2)) / 10;
data["U3"] = ((double) hanReader.getInt((int)Aidon_List2::VoltageL3)) / 10;
}
// Write the json to the debug port
if (debugger) {
debugger->print("Sending data to MQTT: ");
root.printTo(*debugger);
debugger->println();
}
// Make sure we have configured a publish topic
if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
return;
// Publish the json to the MQTT server
char msg[1024];
root.printTo(msg, 1024);
mqtt.publish(ap.config.mqttPublishTopic, msg);
mqtt.loop();
}
}
void readHanPort_Kamstrup(int listSize)
{
// Only care for the ACtive Power Imported, which is found in the first list
if (listSize == (int)Kamstrup::List1 || listSize == (int)Kamstrup::List2)
{
if (listSize == (int)Kamstrup::List1)
{
String id = hanReader.getString((int)Kamstrup_List1::ListVersionIdentifier);
if (debugger) debugger->println(id);
}
else if (listSize == (int)Kamstrup::List2)
{
String id = hanReader.getString((int)Kamstrup_List2::ListVersionIdentifier);
if (debugger) debugger->println(id);
}
// Get the timestamp (as unix time) from the package
time_t time = hanReader.getPackageTime();
if (debugger) debugger->print("Time of the package is: ");
if (debugger) debugger->println(time);
// Define a json object to keep the data
StaticJsonBuffer<500> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Any generic useful info here
root["id"] = WiFi.macAddress();
root["up"] = millis();
root["t"] = time;
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject& data = root.createNestedObject("data");
// Get the temperature too
tempSensor.requestTemperatures();
float temperature = tempSensor.getTempCByIndex(0);
data["temp"] = temperature;
// Based on the list number, get all details
// according to OBIS specifications for the meter
if (listSize == (int)Kamstrup::List1)
{
data["lv"] = hanReader.getString((int)Kamstrup_List1::ListVersionIdentifier);
data["id"] = hanReader.getString((int)Kamstrup_List1::MeterID);
data["type"] = hanReader.getString((int)Kamstrup_List1::MeterType);
data["P"] = hanReader.getInt((int)Kamstrup_List1::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kamstrup_List1::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kamstrup_List1::CurrentL1);
data["I2"] = hanReader.getInt((int)Kamstrup_List1::CurrentL2);
data["I3"] = hanReader.getInt((int)Kamstrup_List1::CurrentL3);
data["U1"] = hanReader.getInt((int)Kamstrup_List1::VoltageL1);
data["U2"] = hanReader.getInt((int)Kamstrup_List1::VoltageL2);
data["U3"] = hanReader.getInt((int)Kamstrup_List1::VoltageL3);
}
else if (listSize == (int)Kamstrup::List2)
{
data["lv"] = hanReader.getString((int)Kamstrup_List2::ListVersionIdentifier);;
data["id"] = hanReader.getString((int)Kamstrup_List2::MeterID);
data["type"] = hanReader.getString((int)Kamstrup_List2::MeterType);
data["P"] = hanReader.getInt((int)Kamstrup_List2::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kamstrup_List2::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kamstrup_List2::CurrentL1);
data["I2"] = hanReader.getInt((int)Kamstrup_List2::CurrentL2);
data["I3"] = hanReader.getInt((int)Kamstrup_List2::CurrentL3);
data["U1"] = hanReader.getInt((int)Kamstrup_List2::VoltageL1);
data["U2"] = hanReader.getInt((int)Kamstrup_List2::VoltageL2);
data["U3"] = hanReader.getInt((int)Kamstrup_List2::VoltageL3);
data["tPI"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveImportEnergy);
data["tPO"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveExportEnergy);
data["tQI"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveImportEnergy);
data["tQO"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveExportEnergy);
}
// Write the json to the debug port
if (debugger) {
debugger->print("Sending data to MQTT: ");
root.printTo(*debugger);
debugger->println();
}
// Make sure we have configured a publish topic
if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
return;
// Publish the json to the MQTT server
char msg[1024];
root.printTo(msg, 1024);
mqtt.publish(ap.config.mqttPublishTopic, msg);
}
}
void readHanPort_Kaifa(int listSize)
{
// Only care for the ACtive Power Imported, which is found in the first list
if (listSize == (int)Kaifa::List1 || listSize == (int)Kaifa::List2 || listSize == (int)Kaifa::List3)
{
if (listSize == (int)Kaifa::List1)
{
if (debugger) debugger->println(" (list #1 has no ID)");
}
else
{
String id = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier);
if (debugger) debugger->println(id);
}
// Get the timestamp (as unix time) from the package
time_t time = hanReader.getPackageTime();
if (debugger) debugger->print("Time of the package is: ");
if (debugger) debugger->println(time);
// Define a json object to keep the data
//StaticJsonBuffer<500> jsonBuffer;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Any generic useful info here
root["id"] = WiFi.macAddress();
root["up"] = millis();
root["t"] = time;
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject& data = root.createNestedObject("data");
// Get the temperature too
tempSensor.requestTemperatures();
float temperature = tempSensor.getTempCByIndex(0);
data["temp"] = String(temperature);
// Based on the list number, get all details
// according to OBIS specifications for the meter
if (listSize == (int)Kaifa::List1)
{
data["P"] = hanReader.getInt((int)Kaifa_List1::ActivePowerImported);
}
else if (listSize == (int)Kaifa::List2)
{
data["lv"] = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier);
data["id"] = hanReader.getString((int)Kaifa_List2::MeterID);
data["type"] = hanReader.getString((int)Kaifa_List2::MeterType);
data["P"] = hanReader.getInt((int)Kaifa_List2::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kaifa_List2::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kaifa_List2::CurrentL1);
data["I2"] = hanReader.getInt((int)Kaifa_List2::CurrentL2);
data["I3"] = hanReader.getInt((int)Kaifa_List2::CurrentL3);
data["U1"] = hanReader.getInt((int)Kaifa_List2::VoltageL1);
data["U2"] = hanReader.getInt((int)Kaifa_List2::VoltageL2);
data["U3"] = hanReader.getInt((int)Kaifa_List2::VoltageL3);
}
else if (listSize == (int)Kaifa::List3)
{
data["lv"] = hanReader.getString((int)Kaifa_List3::ListVersionIdentifier);;
data["id"] = hanReader.getString((int)Kaifa_List3::MeterID);
data["type"] = hanReader.getString((int)Kaifa_List3::MeterType);
data["P"] = hanReader.getInt((int)Kaifa_List3::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kaifa_List3::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kaifa_List3::CurrentL1);
data["I2"] = hanReader.getInt((int)Kaifa_List3::CurrentL2);
data["I3"] = hanReader.getInt((int)Kaifa_List3::CurrentL3);
data["U1"] = hanReader.getInt((int)Kaifa_List3::VoltageL1);
data["U2"] = hanReader.getInt((int)Kaifa_List3::VoltageL2);
data["U3"] = hanReader.getInt((int)Kaifa_List3::VoltageL3);
data["tPI"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveImportEnergy);
data["tPO"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveExportEnergy);
data["tQI"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveImportEnergy);
data["tQO"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveExportEnergy);
}
// Write the json to the debug port
if (debugger) {
debugger->print("Sending data to MQTT: ");
root.printTo(*debugger);
debugger->println();
}
// Make sure we have configured a publish topic
if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
return;
// Publish the json to the MQTT server
char msg[1024];
root.printTo(msg, 1024);
mqtt.publish(ap.config.mqttPublishTopic, msg);
}
}
// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
void MQTT_connect()
{
// Connect to WiFi access point.
if (debugger)
{
debugger->println();
debugger->println();
debugger->print("Connecting to WiFi network ");
debugger->println(ap.config.ssid);
}
if (WiFi.status() != WL_CONNECTED)
{
// Make one first attempt at connect, this seems to considerably speed up the first connection
WiFi.disconnect();
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
delay(1000);
}
// Wait for the WiFi connection to complete
long vTimeout = millis() + WIFI_CONNECTION_TIMEOUT;
while (WiFi.status() != WL_CONNECTED) {
delay(50);
if (debugger) debugger->print(".");
// If we timed out, disconnect and try again
if (vTimeout < millis())
{
if (debugger)
{
debugger->print("Timout during connect. WiFi status is: ");
debugger->println(WiFi.status());
}
WiFi.disconnect();
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
vTimeout = millis() + WIFI_CONNECTION_TIMEOUT;
}
yield();
}
if (debugger) {
debugger->println();
debugger->println("WiFi connected");
debugger->println("IP address: ");
debugger->println(WiFi.localIP());
debugger->print("\nconnecting to MQTT: ");
debugger->print(ap.config.mqtt);
debugger->print(", port: ");
debugger->print(ap.config.mqttPort);
debugger->println();
}
// Wait for the MQTT connection to complete
while (!mqtt.connected()) {
// Connect to a unsecure or secure MQTT server
if ((ap.config.mqttUser == 0 && mqtt.connect(ap.config.mqttClientID)) ||
(ap.config.mqttUser != 0 && mqtt.connect(ap.config.mqttClientID, ap.config.mqttUser, ap.config.mqttPass)))
{
if (debugger) debugger->println("\nSuccessfully connected to MQTT!");
// Subscribe to the chosen MQTT topic, if set in configuration
if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0)
{
mqtt.subscribe(ap.config.mqttSubscribeTopic);
if (debugger) debugger->printf(" Subscribing to [%s]\r\n", ap.config.mqttSubscribeTopic);
}
}
else
{
if (debugger)
{
debugger->print(".");
debugger->print("failed, ");
debugger->println(" trying again in 5 seconds");
}
// Wait 2 seconds before retrying
mqtt.disconnect();
delay(2000);
}
// Allow some resources for the WiFi connection
yield();
delay(2000);
}
}
// Send a simple string embedded in json over MQTT
void sendMqttData(String data)
{
// Make sure we have configured a publish topic
if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
return;
// Make sure we're connected
if (!client->connected() || !mqtt.connected()) {
MQTT_connect();
}
// Build a json with the message in a "data" attribute
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
json["id"] = WiFi.macAddress();
json["up"] = millis();
json["data"] = data;
// Stringify the json
String msg;
json.printTo(msg);
// Send the json over MQTT
mqtt.publish(ap.config.mqttPublishTopic, msg.c_str());
}

View File

@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AmsToMqttBridge", "AmsToMqttBridge.vcxproj", "{C5F80730-F44F-4478-BDAE-6634EFC2CA88}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HanReader", "..\HanReader\HanReader.vcxitems", "{CD0F5364-923B-49E4-8BE5-EA7D8A60DF80}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\HanReader\HanReader.vcxitems*{c5f80730-f44f-4478-bdae-6634efc2ca88}*SharedItemsImports = 4
..\HanReader\HanReader.vcxitems*{cd0f5364-923b-49e4-8be5-ea7d8a60df80}*SharedItemsImports = 9
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.ActiveCfg = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Debug|x86.Build.0 = Debug|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.ActiveCfg = Release|Win32
{C5F80730-F44F-4478-BDAE-6634EFC2CA88}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5DFC14B6-4C33-4307-8BDA-C050F68A74F6}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C5F80730-F44F-4478-BDAE-6634EFC2CA88}</ProjectGuid>
<RootNamespace>AmsToMqttBridge</RootNamespace>
<ProjectName>AmsToMqttBridge</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>
</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>
</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
<Import Project="..\HanReader\HanReader.vcxitems" Label="Shared" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(ProjectDir)..\AmsToMqttBridge;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\EEPROM;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi\src;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WebServer\src;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\DNSServer\src;$(ProjectDir)..\HanReader\src;$(ProjectDir)..\..\..\..\..\..\..\..\..\Program Files (x86)\Arduino\libraries;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries;$(ProjectDir)..\..\..\..\..\..\..\Google Drive\Private\Elektronikk\Arduino\libraries;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\libb64;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\spiffs;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266\umm_malloc;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\variants\generic;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\lwip\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include\c++\4.8.2\xtensa-lx106-elf;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\xtensa-lx106-elf\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\tools\xtensa-lx106-elf-gcc\1.20.0-26-gb404fb9-2\lib\gcc\xtensa-lx106-elf\4.8.2\include;$(ProjectDir)..\..\..\..\..\..\..\AppData\Local\arduino15\packages\esp8266\hardware\esp8266\2.3.0\tools\sdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>$(ProjectDir)__vm\.AmsToMqttBridge.vsarduino.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<IgnoreStandardIncludePath>false</IgnoreStandardIncludePath>
<PreprocessorDefinitions>__ESP8266_ESp8266__;__ESP8266_ESP8266__;__ets__;ICACHE_FLASH;F_CPU=80000000L;LWIP_OPEN_SRC;ARDUINO=106012;ARDUINO_ESP8266_ESP01;ARDUINO_ARCH_ESP8266;ESP8266;__cplusplus=201103L;_VMICRO_INTELLISENSE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectCapability Include="VisualMicro" />
</ItemGroup>
<PropertyGroup>
<DebuggerFlavor>VisualMicroDebugger</DebuggerFlavor>
</PropertyGroup>
<ItemGroup>
<None Include="AmsToMqttBridge.ino">
<FileType>CppCode</FileType>
</None>
</ItemGroup>
<ItemGroup>
<ClInclude Include="accesspoint.h" />
<ClInclude Include="configuration.h" />
<ClInclude Include="__vm\.AmsToMqttBridge.vsarduino.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="accesspoint.cpp" />
<ClCompile Include="configuration.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties custom_esp8266_generic_UploadSpeed="256000" />
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="AmsToMqttBridge.ino" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="__vm\.AmsToMqttBridge.vsarduino.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,46 @@
// ap.h
#ifndef _ACCESSPOINT_h
#define _ACCESSPOINT_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include "configuration.h"
class accesspoint {
public:
void setup(int accessPointButtonPin, Stream& debugger);
bool loop();
bool hasConfig();
configuration config;
bool isActivated = false;
private:
const char* AP_SSID = "AMS2MQTT";
// DNS server
const byte DNS_PORT = 53;
DNSServer dnsServer;
static size_t print(const char* text);
static size_t println(const char* text);
static size_t print(const Printable& data);
static size_t println(const Printable& data);
// Web server
static void handleRoot();
static void handleSave();
static ESP8266WebServer server;
static Stream* debugger;
};
#endif

View File

@@ -0,0 +1,230 @@
//
//
//
#include "configuration.h"
bool configuration::hasConfig()
{
bool hasConfig = false;
EEPROM.begin(EEPROM_SIZE);
hasConfig = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM;
EEPROM.end();
return hasConfig;
}
bool configuration::save()
{
int address = EEPROM_CONFIG_ADDRESS;
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(address, EEPROM_CHECK_SUM);
address++;
address += saveString(address, ssid);
address += saveString(address, ssidPassword);
address += saveByte(address, meterType);
address += saveString(address, mqtt);
address += saveInt(address, mqttPort);
address += saveString(address, mqttClientID);
address += saveString(address, mqttPublishTopic);
address += saveString(address, mqttSubscribeTopic);
if (isSecure()) {
address += saveBool(address, true);
address += saveString(address, mqttUser);
address += saveString(address, mqttPass);
}
else
address += saveBool(address, false);
bool success = EEPROM.commit();
EEPROM.end();
return success;
}
bool configuration::load()
{
int address = EEPROM_CONFIG_ADDRESS;
bool success = false;
EEPROM.begin(EEPROM_SIZE);
if (EEPROM.read(address) == EEPROM_CHECK_SUM)
{
address++;
address += readString(address, &ssid);
address += readString(address, &ssidPassword);
address += readByte(address, &meterType);
address += readString(address, &mqtt);
address += readInt(address, &mqttPort);
address += readString(address, &mqttClientID);
address += readString(address, &mqttPublishTopic);
address += readString(address, &mqttSubscribeTopic);
bool secure = false;
address += readBool(address, &secure);
if (secure)
{
address += readString(address, &mqttUser);
address += readString(address, &mqttPass);
}
else
{
mqttUser = 0;
mqttPass = 0;
}
success = true;
}
else
{
ssid = (char*)String("").c_str();
ssidPassword = (char*)String("").c_str();
meterType = (byte)0;
mqtt = (char*)String("").c_str();
mqttClientID = (char*)String("").c_str();
mqttPublishTopic = (char*)String("").c_str();
mqttSubscribeTopic = (char*)String("").c_str();
mqttUser = 0;
mqttPass = 0;
mqttPort = 1883;
}
EEPROM.end();
return success;
}
bool configuration::isSecure()
{
return (mqttUser != 0) && (String(mqttUser).length() > 0);
}
int configuration::readInt(int address, int *value)
{
int lower = EEPROM.read(address);
int higher = EEPROM.read(address + 1);
*value = lower + (higher << 8);
return 2;
}
int configuration::saveInt(int address, int value)
{
byte lowByte = value & 0xFF;
byte highByte = ((value >> 8) & 0xFF);
EEPROM.write(address, lowByte);
EEPROM.write(address + 1, highByte);
return 2;
}
int configuration::readBool(int address, bool *value)
{
byte y = EEPROM.read(address);
*value = (bool)y;
return 1;
}
int configuration::saveBool(int address, bool value)
{
byte y = (byte)value;
EEPROM.write(address, y);
return 1;
}
int configuration::readByte(int address, byte *value)
{
*value = EEPROM.read(address);
return 1;
}
int configuration::saveByte(int address, byte value)
{
EEPROM.write(address, value);
return 1;
}
void configuration::print(Stream& serial)
{
/*
char* ssid;
char* ssidPassword;
byte meterType;
char* mqtt;
int mqttPort;
char* mqttClientID;
char* mqttPublishTopic;
char* mqttSubscribeTopic;
bool secure;
char* mqttUser;
char* mqttPass;
*/
serial.println("Configuration:");
serial.println("-----------------------------------------------");
serial.printf("ssid: %s\r\n", this->ssid);
serial.printf("ssidPassword: %s\r\n", this->ssidPassword);
serial.printf("meterType: %i\r\n", this->meterType);
serial.printf("mqtt: %s\r\n", this->mqtt);
serial.printf("mqttPort: %i\r\n", this->mqttPort);
serial.printf("mqttClientID: %s\r\n", this->mqttClientID);
serial.printf("mqttPublishTopic: %s\r\n", this->mqttPublishTopic);
serial.printf("mqttSubscribeTopic: %s\r\n", this->mqttSubscribeTopic);
if (this->isSecure())
{
serial.printf("SECURE MQTT CONNECTION:\r\n");
serial.printf("mqttUser: %s\r\n", this->mqttUser);
serial.printf("mqttPass: %s\r\n", this->mqttPass);
}
serial.println("-----------------------------------------------");
}
template <class T> int configuration::writeAnything(int ee, const T& value)
{
const byte* p = (const byte*)(const void*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
EEPROM.write(ee++, *p++);
return i;
}
template <class T> int configuration::readAnything(int ee, T& value)
{
byte* p = (byte*)(void*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
*p++ = EEPROM.read(ee++);
return i;
}
int configuration::readString(int pAddress, char* pString[])
{
int address = 0;
byte length = EEPROM.read(pAddress + address);
address++;
char* buffer = new char[length];
for (int i = 0; i<length; i++)
{
buffer[i] = EEPROM.read(pAddress + address++);
}
*pString = buffer;
return address;
}
int configuration::saveString(int pAddress, char* pString)
{
int address = 0;
int length = strlen(pString) + 1;
EEPROM.put(pAddress + address, length);
address++;
for (int i = 0; i < length; i++)
{
EEPROM.put(pAddress + address, pString[i]);
address++;
}
return address;
}

View File

@@ -0,0 +1,56 @@
// config.h
#ifndef _CONFIGURATION_h
#define _CONFIGURATION_h
#include <EEPROM.h>
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
class configuration {
public:
char* ssid;
char* ssidPassword;
char* mqtt;
int mqttPort;
char* mqttClientID;
char* mqttPublishTopic;
char* mqttSubscribeTopic;
char* mqttUser;
char* mqttPass;
byte meterType;
bool hasConfig();
bool isSecure();
bool save();
bool load();
void print(Stream& serial);
protected:
private:
const int EEPROM_SIZE = 512;
const byte EEPROM_CHECK_SUM = 71; // Used to check if config is stored. Change if structure changes
const int EEPROM_CONFIG_ADDRESS = 0;
int saveString(int pAddress, char* pString);
int readString(int pAddress, char* pString[]);
int saveInt(int pAddress, int pValue);
int readInt(int pAddress, int *pValue);
int saveBool(int pAddress, bool pValue);
int readBool(int pAddress, bool *pValue);
int saveByte(int pAddress, byte pValue);
int readByte(int pAddress, byte *pValue);
template <class T> int writeAnything(int ee, const T& value);
template <class T> int readAnything(int ee, T& value);
};
#endif

View File

@@ -0,0 +1,15 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HanReader", "HanReader.vcxitems", "{CD0F5364-923B-49E4-8BE5-EA7D8A60DF80}"
EndProject
Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AFFB18CF-A4FB-46A9-8148-C5B4A380C48B}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<ItemsProjectGuid>{cd0f5364-923b-49e4-8be5-ea7d8a60df80}</ItemsProjectGuid>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectCapability Include="SourceItemsFromImports" />
</ItemGroup>
<ItemGroup>
<Text Include="$(MSBuildThisFileDirectory)keywords.txt" />
<Text Include="$(MSBuildThisFileDirectory)readme.txt" />
<Text Include="$(MSBuildThisFileDirectory)library.properties" />
</ItemGroup>
<ItemGroup>
<!-- <ClInclude Include="$(MSBuildThisFileDirectory)HanReader.h" /> -->
<ClInclude Include="$(MSBuildThisFileDirectory)src\Crc16.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)src\DlmsReader.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)src\HanReader.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)src\Kaifa.h" />
<ClInclude Include="$(MSBuildThisFileDirectory)src\Kamstrup.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)src\Crc16.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)src\DlmsReader.cpp" />
<ClCompile Include="$(MSBuildThisFileDirectory)src\HanReader.cpp" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;s</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(MSBuildThisFileDirectory)src\Crc16.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)src\DlmsReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(MSBuildThisFileDirectory)src\HanReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Text Include="$(MSBuildThisFileDirectory)readme.txt" />
<Text Include="$(MSBuildThisFileDirectory)library.properties" />
<Text Include="$(MSBuildThisFileDirectory)keywords.txt" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)src\Crc16.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)src\DlmsReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)src\HanReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)src\Kaifa.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(MSBuildThisFileDirectory)src\Kamstrup.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,197 @@
/*
* Simple sketch to simulate reading data from a Kamstrup
* AMS Meter.
*
* Created 24. October 2017 by Roar Fredriksen
* Modified 06. November 2017 by Ruben Andreassen
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <HanReader.h>
#include <Kamstrup.h>
// The HAN Port reader
HanReader hanReader;
// WiFi and MQTT endpoints
const char* ssid = "ssid";
const char* password = "password";
const char* mqtt_server = "ip or dns";
const char* mqtt_topic = "sensors/out/espams";
const char* device_name = "espams";
bool enableDebug = false;
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
//setupDebugPort(); //Comment out this line if you dont need debugging on Serial1
setupWiFi();
setupMqtt();
// initialize the HanReader
// (passing no han port, as we are feeding data manually, but provide Serial for debugging)
if (enableDebug) {
hanReader.setup(&Serial, 2400, SERIAL_8N1, &Serial1);
} else {
hanReader.setup(&Serial, 2400, SERIAL_8N1, NULL);
}
}
void setupMqtt()
{
client.setServer(mqtt_server, 1883);
}
void setupDebugPort()
{
enableDebug = true;
// Initialize the Serial port for debugging
Serial1.begin(115200);
while (!Serial1) {}
Serial1.setDebugOutput(true);
Serial1.println("Serial1");
Serial1.println("Serial debugging port initialized");
}
void setupWiFi()
{
// Initialize wifi
if (enableDebug) {
Serial1.print("Connecting to ");
Serial1.println(ssid);
}
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
if (enableDebug) Serial1.print(".");
}
if (enableDebug) {
Serial1.println("");
Serial1.println("WiFi connected");
Serial1.println("IP address: ");
Serial1.println(WiFi.localIP());
}
}
void loop() {
loopMqtt();
// Read one byte from the port, and see if we got a full package
if (hanReader.read())
{
// Get the list identifier
int listSize = hanReader.getListSize();
if (enableDebug) {
Serial1.println("");
Serial1.print("List size: ");
Serial1.print(listSize);
Serial1.print(": ");
}
// Only care for the ACtive Power Imported, which is found in the first list
if (listSize == (int)Kamstrup::List1 || listSize == (int)Kamstrup::List2)
{
// Define a json object to keep the data
StaticJsonBuffer<MQTT_MAX_PACKET_SIZE> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Any generic useful info here
root["dn"] = device_name;
root["up"] = millis();
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject& data = root.createNestedObject("data");
data["ls"] = listSize;
data["lvi"] = hanReader.getString((int)Kamstrup_List1::ListVersionIdentifier);
data["mid"] = hanReader.getString((int)Kamstrup_List1::MeterID);
data["mt"] = hanReader.getString((int)Kamstrup_List1::MeterType);
data["t"] = hanReader.getPackageTime();
data["aip"] = hanReader.getInt((int)Kamstrup_List1::ActiveImportPower); //power
data["aep"] = hanReader.getInt((int)Kamstrup_List1::ActiveExportPower);
data["rip"] = hanReader.getInt((int)Kamstrup_List1::ReactiveImportPower);
data["rep"] = hanReader.getInt((int)Kamstrup_List1::ReactiveExportPower);
data["al1"] = (float)hanReader.getInt((int)Kamstrup_List1::CurrentL1) / 100.0;
data["al2"] = (float)hanReader.getInt((int)Kamstrup_List1::CurrentL2) / 100.0;
data["al3"] = (float)hanReader.getInt((int)Kamstrup_List1::CurrentL3) / 100.0;
data["vl1"] = hanReader.getInt((int)Kamstrup_List1::VoltageL1);
data["vl2"] = hanReader.getInt((int)Kamstrup_List1::VoltageL2);
data["vl3"] = hanReader.getInt((int)Kamstrup_List1::VoltageL3);
if (listSize == (int)Kamstrup::List2)
{
data["cl"] = hanReader.getTime((int)Kamstrup_List2::MeterClock);
data["caie"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveImportEnergy);
data["caee"] = hanReader.getInt((int)Kamstrup_List2::CumulativeActiveExportEnergy);
data["crie"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveImportEnergy);
data["cree"] = hanReader.getInt((int)Kamstrup_List2::CumulativeReactiveExportEnergy);
}
if (enableDebug) {
root.printTo(Serial1);
Serial1.println("JSON length");
Serial1.println(root.measureLength());
Serial1.println("");
}
// Publish the json to the MQTT server
char msg[MQTT_MAX_PACKET_SIZE];
root.printTo(msg, MQTT_MAX_PACKET_SIZE);
bool result = client.publish(mqtt_topic, msg);
if (enableDebug) {
Serial1.println("MQTT publish result:");
Serial1.println(result);
}
}
}
}
// Ensure the MQTT lirary gets some attention too
void loopMqtt()
{
if (!client.connected()) {
reconnectMqtt();
}
client.loop();
}
void reconnectMqtt() {
// Loop until we're reconnected
while (!client.connected()) {
if (enableDebug) Serial1.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266Client")) {
if (enableDebug) Serial1.println("connected");
// Once connected, publish an announcement...
// client.publish("sensors", "hello world");
// ... and resubscribe
// client.subscribe("inTopic");
}
else {
if (enableDebug) {
Serial1.print("failed, rc=");
Serial1.print(client.state());
Serial1.println(" try again in 5 seconds");
}
// Wait 5 seconds before retrying
delay(5000);
}
}
}

View File

@@ -0,0 +1,95 @@
# Setup
1. Copy AmsToMqttBridge\Code\Arduino\HanReader\src to Arduino\libraries
2. Download the following libraries and put them in Arduino\libraries
- ESP8266WiFi
- PubSubClient
- ArduinoJson
3. **Set MQTT_MAX_PACKET_SIZE in PubSubClient.h to at least 512 (i used 1024)**
4. Edit the following variables in the project:
- ssid
- password
- mqtt_server
- mqtt_topic
- device_name
## Output example:
### List 1
```
{
"dn": "espams",
"up": 1475902,
"data": {
"ls": 25,
"lvi": "Kamstrup_V0001",
"mid": "5706567274389702",
"mt": "6841121BN243101040",
"t": 1510088840,
"aip": 3499,
"aep": 0,
"rip": 0,
"rep": 424,
"al1": 10.27,
"al2": 6.37,
"al3": 11.79,
"vl1": 231,
"vl2": 226,
"vl3": 231
}
}
```
### List 2
```
{
"dn": "espams",
"up": 1041212,
"data": {
"ls": 35,
"lvi": "Kamstrup_V0001",
"mid": "5706567274389702",
"mt": "6841121BN243101040",
"t": 1510088405,
"aip": 4459,
"aep": 0,
"rip": 0,
"rep": 207,
"al1": 14.72,
"al2": 6.39,
"al3": 15.02,
"vl1": 231,
"vl2": 227,
"vl3": 231,
"cl": 1510088405,
"caie": 588500,
"caee": 0,
"crie": 93,
"cree": 80831
}
}
```
### List 1 and 2 fields
- dn = Device Name
- up = MS since last reboot
- ls = List Size
- lvi = List Version Identifier
- mid = Meter ID
- mt = Meter Type
- t = Time
- aie = Active Import Power
- aep = Active Export Power
- rip = Reactive Import Power
- rep = Reactive Export Power
- al1 = Current L1
- al2 = Current L2
- al3 = Current L3
- cl1 = Voltage L1
- cl2 = Voltage L2
- cl3 = Voltage L3
### List 2 additional fields
- cl = Meter Clock
- caie = Cumulative Active Import Energy
- caee = Cumulative Active Export Energy
- crie = Cumulative Reactive Import Energy
- cree = Cumulative Reactive Export Energy

View File

@@ -0,0 +1,61 @@
/*
* Simple sketch to read MBus data from electrical meter
* As the protocol requires "Even" parity, and this is
* only supported on the hardware port of the ESP8266,
* we'll have to use Serial1 for debugging.
*
* This means you'll have to program the ESP using the
* regular RX/TX port, and then you must remove the FTDI
* and connect the MBus signal from the meter to the
* RS pin. The FTDI/RX can be moved to Pin2 for debugging
*
* Created 14. september 2017 by Roar Fredriksen
*/
#include "HanReader.h"
#include "Kaifa.h"
// The HAN Port reader
HanReader hanReader;
void setup() {
setupDebugPort();
// initialize the HanReader
// (passing Serial as the HAN port and Serial1 for debugging)
hanReader.setup(&Serial, &Serial1);
}
void setupDebugPort()
{
// Initialize the Serial1 port for debugging
// (This port is fixed to Pin2 of the ESP8266)
Serial1.begin(115200);
while (!Serial1) {}
Serial1.setDebugOutput(true);
Serial1.println("Serial1");
Serial1.println("Serial debugging port initialized");
}
void loop() {
// Read one byte from the port, and see if we got a full package
if (hanReader.read())
{
// Get the list identifier
int listSize = hanReader.getListSize();
Serial1.println("");
Serial1.print("List size: ");
Serial1.print(listSize);
Serial1.print(": ");
// Only care for the ACtive Power Imported, which is found in the first list
if (listSize == (int)Kaifa::List1)
{
int power = hanReader.getInt((int)Kaifa_List1::ActivePowerImported);
Serial1.print("Power consumtion is right now: ");
Serial1.print(power);
Serial1.println(" W");
}
}
}

View File

@@ -0,0 +1,202 @@
/*
* Simple sketch to read MBus data from electrical meter
* As the protocol requires "Even" parity, and this is
* only supported on the hardware port of the ESP8266,
* we'll have to use Serial1 for debugging.
*
* This means you'll have to program the ESP using the
* regular RX/TX port, and then you must remove the FTDI
* and connect the MBus signal from the meter to the
* RS pin. The FTDI/RX can be moved to Pin2 for debugging
*
* Created 14. september 2017 by Roar Fredriksen
*/
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "HanReader.h"
#include "Kaifa.h"
// The HAN Port reader
HanReader hanReader;
// WiFi and MQTT endpoints
const char* ssid = "Roar_Etne";
const char* password = "**********";
const char* mqtt_server = "192.168.10.203";
WiFiClient espClient;
PubSubClient client(espClient);
void setup() {
setupDebugPort();
setupWiFi();
setupMqtt();
// initialize the HanReader
// (passing Serial as the HAN port and Serial1 for debugging)
hanReader.setup(&Serial, &Serial1);
}
void setupMqtt()
{
client.setServer(mqtt_server, 1883);
}
void setupDebugPort()
{
// Initialize the Serial1 port for debugging
// (This port is fixed to Pin2 of the ESP8266)
Serial1.begin(115200);
while (!Serial1) {}
Serial1.setDebugOutput(true);
Serial1.println("Serial1");
Serial1.println("Serial debugging port initialized");
}
void setupWiFi()
{
// Initialize wifi
Serial1.print("Connecting to ");
Serial1.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial1.print(".");
}
Serial1.println("");
Serial1.println("WiFi connected");
Serial1.println("IP address: ");
Serial1.println(WiFi.localIP());
}
void loop() {
loopMqtt();
// Read one byt from the port, and see if we got a full package
if (hanReader.read())
{
// Get the list identifier
int listSize = hanReader.getListSize();
Serial1.println("");
Serial1.print("List size: ");
Serial1.print(listSize);
Serial1.print(": ");
// Only care for the ACtive Power Imported, which is found in the first list
if (listSize == (int)Kaifa::List1 || listSize == (int)Kaifa::List2 || listSize == (int)Kaifa::List3)
{
if (listSize == (int)Kaifa::List1)
{
Serial1.println(" (list #1 has no ID)");
}
else
{
String id = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier);
Serial1.println(id);
}
// Get the timestamp (as unix time) from the package
time_t time = hanReader.getPackageTime();
Serial.print("Time of the package is: ");
Serial.println(time);
// Define a json object to keep the data
StaticJsonBuffer<500> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// Any generic useful info here
root["id"] = "espdebugger";
root["up"] = millis();
root["t"] = time;
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject& data = root.createNestedObject("data");
// Based on the list number, get all details
// according to OBIS specifications for the meter
if (listSize == (int)Kaifa::List1)
{
data["P"] = hanReader.getInt((int)Kaifa_List1::ActivePowerImported);
}
else if (listSize == (int)Kaifa::List2)
{
data["lv"] = hanReader.getString((int)Kaifa_List2::ListVersionIdentifier);
data["id"] = hanReader.getString((int)Kaifa_List2::MeterID);
data["type"] = hanReader.getString((int)Kaifa_List2::MeterType);
data["P"] = hanReader.getInt((int)Kaifa_List2::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kaifa_List2::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kaifa_List2::CurrentL1);
data["I2"] = hanReader.getInt((int)Kaifa_List2::CurrentL2);
data["I3"] = hanReader.getInt((int)Kaifa_List2::CurrentL3);
data["U1"] = hanReader.getInt((int)Kaifa_List2::VoltageL1);
data["U2"] = hanReader.getInt((int)Kaifa_List2::VoltageL2);
data["U3"] = hanReader.getInt((int)Kaifa_List2::VoltageL3);
}
else if (listSize == (int)Kaifa::List3)
{
data["lv"] = hanReader.getString((int)Kaifa_List3::ListVersionIdentifier);;
data["id"] = hanReader.getString((int)Kaifa_List3::MeterID);
data["type"] = hanReader.getString((int)Kaifa_List3::MeterType);
data["P"] = hanReader.getInt((int)Kaifa_List3::ActiveImportPower);
data["Q"] = hanReader.getInt((int)Kaifa_List3::ReactiveImportPower);
data["I1"] = hanReader.getInt((int)Kaifa_List3::CurrentL1);
data["I2"] = hanReader.getInt((int)Kaifa_List3::CurrentL2);
data["I3"] = hanReader.getInt((int)Kaifa_List3::CurrentL3);
data["U1"] = hanReader.getInt((int)Kaifa_List3::VoltageL1);
data["U2"] = hanReader.getInt((int)Kaifa_List3::VoltageL2);
data["U3"] = hanReader.getInt((int)Kaifa_List3::VoltageL3);
data["tPI"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveImportEnergy);
data["tPO"] = hanReader.getInt((int)Kaifa_List3::CumulativeActiveExportEnergy);
data["tQI"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveImportEnergy);
data["tQO"] = hanReader.getInt((int)Kaifa_List3::CumulativeReactiveExportEnergy);
}
// Write the json to the debug port
root.printTo(Serial1);
Serial1.println();
// Publish the json to the MQTT server
char msg[1024];
root.printTo(msg, 1024);
client.publish("sensors/out/espdebugger", msg);
}
}
}
// Ensure the MQTT lirary gets some attention too
void loopMqtt()
{
if (!client.connected()) {
reconnectMqtt();
}
client.loop();
}
void reconnectMqtt() {
// Loop until we're reconnected
while (!client.connected()) {
Serial1.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("ESP8266Client")) {
Serial1.println("connected");
// Once connected, publish an announcement...
// client.publish("sensors", "hello world");
// ... and resubscribe
// client.subscribe("inTopic");
}
else {
Serial1.print("failed, rc=");
Serial1.print(client.state());
Serial1.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}

View File

@@ -0,0 +1,9 @@
name=HANreader
version=1.0.0
author=roarfred
maintainer=roarfred <not@important.com>
sentence=HAN support
paragraph=HAN support
category=Sensors
url=https://github.com/roarfred/AmsToMqttBridge
architectures=*

View File

@@ -0,0 +1,19 @@
Arduino Compatible Cross Platform C++ Library Project : For more information see http://www.visualmicro.com
This project works exactly the same way as an Arduino library.
Add this project to any solution that contains an Arduino project and #include <headers.h> in code as you would any normal Arduino library headers.
To enable intellisense and to support live build discovery outside of the "standard" Arduino library locations, ensure that the library is added as a shared project reference to the master Arduino project. To do this, right click the master project "References" node and then click "Add Reference". A window will open and the library will appear on the "Shared Projects" tab. Click the checkbox next to the library name to add the reference. If this library is moved the shared referencemust be removed and re-added.
VS2017 has a bug, workround: After moving existing source code within a "library or shared project", close and re-open the solution.
Visual Studio will display intellisense for libraries based on the platform/board that has been specified for the currently active "Startup Project" of the current solution.
IMPORTANT: The arduino.cc Library Rules must be followed when adding code or restructing libraries.
blog: http://www.visualmicro.com/post/2017/01/16/Arduino-Cross-Platform-Library-Development.aspx

View File

@@ -0,0 +1,37 @@
#include "Crc16.h"
Crc16Class::Crc16Class()
{
unsigned short value;
unsigned short temp;
for (unsigned short i = 0; i < 256; ++i)
{
value = 0;
temp = i;
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x0001) != 0)
{
value = (ushort)((value >> 1) ^ polynomial);
}
else
{
value >>= 1;
}
temp >>= 1;
}
table[i] = value;
}
}
unsigned short Crc16Class::ComputeChecksum(byte *data, int start, int length)
{
ushort fcs = 0xffff;
for (int i = start; i < (start + length); i++)
{
byte index = (fcs ^ data[i]) & 0xff;
fcs = (ushort)((fcs >> 8) ^ table[index]);
}
fcs ^= 0xffff;
return fcs;
}

View File

@@ -0,0 +1,23 @@
#ifndef _CRC16_h
#define _CRC16_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
class Crc16Class
{
public:
Crc16Class();
unsigned short ComputeChecksum(byte *data, int start, int length);
protected:
private:
const unsigned short polynomial = 0x8408;
unsigned short table[256];
};
#endif

View File

@@ -0,0 +1,154 @@
#include "DlmsReader.h"
DlmsReader::DlmsReader()
{
//this->Clear();
}
void DlmsReader::Clear()
{
this->position = 0;
this->dataLength = 0;
this->destinationAddressLength = 0;
this->sourceAddressLength = 0;
this->frameFormatType = 0;
}
bool DlmsReader::Read(byte data)
{
if (position == 0 && data != 0x7E)
{
// we haven't started yet, wait for the start flag (no need to capture any data yet)
return false;
}
else
{
// We have completed reading of one package, so clear and be ready for the next
if (dataLength > 0 && position >= dataLength + 2)
Clear();
// Check if we're about to run into a buffer overflow
if (position >= DLMS_READER_BUFFER_SIZE)
Clear();
// Check if this is a second start flag, which indicates the previous one was a stop from the last package
if (position == 1 && data == 0x7E)
{
// just return, we can keep the one byte we had in the buffer
return false;
}
// We have started, so capture every byte
buffer[position++] = data;
if (position == 1)
{
// This was the start flag, we're not done yet
return false;
}
else if (position == 2)
{
// Capture the Frame Format Type
frameFormatType = (byte)(data & 0xF0);
if (!IsValidFrameFormat(frameFormatType))
Clear();
return false;
}
else if (position == 3)
{
// Capture the length of the data package
dataLength = ((buffer[1] & 0x0F) << 8) | buffer[2];
return false;
}
else if (destinationAddressLength == 0)
{
// Capture the destination address
destinationAddressLength = GetAddress(3, destinationAddress, 0, DLMS_READER_MAX_ADDRESS_SIZE);
if (destinationAddressLength > 3)
Clear();
return false;
}
else if (sourceAddressLength == 0)
{
// Capture the source address
sourceAddressLength = GetAddress(3 + destinationAddressLength, sourceAddress, 0, DLMS_READER_MAX_ADDRESS_SIZE);
if (sourceAddressLength > 3)
Clear();
return false;
}
else if (position == 4 + destinationAddressLength + sourceAddressLength + 2)
{
// Verify the header checksum
ushort headerChecksum = GetChecksum(position - 3);
if (headerChecksum != Crc16.ComputeChecksum(buffer, 1, position - 3))
Clear();
return false;
}
else if (position == dataLength + 1)
{
// Verify the data package checksum
ushort checksum = this->GetChecksum(position - 3);
if (checksum != Crc16.ComputeChecksum(buffer, 1, position - 3))
Clear();
return false;
}
else if (position == dataLength + 2)
{
// We're done, check the stop flag and signal we're done
if (data == 0x7E)
return true;
else
{
Clear();
return false;
}
}
}
return false;
}
bool DlmsReader::IsValidFrameFormat(byte frameFormatType)
{
return frameFormatType == 0xA0;
}
int DlmsReader::GetRawData(byte *dataBuffer, int start, int length)
{
if (dataLength > 0 && position == dataLength + 2)
{
int headerLength = 3 + destinationAddressLength + sourceAddressLength + 2;
int bytesWritten = 0;
for (int i = headerLength + 1; i < dataLength - 1; i++)
{
dataBuffer[i + start - headerLength - 1] = buffer[i];
bytesWritten++;
}
return bytesWritten;
}
else
return 0;
}
int DlmsReader::GetAddress(int addressPosition, byte* addressBuffer, int start, int length)
{
int addressBufferPos = start;
for (int i = addressPosition; i < position; i++)
{
addressBuffer[addressBufferPos++] = buffer[i];
// LSB=1 means this was the last address byte
if ((buffer[i] & 0x01) == 0x01)
break;
// See if we've reached last byte, try again when we've got more data
else if (i == position - 1)
return 0;
}
return addressBufferPos - start;
}
ushort DlmsReader::GetChecksum(int checksumPosition)
{
return (ushort)(buffer[checksumPosition + 2] << 8 |
buffer[checksumPosition + 1]);
}

View File

@@ -0,0 +1,42 @@
#ifndef _DLMSREADER_h
#define _DLMSREADER_h
#include "Crc16.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#define DLMS_READER_BUFFER_SIZE 512
#define DLMS_READER_MAX_ADDRESS_SIZE 5
class DlmsReader
{
public:
DlmsReader();
bool Read(byte data);
int GetRawData(byte *buffer, int start, int length);
protected:
Crc16Class Crc16;
private:
byte buffer[DLMS_READER_BUFFER_SIZE];
int position;
int dataLength;
byte frameFormatType;
byte destinationAddress[DLMS_READER_MAX_ADDRESS_SIZE];
byte destinationAddressLength;
byte sourceAddress[DLMS_READER_MAX_ADDRESS_SIZE];
byte sourceAddressLength;
void Clear();
int GetAddress(int addressPosition, byte* buffer, int start, int length);
unsigned short GetChecksum(int checksumPosition);
bool IsValidFrameFormat(byte frameFormatType);
void WriteBuffer();
};
#endif

View File

@@ -0,0 +1,297 @@
#include "HanReader.h"
HanReader::HanReader()
{
}
void HanReader::setup(HardwareSerial *hanPort, unsigned long baudrate, SerialConfig config, Stream *debugPort)
{
// Initialize H/W serial port for MBus communication
if (hanPort != NULL)
{
hanPort->begin(baudrate, config);
while (!hanPort) {}
}
han = hanPort;
bytesRead = 0;
debug = debugPort;
if (debug) debug->println("MBUS serial setup complete");
}
void HanReader::setup(HardwareSerial *hanPort)
{
setup(hanPort, 2400, SERIAL_8E1, NULL);
}
void HanReader::setup(HardwareSerial *hanPort, Stream *debugPort)
{
setup(hanPort, 2400, SERIAL_8E1, debugPort);
}
bool HanReader::read(byte data)
{
if (reader.Read(data))
{
bytesRead = reader.GetRawData(buffer, 0, 512);
if (debug)
{
debug->print("Got valid DLMS data (");
debug->print(bytesRead);
debug->println(" bytes):");
debugPrint(buffer, 0, bytesRead);
}
/*
Data should start with E6 E7 00 0F
and continue with four bytes for the InvokeId
*/
if (bytesRead < 9)
{
if (debug) debug->println("Invalid HAN data: Less than 9 bytes received");
return false;
}
else if (
buffer[0] != 0xE6 ||
buffer[1] != 0xE7 ||
buffer[2] != 0x00 ||
buffer[3] != 0x0F
)
{
if (debug) debug->println("Invalid HAN data: Start should be E6 E7 00 0F");
return false;
}
if (debug) debug->println("HAN data is valid");
listSize = getInt(0, buffer, 0, bytesRead);
return true;
}
}
void HanReader::debugPrint(byte *buffer, int start, int length)
{
for (int i = start; i < start + length; i++)
{
if (buffer[i] < 0x10)
debug->print("0");
debug->print(buffer[i], HEX);
debug->print(" ");
if ((i - start + 1) % 16 == 0)
debug->println("");
else if ((i - start + 1) % 4 == 0)
debug->print(" ");
yield(); // Let other get some resources too
}
debug->println("");
}
bool HanReader::read()
{
if (han->available())
{
byte newByte = han->read();
return read(newByte);
}
return false;
}
int HanReader::getListSize()
{
return listSize;
}
time_t HanReader::getPackageTime()
{
int packageTimePosition = dataHeader
+ (compensateFor09HeaderBug ? 1 : 0);
return getTime(buffer, packageTimePosition, bytesRead);
}
time_t HanReader::getTime(int objectId)
{
return getTime(objectId, buffer, 0, bytesRead);
}
int HanReader::getInt(int objectId)
{
return getInt(objectId, buffer, 0, bytesRead);
}
String HanReader::getString(int objectId)
{
return getString(objectId, buffer, 0, bytesRead);
}
int HanReader::findValuePosition(int dataPosition, byte *buffer, int start, int length)
{
// The first byte after the header gives the length
// of the extended header information (variable)
int headerSize = dataHeader + (compensateFor09HeaderBug ? 1 : 0);
int firstData = headerSize + buffer[headerSize] + 1;
for (int i = start + firstData; i<length; i++)
{
if (dataPosition-- == 0)
return i;
else if (buffer[i] == 0x00) // null
i += 0;
else if (buffer[i] == 0x0A) // String
i += buffer[i + 1] + 1;
else if (buffer[i] == 0x09) // byte array
i += buffer[i + 1] + 1;
else if (buffer[i] == 0x01) // array (1 byte for reading size)
i += 1;
else if (buffer[i] == 0x02) // struct (1 byte for reading size)
i += 1;
else if (buffer[i] == 0x10) // int16 value (2 bytes)
i += 2;
else if (buffer[i] == 0x12) // uint16 value (2 bytes)
i += 2;
else if (buffer[i] == 0x06) // uint32 value (4 bytes)
i += 4;
else if (buffer[i] == 0x0F) // int8 value (1 bytes)
i += 1;
else if (buffer[i] == 0x16) // enum (1 bytes)
i += 1;
else
{
if (debug)
{
debug->print("Unknown data type found: 0x");
debug->println(buffer[i], HEX);
}
return 0; // unknown data type found
}
}
if (debug)
{
debug->print("Passed the end of the data. Length was: ");
debug->println(length);
}
return 0;
}
time_t HanReader::getTime(int dataPosition, byte *buffer, int start, int length)
{
// TODO: check if the time is represented always as a 12 byte string (0x09 0x0C)
int timeStart = findValuePosition(dataPosition, buffer, start, length);
timeStart += 1;
return getTime(buffer, start + timeStart, length - timeStart);
}
time_t HanReader::getTime(byte *buffer, int start, int length)
{
int pos = start;
int dataLength = buffer[pos++];
if (dataLength == 0x0C)
{
int year = buffer[pos] << 8 |
buffer[pos + 1];
int month = buffer[pos + 2];
int day = buffer[pos + 3];
int hour = buffer[pos + 5];
int minute = buffer[pos + 6];
int second = buffer[pos + 7];
return toUnixTime(year, month, day, hour, minute, second);
}
else
{
// Date format not supported
return (time_t)0L;
}
}
int HanReader::getInt(int dataPosition, byte *buffer, int start, int length)
{
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0)
{
int value = 0;
int bytes = 0;
switch (buffer[valuePosition++])
{
case 0x10:
bytes = 2;
break;
case 0x12:
bytes = 2;
break;
case 0x06:
bytes = 4;
break;
case 0x02:
bytes = 1;
break;
case 0x01:
bytes = 1;
break;
case 0x0F:
bytes = 1;
break;
case 0x16:
bytes = 1;
break;
}
for (int i = valuePosition; i < valuePosition + bytes; i++)
{
value = value << 8 | buffer[i];
}
return value;
}
return 0;
}
String HanReader::getString(int dataPosition, byte *buffer, int start, int length)
{
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0)
{
String value = String("");
for (int i = valuePosition + 2; i < valuePosition + buffer[valuePosition + 1] + 2; i++)
{
value += String((char)buffer[i]);
}
return value;
}
return String("");
}
time_t HanReader::toUnixTime(int year, int month, int day, int hour, int minute, int second)
{
byte daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
long secondsPerMinute = 60;
long secondsPerHour = secondsPerMinute * 60;
long secondsPerDay = secondsPerHour * 24;
long time = (year - 1970) * secondsPerDay * 365L;
for (int yearCounter = 1970; yearCounter<year; yearCounter++)
if ((yearCounter % 4 == 0) && ((yearCounter % 100 != 0) || (yearCounter % 400 == 0)))
time += secondsPerDay;
if (month > 2 && (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))
time += secondsPerDay;
for (int monthCounter = 1; monthCounter<month; monthCounter++)
time += daysInMonth[monthCounter - 1] * secondsPerDay;
time += (day - 1) * secondsPerDay;
time += hour * secondsPerHour;
time += minute * secondsPerMinute;
time += second;
return (time_t)time;
}

View File

@@ -0,0 +1,53 @@
#ifndef _HANREADER_h
#define _HANREADER_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "DlmsReader.h"
class HanReader
{
public:
const uint dataHeader = 8;
bool compensateFor09HeaderBug = false;
HanReader();
void setup(HardwareSerial *hanPort);
void setup(HardwareSerial *hanPort, Stream *debugPort);
void setup(HardwareSerial *hanPort, unsigned long baudrate, SerialConfig config, Stream *debugPort);
bool read();
bool read(byte data);
int getListSize();
time_t getPackageTime();
int getInt(int objectId);
String getString(int objectId);
time_t getTime(int objectId);
private:
Stream *debug;
HardwareSerial *han;
byte buffer[512];
int bytesRead;
DlmsReader reader;
int listSize;
int findValuePosition(int dataPosition, byte *buffer, int start, int length);
time_t getTime(int dataPosition, byte *buffer, int start, int length);
time_t getTime(byte *buffer, int start, int length);
int getInt(int dataPosition, byte *buffer, int start, int length);
String getString(int dataPosition, byte *buffer, int start, int length);
time_t toUnixTime(int year, int month, int day, int hour, int minute, int second);
void debugPrint(byte *buffer, int start, int length);
};
#endif

View File

@@ -0,0 +1,54 @@
#ifndef _KAIFA_h
#define _KAIFA_h
enum class Kaifa : byte {
List1 = 0x01,
List2 = 0x0D,
List3 = 0x12
};
enum class Kaifa_List1 {
ListSize,
ActivePowerImported
};
enum class Kaifa_List2 {
ListSize,
ListVersionIdentifier,
MeterID,
MeterType,
ActiveImportPower,
ActiveExportPower,
ReactiveImportPower,
ReactiveExportPower,
CurrentL1,
CurrentL2,
CurrentL3,
VoltageL1,
VoltageL2,
VoltageL3
};
enum class Kaifa_List3 {
ListSize,
ListVersionIdentifier,
MeterID,
MeterType,
ActiveImportPower,
ActiveExportPower,
ReactiveImportPower,
ReactiveExportPower,
CurrentL1,
CurrentL2,
CurrentL3,
VoltageL1,
VoltageL2,
VoltageL3,
MeterClock,
CumulativeActiveImportEnergy,
CumulativeActiveExportEnergy,
CumulativeReactiveImportEnergy,
CumulativeReactiveExportEnergy
};
#endif

View File

@@ -0,0 +1,86 @@
// Kamstrup.h
#ifndef _KAMSTRUP_h
#define _KAMSTRUP_h
enum class Kamstrup
{
List1 = 0x19,
List2 = 0x23
};
enum class Kamstrup_List1
{
ListSize,
ListVersionIdentifier,
MeterID_OBIS,
MeterID,
MeterType_OBIS,
MeterType,
ActiveImportPower_OBIS,
ActiveImportPower,
ActiveExportPower_OBIS,
ActiveExportPower,
ReactiveImportPower_OBIS,
ReactiveImportPower,
ReactiveExportPower_OBIS,
ReactiveExportPower,
CurrentL1_OBIS,
CurrentL1,
CurrentL2_OBIS,
CurrentL2,
CurrentL3_OBIS,
CurrentL3,
VoltageL1_OBIS,
VoltageL1,
VoltageL2_OBIS,
VoltageL2,
VoltageL3_OBIS,
VoltageL3
};
enum class Kamstrup_List2
{
ListSize,
ListVersionIdentifier,
MeterID_OBIS,
MeterID,
MeterType_OBIS,
MeterType,
ActiveImportPower_OBIS,
ActiveImportPower,
ActiveExportPower_OBIS,
ActiveExportPower,
ReactiveImportPower_OBIS,
ReactiveImportPower,
ReactiveExportPower_OBIS,
ReactiveExportPower,
CurrentL1_OBIS,
CurrentL1,
CurrentL2_OBIS,
CurrentL2,
CurrentL3_OBIS,
CurrentL3,
VoltageL1_OBIS,
VoltageL1,
VoltageL2_OBIS,
VoltageL2,
VoltageL3_OBIS,
VoltageL3,
MeterClock_OBIS,
MeterClock,
CumulativeActiveImportEnergy_OBIS,
CumulativeActiveImportEnergy,
CumulativeActiveExportEnergy_OBIS,
CumulativeActiveExportEnergy,
CumulativeReactiveImportEnergy_OBIS,
CumulativeReactiveImportEnergy,
CumulativeReactiveExportEnergy_OBIS,
CumulativeReactiveExportEnergy
};
#endif