commit e21b483862e233c2615f96b3b06af5f523d796ee Author: joergzeidler Date: Tue Sep 23 10:21:23 2025 +0200 init sensormat diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2baf0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,358 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +*.7z +*.mde + + + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml +nuget.exe + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ +#/Office Line/8.1/MHK81/LBAbfKirstein81 - Kopie.laccdb diff --git a/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.cs b/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.cs new file mode 100644 index 0000000..3c4021f --- /dev/null +++ b/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.cs @@ -0,0 +1,38 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.ESP8266_Gaszaehler.ESP8266_Gaszaehler +// Assembly: ESP8266_Gaszaehler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 4783B34D-C78D-4F6E-946E-4E4DC92855C4 +// Assembly location: \\192.168.178.26\Freigabe\ESP8266_Gaszaehler.dll + +using Newtonsoft.Json; +using Sensormat.Entities; +using System; + +#nullable disable +namespace Sensormat.ESP8266_Gaszaehler +{ + public class ESP8266_Gaszaehler : ISensor + { + public override bool Execute() + { + try + { + int id = this.Sensor.ID; + new ResultSet().XMLSerializeData(); + string str = this.HttpRequest(this.Sensor.URL); + if (str != null) + { + this.LastValue = JsonConvert.DeserializeObject(str).GasMeter; + this.LastValueDate = new DateTime?(DateTime.Now); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine((object)ex); + this.ErrorMsg = ex.Message; + } + return false; + } + } +} diff --git a/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.csproj b/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.csproj new file mode 100644 index 0000000..e03b92d --- /dev/null +++ b/Sensormat/ESP8266_Gaszaehler/ESP8266_Gaszaehler.csproj @@ -0,0 +1,18 @@ + + + + Library + netcoreapp3.1 + + + + + + + + + ..\Sensormat\References\Newtonsoft.Json.dll + + + + diff --git a/Sensormat/ESP8266_Gaszaehler/ResultSet.cs b/Sensormat/ESP8266_Gaszaehler/ResultSet.cs new file mode 100644 index 0000000..d8a9a17 --- /dev/null +++ b/Sensormat/ESP8266_Gaszaehler/ResultSet.cs @@ -0,0 +1,24 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.ESP8266_Gaszaehler.ResultSet +// Assembly: ESP8266_Gaszaehler, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 4783B34D-C78D-4F6E-946E-4E4DC92855C4 +// Assembly location: \\192.168.178.26\Freigabe\ESP8266_Gaszaehler.dll + +using System; + +#nullable disable +namespace Sensormat.ESP8266_Gaszaehler +{ + public class ResultSet + { + public DateTime? StartTime { get; set; } + + public string Version { get; set; } + + public int WIFIConnectCounter { get; set; } + + public string GasMeter { get; set; } + + public string ErrorMsg { get; set; } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/FritzSoapAccess.cs b/Sensormat/FritzBox/API/FritzWebAccess/FritzSoapAccess.cs new file mode 100644 index 0000000..7770255 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/FritzSoapAccess.cs @@ -0,0 +1,206 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.FritzSoapAccess +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using FritzBox.API.FritzWebAccess.SoapTypes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Xml.XPath; + +#nullable disable +namespace FritzBox.API.FritzWebAccess +{ + public class FritzSoapAccess + { + public Uri BaseAddress { get; set; } = new Uri("https://fritz.box/"); + + public string Username { get; set; } = "admin"; + + public string Password { get; set; } = string.Empty; + + public int GetHostNumberOfEntries() + { + string elementValue = this.GetElementValue(((WebResponse)this.SendSoapRequest("tr064/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1#GetHostNumberOfEntries")).GetResponseStream(), "NewHostNumberOfEntries"); + return elementValue != null ? Convert.ToInt32(elementValue) : -1; + } + + public HostInfo GetGenericHostEntryExt(int index) + { + Dictionary elementValues = this.GetElementValues(((WebResponse)this.SendSoapRequest("tr064/upnp/control/hosts", "urn:dslforum-org:service:Hosts:1#X_AVM-DE_GetGenericHostEntryExt", new Dictionary() + { + { + "NewIndex", + index.ToString() + } + })).GetResponseStream(), (IEnumerable)new string[7] + { + "NewIPAddress", + "NewMACAddress", + "NewActive", + "NewHostName", + "NewInterfaceType", + "NewX_AVM-DE_Port", + "NewX_AVM-DE_Speed" + }); + return new HostInfo() + { + IPAddress = elementValues["NewIPAddress"], + MACAddress = elementValues["NewMACAddress"], + IsActive = elementValues["NewActive"] == "1", + HostName = elementValues["NewHostName"], + InterfaceType = elementValues["NewInterfaceType"], + Port = elementValues["NewX_AVM-DE_Port"], + Speed = elementValues["NewX_AVM-DE_Speed"] + }; + } + + public int GetTotalBytesSent() + { + string elementValue = this.GetElementValue(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wancommonifconfig1", "urn:dslforum-org:service:WANCommonInterfaceConfig:1#GetTotalBytesSent")).GetResponseStream(), "NewTotalBytesSent"); + return elementValue == null ? -1 : Convert.ToInt32(elementValue); + } + + public int GetTotalBytesReceived() + { + string elementValue = this.GetElementValue(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wancommonifconfig1", "urn:dslforum-org:service:WANCommonInterfaceConfig:1#GetTotalBytesReceived")).GetResponseStream(), "NewTotalBytesReceived"); + return elementValue == null ? -1 : Convert.ToInt32(elementValue); + } + + public CommonLinkProperties GetCommonLinkProperties() + { + Dictionary elementValues = this.GetElementValues(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wancommonifconfig1", "urn:dslforum-org:service:WANCommonInterfaceConfig:1#GetCommonLinkProperties")).GetResponseStream(), (IEnumerable)new string[4] + { + "NewWANAccessType", + "NewLayer1UpstreamMaxBitRate", + "NewLayer1DownstreamMaxBitRate", + "NewPhysicalLinkStatus" + }); + return new CommonLinkProperties() + { + AccessType = elementValues["NewWANAccessType"], + MaxUpstreamBitRate = elementValues["NewLayer1UpstreamMaxBitRate"], + MaxDownstreamBitRate = elementValues["NewLayer1DownstreamMaxBitRate"], + Status = elementValues["NewPhysicalLinkStatus"] + }; + } + + public DslInterfaceInfo GetDslInterfaceInfo() + { + Dictionary elementValues = this.GetElementValues(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wandslifconfig1", "urn:dslforum-org:service:WANDSLInterfaceConfig:1#GetInfo")).GetResponseStream(), (IEnumerable)new string[9] + { + "NewStatus", + "NewUpstreamCurrRate", + "NewDownstreamCurrRate", + "NewUpstreamMaxRate", + "NewDownstreamMaxRate", + "NewUpstreamNoiseMargin", + "NewDownstreamNoiseMargin", + "NewUpstreamAttenuation", + "NewDownstreamAttenuation" + }); + return new DslInterfaceInfo() + { + Status = elementValues["NewStatus"], + CurrentUpstreamRate = elementValues["NewUpstreamCurrRate"], + CurrentDownstreamRate = elementValues["NewDownstreamCurrRate"], + MaxUpstreamRate = elementValues["NewUpstreamMaxRate"], + MaxDownstreamRate = elementValues["NewDownstreamMaxRate"], + UpstreamNoiseMargin = elementValues["NewUpstreamNoiseMargin"], + DownstreamNoiseMargin = elementValues["NewDownstreamNoiseMargin"], + UpstreamAttenuation = elementValues["NewUpstreamAttenuation"], + DownstreamAttenuation = elementValues["NewDownstreamAttenuation"] + }; + } + + public string GetExternalIPAddress() + { + return this.GetElementValue(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wanpppconn1", "urn:dslforum-org:service:WANPPPConnection:1#GetExternalIPAddress")).GetResponseStream(), "NewExternalIPAddress"); + } + + public WirelessLanInfo GetWirelessLanInfo() + { + Dictionary elementValues = this.GetElementValues(((WebResponse)this.SendSoapRequest("tr064/upnp/control/wlanconfig1", "urn:dslforum-org:service:WLANConfiguration:1#GetInfo")).GetResponseStream(), (IEnumerable)new string[4] + { + "NewEnable", + "NewStatus", + "NewChannel", + "NewSSID" + }); + return new WirelessLanInfo() + { + IsEnabled = elementValues["NewEnable"] == "1", + Status = elementValues["NewStatus"], + Channel = elementValues["NewChannel"], + SSID = elementValues["NewSSID"] + }; + } + + public void SetWirelessLan(bool enable) + { + this.SendSoapRequest("tr064/upnp/control/wlanconfig1", "urn:dslforum-org:service:WLANConfiguration:1#SetEnable", new Dictionary() + { + { + "NewEnable", + enable ? "1" : "0" + } + }); + } + + private HttpWebResponse SendSoapRequest( + string relativeUrl, + string soapAction, + Dictionary soapActionParameters = null) + { + HttpWebRequest httpWebRequest = WebRequest.Create(new Uri(this.BaseAddress, relativeUrl)) as HttpWebRequest; + httpWebRequest.Method = "POST"; + httpWebRequest.ContentType = "text/xml; charset=utf-8"; + httpWebRequest.Headers.Add("SOAPAction", soapAction); + httpWebRequest.Credentials = (ICredentials)new NetworkCredential(this.Username, this.Password); + string[] strArray = soapAction.Split('#', StringSplitOptions.None); + StreamWriter streamWriter = new StreamWriter(((WebRequest)httpWebRequest).GetRequestStream(), Encoding.ASCII); + ((TextWriter)streamWriter).WriteLine(""); + ((TextWriter)streamWriter).WriteLine(""); + ((TextWriter)streamWriter).WriteLine(""); + if (soapActionParameters == null) + { + ((TextWriter)streamWriter).WriteLine("<{1} xmlns=\"{0}\"/>", (object)strArray[0], (object)strArray[1]); + } + else + { + ((TextWriter)streamWriter).WriteLine("<{1} xmlns=\"{0}\">", (object)strArray[0], (object)strArray[1]); + foreach (string key in soapActionParameters.Keys) + ((TextWriter)streamWriter).WriteLine("<{0}>{1}", (object)key, (object)soapActionParameters[key]); + ((TextWriter)streamWriter).WriteLine("", (object)strArray[1]); + } + ((TextWriter)streamWriter).WriteLine(""); + ((TextWriter)streamWriter).WriteLine(""); + ((TextWriter)streamWriter).Close(); + return httpWebRequest.GetResponse() as HttpWebResponse; + } + + private string GetElementValue(Stream xmlStream, string elementName) + { + return ((XPathItem)new XPathDocument(xmlStream).CreateNavigator().SelectSingleNode("//" + elementName))?.Value; + } + + private Dictionary GetElementValues( + Stream xmlStream, + IEnumerable elementNames) + { + Dictionary elementValues = new Dictionary(); + XPathNavigator navigator = new XPathDocument(xmlStream).CreateNavigator(); + foreach (string elementName in elementNames) + { + XPathNavigator xpathNavigator = navigator.SelectSingleNode("//" + elementName); + if (xpathNavigator != null) + elementValues[elementName] = ((XPathItem)xpathNavigator).Value; + } + return elementValues; + } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/FritzWebAccess.cs b/Sensormat/FritzBox/API/FritzWebAccess/FritzWebAccess.cs new file mode 100644 index 0000000..fffb9e4 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/FritzWebAccess.cs @@ -0,0 +1,87 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.FritzWebAccess +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Xml.Linq; + +#nullable disable +namespace FritzBox.API.FritzWebAccess +{ + public class FritzWebAccess + { + public Uri Address { get; set; } = new Uri("http://fritz.box/"); + + public Uri SoapAddress => new Uri(this.Address.Scheme + "://" + this.Address.Host + ":49000/"); + + public string SessionId { get; private set; } + + public bool IsAuthenticated + { + get => !string.IsNullOrEmpty(this.SessionId) && this.SessionId != "0000000000000000"; + } + + public FritzWebAccess() + { + } + + public FritzWebAccess(string username, string password) => this.LogIn(username, password); + + public void LogIn(string password) => this.LogIn(string.Empty, password); + + public void LogIn(string username, string password) + { + Uri uri = new Uri(this.Address, "login_sid.lua"); + XDocument doc = XDocument.Load(uri.AbsoluteUri); + this.SessionId = this.GetValue(doc, "SID"); + if (this.IsAuthenticated) + return; + string challenge = this.GetValue(doc, "Challenge"); + this.SessionId = this.GetValue(XDocument.Load(string.Format("{0}?username={1}&response={2}", (object)uri.AbsoluteUri, (object)username, (object)this.GetAuthResponse(challenge, password))), "SID"); + } + + private string GetValue(XDocument doc, string elementName) + { + return (doc.FirstNode as XElement).Element((XName)elementName).Value; + } + + private string GetAuthResponse(string challenge, string password) + { + return challenge + "-" + this.GetMD5Hash(challenge + "-" + password); + } + + private string GetMD5Hash(string input) + { + byte[] hash = MD5.Create().ComputeHash(Encoding.Unicode.GetBytes(input)); + StringBuilder stringBuilder = new StringBuilder(); + for (int index = 0; index < hash.Length; ++index) + stringBuilder.Append(hash[index].ToString("x2")); + return stringBuilder.ToString(); + } + + public string GetWebContent(string relativeUrl, string parameter = "") + { + return this.GetContent(this.Address, relativeUrl, parameter); + } + + public string GetSoapContent(string relativeUrl, string parameter = "") + { + return this.GetContent(this.SoapAddress, relativeUrl, parameter); + } + + public string GetContent(Uri baseAddress, string relativeUrl, string parameter = "") + { + using (HttpWebResponse response = (WebRequest.Create(new Uri(baseAddress, relativeUrl + "?sid=" + this.SessionId + parameter)) as HttpWebRequest).GetResponse() as HttpWebResponse) + { + using (StreamReader streamReader = new StreamReader(((WebResponse)response).GetResponseStream())) + return ((TextReader)streamReader).ReadToEnd(); + } + } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/CommonLinkProperties.cs b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/CommonLinkProperties.cs new file mode 100644 index 0000000..0c7f921 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/CommonLinkProperties.cs @@ -0,0 +1,25 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.SoapTypes.CommonLinkProperties +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +#nullable disable +namespace FritzBox.API.FritzWebAccess.SoapTypes +{ + public class CommonLinkProperties + { + public string AccessType { get; set; } + + public string MaxUpstreamBitRate { get; set; } + + public string MaxDownstreamBitRate { get; set; } + + public string Status { get; set; } + + public override string ToString() + { + return string.Format("AccessType: {0}, MaxUpstreamBitRate: {1}, MaxDownstreamBitRate: {2}, Status: {3}", (object)this.AccessType, (object)this.MaxUpstreamBitRate, (object)this.MaxDownstreamBitRate, (object)this.Status); + } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/DslInterfaceInfo.cs b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/DslInterfaceInfo.cs new file mode 100644 index 0000000..e33fc71 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/DslInterfaceInfo.cs @@ -0,0 +1,35 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.SoapTypes.DslInterfaceInfo +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +#nullable disable +namespace FritzBox.API.FritzWebAccess.SoapTypes +{ + public class DslInterfaceInfo + { + public string Status { get; set; } + + public string CurrentUpstreamRate { get; set; } + + public string CurrentDownstreamRate { get; set; } + + public string MaxUpstreamRate { get; set; } + + public string MaxDownstreamRate { get; set; } + + public string UpstreamNoiseMargin { get; set; } + + public string DownstreamNoiseMargin { get; set; } + + public string UpstreamAttenuation { get; set; } + + public string DownstreamAttenuation { get; set; } + + public override string ToString() + { + return string.Format("Status: {0}, Up/Down- RateCurrent: {1}/{2}, RateMax: {3}/{4}, NoiseMargin: {5}/{6}, Attenuation: {7}/{8}", (object)this.Status, (object)this.CurrentUpstreamRate, (object)this.CurrentDownstreamRate, (object)this.MaxUpstreamRate, (object)this.MaxDownstreamRate, (object)this.UpstreamNoiseMargin, (object)this.DownstreamNoiseMargin, (object)this.UpstreamAttenuation, (object)this.DownstreamAttenuation); + } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/HostInfo.cs b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/HostInfo.cs new file mode 100644 index 0000000..a865c37 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/HostInfo.cs @@ -0,0 +1,31 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.SoapTypes.HostInfo +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +#nullable disable +namespace FritzBox.API.FritzWebAccess.SoapTypes +{ + public class HostInfo + { + public string IPAddress { get; set; } + + public string MACAddress { get; set; } + + public bool IsActive { get; set; } + + public string HostName { get; set; } + + public string InterfaceType { get; set; } + + public string Port { get; set; } + + public string Speed { get; set; } + + public override string ToString() + { + return string.Format("IP: {0}, MAC: {1}, IsActive: {2}, HostName: {3}, Interface: {4}, Port: {5}, Speed: {6}", (object)this.IPAddress, (object)this.MACAddress, (object)this.IsActive, (object)this.HostName, (object)this.InterfaceType, (object)this.Port, (object)this.Speed); + } + } +} diff --git a/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/WirelessLanInfo.cs b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/WirelessLanInfo.cs new file mode 100644 index 0000000..1de3bf3 --- /dev/null +++ b/Sensormat/FritzBox/API/FritzWebAccess/SoapTypes/WirelessLanInfo.cs @@ -0,0 +1,25 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.API.FritzWebAccess.SoapTypes.WirelessLanInfo +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +#nullable disable +namespace FritzBox.API.FritzWebAccess.SoapTypes +{ + public class WirelessLanInfo + { + public bool IsEnabled { get; set; } + + public string Status { get; set; } + + public string Channel { get; set; } + + public string SSID { get; set; } + + public override string ToString() + { + return string.Format("IsEnabled: {0}, Status: {1}, Channel: {2}, SSID: {3}", (object)this.IsEnabled, (object)this.Status, (object)this.Channel, (object)this.SSID); + } + } +} diff --git a/Sensormat/FritzBox/Config.cs b/Sensormat/FritzBox/Config.cs new file mode 100644 index 0000000..155e504 --- /dev/null +++ b/Sensormat/FritzBox/Config.cs @@ -0,0 +1,20 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.Config +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +#nullable disable +namespace FritzBox +{ + public class Config + { + public string UserName { get; set; } + + public string Password { get; set; } + + public string AIN { get; set; } + + public string Property { get; set; } + } +} diff --git a/Sensormat/FritzBox/FritzBox.csproj b/Sensormat/FritzBox/FritzBox.csproj new file mode 100644 index 0000000..7acb823 --- /dev/null +++ b/Sensormat/FritzBox/FritzBox.csproj @@ -0,0 +1,12 @@ + + + + Library + netcoreapp3.1 + + + + + + + diff --git a/Sensormat/FritzBox/GetDeviceInfoResponse.cs b/Sensormat/FritzBox/GetDeviceInfoResponse.cs new file mode 100644 index 0000000..bf9768d --- /dev/null +++ b/Sensormat/FritzBox/GetDeviceInfoResponse.cs @@ -0,0 +1,55 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.GetDeviceInfoResponse +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System.Diagnostics; +using System.Xml.Serialization; + +#nullable disable +namespace FritzBox +{ + [XmlRoot(ElementName = "device")] + public class GetDeviceInfoResponse + { + [XmlElement(ElementName = "present")] + public int Present { get; set; } + + [XmlElement(ElementName = "txbusy")] + public int Txbusy { get; set; } + + [XmlElement(ElementName = "name")] + public string Name { get; set; } + + [XmlElement(ElementName = "switch")] + public Switch Switch { get; set; } + + [XmlElement(ElementName = "simpleonoff")] + public Simpleonoff Simpleonoff { get; set; } + + [XmlElement(ElementName = "powermeter")] + public Powermeter Powermeter { get; set; } + + [XmlElement(ElementName = "temperature")] + public Temperature Temperature { get; set; } + + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + + [XmlAttribute(AttributeName = "id")] + public int Id { get; set; } + + [XmlAttribute(AttributeName = "functionbitmask")] + public int Functionbitmask { get; set; } + + [XmlAttribute(AttributeName = "fwversion")] + public string Fwversion { get; set; } + + [XmlAttribute(AttributeName = "manufacturer")] + public string Manufacturer { get; set; } + + [XmlAttribute(AttributeName = "productname")] + public string Productname { get; set; } + } +} diff --git a/Sensormat/FritzBox/GetValue.cs b/Sensormat/FritzBox/GetValue.cs new file mode 100644 index 0000000..397d273 --- /dev/null +++ b/Sensormat/FritzBox/GetValue.cs @@ -0,0 +1,60 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.GetValue +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using Sensormat; +using Sensormat.Entities; +using System; + +#nullable disable +namespace FritzBox +{ + public class GetValue : ISensor + { + private Config cfg; + + public override bool Execute() + { + try + { + this.cfg = this.getConfiguration(); + string webContent = new FritzBox.API.FritzWebAccess.FritzWebAccess(this.cfg.UserName, this.cfg.Password) + { + Address = new Uri(this.Sensor.URL) + }.GetWebContent("webservices/homeautoswitch.lua", "&ain=" + this.cfg.AIN + "&switchcmd=getdeviceinfos"); + if (webContent != null) + { + GetDeviceInfoResponse deviceInfoResponse = webContent.XMLDeserializeData(); + if (deviceInfoResponse != null) + { + this.LastValueDate = new DateTime?(DateTime.Now); + switch (this.cfg.Property?.ToLower()) + { + case "power": + this.LastValue = deviceInfoResponse.Powermeter?.Power.ToString(); + break; + case "energy": + this.LastValue = deviceInfoResponse.Powermeter?.Energy.ToString(); + break; + default: + this.LastValue = "property " + this.cfg.Property + " unknown"; + break; + } + return true; + } + this.ErrorMsg = "empty result"; + } + else + this.ErrorMsg = "response is empty"; + } + catch (Exception ex) + { + Console.WriteLine((object)ex); + this.ErrorMsg = ex.Message; + } + return false; + } + } +} diff --git a/Sensormat/FritzBox/Powermeter.cs b/Sensormat/FritzBox/Powermeter.cs new file mode 100644 index 0000000..fee645c --- /dev/null +++ b/Sensormat/FritzBox/Powermeter.cs @@ -0,0 +1,24 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.Powermeter +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System.Xml.Serialization; + +#nullable disable +namespace FritzBox +{ + [XmlRoot(ElementName = "powermeter")] + public class Powermeter + { + [XmlElement(ElementName = "voltage")] + public int Voltage { get; set; } + + [XmlElement(ElementName = "power")] + public int Power { get; set; } + + [XmlElement(ElementName = "energy")] + public int Energy { get; set; } + } +} diff --git a/Sensormat/FritzBox/Simpleonoff.cs b/Sensormat/FritzBox/Simpleonoff.cs new file mode 100644 index 0000000..b3cf591 --- /dev/null +++ b/Sensormat/FritzBox/Simpleonoff.cs @@ -0,0 +1,18 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.Simpleonoff +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System.Xml.Serialization; + +#nullable disable +namespace FritzBox +{ + [XmlRoot(ElementName = "simpleonoff")] + public class Simpleonoff + { + [XmlElement(ElementName = "state")] + public int State { get; set; } + } +} diff --git a/Sensormat/FritzBox/Switch.cs b/Sensormat/FritzBox/Switch.cs new file mode 100644 index 0000000..a1e3524 --- /dev/null +++ b/Sensormat/FritzBox/Switch.cs @@ -0,0 +1,27 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.Switch +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System.Xml.Serialization; + +#nullable disable +namespace FritzBox +{ + [XmlRoot(ElementName = "switch")] + public class Switch + { + [XmlElement(ElementName = "state")] + public int State { get; set; } + + [XmlElement(ElementName = "mode")] + public string Mode { get; set; } + + [XmlElement(ElementName = "lock")] + public int Lock { get; set; } + + [XmlElement(ElementName = "devicelock")] + public int Devicelock { get; set; } + } +} diff --git a/Sensormat/FritzBox/Temperature.cs b/Sensormat/FritzBox/Temperature.cs new file mode 100644 index 0000000..cd2835e --- /dev/null +++ b/Sensormat/FritzBox/Temperature.cs @@ -0,0 +1,21 @@ +// Decompiled with JetBrains decompiler +// Type: FritzBox.Temperature +// Assembly: FritzBox, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: DC9FD63C-0A96-43D7-A76E-0506FEB07200 +// Assembly location: \\192.168.178.26\Freigabe\FritzBox.dll + +using System.Xml.Serialization; + +#nullable disable +namespace FritzBox +{ + [XmlRoot(ElementName = "temperature")] + public class Temperature + { + [XmlElement(ElementName = "celsius")] + public int Celsius { get; set; } + + [XmlElement(ElementName = "offset")] + public int Offset { get; set; } + } +} diff --git a/Sensormat/SQL/spSensorCalculate.sql b/Sensormat/SQL/spSensorCalculate.sql new file mode 100644 index 0000000..40010c1 --- /dev/null +++ b/Sensormat/SQL/spSensorCalculate.sql @@ -0,0 +1,85 @@ +DELIMITER $$ + + +DROP PROCEDURE IF EXISTS spSensorCalculate$$ + + +CREATE DEFINER=`root`@`%` PROCEDURE `spSensorCalculate`() + NOT DETERMINISTIC + SQL SECURITY DEFINER +BEGIN + DECLARE dfrom DATETIME; + SET dfrom = NOW() - INTERVAL 14 DAY; + + /* Stundenwerte erzeugen / aktualisieren (ohne CTE) */ + INSERT INTO tblSensorDataStunde + (SensorID, Datum, StartValue, EndValue, ValueMIN, ValueMAX, ValueAVG) + SELECT + sd.SensorID, + DATE_FORMAT(sd.ValueDate, '%Y-%m-%d %H:00:00') AS Datum, + CASE s.Berechnung + WHEN 1 THEN MIN(sd.Value) + WHEN 2 THEN MAX(sd.Value) + END AS StartValue, + NULL AS EndValue, + MIN(sd.Value) AS ValueMIN, + MAX(sd.Value) AS ValueMAX, + AVG(sd.Value) AS ValueAVG + FROM tblSensoren s + JOIN tblSensorData sd ON s.ID = sd.SensorID + WHERE sd.Value IS NOT NULL + AND sd.ValueDate >= dfrom + AND s.Berechnung <> 0 + GROUP BY sd.SensorID, DATE_FORMAT(sd.ValueDate, '%Y-%m-%d %H:00:00') + ON DUPLICATE KEY UPDATE + StartValue = VALUES(StartValue), + EndValue = NULL, + ValueMIN = VALUES(ValueMIN), + ValueMAX = VALUES(ValueMAX), + ValueAVG = VALUES(ValueAVG); + + /* --- 1093-sicher: NextStart in TEMP-Tabelle berechnen --- */ + DROP TEMPORARY TABLE IF EXISTS tmp_next; + + /* Variante ohne Window-Funktionen (läuft auch auf älteren Versionen): + Wir sortieren pro Sensor DESC nach Datum, dann ist "prev_start" die nächste Stunde. */ + CREATE TEMPORARY TABLE tmp_next + AS + SELECT SensorID, Datum, NextStart + FROM ( + SELECT + x.SensorID, + x.Datum, + /* beim Abstieg ist die "vorherige" StartValue die nächste in der Zukunft */ + CASE WHEN @cur_sid = x.SensorID THEN @prev_start ELSE NULL END AS NextStart, + /* Hilfszuweisungen für den nächsten Datensatz */ + @prev_start := x.StartValue AS _set_prev, + @cur_sid := x.SensorID AS _set_sid + FROM ( + SELECT SensorID, Datum, StartValue + FROM tblSensorDataStunde + WHERE Datum >= dfrom + ORDER BY SensorID, Datum DESC + ) x + /* Variablen initialisieren */ + JOIN (SELECT @cur_sid := NULL, @prev_start := NULL) vars + ) z; + + + /* Jetzt gefahrlos updaten, weil wir aus tmp_next lesen, nicht aus der Zieltabelle */ + UPDATE tblSensorDataStunde t + JOIN tmp_next n + ON n.SensorID = t.SensorID AND n.Datum = t.Datum + SET t.EndValue = n.NextStart + WHERE t.Datum >= dfrom + AND t.EndValue IS NULL + AND n.NextStart IS NOT NULL; + + DROP TEMPORARY TABLE IF EXISTS tmp_next; + +END$$ + +DELIMITER ; + +CALL spSensorCalculate; + diff --git a/Sensormat/Sensormat.sln b/Sensormat/Sensormat.sln new file mode 100644 index 0000000..fe2d37c --- /dev/null +++ b/Sensormat/Sensormat.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36414.22 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sensormat", "Sensormat\Sensormat.csproj", "{AD805DAC-A72F-4785-9CAD-4DEE29D7B647}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FritzBox", "FritzBox\FritzBox.csproj", "{73C49A84-293E-442C-8198-B3CBE2E8CFF8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ESP8266_Gaszaehler", "ESP8266_Gaszaehler\ESP8266_Gaszaehler.csproj", "{CEB7D1F4-BB04-44D1-9CE1-0EF04D3EE958}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shelly", "Shelly\Shelly.csproj", "{A63DCF66-60EF-4256-8609-6C423F484EFF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AD805DAC-A72F-4785-9CAD-4DEE29D7B647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD805DAC-A72F-4785-9CAD-4DEE29D7B647}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD805DAC-A72F-4785-9CAD-4DEE29D7B647}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD805DAC-A72F-4785-9CAD-4DEE29D7B647}.Release|Any CPU.Build.0 = Release|Any CPU + {73C49A84-293E-442C-8198-B3CBE2E8CFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73C49A84-293E-442C-8198-B3CBE2E8CFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73C49A84-293E-442C-8198-B3CBE2E8CFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73C49A84-293E-442C-8198-B3CBE2E8CFF8}.Release|Any CPU.Build.0 = Release|Any CPU + {CEB7D1F4-BB04-44D1-9CE1-0EF04D3EE958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CEB7D1F4-BB04-44D1-9CE1-0EF04D3EE958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CEB7D1F4-BB04-44D1-9CE1-0EF04D3EE958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CEB7D1F4-BB04-44D1-9CE1-0EF04D3EE958}.Release|Any CPU.Build.0 = Release|Any CPU + {A63DCF66-60EF-4256-8609-6C423F484EFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A63DCF66-60EF-4256-8609-6C423F484EFF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A63DCF66-60EF-4256-8609-6C423F484EFF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A63DCF66-60EF-4256-8609-6C423F484EFF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B15582B7-B501-4507-8D60-4312FE32CC79} + EndGlobalSection +EndGlobal diff --git a/Sensormat/Sensormat/Config.cs b/Sensormat/Sensormat/Config.cs new file mode 100644 index 0000000..9ced1b1 --- /dev/null +++ b/Sensormat/Sensormat/Config.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; +using System.Text.Json; + +namespace Sensormat +{ + public class Config + { + private const string ConfigName = "cfg/config.json"; + + // Beispiel-Properties (passen an deine JSON-Struktur an!) + public string MainUrl { get; set; } = @"http://*:810/"; + + public string MYSQL_Server { get; set; } = "docker-1"; + public string MYSQL_User { get; set; } = "ESP8266_Gaszaehler"; + public string MYSQL_Password { get; set; } = "gFKWa8lFJwFUcBysn4HI"; + public string MYSQL_Database { get; set; } = "zeidlerme"; + + public static Config Load() + { + string basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + ?? AppContext.BaseDirectory; + + string configPath = Path.Combine(basePath, ConfigName); + + var options = new JsonSerializerOptions + { + WriteIndented = true, + PropertyNameCaseInsensitive = true, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + // Datei fehlt → Standardobjekt erzeugen und abspeichern + if (!File.Exists(configPath)) + { + var defaultConfig = new Config(); + string json = JsonSerializer.Serialize(defaultConfig, options); + File.WriteAllText(configPath, json); + Console.WriteLine($"Config created {configPath}"); + return defaultConfig; + } + + // Datei vorhanden → einlesen + string content = File.ReadAllText(configPath); + var cfg = JsonSerializer.Deserialize(content, options); + + if (cfg == null) + throw new InvalidOperationException("Konfigurationsdatei konnte nicht geladen werden."); + + Console.WriteLine($"config loaded {configPath}"); + return cfg; + } + } +} \ No newline at end of file diff --git a/Sensormat/Sensormat/Dockerfile b/Sensormat/Sensormat/Dockerfile new file mode 100644 index 0000000..95bccad --- /dev/null +++ b/Sensormat/Sensormat/Dockerfile @@ -0,0 +1,6 @@ +FROM mcr.microsoft.com/dotnet/core/sdk:3.1 +COPY ./bin/Release/netcoreapp3.1/. /app +WORKDIR /app +EXPOSE 810/tcp +ENV TZ=Europe/Berlin +CMD ["sh", "-c", "dotnet Sensormat.dll $Config" ] \ No newline at end of file diff --git a/Sensormat/Sensormat/Entities/ISensor.cs b/Sensormat/Sensormat/Entities/ISensor.cs new file mode 100644 index 0000000..a11422f --- /dev/null +++ b/Sensormat/Sensormat/Entities/ISensor.cs @@ -0,0 +1,100 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.Entities.ISensor +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using MySqlConnector; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text.Json.Serialization; + +#nullable disable +namespace Sensormat.Entities +{ + public class ISensor + { + private Sensoren _sensor; + + public ISensor() + { + } + + public ISensor(int SensorID) => this.SensorID = SensorID; + + public MySqlConnection connection { get; set; } + + public string ErrorMsg { get; set; } + + public string LastValue { get; set; } + + public DateTime? LastValueDate { get; set; } + + public virtual int SensorID { get; set; } + + public virtual bool Execute() => false; + + public Sensoren Sensor + { + get + { + if (this.SensorID == 0) + throw new Exception("SensorID = 0"); + if (this._sensor == null) + { + using (MySqlCommand command = this.connection.CreateCommand()) + { + command.CommandText = "SELECT * FROM tblSensoren WHERE ID = @SensorID"; + command.Parameters.AddWithValue("SensorID", (object)this.SensorID); + using (MySqlDataReader rd = command.ExecuteReader()) + { + if (rd.HasRows) + { + rd.Read(); + this._sensor = rd.ParseObject(); + } + } + } + } + return this._sensor; + } + } + + public T getConfiguration() + { + if (this.Sensor != null) + { + if (!string.IsNullOrEmpty(this.Sensor.config)) + { + try + { + return JsonConvert.DeserializeObject(this.Sensor.config); + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, string.Format("{0}", (object)this.Sensor.ID), (object)ex); + return default(T); + } + } + } + return default(T); + } + + public string HttpRequest(string URL) + { + WebRequest webRequest = WebRequest.Create(URL); + webRequest.Method = "GET"; + using (WebResponse response = webRequest.GetResponse()) + { + using (Stream responseStream = response.GetResponseStream()) + { + using (StreamReader streamReader = new StreamReader(responseStream)) + return ((TextReader)streamReader).ReadToEnd(); + } + } + } + } +} diff --git a/Sensormat/Sensormat/Entities/ISensorData.cs b/Sensormat/Sensormat/Entities/ISensorData.cs new file mode 100644 index 0000000..1619075 --- /dev/null +++ b/Sensormat/Sensormat/Entities/ISensorData.cs @@ -0,0 +1,20 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.Entities.SensorData +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using System; + +#nullable disable +namespace Sensormat.Entities +{ + public class SensorData + { + public int SensorID { get; set; } + + public Decimal Value { get; set; } + + public DateTime? ValueDate { get; set; } + } +} diff --git a/Sensormat/Sensormat/Entities/Sensoren.cs b/Sensormat/Sensormat/Entities/Sensoren.cs new file mode 100644 index 0000000..ec28786 --- /dev/null +++ b/Sensormat/Sensormat/Entities/Sensoren.cs @@ -0,0 +1,50 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.Entities.Sensoren +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using System; + +#nullable disable +namespace Sensormat.Entities +{ + public class Sensoren + { + public int ID { get; set; } + + public string Description { get; set; } + + public string URL { get; set; } + + public string LastValue { get; set; } + + public DateTime? LastCall { get; set; } + + public string ErrorMsg { get; set; } + + public int Intervall { get; set; } + + public string Assembly { get; set; } + + public string TypeName { get; set; } + + public int Aktiv { get; set; } + + public DateTime? LastValueDate { get; set; } + + public Sensoren.CheckChangedMethods CheckChanged { get; set; } + + public string Passphrase { get; set; } + + public string config { get; set; } + + public enum CheckChangedMethods + { + none, + value, + valuedate, + valueorvaluedate, + } + } +} diff --git a/Sensormat/Sensormat/EventLog.cs b/Sensormat/Sensormat/EventLog.cs new file mode 100644 index 0000000..48a7ae9 --- /dev/null +++ b/Sensormat/Sensormat/EventLog.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; + +namespace Sensormat +{ + public class EventLog + { + public static void Write(string Source, string Message, params object[] param) + { + try + { + if (!Directory.Exists("LOG")) + Directory.CreateDirectory("LOG"); + string path = "LOG/" + DateTime.Now.ToString("yyyy-MM-dd") + ".txt"; + string contents = Environment.NewLine + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + Source + Environment.NewLine + " " + Message; + if (param != null) + { + foreach (object obj in param) + { + if (obj != null) + { + if (obj.GetType() == typeof(string)) + contents += string.Format("{0}{1}", obj, (object)Environment.NewLine); + if (obj.GetType() == typeof(Exception)) + contents += string.Format("{0}{1} {2} {3}{4}", obj, (object)((Exception)obj).Message, (object)((Exception)obj).Message, (object)((Exception)obj).InnerException, (object)Environment.NewLine); + } + } + } + Console.WriteLine(contents); + File.AppendAllText(path, contents); + } + catch (Exception ex) + { + Console.WriteLine("Logging Error: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " " + ex.Message); + } + } + } +} diff --git a/Sensormat/Sensormat/HttpServer.cs b/Sensormat/Sensormat/HttpServer.cs new file mode 100644 index 0000000..0bbc086 --- /dev/null +++ b/Sensormat/Sensormat/HttpServer.cs @@ -0,0 +1,89 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.HttpServer +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using System; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +#nullable disable +namespace Sensormat +{ + public class HttpServer + { + public HttpListener listener; + private string URL; + + public HttpServer.HttpServerIncomingRequestCallBack httpServerIncomingRequest { get; set; } + + public async Task HandleIncomingConnections() + { + bool runServer = true; + while (runServer) + { + HttpListenerContext contextAsync = await this.listener.GetContextAsync(); + HttpListenerRequest request = contextAsync.Request; + HttpListenerResponse resp = contextAsync.Response; + Console.WriteLine(request.Url.ToString()); + Console.WriteLine(request.HttpMethod); + Console.WriteLine(request.UserHostName); + Console.WriteLine(request.UserAgent); + Console.WriteLine(); + HttpServerIncomingRequestCallBackParameter e = new HttpServerIncomingRequestCallBackParameter() + { + StatusCode = 200, + StatusDescription = "", + Result = "", + URL = this.URL, + Request = request, + AbsolutePath = request.Url.AbsolutePath.Trim('/') + }; + if (request.QueryString != null) + { + foreach (string allKey in request.QueryString.AllKeys) + e.Query.Add(allKey, request.QueryString[allKey]); + } + if (this.httpServerIncomingRequest != null) + this.httpServerIncomingRequest(e); + byte[] bytes = Encoding.UTF8.GetBytes(e.Result); + resp.ContentType = "text/html"; + resp.ContentEncoding = Encoding.UTF8; + resp.ContentLength64 = (long)bytes.Length; + resp.StatusCode = e.StatusCode; + resp.StatusDescription = e.StatusDescription; + await resp.OutputStream.WriteAsync(bytes, 0, bytes.Length); + resp.Close(); + resp = (HttpListenerResponse)null; + } + } + + private string getValue(string ID) => "OK " + ID; + + private string setValue(string ID, string Parameter) => "setValue " + ID + " " + Parameter; + + public HttpServer(string url) + { + this.listener = new HttpListener(); + this.listener.Prefixes.Add(url); + this.listener.Start(); + Console.WriteLine("Listening for connections on {0}", (object)url); + this.URL = url; + this.HandleIncomingConnections(); + } + + public bool Test() => !string.IsNullOrEmpty(new WebClient().DownloadString(this.URL)); + + public void Release() + { + if (this.listener == null) + return; + this.listener.Close(); + } + + public delegate void HttpServerIncomingRequestCallBack( + HttpServerIncomingRequestCallBackParameter e); + } +} diff --git a/Sensormat/Sensormat/HttpServerIncomingRequestCallBackParameter.cs b/Sensormat/Sensormat/HttpServerIncomingRequestCallBackParameter.cs new file mode 100644 index 0000000..cb4e008 --- /dev/null +++ b/Sensormat/Sensormat/HttpServerIncomingRequestCallBackParameter.cs @@ -0,0 +1,29 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.HttpServerIncomingRequestCallBackParameter +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using System.Collections.Generic; +using System.Net; + +#nullable disable +namespace Sensormat +{ + public class HttpServerIncomingRequestCallBackParameter + { + public string Result { get; set; } + + public string StatusDescription { get; set; } + + public int StatusCode { get; set; } + + public string URL { get; set; } + + public HttpListenerRequest Request { get; set; } + + public string AbsolutePath { get; set; } + + public Dictionary Query { get; set; } = new Dictionary(); + } +} diff --git a/Sensormat/Sensormat/Manager.cs b/Sensormat/Sensormat/Manager.cs new file mode 100644 index 0000000..72e6eae --- /dev/null +++ b/Sensormat/Sensormat/Manager.cs @@ -0,0 +1,295 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.Manager +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using MySqlConnector; +using Sensormat.Entities; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Threading; + +#nullable disable +namespace Sensormat +{ + public class Manager + { + private Config config; + private MySqlConnection connection; + private static Semaphore semaphore = new Semaphore(1, 1); + private HttpServer httpServer; + private DateTime lastCalculate; + public string HelpData = "\r\n \r\n \r\n Sensormat WebInterface\r\n \r\n \r\n

