├── AVRControl ├── icons │ └── Speaker.png ├── Manifest.json ├── Properties │ └── AssemblyInfo.cs ├── AVRControl.csproj └── AvrControl.cs ├── DewPoint ├── icons │ ├── DewPoint.png │ └── LICENSE.txt ├── Manifest.json ├── Properties │ └── AssemblyInfo.cs ├── DewPointNode.cs └── DewPoint.csproj ├── SendMail ├── icons │ ├── SendMail.png │ └── LICENSE.txt ├── packages.config ├── app.config ├── Manifest.json ├── Properties │ └── AssemblyInfo.cs ├── SendMail.csproj └── SendMail.cs ├── InfluxDbNode ├── icons │ └── Influxdb.png ├── Properties │ └── AssemblyInfo.cs ├── Manifest.json ├── InfluxDbNode.csproj ├── InfluxWriterHelper.cs ├── WriteElectricMeter.cs ├── WriteNode.cs └── WriteThreePhaseElectricMeter.cs ├── HuaweiModbus ├── icons │ └── GenericLogicNode.png ├── packages.config ├── Manifest.json ├── Properties │ └── AssemblyInfo.cs ├── HuaweiModbus.csproj └── ModbusReader.cs ├── ModbusClient ├── icons │ └── GenericLogicNode.png ├── packages.config ├── Manifest.json ├── Properties │ └── AssemblyInfo.cs ├── ModbusClient.csproj └── ModbusClient.cs ├── .gitignore ├── LICENSE └── README.md /AVRControl/icons/Speaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/AVRControl/icons/Speaker.png -------------------------------------------------------------------------------- /DewPoint/icons/DewPoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/DewPoint/icons/DewPoint.png -------------------------------------------------------------------------------- /SendMail/icons/SendMail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/SendMail/icons/SendMail.png -------------------------------------------------------------------------------- /InfluxDbNode/icons/Influxdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/InfluxDbNode/icons/Influxdb.png -------------------------------------------------------------------------------- /HuaweiModbus/icons/GenericLogicNode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/HuaweiModbus/icons/GenericLogicNode.png -------------------------------------------------------------------------------- /ModbusClient/icons/GenericLogicNode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alramlechner/CommonLogicNodes/HEAD/ModbusClient/icons/GenericLogicNode.png -------------------------------------------------------------------------------- /DewPoint/icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Icon provided by https://icons8.com/. 2 | Licensed under the Creative Commons Attribution-NoDerivs 3.0 Unported: https://icons8.com/license -------------------------------------------------------------------------------- /SendMail/icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Icon provided by https://icons8.com/. 2 | Licensed under the Creative Commons Attribution-NoDerivs 3.0 Unported: https://icons8.com/license -------------------------------------------------------------------------------- /HuaweiModbus/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ModbusClient/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SendMail/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /ExampleNodes 2 | /ExampleNodesTest 3 | /LogicNodes 4 | /LogicNodesSDK 5 | /LogicNodesTest 6 | /PersistenceNodes 7 | /packages 8 | /*.sln 9 | /.vs 10 | /DewPoint/bin 11 | /DewPoint/obj 12 | /SendMail/bin 13 | /SendMail/obj 14 | /Zip 15 | /ModbusClient/bin 16 | /ModbusClient/obj 17 | /InfluxDbNode/bin 18 | /InfluxDbNode/obj 19 | /AVRControl/bin/Debug 20 | /AVRControl/obj/Debug 21 | -------------------------------------------------------------------------------- /SendMail/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AVRControl/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.AvrControl.dll", 4 | "DependentFiles": [ 5 | "LogicModule.Nodes.Helpers.dll" 6 | ], 7 | "Version": "0.0.22", 8 | "Author": "Alram Lechner", 9 | "Copyright": "Alram Lechner", 10 | "DeveloperId": "alram_lechner_gmx_at", 11 | "License": "Free", 12 | "PackageId": "CFEA590E-7C34-47A6-AC76-E9D99F7A7678", 13 | "Nodes": [ 14 | { 15 | "Type": "alram_lechner_gmx_at.logic.AvrControl.RunMacro", 16 | "Name": { 17 | "en": "Send macro to AVR Control unit", 18 | "de": "Sendet macro zum AVR Control" 19 | }, 20 | "IsConverter": false, 21 | "Category": "Node", 22 | "DefaultIcon": "icons/Speaker.png", 23 | "HelpTooltip": { 24 | "en": "Run AVR Control Macro", 25 | "de": "Startet AVR Control Makro" 26 | }, 27 | "HelpFileReference": null 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /ModbusClient/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.Modbus.dll", 4 | "DependentFiles": [ 5 | "LogicModule.Nodes.Helpers.dll", 6 | "EasyModbus.dll" 7 | ], 8 | "Version": "0.0.13", 9 | "Author": "Alram Lechner", 10 | "Copyright": "Alram Lechner", 11 | "DeveloperId": "alram_lechner_gmx_at", 12 | "License": "Free", 13 | "PackageId": "764BD112-8C3C-484B-95F0-31B4FD84193D", 14 | "Nodes": [ 15 | { 16 | "Type": "alram_lechner_gmx_at.logic.Modbus.ModbusClientNode", 17 | "Name": { 18 | "en": "QueryModbus TCP", 19 | "de": "Modbus TCP" 20 | }, 21 | "IsConverter": false, 22 | "Category": "Node", 23 | "DefaultIcon": "icons/GenericLogicNode.png", 24 | "HelpTooltip": { 25 | "en": "Query values from modbus tcp server.", 26 | "de": "Fragt Werte von einem Modbus TCP server ab." 27 | }, 28 | "HelpFileReference": null 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /HuaweiModbus/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.HuaweiModbus.dll", 4 | "DependentFiles": [ 5 | "LogicModule.Nodes.Helpers.dll", 6 | "EasyModbus.dll" 7 | ], 8 | "Version": "0.0.6", 9 | "Author": "Alram Lechner", 10 | "Copyright": "Alram Lechner", 11 | "DeveloperId": "alram_lechner_gmx_at", 12 | "License": "Free", 13 | "PackageId": "F2330AA0-51CA-4DF6-A5AE-4BE269B9FE13", 14 | "Nodes": [ 15 | { 16 | "Type": "alram_lechner_gmx_at.logic.HuaweiModbus.HuaweiModbusClientNode", 17 | "Name": { 18 | "en": "Query Huawei inverter", 19 | "de": "Huawei Wechselrichter auslesen" 20 | }, 21 | "IsConverter": false, 22 | "Category": "Node", 23 | "DefaultIcon": "icons/GenericLogicNode.png", 24 | "HelpTooltip": { 25 | "en": "Query values from Huawei Inverter.", 26 | "de": "Huawei Wechselrichter auslesen." 27 | }, 28 | "HelpFileReference": null 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /DewPoint/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.DewPoint.dll", 4 | "DependentFiles": [ 5 | "LogicModule.Nodes.Helpers.dll" 6 | ], 7 | "Version": "0.0.15", 8 | "Author": "Alram Lechner", 9 | "Copyright": "Alram Lechner", 10 | "DeveloperId": "alram_lechner_gmx_at", 11 | "License": "Free", 12 | "PackageId": "0E32B935-660A-4519-8D02-BC6666A31504", 13 | "Nodes": [ 14 | { 15 | "Type": "alram_lechner_gmx_at.logic.DewPoint.DewPointNode", 16 | "Name": { 17 | "en": "Dew point calculator", 18 | "de": "Taupunktrechner" 19 | }, 20 | "IsConverter": false, 21 | "Category": "Node", 22 | "DefaultIcon": "icons/DewPoint.png", 23 | "HelpTooltip": { 24 | "en": "Calculates dew point by temperature and rel. humidity. Icon provided by https://icons8.com/.", 25 | "de": "Berechnet den Taupunkt anhand von Temperatur und rel. Luftfeuchte. Icon zur Verfügung gestellt von https://icons8.com/." 26 | }, 27 | "HelpFileReference": null 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /SendMail/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.Mail.dll", 4 | "DependentFiles": [ 5 | "LogicModule.Nodes.Helpers.dll", 6 | "BouncyCastle.Crypto.dll", 7 | "Mail.dll", 8 | "MailKit.dll", 9 | "MimeKit.dll" 10 | ], 11 | "Version": "0.0.16", 12 | "Author": "Alram Lechner", 13 | "Copyright": "Alram Lechner", 14 | "DeveloperId": "alram_lechner_gmx_at", 15 | "License": "Free", 16 | "PackageId": "0E32B935-660A-4519-8D02-BC6666A31504", 17 | "Nodes": [ 18 | { 19 | "Type": "alram_lechner_gmx_at.logic.Mail.SendMail", 20 | "Name": { 21 | "en": "Send e-mail", 22 | "de": "eMail senden" 23 | }, 24 | "IsConverter": false, 25 | "Category": "Node", 26 | "DefaultIcon": "icons/SendMail.png", 27 | "HelpTooltip": { 28 | "en": "Send an e-mail via SMTP. Icon provided by https://icons8.com/.", 29 | "de": "Sendet ein eMail über das SMTP Protokoll. Icon zur Verfügung gestellt von https://icons8.com/." 30 | }, 31 | "HelpFileReference": null 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CommonLogicNodes 2 | Common LogicNodes for Gira X1/L1 Beta-SDK 3 | 4 | ## Nodes 5 | Short description of nodes. Including state (tested hardware, test coverage, etc.) 6 | 7 | ### DewPoint 8 | Dew point calculator. 9 | 10 | Tested environment: GPA, X1 11 | 12 | Dev: Alram Lechner 13 | 14 | Todo: 15 | - increase test coverage 16 | 17 | ### SendMail 18 | Sends mail via SMTP. 19 | Known to work with: 20 | - Synology DSM 6.x, no encryption, no authentication 21 | - GMX, encryption=STARTTLS (port 587), user+password authentication 22 | 23 | Status: POC / in development 24 | 25 | Tested environment: GPA, X1 26 | 27 | Dev: Alram Lechner 28 | 29 | Todo: 30 | - add feature params for subject, mailtext 31 | - increase test coverage 32 | 33 | ### ModbusClient 34 | Modbus TCP client. Takes modbus server, port, address and read length. executes 'read holding register' command. 35 | 36 | Status: POC / in development 37 | Tested environment: GPA, X1 38 | 39 | Dev: Alram Lechner 40 | 41 | Todo: 42 | - increase test coverage 43 | - add support for further commands 44 | 45 | ### InfluxDbNode 46 | Influx DB node to store data in InfluxDB (write datapoint). 47 | 48 | Status: POC/in development 49 | 50 | Dev: Alram Lechner 51 | 52 | Todo (a lot of things): 53 | - add auth 54 | - test encodings 55 | - refactor fields to be dynamic list 56 | - implement better logic to write many value as single datapoint 57 | -------------------------------------------------------------------------------- /SendMail/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("SendMail")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("SendMail")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("df8dd7d6-e549-4f2f-9be8-3c711f476170")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /HuaweiModbus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("ModbusClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ModbusClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("2a9ec00a-3396-4df6-881e-286c50ab863e")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /InfluxDbNode/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("InfluxDbNode")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("InfluxDbNode")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("84b240c2-3b02-4219-84dd-b630ba91f33d")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ModbusClient/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("ModbusClient")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ModbusClient")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("2a9ec00a-3396-4df6-881e-286c50ab863e")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /DewPoint/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("DewPoint")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Alram Lechner")] 12 | [assembly: AssemblyProduct("DewPoint")] 13 | [assembly: AssemblyCopyright("Copyright © 2019 Alram Lechner")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("33b04ee6-7687-4e34-9c27-f6e8f5b48e49")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /AVRControl/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Allgemeine Informationen über eine Assembly werden über die folgenden 6 | // Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, 7 | // die einer Assembly zugeordnet sind. 8 | [assembly: AssemblyTitle("DewPoint")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Alram Lechner")] 12 | [assembly: AssemblyProduct("DewPoint")] 13 | [assembly: AssemblyCopyright("Copyright © 2019 Alram Lechner")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly 18 | // für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von 19 | // COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen. 20 | [assembly: ComVisible(false)] 21 | 22 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird 23 | [assembly: Guid("33b04ee6-7687-4e34-9c27-f6e8f5b48e49")] 24 | 25 | // Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: 26 | // 27 | // Hauptversion 28 | // Nebenversion 29 | // Buildnummer 30 | // Revision 31 | // 32 | // Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden, 33 | // indem Sie "*" wie unten gezeigt eingeben: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /InfluxDbNode/Manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "PackageFormatVersion": "1.0", 3 | "Assembly": "alram_lechner_gmx_at.logic.InfluxDb2.dll", 4 | "PackageName": { 5 | "en": "InfluxDB Nodes v2" 6 | }, 7 | "DependentFiles": [ 8 | "LogicModule.Nodes.Helpers.dll" 9 | ], 10 | "Version": "0.2.17", 11 | "Author": "Alram Lechner", 12 | "Copyright": "Alram Lechner", 13 | "DeveloperId": "alram_lechner_gmx_at", 14 | "License": "Free", 15 | "PackageId": "7A5A65A8-EA71-466E-938D-419EC2C2F38D", 16 | "Nodes": [ 17 | { 18 | "Type": "alram_lechner_gmx_at.logic.InfluxDb2.WriteNode", 19 | "Name": { 20 | "en": "Write data to InfluxDb v2", 21 | "de": "InfluxDB Daten schreiben v2" 22 | }, 23 | "IsConverter": false, 24 | "Category": "Node", 25 | "DefaultIcon": "icons/Influxdb.png", 26 | "HelpTooltip": { 27 | "en": "Write data to InfluxDb measurement via REST API.", 28 | "de": "Schreibt InfluxDB measurement Daten via REST API." 29 | }, 30 | "HelpFileReference": null 31 | }, 32 | { 33 | "Type": "alram_lechner_gmx_at.logic.InfluxDb2.WriteElectricMeter", 34 | "Name": { 35 | "en": "Write value from electric meter to InfluxDb v2", 36 | "de": "Stromzähler Daten in InfluxDB schreiben v2" 37 | }, 38 | "IsConverter": false, 39 | "Category": "Node", 40 | "DefaultIcon": "icons/Influxdb.png", 41 | "HelpTooltip": { 42 | "en": "Write data of electric meter to InfluxDb measurement via REST API.", 43 | "de": "Schreibt Stromzähler Daten zu InfluxDB Daten via REST API." 44 | }, 45 | "HelpFileReference": null 46 | }, 47 | { 48 | "Type": "alram_lechner_gmx_at.logic.InfluxDb2.WriteThreePhaseElectricMeter", 49 | "Name": { 50 | "en": "Write value from 3 phase electric meter to InfluxDb v2", 51 | "de": "Drehstromzähler Daten in InfluxDB schreiben v2" 52 | }, 53 | "IsConverter": false, 54 | "Category": "Node", 55 | "DefaultIcon": "icons/Influxdb.png", 56 | "HelpTooltip": { 57 | "en": "Write data of 3 phase electric meter to InfluxDb measurement via REST API.", 58 | "de": "Schreibt Drehstromzähler Daten zu InfluxDB Daten via REST API." 59 | }, 60 | "HelpFileReference": null 61 | } 62 | ] 63 | } -------------------------------------------------------------------------------- /DewPoint/DewPointNode.cs: -------------------------------------------------------------------------------- 1 | using LogicModule.Nodes.Helpers; 2 | using LogicModule.ObjectModel; 3 | using LogicModule.ObjectModel.TypeSystem; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace alram_lechner_gmx_at.logic.DewPoint 11 | { 12 | public class DewPointNode : LogicNodeBase 13 | { 14 | [Input(DisplayOrder = 1, IsInput = true, IsRequired = false)] 15 | public DoubleValueObject Temperature { get; private set; } 16 | 17 | [Input(DisplayOrder = 2, IsInput = true, IsRequired = false)] 18 | public DoubleValueObject Humidity { get; private set; } 19 | 20 | [Output(DisplayOrder = 1, IsRequired = true)] 21 | public DoubleValueObject DewPoint { get; private set; } 22 | 23 | private ITypeService typeService; 24 | 25 | public DewPointNode(INodeContext context) : base(context) 26 | { 27 | context.ThrowIfNull("context"); 28 | 29 | this.typeService = context.GetService(); 30 | 31 | this.Temperature = this.typeService.CreateDouble(PortTypes.Temperature, "Temperatur (°C)"); 32 | this.Temperature.MinValue = -248; 33 | this.Temperature.MaxValue = 2000; 34 | 35 | this.Humidity = this.typeService.CreateDouble(PortTypes.Float, "rel. Luftfeuchte (%)"); 36 | this.Humidity.MinValue = 0; 37 | this.Humidity.MaxValue = 100; 38 | 39 | this.DewPoint = this.typeService.CreateDouble(PortTypes.Float, "Taupunkt (°C)"); 40 | } 41 | 42 | public override void Startup() 43 | { 44 | } 45 | 46 | public override void Execute() 47 | { 48 | if (!this.Temperature.HasValue || !this.Humidity.HasValue) 49 | { 50 | DewPoint.BlockGraph(); 51 | return; 52 | } 53 | DewPoint.Value = CalculateDewPoint(Temperature.Value, Humidity.Value); 54 | } 55 | 56 | /// 57 | /// TODO: good candiate for unit tests ... 58 | /// 59 | /// temperature (°C) 60 | /// rel .humidity (%) 61 | /// dew point (°C) 62 | public double CalculateDewPoint(double temperature, double humidity) 63 | { 64 | double a, b; 65 | if (temperature >= 0) 66 | { 67 | a = 7.5; 68 | b = 237.3; 69 | } 70 | else 71 | { 72 | a = 7.6; 73 | b = 240.7; 74 | } 75 | double sdd = 6.1078 * Math.Pow(10, (a * temperature) / (b + temperature)); 76 | double dd = humidity / 100 * sdd; 77 | double dewPoint = b * Math.Log10(dd / 6.1078) / (a - Math.Log10(dd / 6.1078)); 78 | return Math.Round(dewPoint, 2); 79 | } 80 | 81 | public override ValidationResult Validate(string language) 82 | { 83 | return base.Validate(language); 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /AVRControl/AVRControl.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {94E9D290-6872-4CC9-9B83-431C396A9857} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.AvrControl 11 | alram_lechner_gmx_at.logic.AvrControl 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 36 | 37 | 38 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 63 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 64 | 65 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 66 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 67 | 68 | -------------------------------------------------------------------------------- /DewPoint/DewPoint.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {33B04EE6-7687-4E34-9C27-F6E8F5B48E49} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.DewPoint 11 | alram_lechner_gmx_at.logic.DewPoint 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 36 | 37 | 38 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | 58 | 59 | PreserveNewest 60 | 61 | 62 | 63 | 64 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 65 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 66 | 67 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 68 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 69 | 70 | -------------------------------------------------------------------------------- /ModbusClient/ModbusClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2A9EC00A-3396-4DF6-881E-286C50AB863E} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.Modbus 11 | alram_lechner_gmx_at.logic.Modbus 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\EasyModbusTCP.5.6.0\lib\net40\EasyModbus.dll 36 | 37 | 38 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 39 | 40 | 41 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | PreserveNewest 58 | 59 | 60 | 61 | 62 | 63 | PreserveNewest 64 | 65 | 66 | 67 | 68 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 69 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 70 | 71 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 72 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 73 | 74 | -------------------------------------------------------------------------------- /HuaweiModbus/HuaweiModbus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {77D9C4CF-EA5F-41D8-B177-2975F2EC90F1} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.HuaweiModbus 11 | alram_lechner_gmx_at.logic.HuaweiModbus 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\EasyModbusTCP.5.6.0\lib\net40\EasyModbus.dll 36 | 37 | 38 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 39 | 40 | 41 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | PreserveNewest 58 | 59 | 60 | 61 | 62 | 63 | PreserveNewest 64 | 65 | 66 | 67 | 68 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 69 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 70 | 71 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 72 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 73 | 74 | -------------------------------------------------------------------------------- /InfluxDbNode/InfluxDbNode.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {84B240C2-3B02-4219-84DD-B630BA91F33D} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.InfluxDb2 11 | alram_lechner_gmx_at.logic.InfluxDb2 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 39 | 40 | 41 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | 65 | 66 | PreserveNewest 67 | 68 | 69 | 70 | 71 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 72 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 73 | 74 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 75 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 76 | 77 | -------------------------------------------------------------------------------- /InfluxDbNode/InfluxWriterHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace alram_lechner_gmx_at.logic.InfluxDb2 11 | { 12 | class InfluxWriterHelper 13 | { 14 | 15 | public static void WriteDatapointSync(String influxUrl, String measureName, String measureTags, String fieldName, double value, Action SetResultCallback) 16 | { 17 | UriBuilder uriBuilder = null; 18 | try 19 | { 20 | uriBuilder = new UriBuilder(influxUrl); 21 | } catch (UriFormatException e) 22 | { 23 | SetResultCallback(997, e.Message + "; URI was " + influxUrl + "; tags: " + measureTags); 24 | return; 25 | } 26 | if (uriBuilder.Port == -1) 27 | { 28 | uriBuilder.Port = 8086; 29 | } 30 | String queryToAppend = "precision=s"; 31 | if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) 32 | uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + queryToAppend; 33 | else 34 | uriBuilder.Query = queryToAppend; 35 | 36 | // Open HTTP connection: 37 | String Body = ""; 38 | try 39 | { 40 | HttpWebRequest client = (HttpWebRequest)HttpWebRequest.Create(uriBuilder.Uri); 41 | client.Method = "POST"; 42 | client.ContentType = "text/plain"; 43 | 44 | using (var request = client.GetRequestStream()) 45 | { 46 | using (var writer = new StreamWriter(request)) 47 | { 48 | Body = measureName; 49 | if (measureTags != null && measureTags.Length > 0) 50 | { 51 | Body += "," + measureTags; 52 | } 53 | 54 | Body += " "; 55 | Body += fieldName + "=" + value.ToString("G", CultureInfo.InvariantCulture); 56 | 57 | writer.Write(Body); 58 | } 59 | } 60 | var response = client.GetResponse(); 61 | using (var result = response.GetResponseStream()) 62 | { 63 | using (var reader = new StreamReader(result)) 64 | { 65 | SetResultCallback(null, null); 66 | } 67 | } 68 | } 69 | catch (WebException e) 70 | { 71 | if (e.Response is HttpWebResponse errorResponse) 72 | { 73 | try 74 | { 75 | using (var result = errorResponse.GetResponseStream()) 76 | { 77 | using (var reader = new StreamReader(result)) 78 | { 79 | SetResultCallback((int)errorResponse.StatusCode, reader.ReadToEnd() + "; Line was: " + Body); 80 | return; 81 | } 82 | } 83 | } 84 | catch (Exception) 85 | { 86 | } 87 | } 88 | SetResultCallback(998, "Unknown error" + "; Line was: " + Body); 89 | } 90 | catch (Exception e) 91 | { 92 | SetResultCallback(999, e.Message + "; Line was: " + Body); 93 | return; 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /SendMail/SendMail.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {DF8DD7D6-E549-4F2F-9BE8-3C711F476170} 8 | Library 9 | Properties 10 | alram_lechner_gmx_at.logic.Mail 11 | alram_lechner_gmx_at.logic.Mail 12 | v4.0 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | false 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | ..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll 39 | 40 | 41 | ..\LogicNodesSDK\LogicModule.Nodes.Helpers.dll 42 | 43 | 44 | ..\LogicNodesSDK\LogicModule.ObjectModel.dll 45 | 46 | 47 | ..\packages\MailKit.1.22.0\lib\net40\MailKit.dll 48 | 49 | 50 | ..\packages\MimeKit.1.22.0\lib\net40\MimeKit.dll 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | PreserveNewest 70 | 71 | 72 | 73 | 74 | 75 | PreserveNewest 76 | 77 | 78 | 79 | 80 | if not exist "$(SolutionDir)\Zip" mkdir "$(SolutionDir)\Zip" 81 | "$(SolutionDir)\LogicNodesSDK\LogicNodeTool.exe" create "$(TargetDir) " "$(SolutionDir)\Zip" 82 | 83 | @REM echo INFO: Logic Nodes are not signed yet and can only be used in the simulation in GPA. 84 | "$(SolutionDir)\LogicNodesSDK\SignLogicNodes.exe" "$(SolutionDir)\..\Alram_Lechner.p12" "" $(SolutionDir)\Zip 85 | 86 | -------------------------------------------------------------------------------- /InfluxDbNode/WriteElectricMeter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using LogicModule.Nodes.Helpers; 9 | using LogicModule.ObjectModel; 10 | using LogicModule.ObjectModel.TypeSystem; 11 | using System.Globalization; 12 | 13 | namespace alram_lechner_gmx_at.logic.InfluxDb2 14 | { 15 | 16 | public class WriteElectricMeter : LogicNodeBase 17 | { 18 | // Parameter 19 | [Parameter(DisplayOrder = 1, IsRequired = true, IsDefaultShown = false)] 20 | public StringValueObject InfluxDbUrl { get; private set; } 21 | 22 | [Parameter(DisplayOrder = 2, IsRequired = true, IsDefaultShown = true)] 23 | public StringValueObject InfluxMeasureName { get; private set; } 24 | 25 | [Parameter(DisplayOrder = 3, IsRequired = true, IsDefaultShown = true)] 26 | public StringValueObject InfluxMeasureTags { get; private set; } 27 | 28 | // Input 29 | [Input(DisplayOrder = 7, IsInput = true, IsRequired = true)] 30 | public DoubleValueObject MainMeterValue { get; private set; } 31 | 32 | [Input(DisplayOrder = 8, IsInput = true, IsRequired = true)] 33 | public DoubleValueObject CurrentPowerValue { get; private set; } 34 | 35 | [Input(DisplayOrder = 9, IsInput = true, IsRequired = true)] 36 | public DoubleValueObject DailyMeterValue { get; private set; } 37 | 38 | // Output 39 | [Output(DisplayOrder = 2, IsRequired = false, IsDefaultShown = false)] 40 | public BoolValueObject ResetDailyMeterCounter { get; private set; } 41 | 42 | [Output(DisplayOrder = 3, IsRequired = false, IsDefaultShown = false)] 43 | public IntValueObject ErrorCode { get; private set; } 44 | 45 | [Output(DisplayOrder = 4, IsRequired = false, IsDefaultShown = false)] 46 | public StringValueObject ErrorMessage { get; private set; } 47 | 48 | private ITypeService TypeService = null; 49 | 50 | private double LastDailyMeterCounterValueSent = -1; 51 | private DateTime LastDailyMeterCounterValueTime = new DateTime(2010, 1, 1); 52 | 53 | public WriteElectricMeter(INodeContext context) : base(context) 54 | { 55 | context.ThrowIfNull("context"); 56 | this.TypeService = context.GetService(); 57 | this.InfluxDbUrl = TypeService.CreateString(PortTypes.String, "Influx DB URL", "http://:/write?db="); 58 | this.InfluxMeasureName = TypeService.CreateString(PortTypes.String, "Measure name", "sensor"); 59 | this.InfluxMeasureTags = TypeService.CreateString(PortTypes.String, "Tags", "room=kitchen,device=washingmachine"); 60 | this.MainMeterValue = TypeService.CreateDouble(PortTypes.Number, "Main counter (kWh)"); 61 | this.CurrentPowerValue = TypeService.CreateDouble(PortTypes.Number, "Current power (W)"); 62 | this.DailyMeterValue = TypeService.CreateDouble(PortTypes.Number, "Daily counter (Wh)"); 63 | this.ResetDailyMeterCounter = TypeService.CreateBool(PortTypes.Bool, "Reset daily counter", false); 64 | this.ErrorCode = TypeService.CreateInt(PortTypes.Integer, "HTTP status-code"); 65 | this.ErrorMessage = TypeService.CreateString(PortTypes.String, "Error message"); 66 | } 67 | 68 | public override void Startup() 69 | { 70 | } 71 | 72 | public override ValidationResult Validate(string language) 73 | { 74 | return base.Validate(language); 75 | } 76 | 77 | public override void Execute() 78 | { 79 | if (!InfluxDbUrl.HasValue || !InfluxMeasureName.HasValue) 80 | { 81 | return; 82 | } 83 | 84 | if (this.CurrentPowerValue.HasValue && this.CurrentPowerValue.WasSet) 85 | { 86 | WriteDatapointAsync("power", this.CurrentPowerValue.Value); 87 | } 88 | 89 | if (this.MainMeterValue.HasValue && this.MainMeterValue.WasSet) 90 | { 91 | WriteDatapointAsync("meter", this.MainMeterValue.Value); 92 | } 93 | 94 | if (this.DailyMeterValue.HasValue && this.DailyMeterValue.WasSet) 95 | { 96 | 97 | if (!(LastDailyMeterCounterValueSent == this.DailyMeterValue.Value 98 | && DateTime.Compare(LastDailyMeterCounterValueTime, DateTime.Now.Subtract(TimeSpan.FromSeconds(10))) > 0)) 99 | { 100 | WriteDatapointAsync("intermediatecounter", this.DailyMeterValue.Value); 101 | LastDailyMeterCounterValueSent = this.DailyMeterValue.Value; 102 | LastDailyMeterCounterValueTime = DateTime.Now; 103 | } else 104 | { 105 | // debugging only! 106 | WriteDatapointAsync("ignored_intermediatecounter", this.DailyMeterValue.Value); 107 | } 108 | 109 | if (this.DailyMeterValue.Value > 0) 110 | { 111 | this.ResetDailyMeterCounter.Value = true; 112 | } 113 | } 114 | 115 | } 116 | 117 | public void WriteDatapointAsync(String fieldName, double value) 118 | { 119 | var thread = new Thread(() => 120 | { 121 | InfluxWriterHelper.WriteDatapointSync( 122 | this.InfluxDbUrl.Value, 123 | this.InfluxMeasureName.Value, 124 | this.InfluxMeasureTags.HasValue ? this.InfluxMeasureTags.Value : null, 125 | fieldName, 126 | value, 127 | (errorCode, errorMessage) => 128 | { 129 | if (errorCode != null) 130 | { 131 | ErrorCode.Value = errorCode.Value; 132 | } 133 | if (errorMessage != null) 134 | { 135 | ErrorMessage.Value = errorMessage; 136 | } 137 | }); 138 | }); 139 | thread.Start(); 140 | } 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /AVRControl/AvrControl.cs: -------------------------------------------------------------------------------- 1 | using LogicModule.Nodes.Helpers; 2 | using LogicModule.ObjectModel; 3 | using LogicModule.ObjectModel.TypeSystem; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace alram_lechner_gmx_at.logic.AvrControl 13 | { 14 | public class RunMacro : LogicNodeBase 15 | { 16 | [Parameter(DisplayOrder = 1, IsRequired = true, IsDefaultShown = false)] 17 | public StringValueObject AvrControlIp { get; private set; } 18 | 19 | [Input(DisplayOrder = 2, IsInput = true, IsRequired = false)] 20 | public StringValueObject MacroName { get; private set; } 21 | 22 | [Input(DisplayOrder = 3, IsInput = true, IsRequired = false)] 23 | public BoolValueObject InputPower { get; private set; } 24 | 25 | [Input(DisplayOrder = 4, IsInput = true, IsRequired = false)] 26 | public BoolValueObject VolumeRelativ { get; private set; } 27 | 28 | [Input(DisplayOrder = 5, IsInput = true, IsRequired = false)] 29 | public BoolValueObject SpeakerBEnable { get; private set; } 30 | 31 | [Output(DisplayOrder = 1, IsDefaultShown = true)] 32 | public BoolValueObject OutputPower { get; private set; } 33 | 34 | [Output(DisplayOrder = 2, IsDefaultShown = true)] 35 | public BoolValueObject OutputSpeakerBEnable { get; private set; } 36 | 37 | 38 | [Output(DisplayOrder = 2, IsRequired = false, IsDefaultShown = false)] 39 | public StringValueObject ErrorMessage { get; private set; } 40 | 41 | 42 | private ITypeService typeService; 43 | private UdpClient udpReceiverClient; 44 | private IPEndPoint ipEndpointAvrControl; 45 | 46 | public RunMacro(INodeContext context) : base(context) 47 | { 48 | context.ThrowIfNull("context"); 49 | this.typeService = context.GetService(); 50 | this.AvrControlIp = typeService.CreateString(PortTypes.String, "IP of AVR Control", "192.168.17.14"); 51 | this.MacroName = this.typeService.CreateString(PortTypes.String, "Macroname", "KITCHEN_RADIO"); 52 | this.MacroName.MaxLength = 20; 53 | this.InputPower = this.typeService.CreateBool(PortTypes.Binary, "Power Switch", false); 54 | this.OutputPower = this.typeService.CreateBool(PortTypes.Binary, "Power Status", false); 55 | this.VolumeRelativ = this.typeService.CreateBool(PortTypes.Binary, "Volume relative", false); 56 | this.SpeakerBEnable = this.typeService.CreateBool(PortTypes.Binary, "Speaker B enable", false); 57 | this.OutputSpeakerBEnable = this.typeService.CreateBool(PortTypes.Binary, "Speaker B status", false); 58 | this.ErrorMessage = this.typeService.CreateString(PortTypes.String, "Errormessage", ""); 59 | } 60 | 61 | public void ReceiveCallback(IAsyncResult ar) 62 | { 63 | byte[] receiveBytes = udpReceiverClient.EndReceive(ar, ref ipEndpointAvrControl); 64 | string receiveString = Encoding.ASCII.GetString(receiveBytes); 65 | if (receiveString.StartsWith("POWER:")) 66 | { 67 | // POWER:00000::off 68 | OutputPower.Value = receiveString.EndsWith("on"); 69 | } else if (receiveString.StartsWith("SPEAKER_B:")) 70 | { 71 | // SPEAKER_B:00001::on 72 | OutputSpeakerBEnable.Value = receiveString.EndsWith("on"); 73 | } 74 | else if (receiveString.StartsWith("MUTE:")) 75 | { 76 | // OutputMute.Value = receiveString.EndsWith("on"); 77 | } 78 | else 79 | { 80 | ErrorMessage.Value = "Unkown status from AVR: '" + receiveString + "'"; 81 | } 82 | 83 | // needed? 84 | udpReceiverClient.BeginReceive(new AsyncCallback(ReceiveCallback), null); 85 | } 86 | 87 | 88 | public override void Startup() 89 | { 90 | // Receive a message and write it to the console. 91 | ipEndpointAvrControl = new IPEndPoint(IPAddress.Parse("0.0.0.0") , 14000); 92 | this.udpReceiverClient = new UdpClient(ipEndpointAvrControl); 93 | udpReceiverClient.BeginReceive(new AsyncCallback(ReceiveCallback), null); 94 | } 95 | 96 | 97 | public override void Execute() 98 | { 99 | if (this.MacroName.HasValue && this.MacroName.WasSet) 100 | { 101 | SendCommand("macro " + this.MacroName.Value); 102 | } 103 | if (this.InputPower.HasValue && this.InputPower.WasSet) 104 | { 105 | if (this.InputPower.Value == true) 106 | { 107 | SendCommand("avr power on"); 108 | } 109 | else 110 | { 111 | SendCommand("avr power off"); 112 | // SendCommand("macro KITCHEN_RADIO_OFF"); 113 | } 114 | } 115 | if (this.VolumeRelativ.HasValue && this.VolumeRelativ.WasSet) 116 | { 117 | if (this.VolumeRelativ.Value) 118 | { 119 | SendCommand("AVR VOLUP"); 120 | } else 121 | { 122 | SendCommand("AVR VOLDOWN"); 123 | } 124 | } 125 | if (this.SpeakerBEnable.HasValue && this.SpeakerBEnable.WasSet) 126 | { 127 | if (this.SpeakerBEnable.Value) 128 | { 129 | SendCommand("AVR SPEAKER B ON"); 130 | } 131 | else 132 | { 133 | SendCommand("AVR SPEAKER B OFF"); 134 | } 135 | } 136 | } 137 | 138 | public void SendCommand(String command) 139 | { 140 | if (!AvrControlIp.HasValue) 141 | { 142 | return; 143 | } 144 | 145 | if (!command.EndsWith("\r")) 146 | { 147 | command += "\r"; 148 | } 149 | 150 | // send command per UDP to port 14000 151 | UdpClient udpClient = new UdpClient(); 152 | Byte[] sendBytes = Encoding.ASCII.GetBytes(command); 153 | try 154 | { 155 | udpClient.Send(sendBytes, sendBytes.Length, AvrControlIp.Value, 14000); 156 | } 157 | catch (Exception e) 158 | { 159 | ErrorMessage.Value = e.ToString(); 160 | // Console.WriteLine(e.ToString()); 161 | } 162 | } 163 | 164 | public override ValidationResult Validate(string language) 165 | { 166 | return base.Validate(language); 167 | } 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /SendMail/SendMail.cs: -------------------------------------------------------------------------------- 1 | using LogicModule.Nodes.Helpers; 2 | using LogicModule.ObjectModel; 3 | using LogicModule.ObjectModel.TypeSystem; 4 | using MailKit.Net.Smtp; 5 | using MimeKit; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace alram_lechner_gmx_at.logic.Mail 13 | { 14 | 15 | static class EncryptionTypes 16 | { 17 | public const string NONE = "Unverschlüsselt"; 18 | public const string SSL = "SSL"; 19 | public const string STARTTLS = "STARTTLS"; 20 | public static string[] VALUES = new[] { NONE,SSL,STARTTLS }; 21 | } 22 | 23 | public class SendMail : LogicNodeBase 24 | { 25 | [Input(DisplayOrder = 1, IsInput = true, IsRequired = true)] 26 | public BoolValueObject SendTrigger { get; private set; } 27 | 28 | [Parameter(DisplayOrder = 2, InitOrder = 1, IsDefaultShown = false)] 29 | public StringValueObject To { get; private set; } 30 | 31 | [Parameter(DisplayOrder = 3, InitOrder = 1, IsDefaultShown = false)] 32 | public StringValueObject From { get; private set; } 33 | 34 | [Parameter(DisplayOrder = 4, InitOrder = 1, IsDefaultShown = false)] 35 | public StringValueObject SmtpHost { get; private set; } 36 | 37 | [Parameter(DisplayOrder = 5, InitOrder = 1, IsDefaultShown = false)] 38 | public IntValueObject SmtpPort { get; private set; } 39 | 40 | [Parameter(DisplayOrder = 6, InitOrder = 1, IsDefaultShown = false)] 41 | public EnumValueObject Encryption { get; private set; } 42 | 43 | [Parameter(DisplayOrder = 7, InitOrder = 1, IsDefaultShown = false, IsRequired = false)] 44 | public StringValueObject SmtpUser { get; private set; } 45 | 46 | [Parameter(DisplayOrder = 8, InitOrder = 1, IsDefaultShown = false, IsRequired = false)] 47 | public StringValueObject SmtpPassword { get; private set; } 48 | 49 | [Input(DisplayOrder = 9, InitOrder = 1, IsDefaultShown = false)] 50 | public StringValueObject Subject { get; private set; } 51 | 52 | [Input(DisplayOrder = 10, InitOrder = 1, IsDefaultShown = false)] 53 | public StringValueObject MailBody { get; private set; } 54 | 55 | [Output(DisplayOrder = 1)] 56 | public StringValueObject ErrorMessage { get; private set; } 57 | 58 | public SendMail(INodeContext context) : base(context) 59 | { 60 | context.ThrowIfNull("context"); 61 | ITypeService typeService = context.GetService(); 62 | this.SendTrigger = typeService.CreateBool(PortTypes.Bool, "Trigger"); 63 | this.To = typeService.CreateString(PortTypes.String, "Empfängeradresse"); 64 | this.From = typeService.CreateString(PortTypes.String, "Senderadresse"); 65 | this.SmtpHost = typeService.CreateString(PortTypes.String, "SMTP Server"); 66 | this.SmtpPort = typeService.CreateInt(PortTypes.Integer, "SMTP Port"); 67 | this.ErrorMessage = typeService.CreateString(PortTypes.String, "Fehlertext"); 68 | this.Encryption = typeService.CreateEnum("SmtpEncryption", "Verschlüsselung", EncryptionTypes.VALUES); 69 | this.SmtpUser = typeService.CreateString(PortTypes.String, "SMTP Benutzer"); 70 | this.SmtpPassword = typeService.CreateString(PortTypes.String, "SMTP Kennwort"); 71 | this.Subject = typeService.CreateString(PortTypes.String, "Betreff"); 72 | this.MailBody = typeService.CreateString(PortTypes.String, "Mailtext"); 73 | } 74 | 75 | public override void Startup() 76 | { 77 | 78 | } 79 | 80 | public override void Execute() 81 | { 82 | if (!SendTrigger.HasValue || !SendTrigger.WasSet || !To.HasValue || !From.HasValue || !SmtpHost.HasValue || !SmtpPort.HasValue 83 | || !Encryption.HasValue) 84 | { 85 | return; 86 | } 87 | 88 | // TODO: schedule as async task ... 89 | try 90 | { 91 | SendMessage(); 92 | this.ErrorMessage.Value = ""; 93 | } 94 | catch (Exception e) 95 | { 96 | this.ErrorMessage.Value = e.ToString(); 97 | } 98 | } 99 | 100 | public void SendMessage() 101 | { 102 | var message = new MimeMessage(); 103 | message.From.Add(new MailboxAddress(From.Value)); 104 | message.To.Add(new MailboxAddress(To.Value)); 105 | if (Subject.HasValue) 106 | { 107 | message.Subject = Subject.Value; 108 | } else 109 | { 110 | message.Subject = Subject.Value; 111 | } 112 | 113 | if (MailBody.HasValue) 114 | { 115 | message.Body = new TextPart("plain") 116 | { 117 | Text = MailBody.Value 118 | }; 119 | } else 120 | { 121 | message.Body = new TextPart("plain") 122 | { 123 | Text = "" 124 | }; 125 | } 126 | 127 | using (var client = new SmtpClient()) 128 | { 129 | // For demo-purposes, accept all SSL certificates (in case the server supports STARTTLS) 130 | client.ServerCertificateValidationCallback = (s, c, h, e) => true; 131 | MailKit.Security.SecureSocketOptions socketOptions; 132 | switch (Encryption.Value) 133 | { 134 | case EncryptionTypes.SSL: 135 | socketOptions = MailKit.Security.SecureSocketOptions.SslOnConnect; 136 | break; 137 | case EncryptionTypes.STARTTLS: 138 | socketOptions = MailKit.Security.SecureSocketOptions.StartTls; 139 | break; 140 | case EncryptionTypes.NONE: 141 | socketOptions = MailKit.Security.SecureSocketOptions.None; 142 | break; 143 | default: 144 | socketOptions = MailKit.Security.SecureSocketOptions.Auto; 145 | break; 146 | } 147 | 148 | client.Connect(SmtpHost.Value, SmtpPort.Value, socketOptions); 149 | 150 | // Note: only needed if the SMTP server requires authentication 151 | if (SmtpUser.HasValue && SmtpPassword.HasValue && SmtpUser.Value.Length >= 1 && SmtpPassword.Value.Length >= 1) 152 | { 153 | client.Authenticate(SmtpUser.Value, SmtpPassword.Value); 154 | } 155 | 156 | client.Send(message); 157 | client.Disconnect(true); 158 | } 159 | } 160 | 161 | public override ValidationResult Validate(string language) 162 | { 163 | return base.Validate(language); 164 | } 165 | 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /InfluxDbNode/WriteNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using LogicModule.Nodes.Helpers; 9 | using LogicModule.ObjectModel; 10 | using LogicModule.ObjectModel.TypeSystem; 11 | using System.Globalization; 12 | 13 | namespace alram_lechner_gmx_at.logic.InfluxDb2 14 | { 15 | 16 | public class WriteNode : LogicNodeBase 17 | { 18 | [Parameter(DisplayOrder = 1, IsRequired = true, IsDefaultShown = false)] 19 | public StringValueObject InfluxDbUrl { get; private set; } 20 | 21 | [Parameter(DisplayOrder = 4, IsRequired = true, IsDefaultShown = true)] 22 | public StringValueObject InfluxMeasureName { get; private set; } 23 | 24 | [Parameter(DisplayOrder = 5, IsRequired = true, IsDefaultShown = true)] 25 | public StringValueObject InfluxMeasureTags { get; private set; } 26 | 27 | [Parameter(DisplayOrder = 6, IsRequired = true, IsDefaultShown = false)] 28 | public StringValueObject InfluxMeasureFieldName { get; private set; } 29 | 30 | [Input(DisplayOrder = 7, IsInput = true, IsRequired = true)] 31 | public DoubleValueObject InfluxMeasureFieldValue { get; private set; } 32 | 33 | [Output(DisplayOrder = 1, IsRequired = false, IsDefaultShown = false)] 34 | public IntValueObject ErrorCode { get; private set; } 35 | 36 | [Output(DisplayOrder = 2, IsRequired = false, IsDefaultShown = false)] 37 | public StringValueObject ErrorMessage { get; private set; } 38 | 39 | //IList changedInputs = new List(); 40 | 41 | private ITypeService typeService = null; 42 | //private ISchedulerService schedulerService; 43 | //private SchedulerToken schedulerToken = null; 44 | 45 | public WriteNode(INodeContext context) : base(context) 46 | { 47 | context.ThrowIfNull("context"); 48 | typeService = context.GetService(); 49 | // schedulerService = context.GetService(); 50 | this.InfluxDbUrl = typeService.CreateString(PortTypes.String, "Influx DB URL", "http://:/write?db="); 51 | this.InfluxMeasureName = typeService.CreateString(PortTypes.String, "Measure name", "sensor"); 52 | this.InfluxMeasureTags = typeService.CreateString(PortTypes.String, "Tags", "room=kitchen"); 53 | // UpdateMeasureFieldCount(null, null); 54 | this.InfluxMeasureFieldName = typeService.CreateString(PortTypes.String, "Measure field name", "temp"); 55 | this.InfluxMeasureFieldValue = typeService.CreateDouble(PortTypes.Number, "Measure value"); 56 | this.ErrorCode = typeService.CreateInt(PortTypes.Integer, "HTTP status-code"); 57 | this.ErrorMessage = typeService.CreateString(PortTypes.String, "Error message"); 58 | } 59 | /* 60 | private void MeasureFieldCountUpdated(object sender, ValueChangedEventArgs args) 61 | { 62 | int desiredLength = MeasureFieldCount.Value; 63 | 64 | if (MeasureFields.Count < desiredLength * InputsPerField) 65 | { 66 | for (int i = MeasureFields.Count; i < desiredLength * InputsPerField; i++) 67 | { 68 | 69 | switch (i % InputsPerField) 70 | { 71 | case 0: 72 | IValueObject fieldName = typeService.CreateString(PortTypes.String, String.Format("Field name {0}", i / InputsPerField + 1), ""); 73 | MeasureFields.Add(fieldName); 74 | break; 75 | case 1: 76 | IValueObject tags = typeService.CreateString(PortTypes.String, String.Format("Tags for {0}", (int)(i / InputsPerField + 1))); 77 | MeasureFields.Add(tags); 78 | break; 79 | case 2: 80 | IValueObject fieldValue = typeService.CreateDouble(PortTypes.Float, String.Format("Value for {0}", (int)(i / InputsPerField + 1)), 0); 81 | MeasureFields.Add(fieldValue); 82 | break; 83 | default: break; 84 | } 85 | } 86 | 87 | } 88 | else 89 | { 90 | while (MeasureFields.Count > desiredLength) 91 | { 92 | MeasureFields.RemoveAt(MeasureFields.Count - 1); 93 | } 94 | } 95 | 96 | while (changedInputs.Count < desiredLength) 97 | { 98 | changedInputs.Add(false); 99 | } 100 | while (changedInputs.Count > desiredLength) 101 | { 102 | changedInputs.RemoveAt(0); 103 | } 104 | } 105 | */ 106 | 107 | public override void Startup() 108 | { 109 | 110 | } 111 | 112 | public override ValidationResult Validate(string language) 113 | { 114 | return base.Validate(language); 115 | } 116 | 117 | public override void Execute() 118 | { 119 | // if (!InfluxDbHost.HasValue || !InfluxDbPort.HasValue || !InfluxDbName.HasValue || !InfluxMeasureName.HasValue) 120 | if (!InfluxDbUrl.HasValue || !InfluxMeasureName.HasValue ||!InfluxMeasureFieldName.HasValue || !InfluxMeasureFieldValue.HasValue) 121 | { 122 | return; 123 | } 124 | WriteDatapointAsync(); 125 | } 126 | 127 | public void WriteDatapointAsync() 128 | { 129 | // schedulerToken = null; 130 | var thread = new Thread(() => 131 | { 132 | WriteDatapointSync( 133 | (errorCode, errorMessage) => 134 | { 135 | if (errorCode != null) 136 | { 137 | ErrorCode.Value = errorCode.Value; 138 | } 139 | if (errorMessage != null) 140 | { 141 | ErrorMessage.Value = errorMessage; 142 | } 143 | }); 144 | }); 145 | thread.Start(); 146 | } 147 | 148 | public void WriteDatapointSync(Action SetResultCallback) 149 | { 150 | // String URL = "http://" + InfluxDbHost.Value + ":" + InfluxDbPort.Value + "/write?db=" + InfluxDbName + "&precision=s"; 151 | UriBuilder uriBuilder = new UriBuilder(InfluxDbUrl.Value); 152 | if (uriBuilder.Port == -1) 153 | { 154 | uriBuilder.Port = 8086; 155 | } 156 | String queryToAppend = "precision=s"; 157 | if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) 158 | uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + queryToAppend; 159 | else 160 | uriBuilder.Query = queryToAppend; 161 | 162 | // Open HTTP connection: 163 | String Body = ""; 164 | try 165 | { 166 | HttpWebRequest client = (HttpWebRequest)HttpWebRequest.Create(uriBuilder.Uri); 167 | client.Method = "POST"; 168 | client.ContentType = "text/plain"; 169 | 170 | using (var request = client.GetRequestStream()) 171 | { 172 | using (var writer = new StreamWriter(request)) 173 | { 174 | Body = InfluxMeasureName.Value; 175 | if (InfluxMeasureTags.HasValue) 176 | { 177 | Body += "," + InfluxMeasureTags.Value; 178 | } 179 | 180 | Body += " "; 181 | Body += InfluxMeasureFieldName.Value + "=" + InfluxMeasureFieldValue.Value.ToString("G", CultureInfo.InvariantCulture); 182 | 183 | writer.Write(Body); 184 | } 185 | } 186 | var response = client.GetResponse(); 187 | using (var result = response.GetResponseStream()) 188 | { 189 | using (var reader = new StreamReader(result)) 190 | { 191 | SetResultCallback(null, null); 192 | } 193 | } 194 | } 195 | catch (WebException e) 196 | { 197 | if (e.Response is HttpWebResponse errorResponse) 198 | { 199 | try 200 | { 201 | using (var result = errorResponse.GetResponseStream()) 202 | { 203 | using (var reader = new StreamReader(result)) 204 | { 205 | SetResultCallback((int)errorResponse.StatusCode, reader.ReadToEnd() + "; Line was: " + Body); 206 | return; 207 | } 208 | } 209 | } 210 | catch (Exception) 211 | { 212 | } 213 | } 214 | SetResultCallback(998, "Unknown error" + "; Line was: " + Body); 215 | } 216 | catch (Exception e) 217 | { 218 | SetResultCallback(999, e.Message + "; Line was: " + Body); 219 | return; 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /ModbusClient/ModbusClient.cs: -------------------------------------------------------------------------------- 1 | using EasyModbus; 2 | using LogicModule.Nodes.Helpers; 3 | using LogicModule.ObjectModel; 4 | using LogicModule.ObjectModel.TypeSystem; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace alram_lechner_gmx_at.logic.Modbus 11 | { 12 | static class FunctionCodeEnum 13 | { 14 | public const string FC_03 = "Read Holding Registers (03)"; 15 | public const string FC_04 = "Read Input Registers (04)"; 16 | public static string[] VALUES = new[] { FC_03, FC_04 }; 17 | } 18 | static class DataTypeEnum 19 | { 20 | public const string INT16_UNSIGNED = "16bit integer"; 21 | public const string INT16_SIGNED = "16bit integer (signed)"; 22 | public const string INT32 = "integer (32bit)"; 23 | public const string FLOAT = "float (32bit)"; 24 | public const string LONG = "long (64bit)"; 25 | public const string DOUBLE = "double (64bit)"; 26 | 27 | public static string[] VALUES = new[] { INT16_SIGNED, INT16_UNSIGNED, INT32, FLOAT, LONG, DOUBLE }; 28 | } 29 | 30 | static class ByteOrderEnum 31 | { 32 | public const string HIGH_LOW = "big-endian"; 33 | public const string LOW_HIGH = "little-endian"; 34 | 35 | public static string[] VALUES = new[] { HIGH_LOW, LOW_HIGH }; 36 | } 37 | 38 | public class ModbusClientNode : LogicNodeBase 39 | { 40 | 41 | [Parameter(DisplayOrder = 1, InitOrder = 1, IsDefaultShown = false)] 42 | public IntValueObject TimeSpan { get; private set; } 43 | 44 | [Parameter(DisplayOrder = 2, InitOrder = 2, IsDefaultShown = false)] 45 | public StringValueObject ModbusHost { get; private set; } 46 | [Parameter(DisplayOrder = 3, InitOrder = 3, IsDefaultShown = false)] 47 | public IntValueObject ModbusPort { get; private set; } 48 | [Parameter(DisplayOrder = 4, InitOrder = 4, IsDefaultShown = false)] 49 | public IntValueObject ModbusID { get; private set; } 50 | 51 | // Modbus Register 52 | [Parameter(DisplayOrder = 5, InitOrder = 5, IsDefaultShown = false)] 53 | public IntValueObject ModbusAddress1 { get; private set; } 54 | // [Parameter(DisplayOrder = 6, InitOrder = 6, IsDefaultShown = false)] 55 | // public IntValueObject ReadCount1 { get; private set; } 56 | 57 | [Parameter(DisplayOrder = 7, InitOrder = 7, IsDefaultShown = false)] 58 | public EnumValueObject FunctionCode { get; private set; } 59 | 60 | [Parameter(DisplayOrder = 8, InitOrder = 8, IsDefaultShown = false)] 61 | public EnumValueObject DataType { get; private set; } 62 | 63 | [Parameter(DisplayOrder = 9, InitOrder = 9, IsDefaultShown = false)] 64 | public EnumValueObject RegisterOrder { get; private set; } 65 | 66 | [Output] 67 | public DoubleValueObject OutputValue1 { get; private set; } 68 | [Output] 69 | public StringValueObject ErrorMessage { get; private set; } 70 | 71 | private ISchedulerService SchedulerService; 72 | 73 | public ModbusClientNode(INodeContext context) 74 | { 75 | context.ThrowIfNull("context"); 76 | ITypeService typeService = context.GetService(); 77 | 78 | this.TimeSpan = typeService.CreateInt(PortTypes.Integer, "Abfrageinterval", 60); 79 | this.ModbusHost = typeService.CreateString(PortTypes.String, "Modbus TCP Host"); 80 | this.ModbusPort = typeService.CreateInt(PortTypes.Integer, "Port", 502); 81 | this.ModbusID = typeService.CreateInt(PortTypes.Integer, "Geräte ID", 1); 82 | this.ModbusID.MinValue = 1; 83 | this.ModbusID.MaxValue = 256; 84 | 85 | // --------------------------------------------------------------------------------------- // 86 | this.ModbusAddress1 = typeService.CreateInt(PortTypes.Integer, "Register Addresse", 1); 87 | this.ModbusAddress1.MinValue = 1; 88 | this.ModbusAddress1.MaxValue = 65535; 89 | 90 | this.FunctionCode = typeService.CreateEnum("ModbusFunction", "Funktion", FunctionCodeEnum.VALUES, FunctionCodeEnum.FC_03); 91 | 92 | this.DataType = typeService.CreateEnum("ModbusDataType", "Datentyp", DataTypeEnum.VALUES, DataTypeEnum.INT32); 93 | 94 | this.RegisterOrder = typeService.CreateEnum("ModbusRegisterOrder", "Register Reihenfolge", ByteOrderEnum.VALUES, ByteOrderEnum.LOW_HIGH); 95 | 96 | this.OutputValue1 = typeService.CreateDouble(PortTypes.Number, "Register Wert"); 97 | 98 | this.ErrorMessage = typeService.CreateString(PortTypes.String, "RAW / Error"); 99 | 100 | SchedulerService = context.GetService(); 101 | } 102 | public override void Startup() 103 | { 104 | this.SchedulerService.InvokeIn(new TimeSpan(0, 0, TimeSpan.Value), FetchFromModbusServer); 105 | } 106 | 107 | public override void Execute() 108 | { 109 | } 110 | 111 | private void FetchFromModbusServer() 112 | { 113 | if (ModbusHost.HasValue && ModbusAddress1.Value > 0) 114 | { 115 | int registerToRead; 116 | switch(DataType.Value) 117 | { 118 | case DataTypeEnum.INT32: 119 | case DataTypeEnum.FLOAT: 120 | registerToRead = 2; 121 | break; 122 | case DataTypeEnum.LONG: 123 | case DataTypeEnum.DOUBLE: 124 | registerToRead = 4; 125 | break; 126 | case DataTypeEnum.INT16_SIGNED: 127 | case DataTypeEnum.INT16_UNSIGNED: 128 | default: 129 | registerToRead = 1; 130 | break; 131 | } 132 | ModbusClient.RegisterOrder regOrder; 133 | if (!RegisterOrder.HasValue || RegisterOrder.Value == ByteOrderEnum.LOW_HIGH) 134 | { 135 | regOrder = ModbusClient.RegisterOrder.LowHigh; 136 | } else 137 | { 138 | regOrder = ModbusClient.RegisterOrder.HighLow; 139 | } 140 | ModbusClient modbusClient = null; 141 | try 142 | { 143 | modbusClient = new ModbusClient(ModbusHost.Value, ModbusPort.Value); 144 | modbusClient.ConnectionTimeout = 5000; 145 | modbusClient.Connect(); 146 | modbusClient.UnitIdentifier = (byte)ModbusID.Value; 147 | 148 | int[] readHoldingRegisters; 149 | switch (FunctionCode.Value) 150 | { 151 | case FunctionCodeEnum.FC_04: 152 | readHoldingRegisters = modbusClient.ReadInputRegisters(ModbusAddress1.Value, registerToRead); 153 | break; 154 | case FunctionCodeEnum.FC_03: 155 | default: 156 | readHoldingRegisters = modbusClient.ReadHoldingRegisters(ModbusAddress1.Value, registerToRead); 157 | break; 158 | } 159 | 160 | double result = 0; 161 | string result_str = ""; 162 | 163 | switch (DataType.Value) 164 | { 165 | case DataTypeEnum.INT32: 166 | // probably signed ... 167 | result = ModbusClient.ConvertRegistersToInt(readHoldingRegisters, regOrder); 168 | break; 169 | case DataTypeEnum.FLOAT: 170 | result = ModbusClient.ConvertRegistersToFloat(readHoldingRegisters, regOrder); 171 | break; 172 | case DataTypeEnum.LONG: 173 | result = ModbusClient.ConvertRegistersToLong(readHoldingRegisters, regOrder); 174 | break; 175 | case DataTypeEnum.DOUBLE: 176 | result = ModbusClient.ConvertRegistersToDouble(readHoldingRegisters, regOrder); 177 | break; 178 | case DataTypeEnum.INT16_SIGNED: 179 | result = readHoldingRegisters[0]; 180 | break; 181 | case DataTypeEnum.INT16_UNSIGNED: 182 | // unsigned 183 | for (int i = 0; i < (readHoldingRegisters.Length); i++) 184 | { 185 | int tmp = readHoldingRegisters[i]; 186 | if (tmp == -32768) // fix for 0x00 187 | tmp = 0; 188 | if (tmp < 0) // no negative values ! 189 | tmp = tmp + (int)Math.Pow(2, 16); 190 | 191 | result = result + (tmp * Math.Pow(2, (16 * ((readHoldingRegisters.Length) - (i + 1))))); 192 | result_str = result_str + " 0x" + tmp.ToString("X4"); 193 | } 194 | break; 195 | default: 196 | result_str = "internal: invalid datatype"; 197 | break; 198 | } 199 | 200 | OutputValue1.Value = result; 201 | ErrorMessage.Value = result_str; 202 | this.SchedulerService.InvokeIn(new TimeSpan(0, 0, TimeSpan.Value), FetchFromModbusServer); 203 | 204 | } 205 | catch (Exception e) 206 | { 207 | this.ErrorMessage.Value = e.ToString(); 208 | this.SchedulerService.InvokeIn(new TimeSpan(0, 1, 0), FetchFromModbusServer); 209 | } 210 | finally 211 | { 212 | if (modbusClient != null) 213 | { 214 | modbusClient.Disconnect(); 215 | } 216 | } 217 | } 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /InfluxDbNode/WriteThreePhaseElectricMeter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Net; 6 | using System.IO; 7 | using System.Threading; 8 | using LogicModule.Nodes.Helpers; 9 | using LogicModule.ObjectModel; 10 | using LogicModule.ObjectModel.TypeSystem; 11 | using System.Globalization; 12 | 13 | namespace alram_lechner_gmx_at.logic.InfluxDb2 14 | { 15 | 16 | public class WriteThreePhaseElectricMeter : LogicNodeBase 17 | { 18 | // Parameter 19 | [Parameter(DisplayOrder = 1, IsRequired = true, IsDefaultShown = false)] 20 | public StringValueObject InfluxDbUrl { get; private set; } 21 | 22 | [Parameter(DisplayOrder = 2, IsRequired = true, IsDefaultShown = true)] 23 | public StringValueObject InfluxMeasureName { get; private set; } 24 | 25 | [Parameter(DisplayOrder = 3, IsRequired = true, IsDefaultShown = true)] 26 | public StringValueObject InfluxMeasureTags { get; private set; } 27 | 28 | // Input 29 | [Input(DisplayOrder = 7, IsInput = true, IsRequired = true)] 30 | public DoubleValueObject L1MainMeterValue { get; private set; } 31 | 32 | [Input(DisplayOrder = 8, IsInput = true, IsRequired = true)] 33 | public DoubleValueObject L2MainMeterValue { get; private set; } 34 | 35 | [Input(DisplayOrder = 9, IsInput = true, IsRequired = true)] 36 | public DoubleValueObject L3MainMeterValue { get; private set; } 37 | 38 | [Input(DisplayOrder = 10, IsInput = true, IsRequired = true)] 39 | public DoubleValueObject L1CurrentPowerValue { get; private set; } 40 | 41 | [Input(DisplayOrder = 11, IsInput = true, IsRequired = true)] 42 | public DoubleValueObject L2CurrentPowerValue { get; private set; } 43 | 44 | [Input(DisplayOrder = 12, IsInput = true, IsRequired = true)] 45 | public DoubleValueObject L3CurrentPowerValue { get; private set; } 46 | 47 | [Input(DisplayOrder = 13, IsInput = true, IsRequired = true)] 48 | public DoubleValueObject L1DailyMeterValue { get; private set; } 49 | 50 | [Input(DisplayOrder = 14, IsInput = true, IsRequired = true)] 51 | public DoubleValueObject L2DailyMeterValue { get; private set; } 52 | 53 | [Input(DisplayOrder = 15, IsInput = true, IsRequired = true)] 54 | public DoubleValueObject L3DailyMeterValue { get; private set; } 55 | 56 | // Output 57 | [Output(DisplayOrder = 2, IsRequired = false, IsDefaultShown = false)] 58 | public BoolValueObject L1ResetDailyMeterCounter { get; private set; } 59 | 60 | [Output(DisplayOrder = 3, IsRequired = false, IsDefaultShown = false)] 61 | public BoolValueObject L2ResetDailyMeterCounter { get; private set; } 62 | 63 | [Output(DisplayOrder = 4, IsRequired = false, IsDefaultShown = false)] 64 | public BoolValueObject L3ResetDailyMeterCounter { get; private set; } 65 | 66 | [Output(DisplayOrder = 5, IsRequired = false, IsDefaultShown = false)] 67 | public IntValueObject ErrorCode { get; private set; } 68 | 69 | [Output(DisplayOrder = 6, IsRequired = false, IsDefaultShown = false)] 70 | public StringValueObject ErrorMessage { get; private set; } 71 | 72 | private ITypeService TypeService = null; 73 | 74 | private double[] LastDailyMeterCounterValueSent = new double[] { -1, -1, -1 }; 75 | private DateTime[] LastDailyMeterCounterValueTime = new DateTime[] { new DateTime(2010, 1, 1), new DateTime(2010, 1, 1), new DateTime(2010, 1, 1) }; 76 | 77 | public WriteThreePhaseElectricMeter(INodeContext context) : base(context) 78 | { 79 | context.ThrowIfNull("context"); 80 | this.TypeService = context.GetService(); 81 | this.InfluxDbUrl = TypeService.CreateString(PortTypes.String, "Influx DB URL", "http://:/write?db="); 82 | this.InfluxMeasureName = TypeService.CreateString(PortTypes.String, "Measure name", "sensor"); 83 | this.InfluxMeasureTags = TypeService.CreateString(PortTypes.String, "Tags", "room=kitchen,device=washingmachine"); 84 | 85 | this.L1MainMeterValue = TypeService.CreateDouble(PortTypes.Number, "L1 Main counter (kWh)"); 86 | this.L1CurrentPowerValue = TypeService.CreateDouble(PortTypes.Number, "L1 Current power (W)"); 87 | this.L1DailyMeterValue = TypeService.CreateDouble(PortTypes.Number, "L1 Daily counter (Wh)"); 88 | this.L1ResetDailyMeterCounter = TypeService.CreateBool(PortTypes.Bool, "L1 Reset daily counter", false); 89 | 90 | this.L2MainMeterValue = TypeService.CreateDouble(PortTypes.Number, "L2 Main counter (kWh)"); 91 | this.L2CurrentPowerValue = TypeService.CreateDouble(PortTypes.Number, "L2 Current power (W)"); 92 | this.L2DailyMeterValue = TypeService.CreateDouble(PortTypes.Number, "L2 Daily counter (Wh)"); 93 | this.L2ResetDailyMeterCounter = TypeService.CreateBool(PortTypes.Bool, "L2 Reset daily counter", false); 94 | 95 | this.L3MainMeterValue = TypeService.CreateDouble(PortTypes.Number, "L3 Main counter (kWh)"); 96 | this.L3CurrentPowerValue = TypeService.CreateDouble(PortTypes.Number, "L3 Current power (W)"); 97 | this.L3DailyMeterValue = TypeService.CreateDouble(PortTypes.Number, "L3 Daily counter (Wh)"); 98 | this.L3ResetDailyMeterCounter = TypeService.CreateBool(PortTypes.Bool, "L3 Reset daily counter", false); 99 | 100 | this.ErrorCode = TypeService.CreateInt(PortTypes.Integer, "HTTP status-code"); 101 | this.ErrorMessage = TypeService.CreateString(PortTypes.String, "Error message"); 102 | } 103 | 104 | public override void Startup() 105 | { 106 | } 107 | 108 | public override ValidationResult Validate(string language) 109 | { 110 | return base.Validate(language); 111 | } 112 | 113 | public override void Execute() 114 | { 115 | if (!InfluxDbUrl.HasValue || !InfluxMeasureName.HasValue) 116 | { 117 | return; 118 | } 119 | 120 | bool writeAggregatedPower = false; 121 | bool writeAggregatedDailyMeter = false; 122 | bool writeAggregatedMainMeter = false; 123 | 124 | // L1 125 | if (this.L1CurrentPowerValue.HasValue && this.L1CurrentPowerValue.WasSet) 126 | { 127 | WriteDatapointAsync("power", "phase=L1", this.L1CurrentPowerValue.Value); 128 | writeAggregatedPower = true; 129 | } 130 | 131 | if (this.L1MainMeterValue.HasValue && this.L1MainMeterValue.WasSet) 132 | { 133 | WriteDatapointAsync("meter", "phase=L1", this.L1MainMeterValue.Value); 134 | writeAggregatedMainMeter = true; 135 | } 136 | 137 | if (this.L1DailyMeterValue.HasValue && this.L1DailyMeterValue.WasSet) 138 | { 139 | // check if same value has been sent a few seconds before ... 140 | if (!(LastDailyMeterCounterValueSent[0] == this.L1DailyMeterValue.Value 141 | && DateTime.Compare(LastDailyMeterCounterValueTime[0], DateTime.Now.Subtract(TimeSpan.FromSeconds(10))) > 0)) 142 | { 143 | WriteDatapointAsync("intermediatecounter", "phase=L1", this.L1DailyMeterValue.Value); 144 | LastDailyMeterCounterValueSent[0] = this.L1DailyMeterValue.Value; 145 | LastDailyMeterCounterValueTime[0] = DateTime.Now; 146 | writeAggregatedDailyMeter = true; 147 | } 148 | if (this.L1DailyMeterValue.Value > 0) 149 | { 150 | this.L1ResetDailyMeterCounter.Value = true; 151 | } 152 | } 153 | 154 | // L2 155 | if (this.L2CurrentPowerValue.HasValue && this.L2CurrentPowerValue.WasSet) 156 | { 157 | WriteDatapointAsync("power", "phase=L2", this.L2CurrentPowerValue.Value); 158 | writeAggregatedPower = true; 159 | } 160 | 161 | if (this.L2MainMeterValue.HasValue && this.L2MainMeterValue.WasSet) 162 | { 163 | WriteDatapointAsync("meter", "phase=L2", this.L2MainMeterValue.Value); 164 | writeAggregatedMainMeter = true; 165 | } 166 | 167 | if (this.L2DailyMeterValue.HasValue && this.L2DailyMeterValue.WasSet) 168 | { 169 | // check if same value has been sent a few seconds before ... 170 | if (!(LastDailyMeterCounterValueSent[1] == this.L2DailyMeterValue.Value 171 | && DateTime.Compare(LastDailyMeterCounterValueTime[1], DateTime.Now.Subtract(TimeSpan.FromSeconds(10))) > 0)) 172 | { 173 | WriteDatapointAsync("intermediatecounter", "phase=L2", this.L2DailyMeterValue.Value); 174 | LastDailyMeterCounterValueSent[1] = this.L2DailyMeterValue.Value; 175 | LastDailyMeterCounterValueTime[1] = DateTime.Now; 176 | writeAggregatedDailyMeter = true; 177 | } 178 | 179 | if (this.L2DailyMeterValue.Value > 0) 180 | { 181 | this.L2ResetDailyMeterCounter.Value = true; 182 | } 183 | } 184 | 185 | // L3 186 | if (this.L3CurrentPowerValue.HasValue && this.L3CurrentPowerValue.WasSet) 187 | { 188 | WriteDatapointAsync("power", "phase=L3", this.L3CurrentPowerValue.Value); 189 | writeAggregatedPower = true; 190 | } 191 | 192 | if (this.L3MainMeterValue.HasValue && this.L3MainMeterValue.WasSet) 193 | { 194 | WriteDatapointAsync("meter", "phase=L3", this.L3MainMeterValue.Value); 195 | writeAggregatedMainMeter = true; 196 | } 197 | 198 | if (this.L3DailyMeterValue.HasValue && this.L3DailyMeterValue.WasSet) 199 | { 200 | // check if same value has been sent a few seconds before ... 201 | if (!(LastDailyMeterCounterValueSent[2] == this.L3DailyMeterValue.Value 202 | && DateTime.Compare(LastDailyMeterCounterValueTime[2], DateTime.Now.Subtract(TimeSpan.FromSeconds(10))) > 0)) 203 | { 204 | WriteDatapointAsync("intermediatecounter", "phase=L3", this.L3DailyMeterValue.Value); 205 | LastDailyMeterCounterValueSent[2] = this.L3DailyMeterValue.Value; 206 | LastDailyMeterCounterValueTime[2] = DateTime.Now; 207 | } 208 | 209 | if (this.L3DailyMeterValue.Value > 0) 210 | { 211 | this.L3ResetDailyMeterCounter.Value = true; 212 | } 213 | writeAggregatedDailyMeter = true; 214 | } 215 | 216 | // aggregated values: 217 | if (writeAggregatedPower) 218 | { 219 | WriteDatapointAsync("power", "phase=ALL", this.L1CurrentPowerValue.Value + this.L2CurrentPowerValue.Value + this.L3CurrentPowerValue.Value); 220 | } 221 | 222 | if (writeAggregatedMainMeter) 223 | { 224 | WriteDatapointAsync("meter", "phase=ALL", this.L1MainMeterValue.Value + this.L2MainMeterValue.Value + this.L3MainMeterValue.Value); 225 | } 226 | 227 | if (writeAggregatedDailyMeter) 228 | { 229 | WriteDatapointAsync("intermediatecounter", "phase=ALL", this.L1DailyMeterValue.Value + this.L2DailyMeterValue.Value + this.L3DailyMeterValue.Value); 230 | } 231 | } 232 | 233 | public void WriteDatapointAsync(String fieldName, String additionalTag, double value) 234 | { 235 | var thread = new Thread(() => 236 | { 237 | String tags = this.InfluxMeasureTags.HasValue ? this.InfluxMeasureTags.Value : ""; 238 | tags += "," + additionalTag; 239 | InfluxWriterHelper.WriteDatapointSync( 240 | this.InfluxDbUrl.Value, 241 | this.InfluxMeasureName.Value, 242 | tags, 243 | fieldName, 244 | value, 245 | (errorCode, errorMessage) => 246 | { 247 | if (errorCode != null) 248 | { 249 | ErrorCode.Value = errorCode.Value; 250 | } 251 | if (errorMessage != null) 252 | { 253 | ErrorMessage.Value = errorMessage; 254 | } 255 | }); 256 | }); 257 | thread.Start(); 258 | } 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /HuaweiModbus/ModbusReader.cs: -------------------------------------------------------------------------------- 1 | using EasyModbus; 2 | using LogicModule.Nodes.Helpers; 3 | using LogicModule.ObjectModel; 4 | using LogicModule.ObjectModel.TypeSystem; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading; 10 | 11 | namespace alram_lechner_gmx_at.logic.HuaweiModbus 12 | { 13 | static class FunctionCodeEnum 14 | { 15 | public const string FC_03 = "Read Holding Registers (03)"; 16 | public const string FC_04 = "Read Input Registers (04)"; 17 | public static string[] VALUES = new[] { FC_03, FC_04 }; 18 | } 19 | static class DataTypeEnum 20 | { 21 | public const string INT16_UNSIGNED = "16bit integer"; 22 | public const string INT16_SIGNED = "16bit integer (signed)"; 23 | public const string INT32 = "integer (32bit)"; 24 | public const string FLOAT = "float (32bit)"; 25 | public const string LONG = "long (64bit)"; 26 | public const string DOUBLE = "double (64bit)"; 27 | 28 | public static string[] VALUES = new[] { INT16_SIGNED, INT16_UNSIGNED, INT32, FLOAT, LONG, DOUBLE }; 29 | } 30 | 31 | static class ByteOrderEnum 32 | { 33 | public const string HIGH_LOW = "big-endian"; 34 | public const string LOW_HIGH = "little-endian"; 35 | 36 | public static string[] VALUES = new[] { HIGH_LOW, LOW_HIGH }; 37 | } 38 | 39 | public class HuaweiModbusClientNode : LogicNodeBase 40 | { 41 | 42 | [Parameter(DisplayOrder = 1, InitOrder = 1, IsDefaultShown = false)] 43 | public IntValueObject TimeSpan { get; private set; } 44 | 45 | [Parameter(DisplayOrder = 2, InitOrder = 2, IsDefaultShown = false)] 46 | public StringValueObject ModbusHost { get; private set; } 47 | [Parameter(DisplayOrder = 3, InitOrder = 3, IsDefaultShown = false)] 48 | public IntValueObject ModbusPort { get; private set; } 49 | 50 | [Input(DisplayOrder = 1, IsInput = true, IsRequired = false)] 51 | public DoubleValueObject chargePowerMax { get; private set; } 52 | 53 | [Input(DisplayOrder = 2, IsInput = true, IsRequired = false)] 54 | public DoubleValueObject dischargePowerMax { get; private set; } 55 | 56 | [Input(DisplayOrder = 3, IsInput = true, IsRequired = false)] 57 | public DoubleValueObject chargingCutoff { get; private set; } 58 | 59 | [Input(DisplayOrder = 4, IsInput = true, IsRequired = false)] 60 | public DoubleValueObject dischargingCutoff { get; private set; } 61 | 62 | [Output] 63 | public DoubleValueObject currentPVPower { get; private set; } 64 | 65 | [Output] 66 | public DoubleValueObject currentACPower { get; private set; } 67 | 68 | [Output] 69 | public DoubleValueObject currentGridPower { get; private set; } 70 | 71 | [Output] 72 | public DoubleValueObject currentBatteryPower { get; private set; } 73 | 74 | [Output] 75 | public DoubleValueObject todayPVEnergy { get; private set; } 76 | 77 | [Output] 78 | public DoubleValueObject totalPVEnergy { get; private set; } 79 | 80 | [Output] 81 | public DoubleValueObject inverterTemperature { get; private set; } 82 | 83 | [Output] 84 | public DoubleValueObject mppt1Voltage { get; private set; } 85 | 86 | [Output] 87 | public DoubleValueObject mppt1Current { get; private set; } 88 | 89 | [Output] 90 | public DoubleValueObject mppt2Voltage { get; private set; } 91 | 92 | [Output] 93 | public DoubleValueObject mppt2Current { get; private set; } 94 | 95 | [Output] 96 | public DoubleValueObject totalGridImportedEnergy { get; private set; } 97 | 98 | [Output] 99 | public DoubleValueObject totalGridExportedEnergy { get; private set; } 100 | 101 | [Output] 102 | public DoubleValueObject currentBatterySOC { get; private set; } 103 | 104 | [Output] 105 | public DoubleValueObject todaysPeakPVPower { get; private set; } 106 | 107 | [Output] 108 | public DoubleValueObject currentReactivePower { get; private set; } 109 | 110 | [Output] 111 | public DoubleValueObject currentBatteryStatus { get; private set; } 112 | 113 | [Output] 114 | public DoubleValueObject todayBatteryChargedEnergy { get; private set; } 115 | 116 | [Output] 117 | public DoubleValueObject todayBatteryDischargedEnergy { get; private set; } 118 | 119 | [Output] 120 | public DoubleValueObject batteryTemperature { get; private set; } 121 | 122 | [Output] 123 | public StringValueObject ErrorMessage { get; private set; } 124 | 125 | private ISchedulerService SchedulerService; 126 | 127 | public HuaweiModbusClientNode(INodeContext context) 128 | { 129 | context.ThrowIfNull("context"); 130 | ITypeService typeService = context.GetService(); 131 | 132 | this.TimeSpan = typeService.CreateInt(PortTypes.Integer, "Abfrageinterval", 60); 133 | this.ModbusHost = typeService.CreateString(PortTypes.String, "Modbus TCP Host"); 134 | this.ModbusPort = typeService.CreateInt(PortTypes.Integer, "Port", 502); 135 | 136 | this.chargePowerMax = typeService.CreateDouble(PortTypes.Number, "Max. battery charge power (W)"); 137 | this.dischargePowerMax = typeService.CreateDouble(PortTypes.Number, "Max. battery discharge power (W)"); 138 | this.chargingCutoff = typeService.CreateDouble(PortTypes.Number, "Charging cutoff capacity (%)"); 139 | this.dischargingCutoff = typeService.CreateDouble(PortTypes.Number, "Discharging cutoff capacity (%)"); 140 | 141 | this.currentPVPower = typeService.CreateDouble(PortTypes.Number, "Current PV power (inverter)"); 142 | this.currentACPower = typeService.CreateDouble(PortTypes.Number, "Current AC power (inverter)"); 143 | this.currentGridPower = typeService.CreateDouble(PortTypes.Number, "Current grid power (smartmeter)"); 144 | this.currentBatteryPower = typeService.CreateDouble(PortTypes.Number, "Current battery power (inverter)"); 145 | this.todayPVEnergy = typeService.CreateDouble(PortTypes.Number, "Today PV energy"); 146 | this.totalPVEnergy = typeService.CreateDouble(PortTypes.Number, "Total PV energy"); 147 | this.inverterTemperature = typeService.CreateDouble(PortTypes.Number, "Inverter temperature"); 148 | this.mppt1Voltage = typeService.CreateDouble(PortTypes.Number, "MPPT 1 voltage"); 149 | this.mppt1Current = typeService.CreateDouble(PortTypes.Number, "MPPT 1 current"); 150 | this.mppt2Voltage = typeService.CreateDouble(PortTypes.Number, "MPPT 2 voltage"); 151 | this.mppt2Current = typeService.CreateDouble(PortTypes.Number, "MPPT 2 current"); 152 | this.totalGridImportedEnergy = typeService.CreateDouble(PortTypes.Number, "Total energy imported (smartmeter)"); 153 | this.totalGridExportedEnergy = typeService.CreateDouble(PortTypes.Number, "Total energy exported (smartmeter)"); 154 | this.currentBatterySOC = typeService.CreateDouble(PortTypes.Number, "Current battery SoC"); 155 | this.todaysPeakPVPower = typeService.CreateDouble(PortTypes.Number, "Today PV peak power"); 156 | this.currentReactivePower = typeService.CreateDouble(PortTypes.Number, "Current reactive power"); 157 | this.currentBatteryStatus = typeService.CreateDouble(PortTypes.Number, "Current battery status"); 158 | this.todayBatteryChargedEnergy = typeService.CreateDouble(PortTypes.Number, "Today battery charged energy"); 159 | this.todayBatteryDischargedEnergy = typeService.CreateDouble(PortTypes.Number, "Today battery discharged engergy"); 160 | this.batteryTemperature = typeService.CreateDouble(PortTypes.Number, "Battery temperature"); 161 | 162 | this.ErrorMessage = typeService.CreateString(PortTypes.String, "RAW / Error"); 163 | SchedulerService = context.GetService(); 164 | } 165 | public override void Startup() 166 | { 167 | this.SchedulerService.InvokeIn(new TimeSpan(0, 0, TimeSpan.Value), FetchFromModbusServer); 168 | } 169 | 170 | public override void Execute() 171 | { 172 | if ((this.chargePowerMax.HasValue && this.chargePowerMax.WasSet)) 173 | { 174 | writeRegister(47075, (int)this.chargePowerMax.Value, DataTypeEnum.INT32); 175 | } 176 | if ((this.dischargePowerMax.HasValue && this.dischargePowerMax.WasSet)) 177 | { 178 | writeRegister(47077, (int)this.dischargePowerMax.Value, DataTypeEnum.INT32); 179 | } 180 | if ((this.chargingCutoff.HasValue && this.chargingCutoff.WasSet)) 181 | { 182 | writeRegister(47081, (int)this.chargingCutoff.Value, DataTypeEnum.INT16_UNSIGNED); 183 | } 184 | if ((this.dischargingCutoff.HasValue && this.dischargingCutoff.WasSet)) 185 | { 186 | writeRegister(47082, (int)this.dischargingCutoff.Value, DataTypeEnum.INT16_UNSIGNED); 187 | } 188 | } 189 | 190 | private void writeRegister(int register, int value, String dataType) 191 | { 192 | ModbusClient modbusClient = null; 193 | try 194 | { 195 | modbusClient = new ModbusClient(ModbusHost.Value, ModbusPort.Value); 196 | modbusClient.ConnectionTimeout = 5000; 197 | modbusClient.Connect(); 198 | modbusClient.UnitIdentifier = 1; 199 | 200 | // needed? 201 | System.Threading.Thread.Sleep(700); 202 | switch (dataType) 203 | { 204 | case DataTypeEnum.INT32: 205 | int[] toWrite = ModbusClient.ConvertIntToRegisters(value, ModbusClient.RegisterOrder.HighLow); 206 | modbusClient.WriteMultipleRegisters(register, toWrite); 207 | break; 208 | case DataTypeEnum.INT16_UNSIGNED: 209 | modbusClient.WriteSingleRegister(register, value); 210 | break; 211 | default: 212 | this.ErrorMessage.Value = "INTERNAL: unsupported datatype"; 213 | break; 214 | } 215 | } 216 | catch (Exception e) 217 | { 218 | this.ErrorMessage.Value = e.ToString(); 219 | } 220 | finally 221 | { 222 | if (modbusClient != null) 223 | { 224 | modbusClient.Disconnect(); 225 | } 226 | } 227 | } 228 | 229 | private int readRegister(ModbusClient modbusClient, int startRegister, String dataType) 230 | { 231 | ModbusClient.RegisterOrder regOrder; 232 | regOrder = ModbusClient.RegisterOrder.HighLow; 233 | 234 | int registerToRead; 235 | switch(dataType) 236 | { 237 | case DataTypeEnum.INT32: 238 | case DataTypeEnum.FLOAT: 239 | registerToRead = 2; 240 | break; 241 | case DataTypeEnum.LONG: 242 | case DataTypeEnum.DOUBLE: 243 | registerToRead = 4; 244 | break; 245 | case DataTypeEnum.INT16_SIGNED: 246 | case DataTypeEnum.INT16_UNSIGNED: 247 | default: 248 | registerToRead = 1; 249 | break; 250 | } 251 | 252 | int[] readHoldingRegisters; 253 | int retry = 5; 254 | 255 | while(true) 256 | try 257 | { 258 | readHoldingRegisters = modbusClient.ReadHoldingRegisters(startRegister, registerToRead); 259 | break; 260 | } catch (System.IO.IOException e) 261 | { 262 | retry--; 263 | if (retry == 0) 264 | { 265 | return -1; 266 | } 267 | System.Threading.Thread.Sleep(500); 268 | } 269 | 270 | double result = 0; 271 | string result_str = ""; 272 | 273 | switch (dataType) 274 | { 275 | case DataTypeEnum.INT32: 276 | result = ModbusClient.ConvertRegistersToInt(readHoldingRegisters, regOrder); 277 | break; 278 | case DataTypeEnum.FLOAT: 279 | result = ModbusClient.ConvertRegistersToFloat(readHoldingRegisters, regOrder); 280 | break; 281 | case DataTypeEnum.LONG: 282 | result = ModbusClient.ConvertRegistersToLong(readHoldingRegisters, regOrder); 283 | break; 284 | case DataTypeEnum.DOUBLE: 285 | result = ModbusClient.ConvertRegistersToDouble(readHoldingRegisters, regOrder); 286 | break; 287 | case DataTypeEnum.INT16_SIGNED: 288 | result = readHoldingRegisters[0]; 289 | break; 290 | case DataTypeEnum.INT16_UNSIGNED: 291 | // unsigned 292 | for (int i = 0; i < (readHoldingRegisters.Length); i++) 293 | { 294 | int tmp = readHoldingRegisters[i]; 295 | if (tmp == -32768) // fix for 0x00 296 | tmp = 0; 297 | if (tmp < 0) // no negative values ! 298 | tmp = tmp + (int)Math.Pow(2, 16); 299 | result = result + (tmp * Math.Pow(2, (16 * ((readHoldingRegisters.Length) - (i + 1))))); 300 | result_str = result_str + " 0x" + tmp.ToString("X4"); 301 | } 302 | break; 303 | default: 304 | result_str = "internal: invalid datatype"; 305 | break; 306 | } 307 | 308 | ErrorMessage.Value += result_str + ";"; 309 | return (int)result; 310 | } 311 | 312 | private void FetchFromModbusServer() 313 | { 314 | Thread thread1 = new Thread(FetchFromModbusServerAsync); 315 | thread1.Start(); 316 | } 317 | 318 | private void FetchFromModbusServerAsync() 319 | { 320 | ErrorMessage.Value = ""; 321 | if (ModbusHost.HasValue) 322 | { 323 | ModbusClient modbusClient = null; 324 | try 325 | { 326 | modbusClient = new ModbusClient(ModbusHost.Value, ModbusPort.Value); 327 | modbusClient.ConnectionTimeout = 5000; 328 | modbusClient.Connect(); 329 | modbusClient.UnitIdentifier = 1; 330 | 331 | // see: https://knx-user-forum.de/forum/%C3%B6ffentlicher-bereich/knx-eib-forum/1643359-gira-x1-und-modbus-tcp-mit-logikbaustein/page7#post1844442 332 | System.Threading.Thread.Sleep(700); 333 | 334 | this.mppt1Voltage.Value = readRegister(modbusClient, 32016, DataTypeEnum.INT16_SIGNED) / 10.0; 335 | this.mppt1Current.Value = readRegister(modbusClient, 32017, DataTypeEnum.INT16_SIGNED) / 100.0; 336 | this.mppt2Voltage.Value = readRegister(modbusClient, 32018, DataTypeEnum.INT16_SIGNED) / 10.0; 337 | this.mppt2Current.Value = readRegister(modbusClient, 32019, DataTypeEnum.INT16_SIGNED) / 100.0; 338 | this.currentPVPower.Value = readRegister(modbusClient, 32064, DataTypeEnum.INT32); 339 | this.todaysPeakPVPower.Value = readRegister(modbusClient, 32078, DataTypeEnum.INT32); 340 | this.currentACPower.Value = readRegister(modbusClient, 32080, DataTypeEnum.INT32); 341 | this.currentReactivePower.Value = readRegister(modbusClient, 32082, DataTypeEnum.INT32); 342 | this.inverterTemperature.Value = readRegister(modbusClient, 32087, DataTypeEnum.INT16_SIGNED) / 10.0; 343 | this.totalPVEnergy.Value = readRegister(modbusClient, 32106, DataTypeEnum.INT32) / 100.0; // unsigned! 344 | this.todayPVEnergy.Value = readRegister(modbusClient, 32114, DataTypeEnum.INT32) / 100.0; // unsigned! 345 | 346 | this.currentBatteryStatus.Value = readRegister(modbusClient, 37000, DataTypeEnum.INT16_UNSIGNED); 347 | this.currentBatteryPower.Value = readRegister(modbusClient, 37001, DataTypeEnum.INT32); 348 | this.currentBatterySOC.Value = readRegister(modbusClient, 37004, DataTypeEnum.INT16_UNSIGNED) / 10.0; 349 | this.todayBatteryChargedEnergy.Value = readRegister(modbusClient, 37015, DataTypeEnum.INT32); // unsigned! 350 | this.todayBatteryDischargedEnergy.Value = readRegister(modbusClient, 37017, DataTypeEnum.INT32); // unsigned! 351 | this.batteryTemperature.Value = readRegister(modbusClient, 37022, DataTypeEnum.INT16_SIGNED) / 10.0; 352 | this.currentGridPower.Value = readRegister(modbusClient, 37113, DataTypeEnum.INT32); 353 | this.totalGridExportedEnergy.Value = readRegister(modbusClient, 37119, DataTypeEnum.INT32) / 100.0; 354 | this.totalGridImportedEnergy.Value = readRegister(modbusClient, 37121, DataTypeEnum.INT32) / 100.0; 355 | 356 | this.SchedulerService.InvokeIn(new TimeSpan(0, 0, TimeSpan.Value), FetchFromModbusServer); 357 | 358 | } 359 | catch (Exception e) 360 | { 361 | this.ErrorMessage.Value = e.ToString(); 362 | this.SchedulerService.InvokeIn(new TimeSpan(0, 1, 0), FetchFromModbusServer); 363 | } 364 | finally 365 | { 366 | if (modbusClient != null) 367 | { 368 | modbusClient.Disconnect(); 369 | } 370 | } 371 | } 372 | } 373 | } 374 | } 375 | --------------------------------------------------------------------------------