diff --git a/.gitignore b/.gitignore index fe482b8..c6afe8a 100644 --- a/.gitignore +++ b/.gitignore @@ -340,3 +340,5 @@ ASALocalRun/ healthchecksdb Zigbee2MqttAssistant/appsettings.*.json .vscode/ +/Zigbee2MqttAssistant/app +/Zigbee2MqttAssistant/apppublish diff --git a/Dockerfile b/Dockerfile index e12e6b5..a2946b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,13 @@ # msbuild /r /p:Configuration=Release /p:OutputPath=app /t:Publish # You should run this file with the following parameters: -# docker build . --build-args DOTNETTAG= --build-arg OSTAG= -t +# docker build . --build-arg DOTNETTAG= --build-arg OSTAG= -t # where: # is the tag of the dotnet aspnet runtime image # is the tag of the runtime for hass.io -ARG DOTNETTAG -ARG OSTAG +ARG DOTNETTAG=3.0-alpine-arm64v8 +ARG OSTAG= FROM mcr.microsoft.com/dotnet/core/aspnet:$DOTNETTAG EXPOSE 80 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 09e2c1d..842d23e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ This project is a _Web GUI_ for the very good [Zigbee2Mqtt](https://www.zigbee2m ``` https://github.com/yllibed/hassio ``` -2. Install `Zigbee2Mq2ttAssistant` +2. Install `Zigbee2MqttAssistant` 3. Configure your credentials for your MQTT server 4. Enjoy! @@ -66,6 +66,7 @@ on allowed settings. Here's the important settings: | `MqttUsername` | `""` | Username for MQTT server | | `MqttPassword` | `""` | Password for MQTT server | | `LowBatteryThreshold` | `30` | Threshold for triggering low-battery warning (%) | +| `TelemetryOptOut` | `false` | Opt out of [integrated telemetry](TELEMETRY.md) | # Roadmap * [X] Build a CI + publish to docker hub @@ -75,6 +76,7 @@ on allowed settings. Here's the important settings: * [X] Automatic update of repo on new version * [X] Support _Zigbee Bindings_ * [X] Support _Docker Manifest_ (support for ARM + Windows) +* [X] Add telemetry into project * [ ] Support _Zigbee groups_ **WAITING FOR NEXT VERSION OF ZIGBEE2MQTT FOR THIS ONE** * [ ] Better display of "routes to coordinator" * [ ] Improve UI diff --git a/TELEMETRY.md b/TELEMETRY.md new file mode 100644 index 0000000..43dba0b --- /dev/null +++ b/TELEMETRY.md @@ -0,0 +1,32 @@ +# Telemetry in Zigbee2MqttAssistant + +The [Zigbee2MqttAssistant](https://github.com/yllibed/Zigbee2MqttAssistant) includes a +[telemetry feature]() that collects usage information. + +The collected data is anonymous. + +The telemetry behavior is based on the [.NET Core telemetry feature](https://docs.microsoft.com/en-us/dotnet/core/tools/telemetry). + +## How to opt out + +The Zigbee2MqttAssistant telemetry is enabled by default. To opt out of the telemetry feature, set the +`TelemetryOptOut` feature to "1" or "true". + +Using docker, you may specify the environment variable `Z2MA_SETTINGS__TELEMETRYOPTOUT=true`. + +Using HASS.IO or any json configuration file, you may add this to the settings: `"TelemetryOptOut": true`. + +## Collected data + +The telemetry feature doesn't collect personal data, such as usernames or passwords. It doesn't scan your +machine nor your network to extract more information. All the code responsible for the telemetry is +located in this file and you can review it if you want: +[Telemetry.cs](https://github.com/yllibed/Zigbee2MqttAssistant/tree/master/Zigbee2MqttAssistant/Telemetry.cs). + +The telemetry feature collects the following data: +* Current date & time +* Compiled version of Zigbee2MqttAssistant +* OS & processor type (Windows/Linux) (amd64/arm) +* Installation type (Docker or HASS.IO) +* Versions: HASS.IO, Zigbee2Mqtt +* Crash reports with anonymized details diff --git a/Zigbee2MqttAssistant.sln b/Zigbee2MqttAssistant.sln index 966a665..cb0c88b 100644 --- a/Zigbee2MqttAssistant.sln +++ b/Zigbee2MqttAssistant.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution azure-pipelines.yaml = azure-pipelines.yaml Dockerfile = Dockerfile README.md = README.md + TELEMETRY.md = TELEMETRY.md EndProjectSection EndProject Global diff --git a/Zigbee2MqttAssistant/Dockerfile b/Zigbee2MqttAssistant/Dockerfile new file mode 100644 index 0000000..469c381 --- /dev/null +++ b/Zigbee2MqttAssistant/Dockerfile @@ -0,0 +1,23 @@ +# This dockerfile is only for development in VisualStudio! +# It's not used for build in CI. + +FROM mcr.microsoft.com/dotnet/core/aspnet:3.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS build +WORKDIR /src +COPY ["Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj", "Zigbee2MqttAssistant/"] +RUN dotnet restore "Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj" +COPY . . +WORKDIR "/src/Zigbee2MqttAssistant" +RUN dotnet build "Zigbee2MqttAssistant.csproj" -c Release -o /app + +FROM build AS publish +RUN dotnet publish "Zigbee2MqttAssistant.csproj" -c Release -o /app + +FROM base AS final +WORKDIR /app +COPY --from=publish /app . +ENTRYPOINT ["dotnet", "Zigbee2MqttAssistant.dll"] diff --git a/Zigbee2MqttAssistant/Models/Mqtt/Bridge.cs b/Zigbee2MqttAssistant/Models/Mqtt/Bridge.cs index 410f675..7d61c92 100644 --- a/Zigbee2MqttAssistant/Models/Mqtt/Bridge.cs +++ b/Zigbee2MqttAssistant/Models/Mqtt/Bridge.cs @@ -9,6 +9,11 @@ namespace Zigbee2MqttAssistant.Models.Mqtt [GeneratedImmutable] public partial class Bridge { + /// + /// If the MQTT is connected + /// + public bool MqttConnected { get; } + /// /// If the bridge is connected to MQTT /// @@ -36,6 +41,15 @@ public partial class Bridge /// public MqttLogLevel LogLevel { get; } = MqttLogLevel.Info; + /// + /// Name & Version of the MQTT broker, null means "unknown" + /// + /// + /// This is the content of the $SYS/broker/version topic + /// if the MQTT brokers supports it (won't work with EMQX, for example) + /// + public string MqttBroker { get; } + /// /// If join is permitted on the bridge /// diff --git a/Zigbee2MqttAssistant/Models/Settings.cs b/Zigbee2MqttAssistant/Models/Settings.cs index f60473b..e74354d 100644 --- a/Zigbee2MqttAssistant/Models/Settings.cs +++ b/Zigbee2MqttAssistant/Models/Settings.cs @@ -54,5 +54,15 @@ public partial class Settings /// Set to zero to disable this feature. /// public decimal LowBatteryThreshold { get; } = 30; + + /// + /// If you want to opt-out of Zigbee2MqttAssistant telemetry. + /// Set to true to disable telemetry. + /// + /// + /// No personal information is collected. More details there: + /// https://github.com/yllibed/Zigbee2MqttAssistant/blob/master/TELEMETRY.md + /// + public bool TelemetryOptOut { get; } = false; } } diff --git a/Zigbee2MqttAssistant/Program.cs b/Zigbee2MqttAssistant/Program.cs index 8781bcc..9ece552 100644 --- a/Zigbee2MqttAssistant/Program.cs +++ b/Zigbee2MqttAssistant/Program.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Zigbee2MqttAssistant @@ -14,7 +15,9 @@ public class Program { public static void Main(string[] args) { - CreateWebHostBuilder(args).Build().Run(); + CreateWebHostBuilder(args) + .Build() + .Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => diff --git a/Zigbee2MqttAssistant/Properties/launchSettings.json b/Zigbee2MqttAssistant/Properties/launchSettings.json index 9c4b562..2cc7553 100644 --- a/Zigbee2MqttAssistant/Properties/launchSettings.json +++ b/Zigbee2MqttAssistant/Properties/launchSettings.json @@ -22,6 +22,14 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:5001;http://localhost:5000" + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "httpPort": 1470, + "useSSL": true, + "sslPort": 44316 } } } \ No newline at end of file diff --git a/Zigbee2MqttAssistant/Services/AppTelemetry.cs b/Zigbee2MqttAssistant/Services/AppTelemetry.cs new file mode 100644 index 0000000..0d3f3e1 --- /dev/null +++ b/Zigbee2MqttAssistant/Services/AppTelemetry.cs @@ -0,0 +1,59 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.ApplicationInsights; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.Extensibility; +using Microsoft.Extensions.Hosting; +using Zigbee2MqttAssistant.Models; + +namespace Zigbee2MqttAssistant.Services +{ + public class AppTelemetry : IAppTelemetry, ITelemetryInitializer, IDisposable + { + private readonly TelemetryClient _client; + + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + + public AppTelemetry(ISettingsService settings, ISystemInformation systemInformation) + { + TelemetryConfiguration.Active.TelemetryInitializers.Add(this); + + _client = new TelemetryClient(SettingsToTelemetryConfig(settings.CurrentSettings)); + } + + private static TelemetryConfiguration SettingsToTelemetryConfig(Settings settings) + { + return null; + //return new TelemetryConfiguration(settings.TelemetryInstrumentationKey); + } + + public async void ReportStart() + { + while (!_cts.IsCancellationRequested) + { + _client.TrackEvent("test"); + + + await Task.Delay(TimeSpan.FromMinutes(15), _cts.Token); + } + } + + public void ReportStop() + { + } + + public void ReportError(string message, Exception exception) + { + } + + void ITelemetryInitializer.Initialize(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry) + { + } + + public void Dispose() + { + _cts.Dispose(); + } + } +} diff --git a/Zigbee2MqttAssistant/Services/BridgeStateService.cs b/Zigbee2MqttAssistant/Services/BridgeStateService.cs index a942d29..c9505c0 100644 --- a/Zigbee2MqttAssistant/Services/BridgeStateService.cs +++ b/Zigbee2MqttAssistant/Services/BridgeStateService.cs @@ -174,6 +174,16 @@ Bridge Update(Bridge state) ImmutableInterlocked.Update(ref _currentState, Update); } + public void SetMqttBrokerVersion(string version) + { + ImmutableInterlocked.Update(ref _currentState, state => state.WithMqttBroker(version)); + } + + public void SetMqttConnected(bool isConnected) + { + ImmutableInterlocked.Update(ref _currentState, state => state.WithMqttConnected(isConnected)); + } + public HomeAssistantEntity SetDeviceEntity( string zigbeeId, string entityClass, diff --git a/Zigbee2MqttAssistant/Services/IAppTelemetry.cs b/Zigbee2MqttAssistant/Services/IAppTelemetry.cs new file mode 100644 index 0000000..10d78ce --- /dev/null +++ b/Zigbee2MqttAssistant/Services/IAppTelemetry.cs @@ -0,0 +1,12 @@ +using System; + +namespace Zigbee2MqttAssistant.Services +{ + public interface IAppTelemetry + { + void ReportStart(); + void ReportStop(); + + void ReportError(string message, Exception exception); + } +} diff --git a/Zigbee2MqttAssistant/Services/IBridgeStateService.cs b/Zigbee2MqttAssistant/Services/IBridgeStateService.cs index b92c8a3..29a64d8 100644 --- a/Zigbee2MqttAssistant/Services/IBridgeStateService.cs +++ b/Zigbee2MqttAssistant/Services/IBridgeStateService.cs @@ -14,6 +14,8 @@ public interface IBridgeStateService void SetDeviceAvailability(string friendlyName, bool isOnline); void SetBridgeState(bool isOnline); void SetBridgeConfig(string configJson, out bool isJoinAllowed, out MqttLogLevel logLevel); + void SetMqttBrokerVersion(string version); + void SetMqttConnected(bool isConnected); HomeAssistantEntity SetDeviceEntity(string zigbeeId, string entityClass, string component, string configPayload, Func friendlyNameFromTopicDelegate); ZigbeeDevice FindDeviceById(string deviceId, out Bridge state); void UpdateDevices(string payload); diff --git a/Zigbee2MqttAssistant/Services/ISystemInformation.cs b/Zigbee2MqttAssistant/Services/ISystemInformation.cs new file mode 100644 index 0000000..ec7ada4 --- /dev/null +++ b/Zigbee2MqttAssistant/Services/ISystemInformation.cs @@ -0,0 +1,27 @@ +using System; + +namespace Zigbee2MqttAssistant.Services +{ + public interface ISystemInformation + { + string OsType { get; } + string OsVersion { get; } + bool OsIs64Bits { get; } + string ProcessorType { get; } + int ProcessorCount { get; } + bool IsDocker { get; } + bool IsHassIo { get; } + string Version { get; } + string FullVersion { get; } + string BuildType { get; } + string EnvironmentType { get; } + string Zigbee2MttVersion { get; } + string CoordinatorVersion { get; } + string CoordinatorZigbeeId { get; } + string MqttBroker { get; } + bool MqttBrokerConnected { get; } + int NumberOfDevices { get; } + bool Telemetry { get; } + DateTimeOffset StartTime { get; } + } +} diff --git a/Zigbee2MqttAssistant/Services/MqttConnectionService.cs b/Zigbee2MqttAssistant/Services/MqttConnectionService.cs index c635700..5d715dd 100644 --- a/Zigbee2MqttAssistant/Services/MqttConnectionService.cs +++ b/Zigbee2MqttAssistant/Services/MqttConnectionService.cs @@ -161,7 +161,14 @@ private void Disconnect() private async Task Subscribe() { var settings = _settings.CurrentSettings; + + // Subscribe to broker information - it may not exists + await _client.SubscribeAsync($"$SYS/broker/version"); + + // Subscribe to Zigbee2Mqtt topics await _client.SubscribeAsync($"{settings.BaseTopic}/#"); + + // Subscribe to entries published by Zigbee2Mqtt for HomeAssistant discovery await _client.SubscribeAsync($"{settings.HomeAssistantDiscoveryBaseTopic}/#"); } @@ -182,7 +189,7 @@ public void Dispose() private Regex _setTopicRegex; - public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) + Task IMqttApplicationMessageReceivedHandler.HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs eventArgs) { var msg = eventArgs.ApplicationMessage; @@ -218,6 +225,11 @@ public Task HandleApplicationMessageReceivedAsync(MqttApplicationMessageReceived return Task.CompletedTask; } + if (DispatchBrokerVersionmessage(msg)) + { + return Task.CompletedTask; + } + _logger.LogWarning($"Unable to qualify a message received on topic '{msg.Topic}'."); return Task.CompletedTask; @@ -430,11 +442,25 @@ private bool DispatchLogMessage(MqttApplicationMessage msg) return false; } - public Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs) + private bool DispatchBrokerVersionmessage(MqttApplicationMessage msg) + { + if (msg.Topic.Equals("$SYS/broker/version", StringComparison.InvariantCultureIgnoreCase) && msg.Payload != null) + { + var version = _utf8.GetString(msg.Payload); + _stateService.SetMqttBrokerVersion(version); + + return true; + } + + return false; + } + + Task IMqttClientConnectedHandler.HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs) { _logger.LogInformation($"Successfully connected to MQTT server {_settings.CurrentSettings.MqttServer}."); _stateService.Clear(); + _stateService.SetMqttConnected(isConnected: true); disconnectWarned = false; return Task.CompletedTask; @@ -443,8 +469,10 @@ public Task HandleConnectedAsync(MqttClientConnectedEventArgs eventArgs) private bool disconnectWarned = false; - public Task HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs) + Task IMqttClientDisconnectedHandler.HandleDisconnectedAsync(MqttClientDisconnectedEventArgs eventArgs) { + _stateService.SetMqttConnected(isConnected: false); + StopPolling(); if (disconnectWarned) diff --git a/Zigbee2MqttAssistant/Services/SettingsService.cs b/Zigbee2MqttAssistant/Services/SettingsService.cs index 6e4a397..a6da55b 100644 --- a/Zigbee2MqttAssistant/Services/SettingsService.cs +++ b/Zigbee2MqttAssistant/Services/SettingsService.cs @@ -7,6 +7,21 @@ namespace Zigbee2MqttAssistant.Services public class SettingsService : ISettingsService { public SettingsService(IConfiguration configuration, ILogger logger) + { + var settings = GetFromConfiguration(configuration); + if(settings == null) + { + logger.LogWarning( + "Section 'settings' in configuration does not exists. Will use default settings instead."); + CurrentSettings = Settings.Default; + } + else + { + CurrentSettings = settings; + } + } + + internal static Settings GetFromConfiguration(IConfiguration configuration) { var section = configuration.GetSection("settings"); @@ -14,14 +29,10 @@ public SettingsService(IConfiguration configuration, ILogger lo { var settingsBuilder = new Settings.Builder(); section.Bind(settingsBuilder); - CurrentSettings = settingsBuilder; - } - else - { - logger.LogWarning( - "Section 'settings' in configuration does not exists. Will use default settings instead."); - CurrentSettings = Settings.Default; + return settingsBuilder; } + + return null; } public Settings CurrentSettings { get; } diff --git a/Zigbee2MqttAssistant/Services/SystemInformation.cs b/Zigbee2MqttAssistant/Services/SystemInformation.cs new file mode 100644 index 0000000..72138c5 --- /dev/null +++ b/Zigbee2MqttAssistant/Services/SystemInformation.cs @@ -0,0 +1,68 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Hosting; + +namespace Zigbee2MqttAssistant.Services +{ + public class SystemInformation : ISystemInformation + { + private readonly IBridgeStateService _stateService; + + public SystemInformation(IHostingEnvironment env, IBridgeStateService stateService, ISettingsService settingsService) + { + _stateService = stateService; + + OsType = RuntimeInformation.OSDescription + "/" + RuntimeInformation.OSArchitecture; + OsVersion = Environment.OSVersion.Version.ToString(); + OsIs64Bits = Environment.Is64BitOperatingSystem; + ProcessorCount = Environment.ProcessorCount; + ProcessorType = RuntimeInformation.ProcessArchitecture.ToString(); + + if (bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), out var isDocker)) + { + IsDocker = isDocker; + } + + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("HASSIO_TOKEN"))) + { + IsHassIo = true; + } + + var assembly = GetType().Assembly; + Version = + assembly.GetCustomAttribute()?.Version + ?? assembly.GetCustomAttribute()?.Version + ?? ""; + FullVersion = + assembly.GetCustomAttribute()?.InformationalVersion + ?? ""; + BuildType = + assembly.GetCustomAttribute()?.Configuration + ?? ""; + EnvironmentType = env.EnvironmentName; + + Telemetry = !settingsService.CurrentSettings.TelemetryOptOut; + } + + public string OsType { get; } + public string OsVersion { get; } + public bool OsIs64Bits { get; } + public int ProcessorCount { get; } + public string ProcessorType { get; } + public bool IsDocker { get; } + public bool IsHassIo { get; } + public string Version { get; } + public string FullVersion { get; } + public string BuildType { get; } + public string EnvironmentType { get; } + public string Zigbee2MttVersion => _stateService.CurrentState.Zigbee2MqttVersion; + public string CoordinatorVersion => _stateService.CurrentState.CoordinatorVersion; + public string CoordinatorZigbeeId => _stateService.CurrentState.CoordinatorZigbeeId; + public string MqttBroker => _stateService.CurrentState.MqttBroker; + public bool MqttBrokerConnected => _stateService.CurrentState.MqttConnected; + public int NumberOfDevices => _stateService.CurrentState.Devices.Length; + public bool Telemetry { get; } + public DateTimeOffset StartTime { get; } = DateTimeOffset.Now; + } +} diff --git a/Zigbee2MqttAssistant/Startup.cs b/Zigbee2MqttAssistant/Startup.cs index eafa23c..7c93ea1 100644 --- a/Zigbee2MqttAssistant/Startup.cs +++ b/Zigbee2MqttAssistant/Startup.cs @@ -1,4 +1,6 @@ using System.IO; +using Microsoft.ApplicationInsights.AspNetCore.Extensions; +using Microsoft.ApplicationInsights.Extensibility; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; @@ -9,6 +11,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Zigbee2MqttAssistant.Services; using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; @@ -16,9 +19,12 @@ namespace Zigbee2MqttAssistant { public class Startup { - public Startup(IConfiguration configuration) + private readonly ILogger _logger; + + public Startup(IConfiguration configuration, ILogger logger) { Configuration = configuration; + _logger = logger; } public IConfiguration Configuration { get; } @@ -33,16 +39,33 @@ public void ConfigureServices(IServiceCollection services) options.MinimumSameSitePolicy = SameSiteMode.None; }); + var appSettings = SettingsService.GetFromConfiguration(Configuration); + services.AddApplicationInsightsTelemetry(o => + { + o.EnableHeartbeat = true; + + if (appSettings?.TelemetryOptOut != true) + { + _logger.LogWarning("Telemetry is activated using ApplicationInsight.\n --> See https://github.com/yllibed/Zigbee2MqttAssistant/blob/master/TELEMETRY.md for more information."); + } + else + { + o.InstrumentationKey = ""; + } + }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(sp => sp.GetService()); + //services.AddSingleton(); + services.Decorate((previous, _) => new RelativeUrlHelperFactory(previous)); if(Directory.Exists("/data")) diff --git a/Zigbee2MqttAssistant/Views/Home/Index.cshtml b/Zigbee2MqttAssistant/Views/Home/Index.cshtml index c6fa746..f1559a0 100644 --- a/Zigbee2MqttAssistant/Views/Home/Index.cshtml +++ b/Zigbee2MqttAssistant/Views/Home/Index.cshtml @@ -10,11 +10,6 @@ var bridge = Model.bridge; var settings = Model.settings; } -@if (bridge.PermitJoin) -{ - DEVICES ARE CURRENTLY ALLOWED TO JOIN NETWORK. -} - diff --git a/Zigbee2MqttAssistant/Views/Home/Status.cshtml b/Zigbee2MqttAssistant/Views/Home/Status.cshtml index 0b1a73b..75fda63 100644 --- a/Zigbee2MqttAssistant/Views/Home/Status.cshtml +++ b/Zigbee2MqttAssistant/Views/Home/Status.cshtml @@ -1,6 +1,8 @@ @using System.Reflection @using Zigbee2MqttAssistant.Models.Mqtt +@using Zigbee2MqttAssistant.Services @model Zigbee2MqttAssistant.Models.Mqtt.Bridge +@inject ISystemInformation systemInformation @{ ViewData["Title"] = "Status"; } @@ -74,22 +76,20 @@ -@{ - var assembly = typeof(Program).Assembly; - var assemblyVersion = assembly.GetCustomAttribute()?.Version - ?? assembly.GetCustomAttribute()?.Version - ?? ""; - var fullVersion = assembly.GetCustomAttribute()?.InformationalVersion ?? ""; - var configuration = assembly.GetCustomAttribute()?.Configuration ?? ""; -}
-
Versions
+
System Information
- Version of Zigbee2MqttAssistant (this software): @assemblyVersion (@fullVersion) @configuration
- Version of Zigbee2Mqtt: @Model.Zigbee2MqttVersion
- Coordinator version: @Model.CoordinatorVersion
- Coordinator Zigbee Id: @Model.CoordinatorZigbeeId + Version (this software): @systemInformation.Version (@systemInformation.FullVersion) @systemInformation.BuildType @systemInformation.EnvironmentType
+ Operating System / Version: @systemInformation.OsType / @systemInformation.OsVersion / @(systemInformation.OsIs64Bits ? "64 bits" : "32 bits")
+ Processor: @systemInformation.ProcessorType, @systemInformation.ProcessorCount cores/threads
+ Docker / HASS.IO: @(systemInformation.IsDocker ? "Docker" : "No - standalone") @(systemInformation.IsHassIo ? "+ HASS.IO" : "")
+ Version of Zigbee2Mqtt: @systemInformation.Zigbee2MttVersion
+ Coordinator version: @systemInformation.CoordinatorVersion
+ Coordinator Zigbee Id: @systemInformation.CoordinatorZigbeeId
+ MQTT Broker: @systemInformation.MqttBroker @(systemInformation.MqttBrokerConnected ? "connected" : "** DISCONNECTED **")
+ Telemetry: @(systemInformation.Telemetry ? "Enabled" : "Disabled") + (More on telemetry here.)
diff --git a/Zigbee2MqttAssistant/Views/Shared/_Layout.cshtml b/Zigbee2MqttAssistant/Views/Shared/_Layout.cshtml index 4090ead..5caa960 100644 --- a/Zigbee2MqttAssistant/Views/Shared/_Layout.cshtml +++ b/Zigbee2MqttAssistant/Views/Shared/_Layout.cshtml @@ -1,4 +1,6 @@ @using Microsoft.AspNetCore.Http +@using Zigbee2MqttAssistant.Services +@inject IBridgeStateService bridgeStateService @@ -15,9 +17,10 @@ asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css" asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" crossorigin="anonymous" - integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/> + integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE=" /> + @Html.Raw(JavaScriptSnippet.FullScript)
@@ -43,9 +46,22 @@
-
- @RenderBody() -
+
+ @if (bridgeStateService.CurrentState.PermitJoin) + { + DEVICES ARE CURRENTLY ALLOWED TO JOIN NETWORK. + } + @if (!bridgeStateService.CurrentState.MqttConnected) + { + NOT CONNECTED TO MQTT BROKER. + } + @if (!bridgeStateService.CurrentState.Online) + { + ZIGBEE2MQTT SEEMS DOWN (NOT CONNECTED TO MQTT BROKER). + } + + @RenderBody() +
diff --git a/Zigbee2MqttAssistant/Views/_ViewImports.cshtml b/Zigbee2MqttAssistant/Views/_ViewImports.cshtml index 156ec6b..ed1abcf 100644 --- a/Zigbee2MqttAssistant/Views/_ViewImports.cshtml +++ b/Zigbee2MqttAssistant/Views/_ViewImports.cshtml @@ -1,3 +1,4 @@ @using Zigbee2MqttAssistant @using Zigbee2MqttAssistant.Models +@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj b/Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj index 3548a6e..e6106bc 100644 --- a/Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj +++ b/Zigbee2MqttAssistant/Zigbee2MqttAssistant.csproj @@ -3,13 +3,18 @@ netcoreapp2.2 InProcess + Linux + 61b433d4-aa97-4972-b887-4518e3a4c459 + + + diff --git a/Zigbee2MqttAssistant/appsettings.json b/Zigbee2MqttAssistant/appsettings.json index 07ba669..787480d 100644 --- a/Zigbee2MqttAssistant/appsettings.json +++ b/Zigbee2MqttAssistant/appsettings.json @@ -9,5 +9,8 @@ //"MqttServer": "mqtt", //"MqttUsername": "", //"MqttPassword": "" - } + }, + "ApplicationInsights": { + "InstrumentationKey": "a07cd338-3c1d-417a-890b-67e56efa2ae9" + } }