Help

\r\n

GetValue

[URL]/GetValue?Passphrase={Passphrase}&ID={SensorID}\r\n

SetValue

\r\n [URL]/SetValue?Passphrase={Passphrase}&ID={SensorID}&Value={Value}&[ValueDate={ValueDate}]\r\n \r\n "; + + private bool UpdateSensor(Sensoren sensor) + { + try + { + using (MySqlCommand command = this.connection.CreateCommand()) + { + command.CommandText = "UPDATE tblSensoren SET LastValue = @LastValue, LastValueDate = @LastValueDate, LastCall = @LastCall, ErrorMsg = @ErrorMsg WHERE ID = @SensorID"; + command.Parameters.AddWithValue("LastValue", (object)sensor.LastValue); + command.Parameters.AddWithValue("LastValueDate", (object)sensor.LastValueDate); + command.Parameters.AddWithValue("LastCall", (object)sensor.LastCall); + command.Parameters.AddWithValue("ErrorMsg", (object)sensor.ErrorMsg); + command.Parameters.AddWithValue("SensorID", (object)sensor.ID); + return command.ExecuteNonQuery() == 1; + } + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)string.Format("SensorID={0}", (object)sensor.ID), (object)ex); + } + return false; + } + + private bool UpdateSensorData(Sensoren sensor) + { + try + { + using (MySqlCommand command = this.connection.CreateCommand()) + { + command.CommandText = "INSERT INTO tblSensorData (SensorID, RawValue, ValueDate,CD) VALUES (@SensorID, @RawValue, @ValueDate, @Datum)"; + command.Parameters.AddWithValue("SensorID", (object)sensor.ID); + command.Parameters.AddWithValue("RawValue", (object)sensor.LastValue); + command.Parameters.AddWithValue("ValueDate", (object)sensor.LastValueDate); + command.Parameters.AddWithValue("Datum", (object)DateTime.Now); + return command.ExecuteNonQuery() == 1; + } + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)string.Format("SensorID={0}", (object)sensor.ID), (object)ex); + sensor.ErrorMsg = string.Format("{0} SensorID={1} {2} {3}", (object)((MemberInfo)MethodBase.GetCurrentMethod()).Name, (object)sensor.ID, (object)ex.Message, (object)ex); + } + return false; + } + + private void HandleSensor(ISensor SensorEntity) + { + try + { + if (SensorEntity == null) + return; + if (SensorEntity.Sensor.LastCall.HasValue) + { + DateTime now = DateTime.Now; + DateTime? lastCall = SensorEntity.Sensor.LastCall; + if ((lastCall.HasValue ? new TimeSpan?(now - lastCall.GetValueOrDefault()) : new TimeSpan?()).Value.TotalMinutes < (double)SensorEntity.Sensor.Intervall) + return; + } + SensorEntity.Execute(); + this.SetValue(SensorEntity, SensorEntity.LastValue, (string)null); + SensorEntity.Sensor.ErrorMsg = ""; + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)string.Format("SensorID={0}", (object)SensorEntity.SensorID), (object)ex); + Console.WriteLine((object)ex); + } + } + + private ISensor GetSensor(int SensorID, string Passphrase) + { + ISensor isensor = new ISensor(SensorID); + isensor.connection = this.connection; + return isensor.Sensor == null || isensor.Sensor.Passphrase != Passphrase ? (ISensor)null : isensor; + } + + private bool GetValue(int SensorID, out string Result) + { + Result = ""; + ISensor isensor = new ISensor(SensorID); + isensor.connection = this.connection; + if (isensor.Sensor == null) + return false; + Result = isensor.Sensor.LastValue; + return true; + } + + private bool SetValue(ISensor SensorEntity, string Value, string ValueDateString) + { + DateTime result; + if (!DateTime.TryParse(ValueDateString, out result)) + result = DateTime.Now; + if (SensorEntity.Sensor.CheckChanged != Sensoren.CheckChangedMethods.none && (SensorEntity.Sensor.CheckChanged != Sensoren.CheckChangedMethods.value || !(SensorEntity.Sensor.LastValue != Value) && SensorEntity.Sensor.LastValue != null)) + { + DateTime? lastValueDate; + if (SensorEntity.Sensor.CheckChanged == Sensoren.CheckChangedMethods.valuedate) + { + lastValueDate = SensorEntity.Sensor.LastValueDate; + DateTime dateTime = result; + if ((lastValueDate.HasValue ? (lastValueDate.HasValue ? (lastValueDate.GetValueOrDefault() != dateTime ? 1 : 0) : 0) : 1) == 0) + { + lastValueDate = SensorEntity.Sensor.LastValueDate; + if (!lastValueDate.HasValue) + goto label_9; + } + else + goto label_9; + } + if (SensorEntity.Sensor.CheckChanged == Sensoren.CheckChangedMethods.valueorvaluedate) + { + lastValueDate = SensorEntity.Sensor.LastValueDate; + DateTime dateTime = result; + if ((lastValueDate.HasValue ? (lastValueDate.HasValue ? (lastValueDate.GetValueOrDefault() != dateTime ? 1 : 0) : 0) : 1) == 0) + { + lastValueDate = SensorEntity.Sensor.LastValueDate; + if (lastValueDate.HasValue && !(SensorEntity.Sensor.LastValue != Value) && SensorEntity.Sensor.LastValue != null) + goto label_10; + } + } + else + goto label_10; + } + label_9: + SensorEntity.Sensor.LastValue = Value; + SensorEntity.Sensor.LastValueDate = new DateTime?(result); + this.UpdateSensorData(SensorEntity.Sensor); + label_10: + SensorEntity.Sensor.LastCall = new DateTime?(DateTime.Now); + SensorEntity.Sensor.ErrorMsg = SensorEntity.ErrorMsg; + this.UpdateSensor(SensorEntity.Sensor); + return true; + } + + private void CheckConnection() + { + if (this.connection.State == ConnectionState.Connecting) + { + do + { + Thread.Sleep(1000); + Console.Write("."); + } + while (this.connection.State == ConnectionState.Connecting); + } + if (this.connection.State == ConnectionState.Open) + return; + this.connection.Open(); + } + + private void CalculateData() + { + try + { + if ((DateTime.Now - this.lastCalculate).TotalMinutes < 10.0) + return; + this.connection.CallCalculate(); + this.lastCalculate = DateTime.Now; + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)ex); + } + } + + public void Run() + { + try + { + this.config = Config.Load(); + this.httpServer = new HttpServer(config.MainUrl); + this.httpServer.httpServerIncomingRequest += new HttpServer.HttpServerIncomingRequestCallBack(this.httpServerIncomingRequest); + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, "cant't start server " + ex.Message, (object)ex); + } + this.connection = new MySqlConnection($"Server={config.MYSQL_Server};User ID={config.MYSQL_User};Password={config.MYSQL_Password};Database={config.MYSQL_Database}"); + while (true) + { + try + { + this.CheckConnection(); + Manager.semaphore.WaitOne(); + foreach (Sensoren sensor in this.connection.GetSensoren().ToArray()) + { + try + { + if (string.IsNullOrWhiteSpace(sensor.Assembly)) + throw new Exception("Assemblyfile " + sensor.Assembly + " not found"); + Type type = Assembly.LoadFrom(sensor.Assembly).GetType(sensor.TypeName); + ISensor SensorEntity = !(type == (Type)null) ? Activator.CreateInstance(type) as ISensor : throw new Exception("type " + sensor.TypeName + " not found"); + SensorEntity.SensorID = sensor.ID; + SensorEntity.connection = this.connection; + if (SensorEntity == null) + throw new Exception(string.Format("can't create Instance {0}", (object)type)); + this.HandleSensor(SensorEntity); + } + catch (Exception ex) + { + sensor.ErrorMsg = string.Format("{0} {1} {2} {3}", (object)ex.Message, (object)ex.Source, (object)ex.StackTrace, (object)ex.InnerException); + this.UpdateSensor(sensor); + } + } + this.CalculateData(); + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)ex); + } + finally + { + Manager.semaphore.Release(); + Thread.Sleep(60000); + } + } + } + + private void httpServerIncomingRequest(HttpServerIncomingRequestCallBackParameter e) + { + try + { + Manager.semaphore.WaitOne(); + string str1 = this.HelpData.Replace("[URL]", e.URL); + string[] strArray = e.AbsolutePath.Trim('/').Split('/', StringSplitOptions.None); + KeyValuePair keyValuePair = e.Query.FirstOrDefault>((Func, bool>)(f => f.Key.ToLower() == "id")); + string s = keyValuePair.Value; + keyValuePair = e.Query.FirstOrDefault>((Func, bool>)(f => f.Key.ToLower() == "value")); + string str2 = keyValuePair.Value; + keyValuePair = e.Query.FirstOrDefault>((Func, bool>)(f => f.Key.ToLower() == "valuedate")); + string ValueDateString = keyValuePair.Value; + keyValuePair = e.Query.FirstOrDefault>((Func, bool>)(f => f.Key.ToLower() == "passphrase")); + string Passphrase = keyValuePair.Value; + int result; + if (strArray.Length == 1 && int.TryParse(s, out result) && result > 0) + { + ISensor sensor = this.GetSensor(result, Passphrase); + if (sensor == null) + { + e.StatusCode = 500; + return; + } + switch (strArray[0].ToLower()) + { + case "getvalue": + str1 = sensor.Sensor.LastValue; + break; + case "setvalue": + if (!this.SetValue(sensor, str2, ValueDateString)) + { + e.StatusCode = 404; + break; + } + str1 = "ok"; + break; + } + } + e.Result = str1; + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)ex); + } + finally + { + Manager.semaphore.Release(); + } + } + } +} diff --git a/Sensormat/Sensormat/ParseExtensions.cs b/Sensormat/Sensormat/ParseExtensions.cs new file mode 100644 index 0000000..2e8d7f4 --- /dev/null +++ b/Sensormat/Sensormat/ParseExtensions.cs @@ -0,0 +1,153 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.ParseExtensions +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using MySqlConnector; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Xml; +using System.Xml.Serialization; + +#nullable disable +namespace Sensormat +{ + public static class ParseExtensions + { + public static string XMLSerializeData(this T data) + { + StringBuilder stringBuilder = new StringBuilder(); + new XmlSerializer(typeof(T)).Serialize((TextWriter)new StringWriter(stringBuilder), (object)data); + return stringBuilder.ToString(); + } + + public static T XMLDeserializeData(this string dataXML) + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.LoadXml(dataXML); + return (T)new XmlSerializer(typeof(T)).Deserialize((XmlReader)new XmlNodeReader((XmlNode)xmlDocument.DocumentElement)); + } + public static T ParseObject(this MySqlDataReader rd) where T : class, new() + { + var type = typeof(T); + var instance = new T(); + + // Properties vorbereiten (Case-insensitive, nur schreibbare) + var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanWrite) + .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); + + for (int ordinal = 0; ordinal < rd.FieldCount; ordinal++) + { + if (rd.IsDBNull(ordinal)) continue; + + string fieldName = rd.GetName(ordinal); + if (!props.TryGetValue(fieldName, out var propertyInfo)) continue; // Spalte ohne passende Property + + object value = rd.GetValue(ordinal); + Type targetType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType; + + try + { + object? converted = ConvertTo(value, targetType); + propertyInfo.SetValue(instance, converted); + } + catch + { + // Optional: Logging einbauen, wenn eine Spalte mal nicht passt. + // throw; // oder bewusst schlucken + } + } + + return instance; + } + public static object ParseObject(this MySqlDataReader rd, Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + var instance = Activator.CreateInstance(type) + ?? throw new InvalidOperationException($"Kann Instanz von {type.FullName} nicht erzeugen."); + + // Schreibbare Properties case-insensitiv mappen + var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public) + .Where(p => p.CanWrite) + .ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase); + + for (int ordinal = 0; ordinal < rd.FieldCount; ordinal++) + { + if (rd.IsDBNull(ordinal)) continue; + + string fieldName = rd.GetName(ordinal); + if (!props.TryGetValue(fieldName, out var propertyInfo)) continue; + + object value = rd.GetValue(ordinal); + Type targetType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType; + + try + { + object? converted = ConvertTo(value, targetType); + propertyInfo.SetValue(instance, converted); + } + catch + { + // Optional: Logging/Tracing hier einbauen + // Wenn du strikt sein willst: throw; + } + } + + return instance; + } + + private static object? ConvertTo(object value, Type targetType) + { + if (value == null) return null; + + // Schon richtig typisiert? + if (targetType.IsInstanceOfType(value)) return value; + + // Enums (Zahl oder Name) + if (targetType.IsEnum) + { + if (value is string s) return Enum.Parse(targetType, s, ignoreCase: true); + var underlying = Enum.GetUnderlyingType(targetType); + var num = Convert.ChangeType(value, underlying, CultureInfo.InvariantCulture); + return Enum.ToObject(targetType, num!); + } + + // Guid + if (targetType == typeof(Guid)) + return value is Guid g ? g : Guid.Parse(value.ToString()!); + + // DateTimeOffset + if (targetType == typeof(DateTimeOffset)) + { + if (value is DateTimeOffset dto) return dto; + if (value is DateTime dt) return new DateTimeOffset(dt); + return DateTimeOffset.Parse(value.ToString()!, CultureInfo.InvariantCulture); + } + + // DateTime: ChangeType kann das meist, sonst Parse + if (targetType == typeof(DateTime) && value is string sdt) + return DateTime.Parse(sdt, CultureInfo.InvariantCulture); + + // Bool aus 0/1 oder "0"/"1" + if (targetType == typeof(bool) && value is IConvertible) + { + var s = value.ToString(); + if (s == "0") return false; + if (s == "1") return true; + } + + // Fallback: Convert.ChangeType + return Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); + } + } + + +} diff --git a/Sensormat/Sensormat/Program.cs b/Sensormat/Sensormat/Program.cs new file mode 100644 index 0000000..b7369e4 --- /dev/null +++ b/Sensormat/Sensormat/Program.cs @@ -0,0 +1,29 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.Program +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using System; +using System.Reflection; + +#nullable disable +namespace Sensormat +{ + internal class Program + { + private static void Main(string[] args) + { + try + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, "START"); + new Manager().Run(); + } + catch (Exception ex) + { + EventLog.Write(((MemberInfo)MethodBase.GetCurrentMethod()).Name, ex.Message, (object)ex); + Console.WriteLine((object)ex); + } + } + } +} diff --git a/Sensormat/Sensormat/References/MySqlConnector.dll b/Sensormat/Sensormat/References/MySqlConnector.dll new file mode 100644 index 0000000..19f0d69 Binary files /dev/null and b/Sensormat/Sensormat/References/MySqlConnector.dll differ diff --git a/Sensormat/Sensormat/References/Newtonsoft.Json.dll b/Sensormat/Sensormat/References/Newtonsoft.Json.dll new file mode 100644 index 0000000..513eb32 Binary files /dev/null and b/Sensormat/Sensormat/References/Newtonsoft.Json.dll differ diff --git a/Sensormat/Sensormat/SQLExtensions.cs b/Sensormat/Sensormat/SQLExtensions.cs new file mode 100644 index 0000000..f163c3f --- /dev/null +++ b/Sensormat/Sensormat/SQLExtensions.cs @@ -0,0 +1,56 @@ +// Decompiled with JetBrains decompiler +// Type: Sensormat.SQLExtensions +// Assembly: Sensormat, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 49795081-3467-4846-904D-9A360FB2FA3C +// Assembly location: \\192.168.178.26\Freigabe\Sensormat.dll + +using MySqlConnector; +using Sensormat.Entities; +using System.Collections.Generic; + +#nullable disable +namespace Sensormat +{ + public static class SQLExtensions + { + public static IEnumerable GetSensoren(this MySqlConnection connection) + { + using (MySqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = "SELECT * FROM tblSensoren WHERE Aktiv <> 0"; + MySqlDataReader reader = cmd.ExecuteReader(); + try + { + while (true) + { + if (reader.HasRows) + { + if (reader.Read()) + yield return reader.ParseObject(); + else + break; + } + else + goto label_5; + } + } + finally + { + reader?.Dispose(); + } + yield break; + label_5: + reader = (MySqlDataReader)null; + } + } + + public static void CallCalculate(this MySqlConnection connection) + { + using (MySqlCommand command = connection.CreateCommand()) + { + command.CommandText = "CALL spSensorCalculate; "; + command.ExecuteNonQuery(); + } + } + } +} diff --git a/Sensormat/Sensormat/Sensormat.csproj b/Sensormat/Sensormat/Sensormat.csproj new file mode 100644 index 0000000..93278f9 --- /dev/null +++ b/Sensormat/Sensormat/Sensormat.csproj @@ -0,0 +1,17 @@ + + + + Exe + netcoreapp3.1 + + + + + References\MySqlConnector.dll + + + References\Newtonsoft.Json.dll + + + + diff --git a/Sensormat/Shelly/Class1.cs b/Sensormat/Shelly/Class1.cs new file mode 100644 index 0000000..78ccea7 --- /dev/null +++ b/Sensormat/Shelly/Class1.cs @@ -0,0 +1,8 @@ +using System; + +namespace Shelly +{ + public class Class1 + { + } +} diff --git a/Sensormat/Shelly/Shelly.csproj b/Sensormat/Shelly/Shelly.csproj new file mode 100644 index 0000000..0d6ab43 --- /dev/null +++ b/Sensormat/Shelly/Shelly.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + ..\Sensormat\References\Newtonsoft.Json.dll + + + + diff --git a/Sensormat/Shelly/Shelly1PM/ResultMeters.cs b/Sensormat/Shelly/Shelly1PM/ResultMeters.cs new file mode 100644 index 0000000..ba00253 --- /dev/null +++ b/Sensormat/Shelly/Shelly1PM/ResultMeters.cs @@ -0,0 +1,20 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly1PM.ResultMeters +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +using System; + +#nullable disable +namespace Shelly.Shelly1PM +{ + public class ResultMeters + { + public Decimal power { get; set; } + + public Decimal overload { get; set; } + + public Decimal total { get; set; } + } +} diff --git a/Sensormat/Shelly/Shelly1PM/ResultSet.cs b/Sensormat/Shelly/Shelly1PM/ResultSet.cs new file mode 100644 index 0000000..3d255a5 --- /dev/null +++ b/Sensormat/Shelly/Shelly1PM/ResultSet.cs @@ -0,0 +1,14 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly1PM.ResultSet +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +#nullable disable +namespace Shelly.Shelly1PM +{ + public class ResultSet + { + public ResultMeters[] meters { get; set; } + } +} diff --git a/Sensormat/Shelly/Shelly1PM/SensorEntity.cs b/Sensormat/Shelly/Shelly1PM/SensorEntity.cs new file mode 100644 index 0000000..301ac89 --- /dev/null +++ b/Sensormat/Shelly/Shelly1PM/SensorEntity.cs @@ -0,0 +1,39 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly1PM.SensorEntity +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +using Newtonsoft.Json; +using Sensormat; +using Sensormat.Entities; +using System; + +#nullable disable +namespace Shelly.Shelly1PM +{ + public class SensorEntity : ISensor + { + public override bool Execute() + { + try + { + int id = this.Sensor.ID; + new ResultSet().XMLSerializeData(); + string str = this.HttpRequest(this.Sensor.URL); + if (str != null) + { + this.LastValue = JsonConvert.DeserializeObject(str).meters?[0]?.total.ToString(); + this.LastValueDate = new DateTime?(DateTime.Now); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine((object)ex); + this.ErrorMsg = ex.Message; + } + return false; + } + } +} diff --git a/Sensormat/Shelly/Shelly3PM/Config.cs b/Sensormat/Shelly/Shelly3PM/Config.cs new file mode 100644 index 0000000..a82a9a6 --- /dev/null +++ b/Sensormat/Shelly/Shelly3PM/Config.cs @@ -0,0 +1,16 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly3PM.Config +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +#nullable disable +namespace Shelly.Shelly3PM +{ + public class Config + { + public bool Sum { get; set; } + + public int index { get; set; } + } +} diff --git a/Sensormat/Shelly/Shelly3PM/ResultMeters.cs b/Sensormat/Shelly/Shelly3PM/ResultMeters.cs new file mode 100644 index 0000000..98ed13c --- /dev/null +++ b/Sensormat/Shelly/Shelly3PM/ResultMeters.cs @@ -0,0 +1,24 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly3PM.ResultMeters +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +using System; + +#nullable disable +namespace Shelly.Shelly3PM +{ + public class ResultMeters + { + public Decimal current { get; set; } + + public Decimal power { get; set; } + + public Decimal voltage { get; set; } + + public Decimal total { get; set; } + + public Decimal total_returned { get; set; } + } +} diff --git a/Sensormat/Shelly/Shelly3PM/ResultSet.cs b/Sensormat/Shelly/Shelly3PM/ResultSet.cs new file mode 100644 index 0000000..816269c --- /dev/null +++ b/Sensormat/Shelly/Shelly3PM/ResultSet.cs @@ -0,0 +1,14 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly3PM.ResultSet +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +#nullable disable +namespace Shelly.Shelly3PM +{ + public class ResultSet + { + public ResultMeters[] emeters { get; set; } + } +} diff --git a/Sensormat/Shelly/Shelly3PM/SensorEntity.cs b/Sensormat/Shelly/Shelly3PM/SensorEntity.cs new file mode 100644 index 0000000..0296b44 --- /dev/null +++ b/Sensormat/Shelly/Shelly3PM/SensorEntity.cs @@ -0,0 +1,47 @@ +// Decompiled with JetBrains decompiler +// Type: Shelly.Shelly3PM.SensorEntity +// Assembly: Shelly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null +// MVID: 2DDB6D74-8C37-490A-B928-07FB0E37240C +// Assembly location: \\192.168.178.26\Freigabe\Shelly.dll + +using Newtonsoft.Json; +using Sensormat; +using Sensormat.Entities; +using System; +using System.Collections.Generic; +using System.Linq; + +#nullable disable +namespace Shelly.Shelly3PM +{ + public class SensorEntity : ISensor + { + private Config cfg; + + public override bool Execute() + { + try + { + this.cfg = this.getConfiguration(); + new ResultSet().XMLSerializeData(); + string str = this.HttpRequest(this.Sensor.URL); + if (str != null) + { + ResultSet resultSet = JsonConvert.DeserializeObject(str); + if (this.cfg.Sum) + this.LastValue = ((IEnumerable)resultSet.emeters).Sum((Func)(f => f.total)).ToString(); + else + this.LastValue = resultSet.emeters?[this.cfg.index]?.total.ToString(); + this.LastValueDate = new DateTime?(DateTime.Now); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine((object)ex); + this.ErrorMsg = ex.Message; + } + return false; + } + } +}