├── .devcontainer
├── .bashrc
├── Dockerfile
├── devcontainer.json
└── install_prettyprompt.sh
├── .editorconfig
├── .gitattributes
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ ├── config.yml
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
├── config.yml
├── dependabot.yml
├── matchers
│ └── dotnet.json
├── release-drafter.yml
├── stale.yml
└── workflows
│ ├── check_pr_label.yml
│ ├── ci_analyze.yml
│ ├── ci_build.yml
│ ├── common
│ └── set_netdaemon_version.yml
│ ├── push_docker_addon_manual.yml
│ ├── push_docker_manual.yml
│ ├── push_docker_prerelease.yml
│ ├── push_nuget_prerelease.yml
│ ├── release_drafter.yml
│ ├── tags_docker.yml
│ ├── tags_nuget.yml
│ └── test_docker.yml
├── .gitignore
├── .linting
├── roslynator.config
└── roslynator.ruleset
├── .vscode
├── daemon.code-snippets
├── launch.json
├── settings.json
└── tasks.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEV.md
├── Directory.Build.props
├── Docker
├── rootfs
│ └── etc
│ │ ├── s6-overlay
│ │ └── s6-rc.d
│ │ │ └── usr
│ │ │ ├── .gitattributes
│ │ │ ├── netdaemon
│ │ │ └── netdaemon_addon
│ │ └── services.d
│ │ ├── netdaemon
│ │ ├── .gitattributes
│ │ ├── run
│ │ └── type
│ │ └── netdaemon_addon
│ │ ├── .gitattributes
│ │ ├── run
│ │ └── type
├── run-nd.sh
└── s6.sh
├── Dockerfile
├── Dockerfile.AddOn
├── LICENSE
├── NetDaemon.sln
├── README.md
├── codecov.yaml
├── global.json
├── img
└── icon.png
├── src
├── AppModel
│ ├── NetDaemon.AppModel.SourceDeployedApps
│ │ ├── Compiler
│ │ │ ├── CollectableAssemblyLoadContext.cs
│ │ │ ├── CompileSettings.cs
│ │ │ ├── Compiler.cs
│ │ │ ├── ICompiler.cs
│ │ │ ├── ISyntaxTreeResolver.cs
│ │ │ └── SyntaxTreeResolver.cs
│ │ ├── DynamicallyCompiledAppAssemblyProvider.cs
│ │ ├── GlobalUsings.cs
│ │ ├── NetDaemon.AppModel.SourceDeployedApps.csproj
│ │ └── ServiceCollectionExtension.cs
│ ├── NetDaemon.AppModel.Tests
│ │ ├── .editorconfig
│ │ ├── AppAssemblyProviders
│ │ │ ├── CombinedAppAssemblyProviderTests.cs
│ │ │ ├── DynamicAppAssemblyProviderTests.cs
│ │ │ └── LocalAppAssemblyProviderTests.cs
│ │ ├── AppFactories
│ │ │ ├── DynamicAppFactoryTests.cs
│ │ │ └── LocalAppFactoryTests.cs
│ │ ├── AppFactoryProviders
│ │ │ ├── CombinedAppFactoryProviderTests.cs
│ │ │ ├── DynamicAppFactoryProviderTests.cs
│ │ │ └── LocalAppFactoryProviderTests.cs
│ │ ├── AppModelTests.cs
│ │ ├── Compiler
│ │ │ ├── CompilerIntegrationTests.cs
│ │ │ └── Fixtures
│ │ │ │ └── SimpleApp.cs
│ │ ├── Config
│ │ │ ├── ConfigTests.cs
│ │ │ ├── ConfigurationBinderTests.cs
│ │ │ ├── FailedConfig
│ │ │ │ └── Fail.yaml
│ │ │ └── Fixtures
│ │ │ │ ├── App.json
│ │ │ │ ├── App.yaml
│ │ │ │ ├── AppInjectSettings.yaml
│ │ │ │ ├── AppInjectSettingsy.json
│ │ │ │ └── CollectionTestsFixture.yaml
│ │ ├── Context
│ │ │ ├── ApplicationContextTests.cs
│ │ │ └── ApplicationScopeTests.cs
│ │ ├── Fixtures
│ │ │ ├── Dynamic
│ │ │ │ ├── Application.cs
│ │ │ │ └── settings.yaml
│ │ │ ├── DynamicError
│ │ │ │ └── IDoNotCompileWell.cs
│ │ │ ├── DynamicWithFocus
│ │ │ │ └── Application.cs
│ │ │ ├── DynamicWithServiceCollection
│ │ │ │ └── Application.cs
│ │ │ ├── Local
│ │ │ │ ├── AppThatTakesASlowTimeToDispose.cs
│ │ │ │ ├── LocalApp.cs
│ │ │ │ ├── LocalAppWithDisposable.cs
│ │ │ │ ├── LocalAppWithId.cs
│ │ │ │ ├── LocalAppWithInitializeAsync.cs
│ │ │ │ └── settings.yaml
│ │ │ └── LocalError
│ │ │ │ └── LocalApp.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Helpers
│ │ │ ├── FakeOptions.cs
│ │ │ ├── FakeOptionsExtensions.cs
│ │ │ └── TestHelpers.cs
│ │ └── NetDaemon.AppModel.Tests.csproj
│ └── NetDaemon.AppModel
│ │ ├── Common
│ │ ├── ApplicationState.cs
│ │ ├── Attributes
│ │ │ ├── FocusAttribute.cs
│ │ │ ├── JetBrainsCodeAnnotations.cs
│ │ │ ├── NetDaemonAppAttribute.cs
│ │ │ └── ServiceCollectionExtensionAttribute.cs
│ │ ├── Extensions
│ │ │ ├── IConfigurationBuilderExtensions.cs
│ │ │ └── ServiceCollectionExtension.cs
│ │ ├── IAppModel.cs
│ │ ├── IAppModelContext.cs
│ │ ├── IAppStateManager.cs
│ │ ├── IApplication.cs
│ │ ├── IAsyncInitializable.cs
│ │ ├── IConfig.cs
│ │ └── Settings
│ │ │ └── AppLocationSettings.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Internal
│ │ ├── AppAssemblyProviders
│ │ │ ├── AppAssemblyProvider.cs
│ │ │ └── IAppAssemblyProvider.cs
│ │ ├── AppFactories
│ │ │ ├── FuncAppFactory.cs
│ │ │ └── IAppFactory.cs
│ │ ├── AppFactoryProviders
│ │ │ ├── AssemblyAppFactoryProvider.cs
│ │ │ ├── FuncAppFactoryProvider.cs
│ │ │ ├── IAppFactoryProvider.cs
│ │ │ └── SingleAppFactoryProvider.cs
│ │ ├── AppModel.cs
│ │ ├── AppModelContext.cs
│ │ ├── Application.cs
│ │ ├── Config
│ │ │ ├── AppConfig.cs
│ │ │ ├── BindingPoint.cs
│ │ │ ├── ConfigurationBinder.cs
│ │ │ ├── ConfigurationBinding.cs
│ │ │ ├── IConfigurationBinding.cs
│ │ │ ├── ParameterDefaultValue.cs
│ │ │ └── Yaml
│ │ │ │ ├── YamlConfigurationFileParser.cs
│ │ │ │ ├── YamlConfigurationProvider.cs
│ │ │ │ └── YamlConfigurationSource.cs
│ │ ├── Context
│ │ │ ├── ApplicationContext.cs
│ │ │ └── ApplicationScope.cs
│ │ └── FocusFilter.cs
│ │ ├── NetDaemon.AppModel.csproj
│ │ └── README.md
├── Client
│ ├── NetDaemon.HassClient.Debug
│ │ ├── GlobalUsings.cs
│ │ ├── NetDaemon.HassClient.Debug.csproj
│ │ ├── Program.cs
│ │ ├── Service.cs
│ │ ├── _appsettings.Development.json
│ │ └── appsettings.json
│ ├── NetDaemon.HassClient.Tests
│ │ ├── .editorconfig
│ │ ├── ExtensionsTest
│ │ │ ├── HomeAssistantApiManagerExtensionTests.cs
│ │ │ ├── JsonElementExtensionTest.cs
│ │ │ ├── MqttEntityManagerTests
│ │ │ │ ├── AssuredMqttConnectionTests.cs
│ │ │ │ ├── ByteArrayHelperTests.cs
│ │ │ │ ├── EntityCreationPayloadHelperTests.cs
│ │ │ │ ├── EntityIdParserTests.cs
│ │ │ │ ├── JsonNodeExtensionTests.cs
│ │ │ │ ├── MessageSenderTests.cs
│ │ │ │ ├── MqttClientOptionsFactoryTests.cs
│ │ │ │ ├── MqttEntityManagerTester.cs
│ │ │ │ └── TestHelpers
│ │ │ │ │ └── MockMqttMessageSenderSetup.cs
│ │ │ └── ServiceCollectionExtensionTests.cs
│ │ ├── GlobalUsings.cs
│ │ ├── HelperTest
│ │ │ ├── HttpHandlerHelperTests.cs
│ │ │ ├── ProgressiveTimoutTests.cs
│ │ │ ├── ResultMessageHandlerTests.cs
│ │ │ └── VersionHelperTests.cs
│ │ ├── Helpers
│ │ │ ├── LoggerMockExtensions.cs
│ │ │ └── TestSettings.cs
│ │ ├── HomeAssistantClientTest
│ │ │ ├── HomeAssistantClientTests.cs
│ │ │ ├── HomeAssistantConnectionMock.cs
│ │ │ ├── HomeAssistantConnectionTests.cs
│ │ │ └── TransportPipelineMock.cs
│ │ ├── HomeAssistantRunnerTest
│ │ │ ├── HomeAssistantClientMock.cs
│ │ │ └── HomeAssistantRunnerTests.cs
│ │ ├── Integration
│ │ │ ├── ApiIntegrationTests.cs
│ │ │ ├── HomeAssistantServerMock.cs
│ │ │ ├── HomeAssistantServiceFixture.cs
│ │ │ ├── IntegrationTestBase.cs
│ │ │ ├── TestContext.cs
│ │ │ ├── Testdata
│ │ │ │ ├── auth_notok.json
│ │ │ │ ├── auth_ok.json
│ │ │ │ ├── auth_required.json
│ │ │ │ ├── deviceregistry_update.json
│ │ │ │ ├── event.json
│ │ │ │ ├── pong.json
│ │ │ │ ├── result_calendar_list_event.json
│ │ │ │ ├── result_config.json
│ │ │ │ ├── result_get_areas.json
│ │ │ │ ├── result_get_devices.json
│ │ │ │ ├── result_get_entities.json
│ │ │ │ ├── result_get_floors.json
│ │ │ │ ├── result_get_labels.json
│ │ │ │ ├── result_get_services.json
│ │ │ │ ├── result_msg.json
│ │ │ │ ├── result_msg_error.json
│ │ │ │ ├── result_states.json
│ │ │ │ └── service_event.json
│ │ │ └── WebsocketIntegrationTests.cs
│ │ ├── Json
│ │ │ ├── EnsureStringConverterTests.cs
│ │ │ └── HassDeviceConverterTests.cs
│ │ ├── Net
│ │ │ ├── WebSocketClientFactoryTests.cs
│ │ │ ├── WebSocketClientMock.cs
│ │ │ └── WebSocketTransportPipelineTests.cs
│ │ ├── NetDaemon.HassClient.Tests.csproj
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── README.md
│ │ └── appsettings.json
│ └── NetDaemon.HassClient
│ │ ├── Common
│ │ ├── DisconnectReason.cs
│ │ ├── Exceptions
│ │ │ ├── HomeAssistantApiCallException.cs
│ │ │ └── HomeAssistantConnectionException.cs
│ │ ├── Extensions
│ │ │ └── ServiceCollectionExtension.cs
│ │ ├── HomeAssistant
│ │ │ ├── Extensions
│ │ │ │ ├── HassEventExtensions.cs
│ │ │ │ ├── HomeAssistantApiManagerExtensions.cs
│ │ │ │ ├── HomeAssistantConnectionHelpersExtensions.cs
│ │ │ │ └── IHomeAssistantConnectionExtensions.cs
│ │ │ └── Model
│ │ │ │ ├── CommandMessage.cs
│ │ │ │ ├── HassArea.cs
│ │ │ │ ├── HassAuthResponse.cs
│ │ │ │ ├── HassConfig.cs
│ │ │ │ ├── HassContext.cs
│ │ │ │ ├── HassConversationOptions.cs
│ │ │ │ ├── HassDevice.cs
│ │ │ │ ├── HassEntity.cs
│ │ │ │ ├── HassEntityOptions.cs
│ │ │ │ ├── HassError.cs
│ │ │ │ ├── HassEvent.cs
│ │ │ │ ├── HassFloor.cs
│ │ │ │ ├── HassLabel.cs
│ │ │ │ ├── HassMessage.cs
│ │ │ │ ├── HassMessageBase.cs
│ │ │ │ ├── HassServiceEventData.cs
│ │ │ │ ├── HassServiceResult.cs
│ │ │ │ ├── HassState.cs
│ │ │ │ ├── HassStateChangedEventData.cs
│ │ │ │ ├── HassUnitSystem.cs
│ │ │ │ ├── HassVariable.cs
│ │ │ │ ├── InputBooleanHelper.cs
│ │ │ │ ├── InputNumberHelper.cs
│ │ │ │ └── Targets.cs
│ │ ├── HomeAssistantClientConnector.cs
│ │ ├── IHomeAssistantApiManager.cs
│ │ ├── IHomeAssistantClient.cs
│ │ ├── IHomeAssistantConnection.cs
│ │ ├── IHomeAssistantRunner.cs
│ │ └── Settings
│ │ │ └── Settings.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Internal
│ │ ├── Extensions
│ │ │ ├── CancellationExtensions.cs
│ │ │ └── JsonExtensions.cs
│ │ ├── Helpers
│ │ │ ├── AsyncLazy.cs
│ │ │ ├── HttpHelper.cs
│ │ │ ├── ProgressiveTimeout.cs
│ │ │ ├── ResultMessageHandler.cs
│ │ │ └── VersionHelper.cs
│ │ ├── HomeAssistant
│ │ │ ├── Commands
│ │ │ │ ├── CallExecuteScriptCommand.cs
│ │ │ │ ├── CallServiceCommand.cs
│ │ │ │ ├── CreateHelperCommandBase.cs
│ │ │ │ ├── CreateInputBooleanHelperCommand.cs
│ │ │ │ ├── CreateInputNumberCommand.cs
│ │ │ │ ├── SimpleCommand.cs
│ │ │ │ ├── SubscribeEventCommand.cs
│ │ │ │ ├── SubscribeTriggerCommand.cs
│ │ │ │ ├── SupportedFeaturesCommand.cs
│ │ │ │ └── UnsubscribeEventsCommand.cs
│ │ │ └── Messages
│ │ │ │ └── HassAuthMessage.cs
│ │ ├── HomeAssistantApiManager.cs
│ │ ├── HomeAssistantClient.cs
│ │ ├── HomeAssistantConnection.cs
│ │ ├── HomeAssistantConnectionFactory.cs
│ │ ├── HomeAssistantRunner.cs
│ │ ├── IHomeAssistantConnectionFactory.cs
│ │ ├── Json
│ │ │ ├── EnsureArrayOfArrayOfStringConverter.cs
│ │ │ └── EnsureStringConverter.cs
│ │ └── Net
│ │ │ ├── ITransportPipeline.cs
│ │ │ ├── IWebSocketClient.cs
│ │ │ ├── IWebSocketClientFactory.cs
│ │ │ ├── IWebSocketClientTransportPipelineFactory.cs
│ │ │ ├── WebSocketClientFactory.cs
│ │ │ ├── WebSocketClientImpl.cs
│ │ │ ├── WebSocketTransportPipeline.cs
│ │ │ └── WebSocketTransportPipelineFactory.cs
│ │ └── NetDaemon.Client.csproj
├── Extensions
│ ├── NetDaemon.Extensions.Logging
│ │ ├── Common
│ │ │ └── ServiceCollectionExtensions.cs
│ │ ├── Internal
│ │ │ ├── LoggingConfiguration.cs
│ │ │ ├── NetDaemonLoggingThemes.cs
│ │ │ └── SerilogConfiguratior.cs
│ │ └── NetDaemon.Extensions.Logging.csproj
│ ├── NetDaemon.Extensions.MqttEntityManager
│ │ ├── AssuredMqttConnection.cs
│ │ ├── DependencyInjectionSetup.cs
│ │ ├── Exceptions
│ │ │ ├── MqttConnectionException.cs
│ │ │ └── MqttPublishException.cs
│ │ ├── Helpers
│ │ │ ├── ByteArrayHelper.cs
│ │ │ ├── EntityCreationPayloadHelper.cs
│ │ │ ├── EntityIdParser.cs
│ │ │ ├── IMqttFactoryWrapper.cs
│ │ │ ├── JsonNodeExtensions.cs
│ │ │ └── MqttFactoryWrapper.cs
│ │ ├── IAssuredMqttConnection.cs
│ │ ├── IMessageSender.cs
│ │ ├── IMessageSubscriber.cs
│ │ ├── IMqttClientOptionsFactory.cs
│ │ ├── IMqttEntityManager.cs
│ │ ├── IMqttFactory.cs
│ │ ├── MessageSender.cs
│ │ ├── MessageSubscriber.cs
│ │ ├── Models
│ │ │ ├── EntityCreationOptions.cs
│ │ │ └── EntityCreationPayload.cs
│ │ ├── MqttClientOptionsFactory.cs
│ │ ├── MqttConfiguration.cs
│ │ ├── MqttEntityManager.cs
│ │ ├── MqttFactoryFactory.cs
│ │ └── NetDaemon.Extensions.MqttEntityManager.csproj
│ ├── NetDaemon.Extensions.Scheduling.Tests
│ │ ├── .editorconfig
│ │ ├── NetDaemon.Extensions.Scheduling.Tests.csproj
│ │ └── Scheduling
│ │ │ ├── CronTests.cs
│ │ │ ├── DisposableSchedulerTest.cs
│ │ │ ├── FakeLocalTimeZone.cs
│ │ │ └── SchedulingTests.cs
│ ├── NetDaemon.Extensions.Scheduling
│ │ ├── CronExtensions.cs
│ │ ├── DependencyInjectionSetup.cs
│ │ ├── DisposableScheduler.cs
│ │ ├── INetDaemonScheduler.cs
│ │ ├── NetDaemon.Extensions.Scheduling.csproj
│ │ ├── NetDaemonScheduler.cs
│ │ └── SchedulerExtensions.cs
│ └── NetDaemon.Extensions.Tts
│ │ ├── GlobalUsings.cs
│ │ ├── HostBuilderExtensions.cs
│ │ ├── ITextToSpeechService.cs
│ │ ├── Internal
│ │ ├── TextToSpeechService.cs
│ │ └── TtsMessage.cs
│ │ └── NetDaemon.Extensions.Tts.csproj
├── HassModel
│ ├── NetDaemon.HassModel.CodeGenerator
│ │ ├── .editorconfig
│ │ ├── CodeGeneration
│ │ │ ├── AttributeTypeGenerator.cs
│ │ │ ├── EntitiesGenerator.cs
│ │ │ ├── EntityFactoryGenerator.cs
│ │ │ ├── EnumerateAllGenerator.cs
│ │ │ ├── ExtensionMethodsGenerator.cs
│ │ │ ├── Generator.cs
│ │ │ ├── HelpersGenerator.cs
│ │ │ ├── ServiceArguments.cs
│ │ │ ├── ServicesGenerator.cs
│ │ │ └── SyntaxFactoryHelper.cs
│ │ ├── CodeGenerationSettings.cs
│ │ ├── Controller.cs
│ │ ├── DependencyValidator.cs
│ │ ├── Extensions
│ │ │ ├── CodeGeneratorExtensions.cs
│ │ │ ├── StringExtensions.cs
│ │ │ └── TypeExtensions.cs
│ │ ├── GlobalUsings.cs
│ │ ├── HaRepositry.cs
│ │ ├── Helpers
│ │ │ ├── EntityIdHelper.cs
│ │ │ ├── NamingHelper.cs
│ │ │ └── VersionHelper.cs
│ │ ├── MetaData
│ │ │ ├── DefaultMetadata
│ │ │ │ └── DefaultEntityMetaData.json
│ │ │ ├── EntityMetaData
│ │ │ │ ├── AttributeMetaDataGenerator.cs
│ │ │ │ ├── ClrTypeJsonConverter.cs
│ │ │ │ ├── EntityDomainMetadata.cs
│ │ │ │ ├── EntityMetaDataGenerator.cs
│ │ │ │ ├── EntityMetaDataMerger.cs
│ │ │ │ └── NullableBoolJsonConverter.cs
│ │ │ └── ServicesMetaData
│ │ │ │ ├── HassService.cs
│ │ │ │ ├── HassServiceArgumentMapper.cs
│ │ │ │ ├── HassServiceDomain.cs
│ │ │ │ ├── HassServiceField.cs
│ │ │ │ ├── Response.cs
│ │ │ │ ├── SelectorConverter.cs
│ │ │ │ ├── Selectors.cs
│ │ │ │ ├── ServiceMetaDataParser.cs
│ │ │ │ ├── SingleObjectAsArrayConverter.cs
│ │ │ │ ├── SnakeCaseNamingPolicy.cs
│ │ │ │ ├── StringAsArrayConverter.cs
│ │ │ │ └── StringAsDoubleConverter.cs
│ │ ├── NetDaemon.HassModel.CodeGenerator.csproj
│ │ ├── Program.cs
│ │ ├── Properties
│ │ │ └── launchSettings.json
│ │ ├── _appsettings.Development.json
│ │ └── appsettings.json
│ ├── NetDaemon.HassModel.Integration
│ │ ├── IntegrationHaContextExtensions.cs
│ │ └── NetDaemon.HassModel.Integration.csproj
│ ├── NetDaemon.HassModel.Tests
│ │ ├── .editorconfig
│ │ ├── CodeGenerator
│ │ │ ├── AttributeMetaDataGeneratorTest.cs
│ │ │ ├── CodeGenTestHelper.cs
│ │ │ ├── CodeGeneratorTest.cs
│ │ │ ├── ControllerTest.cs
│ │ │ ├── DependencyValidatorTests.cs
│ │ │ ├── EntityFactoryGeneratorTest.cs
│ │ │ ├── MetaDataMergerTest.cs
│ │ │ ├── NetDaemonTestAppAttribute.cs
│ │ │ ├── ServiceMetaDataParserTest.cs
│ │ │ ├── ServiceMetaDataSamples
│ │ │ │ ├── calendar.json
│ │ │ │ └── light.json
│ │ │ ├── ServicesGeneratorTest.cs
│ │ │ └── TestFiles
│ │ │ │ ├── RawVersions
│ │ │ │ └── RawVersions.csproj
│ │ │ │ └── SubstitutedVersions
│ │ │ │ └── SubstitutedVersions.csproj
│ │ ├── Entities
│ │ │ ├── EntityExtensions.CallServiceWithResponseAsync.Test.cs
│ │ │ ├── EntityExtensions.OnOff.Test.cs
│ │ │ ├── EntityExtensions.WithCurrent.ConcreteEntity.Test.cs
│ │ │ ├── EntityExtensions.WithCurrent.Entity.Test.cs
│ │ │ ├── EntityTest.cs
│ │ │ ├── EnumerableEntityExtensionsTest.cs
│ │ │ └── NumericEntityTest.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Integration
│ │ │ └── IntegrationHaContextExtensionsTests.cs
│ │ ├── Internal
│ │ │ ├── AppScopedHaContextProviderTest.cs
│ │ │ ├── BackgroundTaskTrackerTests.cs
│ │ │ ├── EntityAreaCachTests.cs
│ │ │ ├── EntityStateCacheTest.cs
│ │ │ ├── HassObjectMapperTest.cs
│ │ │ ├── NetDaemonExtensionsTest.cs
│ │ │ ├── QueuedObservabeTest.cs
│ │ │ ├── ScopedObservableTests.cs
│ │ │ └── TriggerManagerTest.cs
│ │ ├── NetDaemon.HassModel.Tests.csproj
│ │ ├── ObservableExtensionsTest.cs
│ │ ├── Registry
│ │ │ └── RegistryNavigationTest.cs
│ │ ├── ServiceTargetTest.cs
│ │ ├── StateObservableExtensionsTest.cs
│ │ └── TestHelpers
│ │ │ ├── Extensions.cs
│ │ │ └── HassClient
│ │ │ ├── HaContextMock.cs
│ │ │ └── TestEntity.cs
│ └── NetDaemon.HassModel
│ │ ├── Context.cs
│ │ ├── DependencyInjectionSetup.cs
│ │ ├── Entities
│ │ ├── Core
│ │ │ ├── CoreInterfaces.cs
│ │ │ ├── IEntityCore.cs
│ │ │ ├── LightAttributesBase.cs
│ │ │ └── MediaPlayerAttributesBase.cs
│ │ ├── DefaultEntityFactory.cs
│ │ ├── Entity.cs
│ │ ├── EntityExtensions.cs
│ │ ├── EntityState.cs
│ │ ├── EnumerableEntityExtensions.cs
│ │ ├── NumericEntity.cs
│ │ ├── NumericEntityState.cs
│ │ └── StateChange.cs
│ │ ├── Event.cs
│ │ ├── GlobalUsings.cs
│ │ ├── HaContextExtensions.cs
│ │ ├── HaRegistry.cs
│ │ ├── ICacheManager.cs
│ │ ├── IEntityFactory.cs
│ │ ├── IHaContext.cs
│ │ ├── IHaRegistry.cs
│ │ ├── IHaRegistryNavigator.cs
│ │ ├── ITriggerManager.cs
│ │ ├── Internal
│ │ ├── AppScopedHaContextProvider.cs
│ │ ├── BackgroundTaskTracker.cs
│ │ ├── CacheManager.cs
│ │ ├── EntityStateCache.cs
│ │ ├── ExtensionMethods.cs
│ │ ├── FormatHelpers.cs
│ │ ├── HassObjectMapper.cs
│ │ ├── IBackgroundTaskTracker.cs
│ │ ├── QueuedObservable.cs
│ │ ├── RegistryCache.cs
│ │ ├── ScopedObservable.cs
│ │ └── TriggerManager.cs
│ │ ├── NetDaemon.HassModel.csproj
│ │ ├── ObservableExtensions.cs
│ │ ├── Registry
│ │ ├── Area.cs
│ │ ├── ConversationOptions.cs
│ │ ├── Device.cs
│ │ ├── EntityOptions.cs
│ │ ├── EntityRegistration.cs
│ │ ├── Floor.cs
│ │ └── Label.cs
│ │ ├── ServiceTarget.cs
│ │ ├── StateObservableExtensions.cs
│ │ └── TriggerManagerExtensions.cs
├── Host
│ ├── .gitignore
│ └── NetDaemon.Host.Default
│ │ ├── NetDaemon.Host.Default.csproj
│ │ ├── Program.cs
│ │ ├── _appsettings.Development.json
│ │ └── appsettings.json
├── Runtime
│ ├── NetDaemon.Runtime.Tests
│ │ ├── .editorconfig
│ │ ├── Fixtures
│ │ │ └── LocalApp.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Helpers
│ │ │ ├── HomeAssistantRunnerMock.cs
│ │ │ └── TestSettings.cs
│ │ ├── Integration
│ │ │ └── TestRuntime.cs
│ │ ├── Internal
│ │ │ ├── AppStateManagerTests.cs
│ │ │ ├── AppStateRepositoryTests.cs
│ │ │ ├── EntityMapperHelperTests.cs
│ │ │ └── NetDaemonRuntimeTests.cs
│ │ ├── NetDaemon.Runtime.Tests.csproj
│ │ └── appsettings.json
│ └── NetDaemon.Runtime
│ │ ├── Common
│ │ ├── Extensions
│ │ │ ├── HostBuilderExtensions.cs
│ │ │ ├── ServiceBuilderExtensions.cs
│ │ │ └── ServiceCollectionExtensions.cs
│ │ ├── INetDaemonRuntime.cs
│ │ └── IRuntime.cs
│ │ ├── GlobalUsings.cs
│ │ ├── Internal
│ │ ├── AppStateManager.cs
│ │ ├── AppStateRepository.cs
│ │ ├── EntityMapperHelper.cs
│ │ ├── IAppStateRepository.cs
│ │ ├── IHandleHomeAssistantAppStateUpdates.cs
│ │ ├── NetDaemonRuntime.cs
│ │ ├── RuntimeService.cs
│ │ └── file.json
│ │ └── NetDaemon.Runtime.csproj
├── Targets
│ └── Sourcelink.targets
└── debug
│ ├── DebugHost
│ ├── DebugHost.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── _appsettings.Development.json
│ ├── apps
│ │ ├── Client
│ │ │ ├── .editorconfig
│ │ │ ├── ClientDebug.cs
│ │ │ ├── HelpersApp.cs
│ │ │ └── LabelDebug.cs
│ │ ├── ConcurrencyTestApp.cs
│ │ ├── Config
│ │ │ ├── Config.yaml
│ │ │ └── ConfigApp.cs
│ │ ├── Extensions
│ │ │ ├── MqttEntityManagerApp.cs
│ │ │ └── MqttEntitySubscriptionApp.cs
│ │ ├── HaRegistry
│ │ │ └── RegistryApp.cs
│ │ ├── HelloApp
│ │ │ └── HelloApp.cs
│ │ ├── ServiceCall
│ │ │ └── ServiceApp.cs
│ │ └── YamlApp
│ │ │ ├── YamlApp.cs
│ │ │ └── YamlApp.yaml
│ ├── apps_src
│ │ └── hellow.cs
│ └── appsettings.json
│ ├── DebugWebHost
│ ├── DebugWebHost.csproj
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── _appsettings.Development.json
│ └── appsettings.json
│ ├── MyLibrary
│ ├── InternalHomeAssistantGenerated
│ │ └── LightEntityExtensionMethods.cs
│ ├── MyLibrary.csproj
│ └── MyLibraryClass.cs
│ └── MyNDApp
│ ├── .gitignore
│ ├── HomeAssistantGenerated.cs
│ ├── MyNDApp.csproj
│ ├── apps
│ └── HassModel
│ │ └── MyInterfaceAutomation
│ │ └── InterfaceUsage.cs
│ └── program.cs
└── tests
└── Integration
├── HA
├── config
│ ├── automations.yaml
│ ├── configuration.yaml
│ ├── scenes.yaml
│ ├── scripts.yaml
│ └── secrets.yaml
└── docker-compose.yaml
├── NetDaemon.Tests.Integration
├── .editorconfig
├── BasicTest.cs
├── CalendarTests.cs
├── CodegenIntegrationTests.cs
├── HA
│ ├── config
│ │ ├── automations.yaml
│ │ ├── configuration.yaml
│ │ ├── groups.yaml
│ │ ├── scenes.yaml
│ │ ├── scripts.yaml
│ │ └── secrets.yaml
│ └── docker-compose.yaml
├── Helpers
│ ├── HomeAssistantCollection.cs
│ ├── HomeAssistantLifetime.cs
│ ├── HomeAssistantTestContainer
│ │ ├── HomeAssistantConfiguration.cs
│ │ ├── HomeAssistantContainer.cs
│ │ └── HomeAssistantContainerBuilder.cs
│ └── NetDaemonIntegrationBase.cs
├── HelpersTest.cs
├── NetDaemon.Tests.Integration.csproj
├── _appsettings.Development.json
├── apps
│ └── Config.yaml
└── appsettings.json
└── README.md
/.devcontainer/.bashrc:
--------------------------------------------------------------------------------
1 | GOPATH=$HOME/go
2 | function _update_ps1() {
3 | PS1="$($GOPATH/bin/powerline-go -error $?)"
4 | }
5 | if [ "$TERM" != "linux" ] && [ -f "$GOPATH/bin/powerline-go" ]; then
6 | PROMPT_COMMAND="_update_ps1; $PROMPT_COMMAND"
7 | fi
--------------------------------------------------------------------------------
/.devcontainer/install_prettyprompt.sh:
--------------------------------------------------------------------------------
1 | cp /workspaces/netdaemon/.devcontainer/.bashrc ~/
2 | chmod 755 ~/.bashrc
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [helto4real]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/ij1qXRM6E']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: New general issue
4 | url: https://github.com/net-daemon/netdaemon/issues/new
5 | about: This is the main issue tracker for NetDaemon. Please report issues with the NetDaemon there.
6 | - name: Report incorrect or missing information on the docs
7 | url: https://github.com/net-daemon/docs/issues
8 | about: Our documentation has its own issue tracker. Please report issues with the website there or even better contribute a PR.
9 | - name: I have a question or need support
10 | url: https://discord.gg/K3xwfcX
11 | about: Joint the Discord server to get support from the community.
12 | - name: Add and discuss a feature request
13 | url: https://github.com/net-daemon/netdaemon/discussions
14 | about: Please join the github discussions and ask for a feature in the feature request channel
15 | - name: I'm unsure where to go
16 | url: https://discord.gg/K3xwfcX
17 | about: If you are unsure where to go, then joining our chat in Discord is recommended; Just ask!
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: 'feature request'
6 | assignees: ''
7 |
8 | ---
9 |
10 |
14 | ## The problem
15 |
16 |
22 | ## The proposed solution
23 |
24 |
28 | ## The alternatives
29 |
30 |
33 | ## Additional context
34 |
35 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | todo:
2 | keyword: "// todo:"
3 | body: "// body:"
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: nuget
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 10
8 | labels:
9 | - dependencies
10 | - "pr: dependency-update"
11 | ignore:
12 | - dependency-name: YamlDotNet
13 | versions:
14 | - 11.1.0
15 |
16 | - package-ecosystem: github-actions
17 | directory: "/"
18 | schedule:
19 | interval: daily
20 | labels:
21 | - dependencies
22 | - "pr: dependency-update"
23 |
--------------------------------------------------------------------------------
/.github/matchers/dotnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "dotnet-file",
5 | "pattern": [
6 | {
7 | "regexp": "^(.+)\\((\\d+).+\\):\\s(\\w+)\\s(.+)\\:\\s(.*)$",
8 | "file": 1,
9 | "line": 2,
10 | "severity": 3,
11 | "code": 4,
12 | "message": 5
13 | }
14 | ]
15 | },
16 | {
17 | "owner": "dotnet-run",
18 | "pattern": [
19 | {
20 | "regexp": "^.+\\s\\:\\s(\\w+)\\s(.*)\\:\\s(.*)$",
21 | "severity": 1,
22 | "code": 2,
23 | "message": 3
24 | }
25 | ]
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'Release $RESOLVED_VERSION'
2 | tag-template: '$RESOLVED_VERSION'
3 | change-template: '- #$NUMBER $TITLE @$AUTHOR'
4 | sort-direction: ascending
5 | categories:
6 | - title: ':boom: Breaking changes'
7 | label: 'pr: breaking change'
8 |
9 | - title: ':sparkles: New features'
10 | label: 'pr: new-feature'
11 |
12 | - title: ':zap: Enhancements'
13 | label: 'pr: enhancement'
14 |
15 | - title: ':bug: Bug Fixes'
16 | label: 'pr: bugfix'
17 |
18 | - title: ':arrow_up: Dependency Updates'
19 | labels:
20 | - 'pr: dependency-update'
21 | - 'dependencies'
22 |
23 | include-labels:
24 | - 'pr: breaking change'
25 | - 'pr: enhancement'
26 | - 'pr: dependency-update'
27 | - 'pr: new-feature'
28 | - 'pr: bugfix'
29 |
30 | template: |
31 | ## 👀 Summary
32 |
33 | $CHANGES
34 |
35 | ## Links
36 | - [Discord server for NetDaemon](https://discord.gg/GFzBYpuCG3)
37 | - [NetDaemon Documentation](https://netdaemon.xyz)
38 | - [If you like what I (@helto4real) do please consider sponsoring me on GitHub](https://github.com/sponsors/helto4real)
39 | - [Or buy me a ☕️ / 🍺](https://www.buymeacoffee.com/ij1qXRM6E)
--------------------------------------------------------------------------------
/.github/workflows/check_pr_label.yml:
--------------------------------------------------------------------------------
1 | name: 🏷️ Check labels
2 | on:
3 | pull_request:
4 | types: [opened, labeled, unlabeled, synchronize, reopened]
5 | jobs:
6 | check_labels:
7 | name: 🏷️ Check valid labels
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Check valid labels
11 | run: |
12 | labels=$(jq -r '.pull_request.labels[] | .name' ${{github.event_path }} | grep 'pr:')
13 | if [[ ! $labels ]]; then
14 | echo "::error::You need to provide one or more labels that starts with 'pr:'"
15 | exit 1
16 | fi
17 | exit 0
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/ci_analyze.yml:
--------------------------------------------------------------------------------
1 | ### Build and tests all pushes, also code coverage
2 | name: 🔍 CI Analyze sources
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | sonarscanner:
10 | name: 🔍 SonarScanner
11 | environment: CI - analyze environment
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: 📤 Checkout the repository
15 | uses: actions/checkout@main
16 | with:
17 | # Shallow clones should be disabled for a better relevancy of analysis
18 | fetch-depth: 0
19 |
20 | - name: 🔍 Analyze code
21 | uses: highbyte/sonarscan-dotnet@v2.4.2
22 | env:
23 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | with:
26 | sonarOrganization: net-daemon
27 | sonarProjectKey: net-daemon_netdaemon
28 | sonarProjectName: netdaemon
29 | dotnetTestArguments: --logger trx /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
30 | sonarBeginArguments: >-
31 | /d:sonar.inclusions="**/src/**"
32 | /d:sonar.test.inclusions="**/tests/**"
33 | /d:sonar.cs.xunit.reportsPaths="**/tests/**/TestResults/*.trx"
34 | /d:sonar.cs.opencover.reportsPaths="**/tests/**/coverage.opencover.xml"
35 |
--------------------------------------------------------------------------------
/.github/workflows/common/set_netdaemon_version.yml:
--------------------------------------------------------------------------------
1 | on:
2 | workflow_call:
3 | inputs:
4 | version:
5 | required: true
6 | type: string
7 | jobs:
8 | set_version_in_source:
9 | name: 📆 Set version in code and docker files
10 | runs-on: ubuntu-latest
11 | steps:
12 | run: |
13 | echo setting source version: ${{ inputs.version }}
14 | sed -i '/ private const string Version = /c\ private const string Version = "${{ inputs.version }}";' ${{github.workspace}}/src/Runtime/NetDaemon.Runtime/Internal/NetDaemonRuntime.cs
15 | sed -i '/ io.hass.version=/c\ io.hass.version="${{ inputs.version }}"' ${{github.workspace}}/Dockerfile.AddOn
--------------------------------------------------------------------------------
/.github/workflows/release_drafter.yml:
--------------------------------------------------------------------------------
1 | name: 📝 Release Drafter
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | jobs:
8 | update:
9 | name: ⏫ Update
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: 📥 Checkout the repository
13 | uses: actions/checkout@main
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: ⏭️ Get next version
18 | id: version
19 | run: |
20 | declare -i newpost
21 | latest=$(git describe --tags $(git rev-list --tags --max-count=1))
22 | latestpre=$(echo "$latest" | awk '{split($0,a,"."); print a[1] "." a[2]}')
23 | datepre=$(date --utc '+%y.%-W')
24 | if [[ "$latestpre" == "$datepre" ]]; then
25 | latestpost=$(echo "$latest" | awk '{split($0,a,"."); print a[3]}')
26 | newpost=$latestpost+1
27 | else
28 | newpost=0
29 | fi
30 | echo Current version: $latest
31 | echo New target version: $datepre.$newpost
32 | echo "version=$datepre.$newpost" >> $GITHUB_OUTPUT
33 |
34 | - name: 🏃 Run Release Drafter
35 | uses: release-drafter/release-drafter@v6
36 | with:
37 | tag: ${{ steps.version.outputs.version }}
38 | name: ${{ steps.version.outputs.version }}
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 |
--------------------------------------------------------------------------------
/.github/workflows/test_docker.yml:
--------------------------------------------------------------------------------
1 | #### Publish tags to docker hub
2 | name: 👀 Test docker
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | - dev
8 |
9 | jobs:
10 | test:
11 | name: 👀 Test image build ${{ matrix.dockerfile }}
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | dockerfile:
16 | - Dockerfile
17 | - Dockerfile.AddOn
18 | steps:
19 | - name: 📤 Checkout the repository
20 | uses: actions/checkout@main
21 |
22 | - name: 📎 Set up QEMU
23 | uses: docker/setup-qemu-action@v3
24 |
25 | - name: 🔧 Set up Docker Buildx
26 | id: buildx
27 | uses: docker/setup-buildx-action@v3
28 |
29 | - name: 🧰 Available platforms
30 | run: echo ${{ steps.buildx.outputs.platforms }}
31 |
32 | - name: 🛠️ Run Buildx
33 | run: |
34 | docker buildx build \
35 | --platform linux/arm,linux/arm64,linux/amd64 \
36 | --output "type=image,push=false" \
37 | --no-cache \
38 | --file ./${{ matrix.dockerfile }} . \
39 | --tag netdaemon/netdaemon5:dev
40 |
--------------------------------------------------------------------------------
/.linting/roslynator.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug OldModel",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "justMyCode": false,
13 | // If you have changed target frameworks, make sure to update the program path.
14 | "program": "${workspaceFolder}/dev/DebugHost/bin/Debug/net6.0/DebugHost.dll",
15 | "args": [],
16 | "cwd": "${workspaceFolder}/dev/DebugHost",
17 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
18 | "console": "internalConsole",
19 | "stopAtEntry": false
20 | }
21 | ]
22 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "**/*.csproj.user": true,
4 | "**/bin": true,
5 | "**/codecover": true,
6 | "**/documentation/site": true,
7 | "**/obj": true,
8 | "**/Properties": true,
9 | "**/TestResults": true
10 | },
11 | "omnisharp.enableRoslynAnalyzers": true,
12 | "cSpell.words": [
13 | "Expando",
14 | "Finalizers",
15 | "Hass",
16 | "Usings",
17 | "Xunit",
18 | "dayofweek",
19 | "entityid",
20 | "hassio",
21 | "idguid",
22 | "mulitinstance",
23 | "mydomain",
24 | "myevent",
25 | "mylight",
26 | "myscript",
27 | "noexist",
28 | "noquotes",
29 | "parentidguid",
30 | "useridguid"
31 | ],
32 | "dotnet.defaultSolution": "NetDaemon.sln"
33 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/NetDaemon.sln",
11 | "/property:GenerateFullPaths=true"
12 | ],
13 | "problemMatcher": "$msCompile"
14 | },
15 | {
16 | "label": "run integration tests",
17 | "command": "dotnet",
18 | "type": "process",
19 | "args": [
20 | "run ",
21 | "${workspaceFolder}/NetDaemon.sln",
22 | "/property:GenerateFullPaths=true"
23 | ],
24 | "problemMatcher": "$msCompile"
25 | },
26 | {
27 | "label": "test coverage",
28 | "command": "dotnet",
29 | "type": "process",
30 | "group": {
31 | "kind": "test",
32 | "isDefault": true
33 | },
34 | "args": [
35 | "test",
36 | "${workspaceFolder}/NetDaemon.sln",
37 | "/p:CollectCoverage=true",
38 | "/p:CoverletOutputFormat=lcov",
39 | "/p:CoverletOutput=../codecover/lcov.info"
40 | ],
41 | "problemMatcher": "$msCompile"
42 | }
43 | ]
44 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to NetDaemon
2 |
3 | Everyone is welcome to contribute to NetDaemon. If you are not a developer you might help out with the documentation at [netdaemon.xyz](netdaemon.xyz).
4 | You will find the docs on [github here](https://github.com/net-daemon/docs).
5 |
6 | ## How to contribute
7 |
8 | - Fork the repository
9 | - Write code and tests
10 | - Make sure the tests run before PR
11 | - Create a pullrequest against the NetDaemon main branch
12 |
13 | ## Code quality
14 |
15 | - Use default code quality checks and fix all warnings before PR review as warnings will fail the build process.
16 |
17 | ## Architecture
18 |
19 | - If you want to do bigger design changes, use the Discord group and discuss in #dev channel before putting too much work into it, see https://discord.gg/K3xwfcX
20 |
21 | ## Features and Issues
22 |
23 | Check if there are features to implement or file issued at [https://github.com/net-daemon/netdaemon/issues](https://github.com/net-daemon/netdaemon/issues)
24 |
25 |
--------------------------------------------------------------------------------
/DEV.md:
--------------------------------------------------------------------------------
1 | # Developing NetDaemon
2 |
3 | Thank you for considering contributing to NetDaemon. You will find a good starting point in the [documentation for developers](https://netdaemon.xyz/docs/developer)
4 |
5 | Good luck
6 |
--------------------------------------------------------------------------------
/Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/net-daemon/netdaemon/bde37bf45bf573decb4c8c293baf9a49aef5bb68/Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon
--------------------------------------------------------------------------------
/Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon_addon:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/net-daemon/netdaemon/bde37bf45bf573decb4c8c293baf9a49aef5bb68/Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon_addon
--------------------------------------------------------------------------------
/Docker/rootfs/etc/services.d/netdaemon/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/Docker/rootfs/etc/services.d/netdaemon/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/Docker/rootfs/etc/services.d/netdaemon_addon/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
--------------------------------------------------------------------------------
/Docker/rootfs/etc/services.d/netdaemon_addon/type:
--------------------------------------------------------------------------------
1 | longrun
2 |
--------------------------------------------------------------------------------
/Docker/s6.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # https://github.com/just-containers/s6-overlay
3 |
4 | S6_VERSION="v2.1.0.2"
5 | ARCH=$(uname -m)
6 |
7 | if [[ "${ARCH}" == "armv7l" ]]; then
8 | wget -q -nv -O /tmp/s6.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/${S6_VERSION}/s6-overlay-armhf.tar.gz"
9 | elif [[ "${ARCH}" == "aarch64" ]]; then
10 | wget -q -nv -O /tmp/s6.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/${S6_VERSION}/s6-overlay-aarch64.tar.gz"
11 | elif [[ "${ARCH}" == "x86_64" ]]; then
12 | wget -q -nv -O /tmp/s6.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/${S6_VERSION}/s6-overlay-amd64.tar.gz"
13 | else
14 | echo "NOT SUPPORTED ARCH: ${ARCH}"
15 | exit 1
16 | fi
17 |
18 | tar xzf /tmp/s6.tar.gz -C /
19 | rm /tmp/s6.tar.gz
20 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Pre-build .NET NetDaemon core project
2 | FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim-amd64 as netbuilder
3 | ARG TARGETPLATFORM
4 | ARG BUILDPLATFORM
5 |
6 | RUN echo "I am running on ${BUILDPLATFORM}"
7 | RUN echo "building for ${TARGETPLATFORM}"
8 | RUN export TARGETPLATFORM="${TARGETPLATFORM}"
9 |
10 | # Copy the source to docker container
11 | COPY . /usr
12 |
13 | RUN dotnet publish /usr/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj -o "/daemon"
14 |
15 | # Final stage, create the runtime container
16 | FROM ghcr.io/net-daemon/netdaemon_base:9
17 |
18 | # # Install S6 and the Admin site
19 | # COPY ./Docker/rootfs/etc/services.d/NetDaemonAdmin /etc/services.d/NetDaemonAdmin
20 | COPY --chmod=755 ./Docker/rootfs/etc/services.d/netdaemon /etc/s6-overlay/s6-rc.d/netdaemon
21 | COPY --chmod=755 ./Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon etc/s6-overlay/s6-rc.d/user/contents.d/netdaemon
22 | # COPY admin
23 | # COPY --from=builder /admin /admin
24 | COPY --from=netbuilder /daemon /daemon
25 |
26 | # This is always set to data as default
27 | ENV NetDaemon__ApplicationConfigurationFolder=/data
28 |
--------------------------------------------------------------------------------
/Dockerfile.AddOn:
--------------------------------------------------------------------------------
1 | # No admin support yet, we need to build the websocket API
2 | # Pre-build .NET NetDaemon core project
3 | FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim-amd64 as netbuilder
4 | ARG TARGETPLATFORM
5 | ARG BUILDPLATFORM
6 |
7 | RUN echo "I am running on ${BUILDPLATFORM}"
8 | RUN echo "building for ${TARGETPLATFORM}"
9 |
10 | RUN export TARGETPLATFORM="${TARGETPLATFORM}"
11 |
12 | # Copy the source to docker container
13 | COPY . /usr
14 | RUN dotnet publish /usr/src/Host/NetDaemon.Host.Default/NetDaemon.Host.Default.csproj -o "/daemon"
15 |
16 | # Final stage, create the runtime container
17 | FROM ghcr.io/net-daemon/netdaemon_addonbase:9
18 |
19 | # # Install S6 and the Admin site
20 | COPY --chmod=755 ./Docker/rootfs/etc/services.d/netdaemon_addon /etc/s6-overlay/s6-rc.d/netdaemon_addon
21 | COPY --chmod=755 ./Docker/rootfs/etc/s6-overlay/s6-rc.d/usr/netdaemon_addon etc/s6-overlay/s6-rc.d/user/contents.d/netdaemon_addon
22 | # COPY admin
23 | # COPY --from=builder /admin /admin
24 | COPY --from=netbuilder /daemon /daemon
25 |
26 | LABEL \
27 | io.hass.version="VERSION"
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Tomas Hellström
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/codecov.yaml:
--------------------------------------------------------------------------------
1 | codecov:
2 | require_ci_to_pass: yes
3 |
4 | coverage:
5 | precision: 0
6 | round: down
7 | range: "70...100"
8 | status:
9 | project:
10 | tests: # declare a new status context "tests"
11 | target: 70% # we always want 70% coverage here
12 | patch:
13 | default:
14 | # basic
15 | target: auto
16 | threshold: 10%
17 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "8.0.0",
4 | "rollForward": "latestMajor",
5 | "allowPrerelease": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/net-daemon/netdaemon/bde37bf45bf573decb4c8c293baf9a49aef5bb68/img/icon.png
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/Compiler/CollectableAssemblyLoadContext.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.Loader;
3 |
4 | namespace NetDaemon.AppModel.Internal.Compiler;
5 |
6 | internal class CollectableAssemblyLoadContext : AssemblyLoadContext
7 | {
8 | public CollectableAssemblyLoadContext() : base(true)
9 | {
10 | }
11 |
12 | protected override Assembly? Load(AssemblyName assemblyName)
13 | {
14 | return null;
15 | }
16 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/Compiler/CompileSettings.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Internal.Compiler;
2 |
3 | ///
4 | /// Used to set debug or release mode for dynamic compiled apps
5 | ///
6 | internal record CompileSettings
7 | {
8 | public bool UseDebug { get; set; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/Compiler/ICompiler.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Internal.Compiler;
2 |
3 | internal interface ICompiler : IDisposable
4 | {
5 | CompiledAssemblyResult Compile();
6 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/Compiler/ISyntaxTreeResolver.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 |
3 | namespace NetDaemon.AppModel.Internal.Compiler;
4 |
5 | ///
6 | /// Gets the syntax tree from any source code (file, string)
7 | ///
8 | internal interface ISyntaxTreeResolver
9 | {
10 | IReadOnlyCollection GetSyntaxTrees();
11 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/DynamicallyCompiledAppAssemblyProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using NetDaemon.AppModel.Internal.Compiler;
3 |
4 | namespace NetDaemon.AppModel.Internal.AppAssemblyProviders;
5 |
6 | internal class DynamicallyCompiledAppAssemblyProvider : IAppAssemblyProvider, IDisposable
7 | {
8 | private readonly ICompiler _compiler;
9 |
10 | private Assembly? _compiledAssembly;
11 | private CollectableAssemblyLoadContext? _currentContext;
12 |
13 | public DynamicallyCompiledAppAssemblyProvider(ICompiler compiler)
14 | {
15 | _compiler = compiler;
16 | }
17 |
18 | public Assembly GetAppAssembly()
19 | {
20 | // We reuse an already compiled assembly since we only compile once per start
21 | if (_compiledAssembly is not null)
22 | return _compiledAssembly;
23 |
24 | var (loadContext, compiledAssembly) = _compiler.Compile();
25 | _currentContext = loadContext;
26 | _compiledAssembly = compiledAssembly;
27 | return compiledAssembly;
28 | }
29 |
30 | public void Dispose()
31 | {
32 | if (_currentContext is null) return;
33 | _currentContext.Unload();
34 | // Finally do cleanup and release memory
35 | GC.Collect();
36 | GC.WaitForPendingFinalizers();
37 | }
38 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.SourceDeployedApps/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Runtime.CompilerServices;
4 | global using System.Text;
5 |
6 | // Make the internal visible to test project
7 | [assembly: InternalsVisibleTo("NetDaemon.AppModel.Tests")]
8 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
9 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | dotnet_diagnostic.xUnit1030.severity = none
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Compiler/Fixtures/SimpleApp.cs:
--------------------------------------------------------------------------------
1 | namespace test;
2 |
3 | public class SimpleApp
4 | {
5 |
6 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/FailedConfig/Fail.yaml:
--------------------------------------------------------------------------------
1 | DuplicateKeySetting:
2 | ThisIsDuplicated: Hello test!
3 | ThisIsDuplicated: Another string
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/Fixtures/App.json:
--------------------------------------------------------------------------------
1 | {
2 | "TestConfig": {
3 | "AString": "Hello test!"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/Fixtures/App.yaml:
--------------------------------------------------------------------------------
1 | TestConfig:
2 | AString: Hello test!
3 |
4 | TestConfigConverter:
5 | Entity: light.testlight
6 | AnArray:
7 | - Hello
8 | - World
9 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/Fixtures/AppInjectSettings.yaml:
--------------------------------------------------------------------------------
1 | NetDaemon.AppModel.Tests.Config.TestSettings:
2 | AString: Hello test yaml!
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/Fixtures/AppInjectSettingsy.json:
--------------------------------------------------------------------------------
1 | {
2 | "NetDaemon.AppModel.Tests.Config.TestSettings": {
3 | "AString": "Hello test!"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Config/Fixtures/CollectionTestsFixture.yaml:
--------------------------------------------------------------------------------
1 | TestCollections:
2 | SomeEnumerable:
3 | - A string
4 | - Another string
5 | SomeList:
6 | - A list item
7 | - Another list item
8 | SomeReadOnlyList:
9 | - Readonly list item
10 | - Another readonly list item
11 | SomeReadOnlyCollection:
12 | - Readonly collection item
13 | - Another readonly collection item
14 | SomeCollection:
15 | - Collection item
16 | - Another collection item
17 | SomeReadOnlyDictionary:
18 | ReadOnlyKey1: Item1
19 | ReadOnlyKey2: Item2
20 | SomeDictionary:
21 | ReadOnlyKey1: Item1
22 | ReadOnlyKey2: Item2
23 |
24 | AnotherTestCollections:
25 | - Plain list
26 | - Should work
27 | - too
28 |
29 | AnotherTestDictionary:
30 | key1: value1
31 | key2: value2
32 | key3: value3
33 |
34 | AnotherTestDictionaryOfLists:
35 | key1:
36 | - Item 1
37 | - Item 2
38 | key2:
39 | - Item 1
40 | - Item 2
41 |
42 | AnotherTestListOfDictionary:
43 | - Key 1: Item 1
44 | Key 2: Item 2
45 | - Key 1: Item 1
46 | Key 2: Item 2
47 |
48 | AnotherTestArray:
49 | - Item 1
50 | - Item 2
51 |
52 | AnotherTestEnum:
53 | - Enum1
54 | - Enum2
55 |
56 | AnotherTestShortEnum:
57 | - Enum1
58 | - Enum2
59 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Context/ApplicationContextTests.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel.Internal;
2 | using NetDaemon.AppModel.Internal.AppFactories;
3 |
4 | namespace NetDaemon.AppModel.Tests.Context;
5 |
6 | public class ApplicationContextTests
7 | {
8 | [Fact]
9 | public async Task TestApplicationContextIsDisposedMultipleTimesNotThrowsException()
10 | {
11 | var serviceProvider = new ServiceCollection().BuildServiceProvider();
12 | var appFactory = Mock.Of();
13 | var applicationContext = new ApplicationContext(serviceProvider, appFactory);
14 |
15 | await applicationContext.DisposeAsync();
16 | await applicationContext.DisposeAsync();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Context/ApplicationScopeTests.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel.Internal;
2 | using NetDaemon.AppModel.Internal.AppFactories;
3 |
4 | namespace NetDaemon.AppModel.Tests.Context;
5 |
6 | public class ApplicationScopeTests
7 | {
8 | ///
9 | /// This test do integration test that uses dynamic compilation. Only thing that is faked is the path to
10 | /// the apps folder that it is injected as transient to fake it.
11 | ///
12 | [Fact]
13 | public void TestFailedInitializedScopeThrows()
14 | {
15 | var scope = new ApplicationScope();
16 |
17 | Assert.Throws(() => scope.ApplicationContext);
18 | }
19 |
20 | [Fact]
21 | public void TestInitializedScopeReturnsOk()
22 | {
23 | var scope = new ApplicationScope
24 | {
25 | ApplicationContext = new ApplicationContext(
26 | new ServiceCollection().BuildServiceProvider(),
27 | Mock.Of()
28 | )
29 | };
30 | var ctx = scope.ApplicationContext;
31 |
32 | ctx.Should().NotBeNull();
33 | }
34 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Dynamic/Application.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel;
2 |
3 | namespace Apps;
4 |
5 | public class TestSettings
6 | {
7 | public string AString { get; set; } = string.Empty;
8 | }
9 |
10 | [NetDaemonApp]
11 | public class MyApp
12 | {
13 | public IAppConfig Settings { get; }
14 | public MyApp(IAppConfig settings)
15 | {
16 | Settings = settings;
17 | }
18 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Dynamic/settings.yaml:
--------------------------------------------------------------------------------
1 | Apps.TestSettings:
2 | AString: Hello world!
3 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/DynamicError/IDoNotCompileWell.cs:
--------------------------------------------------------------------------------
1 | public class IDoNotCompileWell
2 | {
3 | int MissingSemiColon
4 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/DynamicWithFocus/Application.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel;
2 |
3 | namespace Apps;
4 |
5 | [NetDaemonApp]
6 | public class NonFocusApp
7 | {
8 | public NonFocusApp()
9 | {
10 | }
11 | }
12 |
13 | [NetDaemonApp]
14 | [Focus]
15 | public class MyFocusApp
16 | {
17 | public MyFocusApp()
18 | {
19 | }
20 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/DynamicWithServiceCollection/Application.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel;
2 | using Microsoft.Extensions.DependencyInjection;
3 | namespace Apps;
4 |
5 | public static class ServiceCollectionRegister
6 | {
7 | [ServiceCollectionExtension]
8 | public static void RegisterSomeGreatServices(IServiceCollection services)
9 | {
10 | services.AddSingleton();
11 | }
12 | }
13 |
14 | public class InjectMePlease
15 | {
16 | public InjectMePlease()
17 | {
18 |
19 | }
20 |
21 | public string AValue { get; init; } = "SomeInjectedValue";
22 | }
23 |
24 | [NetDaemonApp]
25 | public class InjectedApp
26 | {
27 | public string InjectedValue { get; set; }
28 | public InjectedApp(InjectMePlease injectedClass)
29 | {
30 | InjectedValue = injectedClass.AValue;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Local/AppThatTakesASlowTimeToDispose.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace LocalApps;
3 |
4 |
5 | [NetDaemonApp]
6 | public sealed class SlowDisposableApp : IDisposable
7 | {
8 | public SlowDisposableApp()
9 | {
10 | }
11 |
12 | public void Dispose()
13 | {
14 | Thread.Sleep(3500);
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Local/LocalAppWithDisposable.cs:
--------------------------------------------------------------------------------
1 | namespace LocalApps;
2 |
3 | [NetDaemonApp]
4 | public sealed class MyAppLocalAppWithAsyncDispose : IAsyncDisposable, IDisposable
5 | {
6 | public bool AsyncDisposeIsCalled { get; private set; }
7 | public bool DisposeIsCalled { get; private set; }
8 |
9 | public ValueTask DisposeAsync()
10 | {
11 | AsyncDisposeIsCalled = true;
12 | GC.SuppressFinalize(this);
13 | return ValueTask.CompletedTask;
14 | }
15 |
16 | public void Dispose()
17 | {
18 | DisposeIsCalled = true;
19 | GC.SuppressFinalize(this);
20 | }
21 | }
22 |
23 | [NetDaemonApp]
24 | public sealed class MyAppLocalAppWithDispose : IDisposable
25 | {
26 | public bool DisposeIsCalled { get; private set; }
27 |
28 | public void Dispose()
29 | {
30 | DisposeIsCalled = true;
31 | GC.SuppressFinalize(this);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Local/LocalAppWithId.cs:
--------------------------------------------------------------------------------
1 | namespace LocalApps;
2 |
3 | [NetDaemonApp(Id = Id)]
4 | public class MyAppLocalAppWithId
5 | {
6 | public const string Id = "SomeId";
7 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Local/LocalAppWithInitializeAsync.cs:
--------------------------------------------------------------------------------
1 | namespace LocalApps;
2 |
3 | [NetDaemonApp]
4 | public class MyAppLocalAppWithInitializeAsync : IAsyncInitializable
5 | {
6 | public bool InitializeAsyncCalled { get; private set; }
7 |
8 |
9 | public Task InitializeAsync(CancellationToken cancellationToken)
10 | {
11 | InitializeAsyncCalled = true;
12 | return Task.CompletedTask;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/Local/settings.yaml:
--------------------------------------------------------------------------------
1 | LocalApps.LocalTestSettings:
2 | AString: Hello world!
3 | Entity: light.test
4 | Entity2: light.test2
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Fixtures/LocalError/LocalApp.cs:
--------------------------------------------------------------------------------
1 | namespace LocalAppsWithErrors;
2 |
3 | [NetDaemonApp]
4 | public class MyAppLocalAppWithError
5 | {
6 | public MyAppLocalAppWithError()
7 | {
8 | throw new InvalidOperationException("Some error");
9 | }
10 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using Microsoft.Extensions.Options;
3 | global using Microsoft.Extensions.DependencyInjection;
4 | global using FluentAssertions;
5 | global using Moq;
6 | global using Xunit;
7 | global using NetDaemon.AppModel;
8 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Helpers/FakeOptions.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Tests.Helpers;
2 |
3 | internal sealed class FakeOptions : IOptions
4 | {
5 | public FakeOptions(string path)
6 | {
7 | Value = new AppConfigurationLocationSetting {ApplicationConfigurationFolder = path};
8 | }
9 |
10 | public AppConfigurationLocationSetting Value { get; init; }
11 | }
12 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel.Tests/Helpers/FakeOptionsExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Tests.Helpers;
2 |
3 | internal static class FakeOptionsExtensions
4 | {
5 | public static IServiceCollection AddFakeOptions(this IServiceCollection services, string name)
6 | {
7 | return services.AddTransient(_ => CreateFakeOptions(name));
8 | }
9 |
10 | private static IOptions CreateFakeOptions(string name)
11 | {
12 | var path = Path.Combine(AppContext.BaseDirectory, Path.Combine(AppContext.BaseDirectory, $"Fixtures/{name}"));
13 | return new FakeOptions(path);
14 | }
15 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/ApplicationState.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// The current state of an application
5 | ///
6 | public enum ApplicationState
7 | {
8 | ///
9 | /// Application is enabled
10 | ///
11 | Enabled,
12 | ///
13 | /// Application is disabled
14 | ///
15 | Disabled,
16 | ///
17 | /// The application is in running state
18 | ///
19 | Running,
20 | ///
21 | /// Application is in error state
22 | ///
23 | Error
24 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/Attributes/FocusAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Marks a class to be loaded exclusively while in development environment
5 | ///
6 | ///
7 | /// If one or more app classes have this attribute, only those apps will be loaded
8 | ///
9 | [AttributeUsage(AttributeTargets.Class)]
10 | public sealed class FocusAttribute : Attribute
11 | {
12 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/Attributes/NetDaemonAppAttribute.cs:
--------------------------------------------------------------------------------
1 | using JetBrains.Annotations;
2 |
3 | namespace NetDaemon.AppModel;
4 |
5 | ///
6 | /// Marks a class as a NetDaemonApp
7 | ///
8 | [MeansImplicitUse]
9 | [AttributeUsage(AttributeTargets.Class)]
10 | public sealed class NetDaemonAppAttribute : Attribute
11 | {
12 | ///
13 | /// Id of an app
14 | ///
15 | public string? Id { get; init; }
16 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/Attributes/ServiceCollectionExtensionAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Indicates method in dynamically compile code should be called as a ServiceCollectionExtension to setup services
5 | ///
6 | ///
7 | /// The method should have `public static void RegisterServices(IServiceCollection services){}`
8 | ///
9 | [AttributeUsage(AttributeTargets.Method)]
10 | public sealed class ServiceCollectionExtensionAttribute : Attribute
11 | {
12 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IAppModel.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Application model
5 | ///
6 | public interface IAppModel
7 | {
8 | ///
9 | /// Instance and configure all applications.
10 | ///
11 | /// Cancellation token
12 | ///
13 | /// All apps that are configured with [NetDaemonApp] attribute or in yaml will be handled.
14 | /// [Focus] attribute will only load the apps that has this attribute if exist
15 | /// Depending on the selected compilation type it will be local or dynamically compiled apps
16 | ///
17 | ///
18 | Task LoadNewApplicationContext(CancellationToken cancellationToken);
19 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IAppModelContext.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Manage AppModel state and lifecycle
5 | ///
6 | public interface IAppModelContext : IAsyncInitializable, IAsyncDisposable
7 | {
8 | ///
9 | /// Current instantiated and running applications
10 | ///
11 | IReadOnlyCollection Applications { get; }
12 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IAppStateManager.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Let users manage state of the application with own implementation
5 | ///
6 | public interface IAppStateManager
7 | {
8 | ///
9 | /// Gets application state
10 | ///
11 | /// The unique id of the application
12 | Task GetStateAsync(string applicationId);
13 | ///
14 | /// Saves application state
15 | ///
16 | /// The unique id of the application
17 | /// The application state to save
18 | Task SaveStateAsync(string applicationId, ApplicationState state);
19 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IApplication.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Represents a application and it's state
5 | ///
6 | public interface IApplication : IAsyncDisposable
7 | {
8 | ///
9 | /// Unique id of the application
10 | ///
11 | string? Id { get; }
12 |
13 | ///
14 | /// Current state of the application
15 | ///
16 | ApplicationState State { get; }
17 |
18 | ///
19 | /// Sets state for application
20 | ///
21 | /// The state to set
22 | Task SetStateAsync(ApplicationState state);
23 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IAsyncInitializable.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Allows apps to initialize non-blocking async operations
5 | ///
6 | public interface IAsyncInitializable
7 | {
8 | ///
9 | /// Initialize async non-blocking async operations
10 | ///
11 | /// Cancellation token to that are canceled when application unloads
12 | Task InitializeAsync(CancellationToken cancellationToken);
13 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/IConfig.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// Configuration in a app
5 | ///
6 | /// Type of class representing the config
7 | public interface IAppConfig : IOptions where T : class, new()
8 | {
9 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Common/Settings/AppLocationSettings.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel;
2 |
3 | ///
4 | /// The setting for the location of configuration files for applications
5 | ///
6 | public record AppConfigurationLocationSetting
7 | {
8 | ///
9 | /// Path to the folder where to search for application configuration files
10 | ///
11 | public string ApplicationConfigurationFolder { get; set; } = string.Empty;
12 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Collections.Generic;
3 | global using System.Runtime.CompilerServices;
4 | global using System.Threading.Tasks;
5 | global using Microsoft.Extensions.DependencyInjection;
6 | global using Microsoft.Extensions.Logging;
7 | global using Microsoft.Extensions.Options;
8 | global using NetDaemon.AppModel.Internal;
9 |
10 | // Make the internal visible to test project
11 | [assembly: InternalsVisibleTo("NetDaemon.AppModel.Tests")]
12 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
13 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppAssemblyProviders/AppAssemblyProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace NetDaemon.AppModel.Internal.AppAssemblyProviders;
4 |
5 | internal class AppAssemblyProvider(Assembly assembly) : IAppAssemblyProvider
6 | {
7 | public Assembly GetAppAssembly()
8 | {
9 | return assembly;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppAssemblyProviders/IAppAssemblyProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace NetDaemon.AppModel.Internal.AppAssemblyProviders;
4 |
5 | ///
6 | /// Provides interface for applications residing in assemblies
7 | ///
8 | public interface IAppAssemblyProvider
9 | {
10 | ///
11 | /// Gets the assembly that has the NetDaemon applications
12 | ///
13 | public Assembly GetAppAssembly();
14 | }
15 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppFactories/IAppFactory.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Internal.AppFactories;
2 |
3 | internal interface IAppFactory
4 | {
5 | object Create(IServiceProvider provider);
6 |
7 | string Id { get; }
8 |
9 | bool HasFocus { get; }
10 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppFactoryProviders/AssemblyAppFactoryProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using NetDaemon.AppModel.Internal.AppAssemblyProviders;
3 | using NetDaemon.AppModel.Internal.AppFactories;
4 |
5 | namespace NetDaemon.AppModel.Internal.AppFactoryProviders;
6 |
7 | internal class AssemblyAppFactoryProvider : IAppFactoryProvider
8 | {
9 | private readonly IEnumerable _assemblyResolvers;
10 |
11 | public AssemblyAppFactoryProvider(IEnumerable assemblyResolvers)
12 | {
13 | _assemblyResolvers = assemblyResolvers;
14 | }
15 |
16 | public IReadOnlyCollection GetAppFactories()
17 | {
18 | return _assemblyResolvers
19 | .Select(resolver => resolver.GetAppAssembly())
20 | .SelectMany(assembly => assembly.GetTypes())
21 | .Where(IsNetDaemonAppType)
22 | .Select(type => FuncAppFactory.Create(type))
23 | .ToList();
24 | }
25 |
26 | private static bool IsNetDaemonAppType(Type type)
27 | {
28 | if (!type.IsClass || type.IsGenericType || type.IsAbstract)
29 | {
30 | return false;
31 | }
32 |
33 | return type.GetCustomAttribute() is not null;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppFactoryProviders/FuncAppFactoryProvider.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel.Internal.AppFactories;
2 |
3 | namespace NetDaemon.AppModel.Internal.AppFactoryProviders;
4 |
5 | internal class FuncAppFactoryProvider : IAppFactoryProvider
6 | {
7 | public IReadOnlyCollection GetAppFactories()
8 | {
9 | throw new NotImplementedException();
10 | }
11 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppFactoryProviders/IAppFactoryProvider.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel.Internal.AppFactories;
2 |
3 | namespace NetDaemon.AppModel.Internal.AppFactoryProviders;
4 |
5 | internal interface IAppFactoryProvider
6 | {
7 | IReadOnlyCollection GetAppFactories();
8 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppFactoryProviders/SingleAppFactoryProvider.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.AppModel.Internal.AppFactories;
2 |
3 | namespace NetDaemon.AppModel.Internal.AppFactoryProviders;
4 |
5 | internal sealed class SingleAppFactoryProvider : IAppFactoryProvider
6 | {
7 | private readonly IAppFactory _factory;
8 |
9 | private SingleAppFactoryProvider(IAppFactory factory)
10 | {
11 | _factory = factory;
12 | }
13 |
14 | public IReadOnlyCollection GetAppFactories()
15 | {
16 | return new[] { _factory };
17 | }
18 |
19 | public static IAppFactoryProvider Create(Func func,
20 | string? id = default, bool? focus = default) where TAppType : class
21 | {
22 | var factory = FuncAppFactory.Create(func, id, focus);
23 | return new SingleAppFactoryProvider(factory);
24 | }
25 |
26 | public static IAppFactoryProvider Create(Type type, string? id = default, bool? focus = default)
27 | {
28 | var factory = FuncAppFactory.Create(type, id, focus);
29 | return new SingleAppFactoryProvider(factory);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/AppModel.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Internal;
2 |
3 | ///
4 | /// This class serves as a factory for creating and initializing new ApplicationContexts
5 | ///
6 | internal class AppModelImpl : IAppModel
7 | {
8 | private readonly IServiceProvider _provider;
9 |
10 | public AppModelImpl(IServiceProvider provider)
11 | {
12 | _provider = provider;
13 | }
14 |
15 | public async Task LoadNewApplicationContext(CancellationToken cancellationToken)
16 | {
17 | // Create a new AppModelContext
18 | var appModelContext = _provider.GetRequiredService();
19 | await appModelContext.InitializeAsync(cancellationToken);
20 | return appModelContext;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Config/AppConfig.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace NetDaemon.AppModel.Internal.Config;
4 |
5 | internal class AppConfig : IAppConfig where T : class, new()
6 | {
7 | public AppConfig(IConfiguration config, IConfigurationBinding configBinder, ILogger> logger)
8 | {
9 | var type = typeof(T);
10 | var section = config.GetSection(type.FullName!);
11 |
12 | if (!section.Exists())
13 | {
14 | logger.LogWarning("The configuration for {Type} is not found. Please add config.", typeof(T).FullName);
15 | Value = new T();
16 | }
17 | else
18 | {
19 | Value = configBinder.ToObject(section) ?? new T();
20 | }
21 | }
22 |
23 | public T Value { get; }
24 | }
25 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Config/ConfigurationBinding.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace NetDaemon.AppModel.Internal.Config;
4 |
5 | ///
6 | /// Wrapper around the ConfigurationBinder to make it available from the service provider
7 | /// and to inject the IServiceProvider to allow instancing objects that are registered services
8 | ///
9 | internal class ConfigurationBinding(IServiceProvider provider) : IConfigurationBinding
10 | {
11 | private readonly IServiceProvider _provider = provider;
12 |
13 | public T? ToObject(IConfiguration configuration)
14 | {
15 | return configuration.Get(_provider);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Config/IConfigurationBinding.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace NetDaemon.AppModel.Internal.Config;
4 |
5 | ///
6 | /// Interface for configuration binding to object
7 | ///
8 | internal interface IConfigurationBinding
9 | {
10 | T? ToObject(IConfiguration configuration);
11 | }
12 |
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Config/Yaml/YamlConfigurationProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace NetDaemon.AppModel.Internal.Config;
4 |
5 | internal class YamlConfigurationProvider : FileConfigurationProvider
6 | {
7 | public YamlConfigurationProvider(YamlConfigurationSource source) : base(source)
8 | {
9 | }
10 |
11 | public override void Load(Stream stream)
12 | {
13 | var parser = new YamlConfigurationFileParser();
14 |
15 | Data = parser.Parse(stream);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Config/Yaml/YamlConfigurationSource.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Configuration;
2 |
3 | namespace NetDaemon.AppModel.Internal.Config;
4 |
5 | internal class YamlConfigurationSource : FileConfigurationSource
6 | {
7 | public override IConfigurationProvider Build(IConfigurationBuilder builder)
8 | {
9 | FileProvider ??= builder.GetFileProvider();
10 | return new YamlConfigurationProvider(this);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/Internal/Context/ApplicationScope.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.AppModel.Internal;
2 |
3 | // Helper class to make ApplicationContext resolvable per scope
4 | internal class ApplicationScope
5 | {
6 | private ApplicationContext? _applicationContext;
7 |
8 | public ApplicationContext ApplicationContext
9 | {
10 | get => _applicationContext ??
11 | throw new InvalidOperationException("ApplicationScope.ApplicationContext has not been initialized yet");
12 | set => _applicationContext = value;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/AppModel/NetDaemon.AppModel/README.md:
--------------------------------------------------------------------------------
1 | # AppModel design
2 |
3 | This is the design of the AppModel.
4 |
5 |
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Debug/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System.Threading.Tasks;
2 | global using Microsoft.Extensions.DependencyInjection;
3 | global using Microsoft.Extensions.Logging;
4 | global using Microsoft.Extensions.Hosting;
5 | global using Microsoft.Extensions.Options;
6 | global using NetDaemon.Client;
7 | global using NetDaemon.Client.Extensions;
8 | global using NetDaemon.Client.HomeAssistant.Extensions;
9 | global using NetDaemon.Client.HomeAssistant.Model;
10 | global using NetDaemon.Client.Settings;
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Debug/Program.cs:
--------------------------------------------------------------------------------
1 | using NetDaemon.HassClient.Debug;
2 |
3 | await Host.CreateDefaultBuilder(args)
4 | .ConfigureServices((context, services) =>
5 | {
6 | services.Configure(context.Configuration.GetSection("HomeAssistant"));
7 | services.AddHostedService();
8 | services.AddHomeAssistantClient();
9 | })
10 | .Build()
11 | .RunAsync()
12 | .ConfigureAwait(false);
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Debug/_appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "Microsoft": "Warning"
6 | }
7 | },
8 | "HomeAssistant": {
9 | "Host": "ENTER YOUR IP TO Development Home Assistant here",
10 | "Port": 8124,
11 | "Ssl": false,
12 | "Token": "ENTER YOUR TOKEN",
13 | "InsecureBypassCertificateErrors": false
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Debug/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning"
6 | }
7 | },
8 | "HomeAssistant": {
9 | "Host": "localhost",
10 | "Port": 8123,
11 | "Ssl": false,
12 | "Token": "",
13 | "InsecureBypassCertificateErrors": false
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Tests/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 | dotnet_diagnostic.xUnit1030.severity = none
3 | #Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly
4 | dotnet_diagnostic.CA1861.severity = none
5 |
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Tests/ExtensionsTest/JsonElementExtensionTest.cs:
--------------------------------------------------------------------------------
1 | namespace NetDaemon.HassClient.Tests.ExtensionsTest;
2 |
3 | public class JsonElementExtensionTest
4 | {
5 | [Fact]
6 | public void TestToJsonElementShouldReturnCorrectElement()
7 | {
8 | var cmd = new SimpleCommand("get_services");
9 |
10 | var element = cmd.ToJsonElement();
11 |
12 | element!.Value.GetProperty("type").ToString()
13 | .Should()
14 | .Be("get_services");
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Client/NetDaemon.HassClient.Tests/ExtensionsTest/MqttEntityManagerTests/ByteArrayHelperTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using NetDaemon.Extensions.MqttEntityManager.Helpers;
3 |
4 | namespace NetDaemon.HassClient.Tests.ExtensionsTest.MqttEntityManagerTests;
5 |
6 | public class ByteArrayHelperTests
7 | {
8 | sealed class GoodData : IEnumerable