mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-02-05 16:16:13 +00:00
Merge branch 'master' of github.com:gskjold/AmsToMqttBridge
This commit is contained in:
15
Arduino Code/Arduino Libraries/HanReader/HanReader.sln
Normal file
15
Arduino Code/Arduino Libraries/HanReader/HanReader.sln
Normal 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
|
||||
34
Arduino Code/Arduino Libraries/HanReader/HanReader.vcxitems
Normal file
34
Arduino Code/Arduino Libraries/HanReader/HanReader.vcxitems
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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=*
|
||||
19
Arduino Code/Arduino Libraries/HanReader/readme.txt
Normal file
19
Arduino Code/Arduino Libraries/HanReader/readme.txt
Normal 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
|
||||
203
Arduino Code/Arduino Libraries/HanReader/src/Aidon.h
Normal file
203
Arduino Code/Arduino Libraries/HanReader/src/Aidon.h
Normal file
@@ -0,0 +1,203 @@
|
||||
// Aidon.h
|
||||
|
||||
#ifndef _AIDON_h
|
||||
#define _AIDON_h
|
||||
|
||||
|
||||
enum class Aidon
|
||||
{
|
||||
List1 = 0x01,
|
||||
List2 = 0x0D,
|
||||
List3 = 0x12
|
||||
};
|
||||
|
||||
enum class Aidon_List1
|
||||
{
|
||||
ListSize,
|
||||
IGN_0,
|
||||
ActiveImportPower_OBIS,
|
||||
ActiveImportPower,
|
||||
IGN_1,
|
||||
ActiveImportPowerInt8,
|
||||
ActiveImportPowerEnum
|
||||
};
|
||||
|
||||
|
||||
enum class Aidon_List2
|
||||
{
|
||||
ListSize,
|
||||
IGN_0,
|
||||
ListVersionIdentifier_OBIS,
|
||||
ListVersionIdentifier,
|
||||
IGN_1,
|
||||
MeterID_OBIS,
|
||||
MeterID,
|
||||
IGN_2,
|
||||
MeterType_OBIS,
|
||||
MeterType,
|
||||
IGN_3,
|
||||
ActiveImportPower_OBIS,
|
||||
ActiveImportPower,
|
||||
IGN_4,
|
||||
ActiveImportPowerInt8,
|
||||
ActiveImportPowerEnum,
|
||||
IGN_5,
|
||||
ActiveExportPower_OBIS,
|
||||
ActiveExportPower,
|
||||
IGN_6,
|
||||
ActiveExportPowerInt8,
|
||||
ActiveExportPowerEnum,
|
||||
IGN_7,
|
||||
ReactiveImportPower_OBIS,
|
||||
ReactiveImportPower,
|
||||
IGN_8,
|
||||
ReactiveImportPowerInt8,
|
||||
ReactiveImportPowerEnum,
|
||||
IGN_9,
|
||||
ReactiveExportPower_OBIS,
|
||||
ReactiveExportPower,
|
||||
IGN_10,
|
||||
ReactiveExportPowerInt8,
|
||||
ReactiveExportPowerEnum,
|
||||
IGN_11,
|
||||
CurrentL1_OBIS,
|
||||
CurrentL1,
|
||||
IGN_12,
|
||||
CurrentL1Int8,
|
||||
CurrentL1Enum,
|
||||
IGN_13,
|
||||
CurrentL2_OBIS,
|
||||
CurrentL2,
|
||||
IGN_14,
|
||||
CurrentL2Int8,
|
||||
CurrentL2Enum,
|
||||
IGN_15,
|
||||
CurrentL3_OBIS,
|
||||
CurrentL3,
|
||||
IGN_16,
|
||||
CurrentL3Int8,
|
||||
CurrentL3Enum,
|
||||
IGN_17,
|
||||
VoltageL1_OBIS,
|
||||
VoltageL1,
|
||||
IGN_18,
|
||||
VoltageL1Int8,
|
||||
VoltageL1Enum,
|
||||
IGN_19,
|
||||
VoltageL2_OBIS,
|
||||
VoltageL2,
|
||||
IGN_20,
|
||||
VoltageL2Int8,
|
||||
VoltageL2Enum,
|
||||
IGN_21,
|
||||
VoltageL3_OBIS,
|
||||
VoltageL3,
|
||||
IGN_22,
|
||||
VoltageL3Int8,
|
||||
VoltageL3Enum
|
||||
};
|
||||
|
||||
enum class Aidon_List3
|
||||
{
|
||||
ListSize,
|
||||
IGN_0,
|
||||
ListVersionIdentifier_OBIS,
|
||||
ListVersionIdentifier,
|
||||
IGN_1,
|
||||
MeterID_OBIS,
|
||||
MeterID,
|
||||
IGN_2,
|
||||
MeterType_OBIS,
|
||||
MeterType,
|
||||
IGN_3,
|
||||
ActiveImportPower_OBIS,
|
||||
ActiveImportPower,
|
||||
IGN_4,
|
||||
ActiveImportPowerInt8,
|
||||
ActiveImportPowerEnum,
|
||||
IGN_5,
|
||||
ActiveExportPower_OBIS,
|
||||
ActiveExportPower,
|
||||
IGN_6,
|
||||
ActiveExportPowerInt8,
|
||||
ActiveExportPowerEnum,
|
||||
IGN_7,
|
||||
ReactiveImportPower_OBIS,
|
||||
ReactiveImportPower,
|
||||
IGN_8,
|
||||
ReactiveImportPowerInt8,
|
||||
ReactiveImportPowerEnum,
|
||||
IGN_9,
|
||||
ReactiveExportPower_OBIS,
|
||||
ReactiveExportPower,
|
||||
IGN_10,
|
||||
ReactiveExportPowerInt8,
|
||||
ReactiveExportPowerEnum,
|
||||
IGN_11,
|
||||
CurrentL1_OBIS,
|
||||
CurrentL1,
|
||||
IGN_12,
|
||||
CurrentL1Int8,
|
||||
CurrentL1Enum,
|
||||
IGN_13,
|
||||
CurrentL2_OBIS,
|
||||
CurrentL2,
|
||||
IGN_14,
|
||||
CurrentL2Int8,
|
||||
CurrentL2Enum,
|
||||
IGN_15,
|
||||
CurrentL3_OBIS,
|
||||
CurrentL3,
|
||||
IGN_16,
|
||||
CurrentL3Int8,
|
||||
CurrentL3Enum,
|
||||
IGN_17,
|
||||
VoltageL1_OBIS,
|
||||
VoltageL1,
|
||||
IGN_18,
|
||||
VoltageL1Int8,
|
||||
VoltageL1Enum,
|
||||
IGN_19,
|
||||
VoltageL2_OBIS,
|
||||
VoltageL2,
|
||||
IGN_20,
|
||||
VoltageL2Int8,
|
||||
VoltageL2Enum,
|
||||
IGN_21,
|
||||
VoltageL3_OBIS,
|
||||
VoltageL3,
|
||||
IGN_22,
|
||||
VoltageL3Int8,
|
||||
VoltageL3Enum,
|
||||
IGN_23,
|
||||
Timestamp_OBIS,
|
||||
Timestamp,
|
||||
IGN_24,
|
||||
CumulativeActiveImportEnergy_OBIS,
|
||||
CumulativeActiveImportEnergy,
|
||||
IGN_25,
|
||||
CumulativeActiveImportEnergyInt8,
|
||||
CumulativeActiveImportEnergyEnum,
|
||||
IGN_26,
|
||||
CumulativeActiveExportEnergy_OBIS,
|
||||
CumulativeActiveExportEnergy,
|
||||
IGN_27,
|
||||
CumulativeActiveExportEnergyInt8,
|
||||
CumulativeActiveExportEnergyEnum,
|
||||
IGN_28,
|
||||
CumulativeReactiveImportEnergy_OBIS,
|
||||
CumulativeReactiveImportEnergy,
|
||||
IGN_29,
|
||||
CumulativeReactiveImportEnergyInt8,
|
||||
CumulativeReactiveImportEnergyEnum,
|
||||
IGN_30,
|
||||
CumulativeReactiveExportEnergy_OBIS,
|
||||
CumulativeReactiveExportEnergy,
|
||||
IGN_31,
|
||||
CumulativeReactiveExportEnergyInt8,
|
||||
CumulativeReactiveExportEnergyEnum
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
37
Arduino Code/Arduino Libraries/HanReader/src/Crc16.cpp
Normal file
37
Arduino Code/Arduino Libraries/HanReader/src/Crc16.cpp
Normal 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;
|
||||
}
|
||||
23
Arduino Code/Arduino Libraries/HanReader/src/Crc16.h
Normal file
23
Arduino Code/Arduino Libraries/HanReader/src/Crc16.h
Normal 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
|
||||
|
||||
|
||||
154
Arduino Code/Arduino Libraries/HanReader/src/DlmsReader.cpp
Normal file
154
Arduino Code/Arduino Libraries/HanReader/src/DlmsReader.cpp
Normal 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]);
|
||||
}
|
||||
42
Arduino Code/Arduino Libraries/HanReader/src/DlmsReader.h
Normal file
42
Arduino Code/Arduino Libraries/HanReader/src/DlmsReader.h
Normal 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
|
||||
297
Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp
Normal file
297
Arduino Code/Arduino Libraries/HanReader/src/HanReader.cpp
Normal 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;
|
||||
}
|
||||
53
Arduino Code/Arduino Libraries/HanReader/src/HanReader.h
Normal file
53
Arduino Code/Arduino Libraries/HanReader/src/HanReader.h
Normal 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
|
||||
54
Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h
Normal file
54
Arduino Code/Arduino Libraries/HanReader/src/Kaifa.h
Normal 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
|
||||
86
Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h
Normal file
86
Arduino Code/Arduino Libraries/HanReader/src/Kamstrup.h
Normal 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
|
||||
|
||||
Reference in New Issue
Block a user