├── 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 |
--------------------------------------------------------------------------------