From 685e9f9e1d7e8481b163efe8b3ecbcb70d703324 Mon Sep 17 00:00:00 2001 From: Malte Bitter Date: Tue, 24 Jan 2023 10:54:18 +0100 Subject: [PATCH] Add communication with local MQTT --- TestApp/Interface.proto | 166 ++++++++++++++++++++++++++++++++++++++++ TestApp/Program.cs | 67 +++++++++++++++- TestApp/TestApp.csproj | 10 +++ 3 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 TestApp/Interface.proto diff --git a/TestApp/Interface.proto b/TestApp/Interface.proto new file mode 100644 index 0000000..fca6076 --- /dev/null +++ b/TestApp/Interface.proto @@ -0,0 +1,166 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +// GCRs is the message type wich is published to the mqtt Handler for GCR +// config_uuid - unique identification of config in time (i.e. changes on config change) +message GCRs { + map GCRs = 1; + string config_uuid = 2; +} + +// GDRs is the message type wich is published to the mqtt Handler for GDR +// config_uuid - unique identification of config at time of measurement (same as associated GCR) +message GDRs { + map GDRs = 1; + string config_uuid = 2; +} + +//GCR = Generic Config Record +message GCR { + string id = 1; + string label = 2; + Class class = 3; + repeated string sources = 4; + repeated uint64 codes = 5; + DeviceType devicetype = 6; + map meta = 7; + google.protobuf.Timestamp timestamp = 8; + map flexDefinitions = 9; +} + +//GDR = Generic Data Record +message GDR { + string id = 1; + Status status = 2; + google.protobuf.Timestamp timestamp = 3; + map values = 4; + map flexValues = 5; +} + +// Class Consumer = Consumes Energy, Power, ... +// Class Producer = Produces Energy, Power, ... +// Class Hybrid = Consumes or Produces Energy, Power, ... +enum Class { + CLASS_UNKNOWN = 0; + CLASS_CONSUMER = 1; + CLASS_PRODUCER = 2; + CLASS_HYBRID = 3; +} + +// devicetype - declares which Type of Device the datasource is +enum DeviceType { + DEVICE_TYPE_UNKNOWN = 0; + DEVICE_TYPE_PHOTOVOLTAIC_SYSTEM = 1; + DEVICE_TYPE_ELECTRIC_VEHICLE = 2; + DEVICE_TYPE_BATTERY = 3; + DEVICE_TYPE_OVEN = 4; + DEVICE_TYPE_FLOW_HEATER = 5; + DEVICE_TYPE_BOILER = 6; + DEVICE_TYPE_IMMERSION_HEATER = 7; + DEVICE_TYPE_STOVE = 8; + DEVICE_TYPE_COOLER = 9; + DEVICE_TYPE_VENTILATION = 10; + DEVICE_TYPE_DISHWASHER = 11; + DEVICE_TYPE_DRYER = 12; + DEVICE_TYPE_HEAT_PUMP = 13; + DEVICE_TYPE_WASHING_MACHINE = 14; + DEVICE_TYPE_INVERTER_ONEPHASE = 15; + DEVICE_TYPE_INVERTER_THREEPHASE = 16; + DEVICE_TYPE_CHP = 17; + DEVICE_TYPE_BUILDING_OFFICE = 18; + DEVICE_TYPE_BUILDING_COMMERCIAL = 19; + DEVICE_TYPE_BUILDING_FACTORY = 20; + DEVICE_TYPE_BUILDING_SINGLE_HOME = 21; + DEVICE_TYPE_BUILDING_HOTEL = 22; + DEVICE_TYPE_BUILDING_APARTMENTS = 23; + DEVICE_TYPE_BUILDING_PARKING = 24; + DEVICE_TYPE_BUILDING_RESIDENTIAL = 25; + DEVICE_TYPE_ROOM_BATH = 26; + DEVICE_TYPE_ROOM_GARAGE = 27; + DEVICE_TYPE_ROOM_BASEMENT = 28; + DEVICE_TYPE_ROOM_CHILD = 29; + DEVICE_TYPE_ROOM_KITCHEN = 30; + DEVICE_TYPE_ROOM_SAUNA = 31; + DEVICE_TYPE_ROOM_BED = 32; + DEVICE_TYPE_ROOM_LIVING = 33; + DEVICE_TYPE_ROOM_GENERIC = 34; + DEVICE_TYPE_CONTROLLABLE_LOAD = 35; + DEVICE_TYPE_LIGHTING = 36; + DEVICE_TYPE_OFFICES = 37; + DEVICE_TYPE_DOMESTIC_APPLIANCES = 38; + DEVICE_TYPE_HEATER_OF_HEAT_PUMP = 39; + DEVICE_TYPE_INDUSTRIAL_ENGINE = 40; + DEVICE_TYPE_AIR_CONDITIONING = 41; + DEVICE_TYPE_COMPRESSOR = 42; + DEVICE_TYPE_PC_DATA_CENTER = 43; + DEVICE_TYPE_FUSES_THREE = 44; + DEVICE_TYPE_FUSES_SIX = 45; + DEVICE_TYPE_FUSES_NINE = 46; + DEVICE_TYPE_FUSES_TWELVE = 47; + DEVICE_TYPE_COMPACTOR = 48; + DEVICE_TYPE_WHITE_GOODS = 49; + DEVICE_TYPE_COLD_STORAGE_ROOM = 50; + DEVICE_TYPE_GARDEN_SHED = 51; + DEVICE_TYPE_COOLING_COMBINATION = 52; + DEVICE_TYPE_FACILITIES = 53; + DEVICE_TYPE_FREEZER = 54; + DEVICE_TYPE_FRIDGE = 55; + DEVICE_TYPE_GRID_CONNECTION_POINT = 56; +} + +// Status OK = Datasource updated Data, GDR Updated +// Status WARNING = Datasource updated Data but configuration is needed +// Status Error = Datasource did not updated Data (Maybe broken) +enum Status { + STATUS_UNKNOWN = 0; + STATUS_OK = 1; + STATUS_WARNING = 2; + STATUS_ERROR = 3; +} + +// FlexValue is a message type which handles flexible datapoints inside a GDR +message FlexValue { + int64 int_value = 1; + string string_value = 2; +} + +// FlexDefinition describes the content of the flexible values +message FlexDefinition { + string label = 1; + FlexValueType type = 2; + Unit unit = 3; + sint32 decimalpower = 4; +} + +// FlexValueType declares which field should be used parsing the flexvalue +enum FlexValueType { + FLEX_VALUE_TYPE_INTEGER = 0; + FLEX_VALUE_TYPE_STRING = 1; +} + +// Unit declares the unit of the flexible value +enum Unit { + UNIT_UNKNOWN = 0; + // Electrical values + UNIT_AMPERE = 1; + UNIT_VOLT = 2; + UNIT_WATT = 3; + UNIT_HERTZ = 4; + UNIT_VOLT_AMPERE = 5; + UNIT_VOLT_AMPERE_REACTIVE = 6; + UNIT_WATT_HOUR = 7; + UNIT_KILO_WATT_HOUR = 8; + // Time Values + UNIT_SECOND = 9; + UNIT_MINUTE = 10; + UNIT_HOUR = 11; + UNIT_DAY = 12; + UNIT_WEEK = 13; + UNIT_MONTH = 14; + UNIT_YEAR = 15; + // Other values + UNIT_DEGREE_CELSIUS = 16; + UNIT_KELVIN = 17; + UNIT_DEGREE_FAHRENHEIT = 18; +} diff --git a/TestApp/Program.cs b/TestApp/Program.cs index 6d00cd5..fc549b0 100644 --- a/TestApp/Program.cs +++ b/TestApp/Program.cs @@ -1,12 +1,19 @@ -using NModbus; +using MQTTnet; +using MQTTnet.Client; +using NModbus; using System.Net.Sockets; namespace TestApp { internal class Program { - static void Main(string[] args) + private static IMqttClient _MqttClient; + private static Dictionary _CurrentValues = new Dictionary(); + + static async Task Main(string[] args) { + await ConnectMQTT(); + while (true) { try @@ -32,10 +39,11 @@ namespace TestApp { try { + var currentWirkleistung = _CurrentValues.GetValueOrDefault(1099528667391UL) / 1000; + ushort input = master.ReadHoldingRegisters(1, 1000, 1)[0]; - ushort output = (ushort)(input + 50); + ushort output = (ushort)(input + currentWirkleistung); master.WriteMultipleRegisters(1, 1100, new ushort[] { output }); - Console.WriteLine($"Register values updated to {input} + 50 = {output}"); Thread.Sleep(100); } catch (Exception e) @@ -46,5 +54,56 @@ namespace TestApp } } + private static async Task ConnectMQTT() { + var factory = new MqttFactory(); + + _MqttClient = factory.CreateMqttClient(); + + var clientOptions = new MqttClientOptionsBuilder() + .WithTcpServer("localhost") + .Build(); + + Console.WriteLine("Connecting to local MQTT ..."); + await _MqttClient.ConnectAsync(clientOptions); + Console.WriteLine("Connected to local MQTT!"); + + _MqttClient.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceivedAsync; + + var subscribeOptions = factory.CreateSubscribeOptionsBuilder() + .WithTopicFilter( + f => { + f.WithTopic("gdr/local/values/smart-meter"); + } + ) + .Build(); + + Console.WriteLine("Subscribing to smart-meter values ..."); + var response = await _MqttClient.SubscribeAsync(subscribeOptions); + Console.WriteLine("Subscribed to smart-meter values!"); + } + + private static Task Client_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg) + { + switch (arg.ApplicationMessage.Topic) { + case "gdr/local/values/smart-meter": + ParseGDRs(arg.ApplicationMessage.Payload); + break; + } + + return Task.CompletedTask; + } + + private static void ParseGDRs(byte[] payload) + { + var gdrs = GDRs.Parser.ParseFrom(payload); + + if (!gdrs.GDRs_.TryGetValue("smart-meter", out GDR smartMeterGDR)) { + return; + } + + foreach (var value in smartMeterGDR.Values) { + _CurrentValues[value.Key] = value.Value; + } + } } } \ No newline at end of file diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj index e5cd5bb..a0c5f5b 100644 --- a/TestApp/TestApp.csproj +++ b/TestApp/TestApp.csproj @@ -8,7 +8,17 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + +