├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── .gitignore ├── CHANGELOG.md ├── Dirigera ├── CHANGELOG.md ├── DeviceController.cs ├── Devices │ ├── Device.cs │ ├── EnvironmentSensor.cs │ ├── Gateway.cs │ └── Light.cs ├── Dirigera.csproj ├── DirigeraController.cs ├── README.md └── UserController.cs ├── LICENSE ├── README.md ├── Readme.legacy.md ├── TradFri.sln ├── Tradfri ├── Controllers │ ├── DeviceController.cs │ ├── GatewayController.cs │ ├── GroupController.cs │ └── SmartTaskController.cs ├── Extensions │ ├── CoapClientExtension.cs │ ├── CoapService.cs │ ├── ColorExtension.cs │ ├── EnumExtension.cs │ └── MicrosecondEpochConverter.cs ├── Models │ ├── Constants.cs │ ├── GatewayInfo.cs │ ├── TradfriAuth.cs │ ├── TradfriDevice.cs │ ├── TradfriGroup.cs │ ├── TradfriMood.cs │ └── TradfriSmartTask.cs ├── Tradfri.csproj └── TradfriController.cs ├── TradfriTerminalUI ├── MainLoop.cs ├── Program.cs └── TradfriTerminalUI.csproj ├── TradfriTest ├── BaseTradfriTest.cs ├── ConnectTest.cs ├── Controllers │ ├── DeviceControllerTest.cs │ ├── GatewayControllerTest.cs │ └── GroupControllerTest.cs ├── TradfriControllerTest.cs └── TradfriTest.csproj ├── TradfriUI ├── App.config ├── EnterGatewayPSK.Designer.cs ├── EnterGatewayPSK.cs ├── EnterGatewayPSK.resx ├── Main.Designer.cs ├── Main.cs ├── Main.resx ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Settings │ └── UserData.cs ├── TradfriUI.csproj ├── bulbIcon.ico └── packages.config └── roslynator.ruleset /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] - Subject" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **IKEA Tradfri Gateway (please complete the following information):** 32 | - Firmware Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature] - Subject" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file, starting with version 1.1.0.x 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.6.0.x] - 2024-07-07 10 | ### Changed by [@mjwsteenbergen](https://github.com/mjwsteenbergen) 11 | - Updated ApiLibs and CoAP nugets for Tradfri project 12 | - Added Dirigera support in a separate library - [Tomidix.Dirigera](https://www.nuget.org/packages/Tomidix.Dirigera) 13 | - Added TerminalUI project 14 | 15 | ## [1.5.0.x] - 2020-08-10 16 | ### Changed by [@tomidix](https://github.com/tomidix) 17 | - Added rename support for devices and groups 18 | - Updated ApiLibs and CoAP nugets 19 | - UI project updated accordingly 20 | 21 | ## [1.4.0.x] - 2020-05-28 22 | ### Changed by [@bjornpoppe](https://github.com/bjornpoppe) 23 | - Added support for blinds 24 | 25 | ## [1.3.0.x] - 2020-05-25 26 | ### Changed by [@tomidix](https://github.com/tomidix) 27 | - Implemented possibility to add a new device to gateway (via Gateway controller) 28 | - Implemented possibility to add a secondary control device to a group (via Gateway controller), which is not working anymore on Ikea Trådfri Android app 29 | - Added example to UI project 30 | 31 | ## [1.2.4.x] - 2020-03-16 32 | ### Changed by [@dominikjancik](https://github.com/dominikjancik) 33 | - Implemented possibility to set a color by Hue and Saturation 34 | - Added support for Transition time - ability to specify an optional fade duration for various color setting methods 35 | 36 | ## [1.2.3.x] - 2020-03-14 37 | ### Changed by [@tomidix](https://github.com/tomidix) & [@dominikjancik](https://github.com/dominikjancik) 38 | - Implemented [#25](https://github.com/tomidix/CSharpTradFriLibrary/issues/25) - Wrapper for setting CIE Yxy colors 39 | - Credits for testing and fixes to [dominikjancik](https://github.com/dominikjancik) 40 | - Fixed [#36](https://github.com/tomidix/CSharpTradFriLibrary/issues/36) - GenerateAppSecret Timeout lowered (to 10s) and now throws a custom timeout message 41 | 42 | ## [1.2.2.x] - 2019-11-21 43 | ### Changed by [@tomidix](https://github.com/tomidix) 44 | - Fixed [#30](https://github.com/tomidix/CSharpTradFriLibrary/issues/30) - Custom mood activation not working properly when using mood with different settings for multiple bulbs in group 45 | - Added TransitionTime property to TradfriMoodProperties 46 | 47 | ## [1.2.1.x] - 2019-10-16 48 | ### Changed by [@tomidix](https://github.com/tomidix) 49 | - Nuget packages updated because of [PeterO.Cbor vulnerability](https://github.com/peteroupc/CBOR/security/advisories/GHSA-cxw4-9qv9-vx5h) which was fixed in v4.0 50 | 51 | ## [1.2.0.x] - 2019-07-01 52 | ### Changed by [@johanjonsson1](https://github.com/johanjonsson1) 53 | - Added basic control outlet support 54 | 55 | ## [1.1.0.x] - 2019-03-23 56 | ### Changed by [@tomidix](https://github.com/tomidix) 57 | - Added UI example project 58 | - Rewrite TradfriDevice Controller ObserveDevice method 59 | - Fixed Issue [CoapClient instance needed for ObserveDevice method on DeviceController #21](https://github.com/tomidix/CSharpTradFriLibrary/issues/21) 60 | - Fixed Issue [Lights are not turning on/off when observing device #22](https://github.com/tomidix/CSharpTradFriLibrary/issues/22) 61 | - Minor tweaks and additions 62 | -------------------------------------------------------------------------------- /Dirigera/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file, starting with version 1.0.1.x 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.0.1.x] - 2024-07-07 10 | ### Changed by [@mjwsteenbergen](https://github.com/mjwsteenbergen) 11 | - Initial Dirigera project version by [@mjwsteenbergen] -------------------------------------------------------------------------------- /Dirigera/DeviceController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs.General; 2 | using Tomidix.NetStandard.Dirigera.Devices; 3 | using Newtonsoft.Json; 4 | using ApiLibs; 5 | 6 | namespace Tomidix.NetStandard.Dirigera; 7 | 8 | public class DeviceController : SubService 9 | { 10 | private Service service; 11 | public DeviceController(DirigeraController controller) : base(controller) 12 | { 13 | service = controller; 14 | } 15 | 16 | public Task> GetDevices() => MakeRequest>("devices/").ContinueWith((item) => item.Result.Select(i => 17 | { 18 | i.service = service; 19 | return i; 20 | }).ToList()); 21 | public Task GetDevicesJson() => MakeRequest("devices/"); 22 | 23 | public Task ChangeAttributes(string deviceId, PostingAttributes attributes) where T : PostingAttributesProperties => MakeRequest("devices/" + deviceId, Call.PATCH, content: new object[] { attributes }, statusCode: System.Net.HttpStatusCode.Accepted); 24 | 25 | public Task Toggle(Light l) => Toggle(l.Id, !l.Attributes.IsOn).ContinueWith((a) => 26 | { 27 | l.Attributes.IsOn = !l.Attributes.IsOn; 28 | return a.Result; 29 | }); 30 | 31 | public Task Toggle(string id, bool isOn) => ChangeAttributes(id, new PostingAttributes(new ToggleProperty 32 | { 33 | IsOn = isOn 34 | })); 35 | 36 | public Task SetLightLevel(Light l, int level) => SetLightLevel(l.Id, level).ContinueWith((a) => 37 | { 38 | l.Attributes.LightLevel = level; 39 | return a.Result; 40 | }); 41 | 42 | public Task SetLightLevel(string id, int level) => ChangeAttributes(id, new PostingAttributes(new LightLevelProperty 43 | { 44 | LightLevel = level 45 | })); 46 | 47 | 48 | public Task SetLightTemperature(Light l, int temperature) => Toggle(l.Id, !l.Attributes.IsOn).ContinueWith((a) => 49 | { 50 | l.Attributes.ColorTemperature = temperature; 51 | return a.Result; 52 | }); 53 | 54 | public Task SetLightTemperature(string id, int temperature) => ChangeAttributes(id, new PostingAttributes(new LightTemperatureProperty 55 | { 56 | ColorTemperature = temperature 57 | })); 58 | 59 | } 60 | 61 | public class PostingAttributes where T : PostingAttributesProperties 62 | { 63 | public PostingAttributes(T properties, int? transitionTime = null) 64 | { 65 | // TransitionTime = transitionTime; 66 | Attributes = properties; 67 | } 68 | 69 | [JsonProperty("attributes")] 70 | public T Attributes { get; set; } 71 | 72 | 73 | [JsonProperty("transitionTime")] 74 | public int? TransitionTime { get; set; } 75 | } 76 | 77 | public interface PostingAttributesProperties 78 | { 79 | 80 | } 81 | 82 | public class ToggleProperty : PostingAttributesProperties 83 | { 84 | [JsonProperty("isOn")] 85 | public required bool IsOn { get; set; } 86 | } 87 | 88 | 89 | public class LightLevelProperty : PostingAttributesProperties 90 | { 91 | [JsonProperty("lightLevel")] 92 | public required int LightLevel { get; set; } 93 | } 94 | 95 | public class LightTemperatureProperty : PostingAttributesProperties 96 | { 97 | [JsonProperty("colorTemperature")] 98 | public required int ColorTemperature { get; set; } 99 | } 100 | 101 | 102 | -------------------------------------------------------------------------------- /Dirigera/Devices/Device.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ApiLibs.General; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace Tomidix.NetStandard.Dirigera.Devices; 7 | 8 | public class DeviceConverter : JsonConverter 9 | 10 | { 11 | public override Device ReadJson(JsonReader reader, Type objectType, Device existingValue, bool hasExistingValue, JsonSerializer serializer) 12 | { 13 | JToken jObject = JToken.ReadFrom(reader); 14 | 15 | string type = null; 16 | 17 | try 18 | { 19 | if (jObject.Type is not JTokenType.None and not JTokenType.Null) 20 | { 21 | type = jObject["deviceType"].ToObject(); 22 | } 23 | } 24 | catch { } 25 | 26 | Device result = type switch 27 | { 28 | "environmentSensor" => new EnvironmentSensor(), 29 | "gateway" => new Gateway(), 30 | "light" => new Light(), 31 | _ => throw new ArgumentOutOfRangeException("Cannot convert type " + type + jObject.ToString()) 32 | }; 33 | 34 | 35 | serializer.Populate(jObject.CreateReader(), result); 36 | return result; 37 | } 38 | 39 | public override bool CanWrite => true; 40 | 41 | public void Serialize(PropertyInfo info, Device value, JsonWriter writer) 42 | { 43 | var val = info.GetValue(value); 44 | 45 | if (val != null) 46 | { 47 | var customAttributes = (JsonPropertyAttribute[])info.GetCustomAttributes(typeof(JsonPropertyAttribute), true); 48 | if (customAttributes.Length > 0) 49 | { 50 | var myAttribute = customAttributes[0]; 51 | string propName = myAttribute.PropertyName; 52 | 53 | if (!string.IsNullOrEmpty(propName)) 54 | { 55 | writer.WritePropertyName(propName); 56 | } 57 | else 58 | { 59 | writer.WritePropertyName(info.Name); 60 | } 61 | // TODO: Do something with the value 62 | } 63 | else 64 | { 65 | writer.WritePropertyName(info.Name); 66 | } 67 | 68 | writer.WriteRawValue(JsonConvert.SerializeObject(val, Formatting.None, new JsonSerializerSettings 69 | { 70 | NullValueHandling = NullValueHandling.Ignore 71 | })); 72 | } 73 | } 74 | 75 | public override void WriteJson(JsonWriter writer, Device value, JsonSerializer serializer) 76 | { 77 | throw new System.NotImplementedException(); 78 | } 79 | } 80 | 81 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 82 | 83 | [JsonConverter(typeof(DeviceConverter))] 84 | public class Device : ObjectSearcher 85 | { 86 | [JsonProperty("id")] 87 | public string Id { get; set; } 88 | 89 | [JsonProperty("type")] 90 | public string Type { get; set; } 91 | 92 | [JsonProperty("deviceType")] 93 | public string DeviceType { get; set; } 94 | 95 | [JsonProperty("createdAt")] 96 | public string CreatedAt { get; set; } 97 | 98 | [JsonProperty("isReachable")] 99 | public bool IsReachable { get; set; } 100 | 101 | [JsonProperty("lastSeen")] 102 | public string LastSeen { get; set; } 103 | } 104 | 105 | public partial class Attributes 106 | { 107 | [JsonProperty("customName")] 108 | public string CustomName { get; set; } 109 | 110 | [JsonProperty("model")] 111 | public string Model { get; set; } 112 | 113 | [JsonProperty("manufacturer")] 114 | public string Manufacturer { get; set; } 115 | 116 | [JsonProperty("firmwareVersion")] 117 | public string FirmwareVersion { get; set; } 118 | 119 | [JsonProperty("hardwareVersion")] 120 | public string HardwareVersion { get; set; } 121 | 122 | [JsonProperty("serialNumber")] 123 | public string SerialNumber { get; set; } 124 | 125 | [JsonProperty("productCode")] 126 | public string ProductCode { get; set; } 127 | 128 | [JsonProperty("identifyPeriod")] 129 | public long IdentifyPeriod { get; set; } 130 | 131 | [JsonProperty("identifyStarted")] 132 | public DateTimeOffset IdentifyStarted { get; set; } 133 | 134 | [JsonProperty("permittingJoin")] 135 | public bool PermittingJoin { get; set; } 136 | 137 | [JsonProperty("otaPolicy")] 138 | public string OtaPolicy { get; set; } 139 | 140 | [JsonProperty("otaProgress")] 141 | public long OtaProgress { get; set; } 142 | 143 | [JsonProperty("otaScheduleEnd")] 144 | public string OtaScheduleEnd { get; set; } 145 | 146 | [JsonProperty("otaScheduleStart")] 147 | public string OtaScheduleStart { get; set; } 148 | 149 | [JsonProperty("otaState")] 150 | public string OtaState { get; set; } 151 | 152 | [JsonProperty("otaStatus")] 153 | public string OtaStatus { get; set; } 154 | } -------------------------------------------------------------------------------- /Dirigera/Devices/EnvironmentSensor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using ApiLibs.General; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace Tomidix.NetStandard.Dirigera.Devices; 7 | 8 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 9 | 10 | public class EnvironmentSensor : Device 11 | { 12 | [JsonProperty("attributes")] 13 | public EnvironmentSensorAttributes Attributes { get; set; } 14 | 15 | [JsonProperty("room")] 16 | public Room Room { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return Attributes.CustomName; 21 | } 22 | } 23 | 24 | public partial class Room 25 | { 26 | [JsonProperty("id")] 27 | public string Id { get; set; } 28 | 29 | [JsonProperty("name")] 30 | public string Name { get; set; } 31 | 32 | [JsonProperty("color")] 33 | public string Color { get; set; } 34 | 35 | [JsonProperty("icon")] 36 | public string Icon { get; set; } 37 | } 38 | 39 | public class EnvironmentSensorAttributes : Attributes 40 | { 41 | [JsonProperty("currentTemperature")] 42 | public int CurrentTemperature { get; set; } 43 | 44 | [JsonProperty("currentRH")] 45 | public int CurrentRH { get; set; } 46 | 47 | [JsonProperty("currentPM25")] 48 | public int CurrentPM25 { get; set; } 49 | 50 | [JsonProperty("maxMeasuredPM25")] 51 | public int MaxMeasuredPM25 { get; set; } 52 | 53 | [JsonProperty("minMeasuredPM25")] 54 | public int MinMeasuredPM25 { get; set; } 55 | 56 | [JsonProperty("vocIndex")] 57 | public int VocIndex { get; set; } 58 | } -------------------------------------------------------------------------------- /Dirigera/Devices/Gateway.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Tomidix.NetStandard.Dirigera.Devices; 4 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 5 | 6 | public class Gateway : Device 7 | { 8 | [JsonProperty("relationId")] 9 | public string RelationId { get; set; } 10 | 11 | [JsonProperty("deviceSet")] 12 | public object[] DeviceSet { get; set; } 13 | 14 | [JsonProperty("remoteLinks")] 15 | public object[] RemoteLinks { get; set; } 16 | 17 | [JsonProperty("attributes")] 18 | public GatewayAttributes Attributes { get; set; } 19 | 20 | public override string ToString() 21 | { 22 | return Attributes.CustomName; 23 | } 24 | } 25 | 26 | public partial class GatewayAttributes : Attributes 27 | { 28 | [JsonProperty("backendConnected")] 29 | public bool BackendConnected { get; set; } 30 | 31 | [JsonProperty("backendConnectionPersistent")] 32 | public bool BackendConnectionPersistent { get; set; } 33 | 34 | [JsonProperty("backendOnboardingComplete")] 35 | public bool BackendOnboardingComplete { get; set; } 36 | 37 | [JsonProperty("backendRegion")] 38 | public string BackendRegion { get; set; } 39 | 40 | [JsonProperty("backendCountryCode")] 41 | public string BackendCountryCode { get; set; } 42 | 43 | [JsonProperty("userConsents")] 44 | public UserConsent[] UserConsents { get; set; } 45 | 46 | [JsonProperty("logLevel")] 47 | public long LogLevel { get; set; } 48 | 49 | [JsonProperty("coredump")] 50 | public bool Coredump { get; set; } 51 | 52 | [JsonProperty("timezone")] 53 | public string Timezone { get; set; } 54 | 55 | [JsonProperty("nextSunSet")] 56 | public object NextSunSet { get; set; } 57 | 58 | [JsonProperty("nextSunRise")] 59 | public object NextSunRise { get; set; } 60 | 61 | [JsonProperty("homestate")] 62 | public string Homestate { get; set; } 63 | 64 | [JsonProperty("countryCode")] 65 | public string CountryCode { get; set; } 66 | 67 | [JsonProperty("isOn")] 68 | public bool IsOn { get; set; } 69 | } 70 | 71 | public partial class UserConsent 72 | { 73 | [JsonProperty("name")] 74 | public string Name { get; set; } 75 | 76 | [JsonProperty("value")] 77 | public string Value { get; set; } 78 | } 79 | 80 | #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 81 | -------------------------------------------------------------------------------- /Dirigera/Devices/Light.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Tomidix.NetStandard.Dirigera.Devices; 4 | 5 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. 6 | 7 | public class Light : Device 8 | { 9 | [JsonProperty("attributes")] 10 | public LightAttributes Attributes { get; set; } 11 | 12 | [JsonProperty("room")] 13 | public Room Room { get; set; } 14 | 15 | public override string ToString() 16 | { 17 | var state = Attributes.IsOn ? Attributes.LightLevel.ToString() : "Off"; 18 | return Attributes.CustomName + $"[{state}]"; 19 | } 20 | 21 | public Task Toggle() 22 | { 23 | return (service as DirigeraController)?.DeviceController.Toggle(this) ?? Task.CompletedTask; 24 | } 25 | 26 | public Task SetLightLevel(int lightLevel) 27 | { 28 | return (service as DirigeraController)?.DeviceController.SetLightLevel(this, lightLevel) ?? Task.CompletedTask; 29 | } 30 | 31 | public Task SetLightTemperature(int lightTemperature) 32 | { 33 | return (service as DirigeraController)?.DeviceController.SetLightTemperature(this, lightTemperature) ?? Task.CompletedTask; 34 | } 35 | } 36 | 37 | public class LightAttributes : Attributes 38 | { 39 | 40 | [JsonProperty("isOn")] 41 | public bool IsOn { get; set; } 42 | 43 | [JsonProperty("startupOnOff")] 44 | public string StartupOnOff { get; set; } 45 | 46 | [JsonProperty("lightLevel")] 47 | public long LightLevel { get; set; } 48 | 49 | [JsonProperty("startUpCurrentLevel")] 50 | public long StartUpCurrentLevel { get; set; } 51 | 52 | [JsonProperty("colorMode")] 53 | public string ColorMode { get; set; } 54 | 55 | [JsonProperty("startupTemperature")] 56 | public long StartupTemperature { get; set; } 57 | 58 | [JsonProperty("colorTemperature")] 59 | public long ColorTemperature { get; set; } 60 | 61 | [JsonProperty("colorTemperatureMax")] 62 | public long ColorTemperatureMax { get; set; } 63 | 64 | [JsonProperty("colorTemperatureMin")] 65 | public long ColorTemperatureMin { get; set; } 66 | } -------------------------------------------------------------------------------- /Dirigera/Dirigera.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | Tomidix.Dirigera 8 | https://github.com/dominicusmento/CSharpTradFriLibrary/tree/master/Dirigera 9 | https://github.com/dominicusmento/CSharpTradFriLibrary/blob/master/LICENSE 10 | https://github.com/dominicusmento/CSharpTradFriLibrary.git 11 | git 12 | ikea;tradfri;home;zigbee;csharp;net8;dirigera 13 | This is an open source helper library which you can use to communicate with your ZigBee-based Ikea Dirigera gateway 14 | mjwsteenbergen;tomidix 15 | 1.0.1.0 16 | Tomidix.NetStandard.Dirigera 17 | Tomidix.Dirigera 18 | false 19 | true 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Dirigera/DirigeraController.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Text; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using ApiLibs; 5 | using Newtonsoft.Json; 6 | 7 | namespace Tomidix.NetStandard.Dirigera; 8 | 9 | public class DirigeraController : Service 10 | { 11 | public DirigeraController(string hostUrl) : base(hostUrl + ":8443/v1") 12 | { 13 | UserController = new UserController(this); 14 | DeviceController = new DeviceController(this); 15 | } 16 | 17 | public DirigeraController(string hostUrl, string token) : this(hostUrl) 18 | { 19 | AddStandardHeader(new Param("Authorization", "Bearer " + token)); 20 | } 21 | 22 | public UserController UserController { get; set; } 23 | public DeviceController DeviceController { get; set; } 24 | 25 | public static readonly string CODE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; 26 | public static readonly int CODE_LENGTH = 128; 27 | public static readonly string AUDIENCE = "homesmart.local"; 28 | public static readonly string CHALLENGE_METHOD = "S256"; 29 | 30 | public static string generateCodeVerifier() 31 | { 32 | StringBuilder res = new(); 33 | Random random = new(); 34 | 35 | for (int i = 0; i < CODE_LENGTH; i++) 36 | { 37 | res.Append(CODE_ALPHABET.ElementAt(random.Next(0, CODE_ALPHABET.Length))); 38 | } 39 | return res.ToString(); 40 | 41 | } 42 | 43 | public static string calculateCodeChallenge(string code) 44 | { 45 | SHA256 mySHA256 = SHA256.Create(); 46 | byte[] byteHash = mySHA256.ComputeHash(Encoding.ASCII.GetBytes(code)); 47 | var base64 = Base64SafeEncode(byteHash); 48 | return base64.Substring(0, base64.Length - 1); 49 | } 50 | 51 | public Task Authorize(string challenge) 52 | { 53 | return MakeRequest("oauth/authorize", Call.GET, new List { 54 | new("audience", AUDIENCE), 55 | new("response_type", "code"), 56 | new("code_challenge", challenge), 57 | new("code_challenge_method", CHALLENGE_METHOD) 58 | }); 59 | } 60 | 61 | public Task Pair(string dirigeraCode, string generatedCode, string name) 62 | { 63 | return MakeRequest("oauth/token", Call.POST, new List { 64 | new("code", dirigeraCode), 65 | new("name", name), 66 | new("grant_type", "authorization_code"), 67 | new("code_verifier", generatedCode), 68 | }); 69 | } 70 | 71 | public static string Base64SafeEncode(byte[] encbuff) 72 | { 73 | return System.Convert.ToBase64String(encbuff).Replace("=", ",").Replace("+", "-").Replace("/", "_"); 74 | } 75 | } 76 | 77 | public class Authorize 78 | { 79 | [JsonProperty("code")] 80 | public required string Code { get; set; } 81 | } 82 | 83 | public class TokenObject 84 | { 85 | [JsonProperty("access_token")] 86 | public required string AccessToken { get; set; } 87 | } 88 | -------------------------------------------------------------------------------- /Dirigera/README.md: -------------------------------------------------------------------------------- 1 | ## C# Tradfri Library 2 | This is a .NET8 library to communicate with the [IKEA Dirigera Hub](https://www.ikea.com/gb/en/p/dirigera-hub-for-smart-products-white-smart-50503409/) (Dirigera) smart home Gateway. Using this library you can, by communicating with the gateway, control devices. 3 | 4 | ![Build Status](https://mmustapic.visualstudio.com/CSharp/_apis/build/status/Dirigera-net8) [![GitHub last commit](https://img.shields.io/github/last-commit/tomidix/CSharpTradFriLibrary.svg)]() [![NuGet downloads](https://img.shields.io/nuget/dt/Tomidix.Dirigera.svg)](https://www.nuget.org/packages/Tomidix.Dirigera) 5 | 6 | This library is still in development, latest version: 7 | 8 | [![NuGet downloads](https://img.shields.io/nuget/v/Tomidix.Dirigera.svg)](https://www.nuget.org/packages/Tomidix.Dirigera) 9 | 10 | ## Nuget 11 | [Tomidix.Dirigera](https://www.nuget.org/packages/Tomidix.Dirigera) 12 | 13 | ## 1. Usage 14 | - 15 | 16 | ## 2. Example 17 | - 18 | 19 | ## 3. Acknowledgements 20 | This is an implementation based on analysis [I](https://github.com/tomidix/) found [here](https://github.com/ggravlingen/pytradfri) by [ggravlingen](https://github.com/ggravlingen/) and [here](https://bitsex.net/software/2017/coap-endpoints-on-ikea-tradfri/) by [vidarlo](https://bitsex.net/). 21 | 22 | ## 4. Authors 23 | - [mjwsteenbergen](https://github.com/mjwsteenbergen) - Initial work 24 | - [tomidix](https://github.com/tomidix) - setup of build procedures, nuget package publish 25 | 26 | ## 6. Changelog 27 | You can check the changelog [here](https://github.com/tomidix/CSharpTradFriLibrary/Dirigera/blob/master/CHANGELOG.md). -------------------------------------------------------------------------------- /Dirigera/UserController.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers.Text; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using ApiLibs; 5 | using ApiLibs.General; 6 | using ApiLibs.GitHub; 7 | using Newtonsoft.Json; 8 | 9 | namespace Tomidix.NetStandard.Dirigera; 10 | 11 | public class UserController : SubService 12 | { 13 | public UserController(DirigeraController controller) : base(controller) 14 | { 15 | } 16 | 17 | public Task> GetUsers() => MakeRequest>("users/"); 18 | public Task GetMe() => MakeRequest("users/me/"); 19 | 20 | } 21 | 22 | public class User 23 | { 24 | [JsonProperty("uid")] 25 | public required string UID { get; set; } 26 | 27 | [JsonProperty("name")] 28 | public required string Name { get; set; } 29 | 30 | [JsonProperty("audience")] 31 | public required string Audience { get; set; } 32 | 33 | [JsonProperty("email")] 34 | public required string Email { get; set; } 35 | 36 | // [JsonProperty("createdTimestamp")] 37 | // public string CreatedTimestamp { get; set; } 38 | 39 | [JsonProperty("verifiedUid")] 40 | public required string VerifiedUid { get; set; } 41 | 42 | [JsonProperty("role")] 43 | public required string Role { get; set; } 44 | 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## C# Tradfri Library 2 | This is a .NET Standard (2.0) library to communicate with the [IKEA Trådfri](http://www.ikea.com/us/en/catalog/products/00337813/) (Tradfri) ZigBee-based Gateway. Using this library you can, by communicating with the gateway, control IKEA lights (including the RGB ones). 3 | 4 | ![Build Status](https://mmustapic.visualstudio.com/CSharp/_apis/build/status/CSharpTradFriLibrary-netStandard) [![GitHub last commit](https://img.shields.io/github/last-commit/tomidix/CSharpTradFriLibrary.svg)]() [![NuGet downloads](https://img.shields.io/nuget/dt/Tomidix.CSharpTradFriLibrary.svg)](https://www.nuget.org/packages/Tomidix.CSharpTradFriLibrary) 5 | 6 | This library is still in development, latest version: 7 | 8 | [![NuGet downloads](https://img.shields.io/nuget/v/Tomidix.CSharpTradFriLibrary.svg)](https://www.nuget.org/packages/Tomidix.CSharpTradFriLibrary) 9 | 10 | Latest Gateway version tested and working - 1.10.43. 11 | 12 | - Get information on the gateway 13 | - Observe lights, groups and other resources 14 | - Get notified when lights, groups and other resources change 15 | - List all devices connected to gateway 16 | - List all lights and get attributes of lights (name, state, color temp, dimmer level etc) 17 | - Change attribute values of lights (currently only turn them on/off) 18 | - Restart and reset gateway 19 | 20 | 21 | ## 1. Usage 22 | Download the [nuget package](https://www.nuget.org/packages/Tomidix.CSharpTradFriLibrary) v1.0.0.x+. You will need the following values: 23 | - **gatewayName** is your nickname to your gateway, currently this doesn't have an effect. It is here if you have access to multiple gateways so you can easily differentiate them. 24 | - **gatewayIp** is the IP-address to your gateway. 25 | - **appName** your name for your application. Be creative but wise with characters. 26 | - **appSecret** Instead of reusing key written on the back of your IKEA Tradfri Gateway you have to acquire new secret key specific for your application and then you should use appName and appSecret to connect. TradfriUI has an example on this, but it still does not implement encryption for the appSecret. 27 | 28 | 29 | ## 2. Example 30 | From Gateway version 1.8.25 you can't use original PSK to connect to gateway anymore. You can only use it to create an application secret for your application which you can later reuse with it. 31 | 32 | ```csharp 33 | // recommended 34 | // This line should only be called ONCE!!! per applicationName -> you define applicationName as you want 35 | // Gateway generates one appSecret key per applicationName 36 | TradfriAuth appSecret = controller.GenerateAppSecret("GatewaySecret", "ApplicationName"); 37 | 38 | // You should now save programatically appSecret.PSK value (appsettings) and reuse it 39 | // when connecting to your gateway every other time 40 | controller.ConnectAppKey(appSecret.PSK, "ApplicationName"); 41 | 42 | GatewayController gatewayController = controller.GatewayController; 43 | var devices = await gatewayController.GetDeviceObjects(); 44 | 45 | DeviceController deviceController = controller.DeviceController; 46 | await deviceController.SetLight(devices[0], true); 47 | await deviceController.SetColor(devices[0], TradfriColors.SaturatedRed); 48 | 49 | // same works for `controller.GroupController` 50 | ``` 51 | 52 | ## 3. Acknowledgements 53 | This is an implementation based on analysis [I](https://github.com/tomidix/) found [here](https://github.com/ggravlingen/pytradfri) by [ggravlingen](https://github.com/ggravlingen/) and [here](https://bitsex.net/software/2017/coap-endpoints-on-ikea-tradfri/) by [vidarlo](https://bitsex.net/). 54 | 55 | 56 | ## 4. Authors 57 | - [tomidix](https://github.com/tomidix) - Initial work, later features and maintenance 58 | - [coriumalpha](https://github.com/coriumalpha) - Observe method implementation, refactored by tomidix 59 | - [mjwsteenbergen](https://github.com/mjwsteenbergen) - Converted project to .NetStandard 60 | - [johanjonsson1](https://github.com/johanjonsson1) - Basic control outlet support 61 | - [dominikjancik](https://github.com/dominikjancik) - Testing and fixes of Wrapper for setting CIE Yxy colors 62 | - [bjornpoppe](https://github.com/bjornpoppe) - Blind support 63 | 64 | 65 | ## 5. Old library 66 | Old library is still available as nuget (latest version: 0.3.0.22) and won't be updated anymore. 67 | You can still read it's [ReadMe](https://github.com/tomidix/CSharpTradFriLibrary/blob/master/Readme.legacy.md) if you are using it but we recommend to migrate to newer library as soon as you can. 68 | 69 | 70 | ## 6. Changelog 71 | You can check the changelog [here](https://github.com/tomidix/CSharpTradFriLibrary/blob/master/CHANGELOG.md). -------------------------------------------------------------------------------- /Readme.legacy.md: -------------------------------------------------------------------------------- 1 | ## C# TradFri Library Legacy 2 | This is a .NET Framework (4.5) library to communicate with the [IKEA Trådfri](http://www.ikea.com/us/en/catalog/products/00337813/) (Tradfri) ZigBee-based Gateway. Using this library you can, by communicating with the gateway, control IKEA lights (including the RGB ones). 3 | 4 | Latest version: 0.3.0.22 5 | 6 | Latest Gateway version tested and working - 1.4.15. 7 | 8 | - Get information on the gateway 9 | - Observe lights, groups and other resources 10 | - Get notified when lights, groups and other resources change 11 | - List all devices connected to gateway 12 | - List all lights and get attributes of lights (name, state, color temp, dimmer level etc) 13 | - Change attribute values of lights (currently only turn them on/off) 14 | - Restart and reset gateway 15 | 16 | ## 1. Usage 17 | Clone the repository. Open the solution and then the app.config file under the TradFriGui project. 18 | Edit the values in the app.config file: 19 | - **GatewayName** is your nickname to your gateway, currently this doesn't have an effect. It is here if you have access to multiple gateways so you can easily differentiate them. 20 | - **GatewayIP** is the IP-address to your gateway. 21 | - **GatewaySecret** is written on the back of your IKEA Tradfri Gateway. 22 | 23 | After editing app.config you can hit F5 and run the project. You should have a grid displaying devices connected to your Gateway. Upon selecting a row(s) (row!! and not cell) you can turn On or Off your selected devices. 24 | If everything works, you can proceed with investigating the code written in TradFriGui because that's the way how you will use the library. 25 | 26 | *Note - you can also download the [nuget package](https://www.nuget.org/packages/Tomidix.CSharpTradFriLibrary) prior to 1.0.0.0 version and use it in your project the same way I use it in a TradFriGui project.* 27 | 28 | ## 2. TradFriGui 29 | This project serves only as a demo project with an example on how to use the library, it is not intended to be a complete application but you can use it as a starting point for your main project. 30 | 31 | ## 3. TradFriLibrary 32 | This project contains all the models and controllers needed for communication with the Gateway. You don't need to mess with this project unless you want to further investigate it or to participate in library development. 33 | For now, you can build the .dll of the project in release version and add it as a reference to your project. 34 | 35 | ## 4. Acknowledgements 36 | This is an implementation based on analysis [I](https://github.com/tomidix/) found [here](https://github.com/ggravlingen/pytradfri) by [ggravlingen](https://github.com/ggravlingen/) and [here](https://bitsex.net/software/2017/coap-endpoints-on-ikea-tradfri/) by [vidarlo](https://bitsex.net/). -------------------------------------------------------------------------------- /TradFri.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28302.56 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tradfri", "Tradfri\Tradfri.csproj", "{6B39A6D0-ADF6-4C55-9AEF-00EFE63141C0}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TradfriTest", "TradfriTest\TradfriTest.csproj", "{E2E66E8C-42A3-44A2-B96B-630661879147}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TradfriUI", "TradfriUI\TradfriUI.csproj", "{E74F7B9B-80B3-443B-A34F-EB4A859DD0FF}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TradfriTerminalUI", "TradfriTerminalUI\TradfriTerminalUI.csproj", "{873FD76F-F540-47B5-90EC-9722E1D2D191}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dirigera", "Dirigera\Dirigera.csproj", "{71F17A36-2B01-40FE-9861-CFFEF562A5D5}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {6B39A6D0-ADF6-4C55-9AEF-00EFE63141C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {6B39A6D0-ADF6-4C55-9AEF-00EFE63141C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {6B39A6D0-ADF6-4C55-9AEF-00EFE63141C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {6B39A6D0-ADF6-4C55-9AEF-00EFE63141C0}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {E2E66E8C-42A3-44A2-B96B-630661879147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {E2E66E8C-42A3-44A2-B96B-630661879147}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {E2E66E8C-42A3-44A2-B96B-630661879147}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {E2E66E8C-42A3-44A2-B96B-630661879147}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {E74F7B9B-80B3-443B-A34F-EB4A859DD0FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {E74F7B9B-80B3-443B-A34F-EB4A859DD0FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {E74F7B9B-80B3-443B-A34F-EB4A859DD0FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {E74F7B9B-80B3-443B-A34F-EB4A859DD0FF}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {873FD76F-F540-47B5-90EC-9722E1D2D191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {873FD76F-F540-47B5-90EC-9722E1D2D191}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {873FD76F-F540-47B5-90EC-9722E1D2D191}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {873FD76F-F540-47B5-90EC-9722E1D2D191}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {71F17A36-2B01-40FE-9861-CFFEF562A5D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {71F17A36-2B01-40FE-9861-CFFEF562A5D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {71F17A36-2B01-40FE-9861-CFFEF562A5D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {71F17A36-2B01-40FE-9861-CFFEF562A5D5}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {D4981C63-C624-40AA-BF7C-68D88FD08E92} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Tradfri/Controllers/DeviceController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs.General; 2 | using ApiLibs; 3 | using Com.AugustCellars.CoAP; 4 | using Newtonsoft.Json; 5 | using System; 6 | using System.Net; 7 | using System.Threading.Tasks; 8 | using Tomidix.NetStandard.Tradfri.Extensions; 9 | using Tomidix.NetStandard.Tradfri.Models; 10 | 11 | namespace Tomidix.NetStandard.Tradfri.Controllers 12 | { 13 | public class DeviceController : SubService 14 | { 15 | public DeviceController(TradfriController controller) : base(controller) 16 | { 17 | } 18 | 19 | /// 20 | /// Acquires TradfriDevice object 21 | /// 22 | /// If set to true, than it will ignore existing cached value and ask the gateway for the object 23 | /// 24 | public Task GetTradfriDevice(long id) 25 | { 26 | return MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}"); 27 | } 28 | 29 | /// 30 | /// Renames TradfriDevice object 31 | /// 32 | /// 33 | /// 34 | public Task RenameTradfriDevice(TradfriDevice device) 35 | { 36 | return RenameTradfriDevice(device.ID, device.Name); 37 | } 38 | 39 | /// 40 | /// Renames TradfriDevice by id 41 | /// 42 | /// 43 | /// 44 | /// 45 | public Task RenameTradfriDevice(long id, string newName) 46 | { 47 | if (!string.IsNullOrWhiteSpace(newName)) 48 | { 49 | RenameRequest set = new RenameRequest 50 | { 51 | Name = newName 52 | }; 53 | return MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 54 | } 55 | else 56 | { 57 | throw new Exception("Device cannot be renamed to empty string."); 58 | } 59 | } 60 | 61 | private static bool HasLight(TradfriDevice device) 62 | { 63 | return device?.LightControl != null; 64 | } 65 | 66 | private static bool HasControl(TradfriDevice device) 67 | { 68 | return device?.Control != null; 69 | } 70 | 71 | private static bool HasBlind(TradfriDevice device) 72 | { 73 | return device?.Blind != null; 74 | } 75 | 76 | /// 77 | /// Changes the color of the light device 78 | /// 79 | /// A 80 | /// A color from the class 81 | /// An optional transition duration, defaults to null (no transition) 82 | /// 83 | public async Task SetColor(TradfriDevice device, string value, int? transition = null) 84 | { 85 | await SetColor(device.ID, value, transition); 86 | if (HasLight(device)) 87 | { 88 | device.LightControl[0].ColorHex = value; 89 | } 90 | } 91 | 92 | /// 93 | /// Changes the color of the light device 94 | /// 95 | /// Id of the device 96 | /// A color from the class 97 | /// An optional transition duration, defaults to null (no transition) 98 | /// 99 | public async Task SetColor(long id, string value, int? transition = null) 100 | { 101 | SwitchStateLightRequest set = new SwitchStateLightRequest() 102 | { 103 | Options = new[] 104 | { 105 | new SwitchStateLightRequestOption() 106 | { 107 | Color = value, 108 | TransitionTime = transition 109 | } 110 | } 111 | }; 112 | await MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 113 | } 114 | 115 | /// 116 | /// Changes the color of the light device 117 | /// 118 | /// A 119 | /// Red component, 0-255 120 | /// Green component, 0-255 121 | /// Blue component, 0-255 122 | /// 123 | public async Task SetColor(TradfriDevice device, int r, int g, int b, int? transition = null) 124 | { 125 | (int x, int y) = ColorExtension.CalculateCIEFromRGB(r, g, b); 126 | int intensity = ColorExtension.CalculateIntensity(r, g, b); 127 | 128 | await SetColor(device.ID, x, y, intensity, transition); 129 | if (HasLight(device)) 130 | { 131 | device.LightControl[0].ColorX = x; 132 | device.LightControl[0].ColorY = y; 133 | } 134 | } 135 | 136 | /// 137 | /// Changes the color of the light device 138 | /// 139 | /// Id of the device 140 | /// X component of the color, 0-65535 141 | /// Y component of the color, 0-65535 142 | /// Optional Dimmer, 0-254 143 | /// An optional transition duration, defaults to null (no transition) 144 | /// 145 | public async Task SetColor(long id, int x, int y, int? intensity = null, int? transition = null) 146 | { 147 | SwitchStateLightXYRequest set = new SwitchStateLightXYRequest() 148 | { 149 | Options = new[] 150 | { 151 | new SwitchStateLightXYRequestOption() 152 | { 153 | ColorX = x, 154 | ColorY = y, 155 | LightIntensity = intensity, 156 | TransitionTime = transition 157 | } 158 | } 159 | }; 160 | await MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 161 | } 162 | 163 | /// 164 | /// Changes the color of the light device based on Hue and Saturation values 165 | /// 166 | /// A 167 | /// Hue component of the color, 0-65535 168 | /// Y component of the color, 0-65000 169 | /// Optional Dimmer, 0-254 170 | /// An optional transition duration, defaults to null (no transition) 171 | /// 172 | public async Task SetColorHSV(TradfriDevice device, int hue, int saturation, int? value, int? transition = null) 173 | { 174 | await SetColorHSV(device.ID, hue, saturation, value, transition); 175 | if (HasLight(device)) 176 | { 177 | device.LightControl[0].ColorHue = hue; 178 | device.LightControl[0].ColorSaturation = saturation; 179 | if (value != null) device.LightControl[0].Dimmer = (int)value; 180 | } 181 | } 182 | 183 | /// 184 | /// Changes the color of the light device based on Hue and Saturation values 185 | /// 186 | /// Id of the device 187 | /// Hue component of the color, 0-65535 188 | /// Y component of the color, 0-65000 189 | /// Optional Dimmer, 0-254 190 | /// An optional transition duration, defaults to null (no transition) 191 | /// 192 | public async Task SetColorHSV(long id, int hue, int saturation, int? value = null, int? transition = null) 193 | { 194 | SwitchStateLightHSRequest set = new SwitchStateLightHSRequest() 195 | { 196 | Options = new[] 197 | { 198 | new SwitchStateLightHSRequestOption() 199 | { 200 | ColorHue = hue, 201 | ColorSaturation = saturation, 202 | TransitionTime = transition, 203 | LightIntensity = value 204 | } 205 | } 206 | }; 207 | await MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 208 | } 209 | 210 | /// 211 | /// Set Dimmer for Light Device 212 | /// 213 | /// A 214 | /// Dimmer intensity (0-255) 215 | /// An optional transition duration, defaults to null (no transition) 216 | public async Task SetDimmer(TradfriDevice device, int value, int? transition = null) 217 | { 218 | await SetDimmer(device.ID, value, transition); 219 | device.LightControl[0].Dimmer = value; 220 | } 221 | 222 | /// 223 | /// Set Dimmer for Light Device 224 | /// 225 | /// Id of the device 226 | /// Dimmer intensity (0-255) 227 | /// An optional transition duration, defaults to null (no transition) 228 | /// 229 | public async Task SetDimmer(long id, int value, int? transition = null) 230 | { 231 | SwitchStateLightRequest set = new SwitchStateLightRequest() 232 | { 233 | Options = new[] 234 | { 235 | new SwitchStateLightRequestOption() 236 | { 237 | LightIntensity = value, 238 | TransitionTime = transition 239 | } 240 | } 241 | }; 242 | await MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 243 | } 244 | 245 | /// 246 | /// Turns a specific light on or off 247 | /// 248 | /// A 249 | /// On (True) or Off(false) 250 | /// 251 | public async Task SetLight(TradfriDevice device, bool state) 252 | { 253 | await SetLight(device.ID, state); 254 | if (HasLight(device)) 255 | { 256 | device.LightControl[0].State = state ? Bool.True : Bool.False; 257 | } 258 | } 259 | 260 | /// 261 | /// Turns a specific light on or off 262 | /// 263 | /// Id of the device 264 | /// On (True) or Off(false) 265 | /// 266 | public Task SetLight(long id, bool state) 267 | { 268 | SwitchStateLightRequest set = new SwitchStateLightRequest() 269 | { 270 | Options = new[] 271 | { 272 | new SwitchStateLightRequestOption() 273 | { 274 | IsOn = state ? 1 : 0 275 | } 276 | } 277 | }; 278 | return MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 279 | } 280 | 281 | /// 282 | /// Turns a specific control outlet on or off 283 | /// 284 | /// An 285 | /// On (True) or Off (false) 286 | /// 287 | public async Task SetOutlet(TradfriDevice device, bool state) 288 | { 289 | await SetOutlet(device.ID, state); 290 | if (HasControl(device)) 291 | { 292 | device.Control[0].State = state ? Bool.True : Bool.False; 293 | } 294 | } 295 | 296 | /// 297 | /// Turns a specific control outlet on or off 298 | /// 299 | /// Id of the device 300 | /// On (True) or Off (false) 301 | /// 302 | public Task SetOutlet(long id, bool state) 303 | { 304 | SwitchStateOutletRequest set = new SwitchStateOutletRequest() 305 | { 306 | Options = new[] 307 | { 308 | new SwitchStateLightRequestOption() 309 | { 310 | IsOn = state ? 1 : 0 311 | } 312 | } 313 | }; 314 | return MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 315 | } 316 | 317 | /// 318 | /// Set Position for Blind 319 | /// 320 | /// An 321 | /// Position (0-100) 322 | /// 323 | public async Task SetBlind(TradfriDevice device, int position) 324 | { 325 | await SetBlind(device.ID, position); 326 | if (HasBlind(device)) 327 | { 328 | device.Blind[0].Position = position; 329 | } 330 | } 331 | 332 | /// 333 | /// Set Position for Blind 334 | /// 335 | /// Id of the device 336 | /// Position (0-100) 337 | /// 338 | public async Task SetBlind(long id, int position) 339 | { 340 | SwitchStateBlindRequest set = new SwitchStateBlindRequest() 341 | { 342 | Options = new[] 343 | { 344 | new SwitchStateBlindRequestOption() 345 | { 346 | Position = position 347 | } 348 | } 349 | }; 350 | await MakeRequest($"/{(int)TradfriConstRoot.Devices}/{id}", Call.PUT, content: set); 351 | } 352 | 353 | /// 354 | /// Observes a device and gets update notifications 355 | /// 356 | /// Device on which you want to be notified 357 | /// Action to take for each device update 358 | public void ObserveDevice(TradfriDevice device, Action callback) 359 | { 360 | Action update = (Response response, Action cancel) => 361 | { 362 | if (!string.IsNullOrWhiteSpace(response?.PayloadString)) 363 | { 364 | device = JsonConvert.DeserializeObject(response.PayloadString); 365 | callback.Invoke(device, cancel); 366 | } 367 | }; 368 | 369 | MakeRequest(new WatchRequest($"/{(int)TradfriConstRoot.Devices}/{device.ID}") 370 | { 371 | EventHandler = update, 372 | RequestHandler = (resp) => { }, 373 | ExpectedStatusCode = System.Net.HttpStatusCode.OK 374 | }); 375 | } 376 | } 377 | internal class RenameRequest 378 | { 379 | [JsonProperty("9001")] 380 | public string Name { get; set; } 381 | } 382 | 383 | internal class SwitchStateLightRequest 384 | { 385 | [JsonProperty("3311")] 386 | public SwitchStateLightRequestOption[] Options { get; set; } 387 | } 388 | 389 | internal class SwitchStateLightXYRequest 390 | { 391 | [JsonProperty("3311")] 392 | public SwitchStateLightXYRequestOption[] Options { get; set; } 393 | } 394 | 395 | internal class SwitchStateLightHSRequest 396 | { 397 | [JsonProperty("3311")] 398 | public SwitchStateLightHSRequestOption[] Options { get; set; } 399 | } 400 | 401 | internal class SwitchStateOutletRequest 402 | { 403 | [JsonProperty("3312")] 404 | public SwitchStateLightRequestOption[] Options { get; set; } 405 | } 406 | 407 | internal class SwitchStateBlindRequest 408 | { 409 | [JsonProperty("15015")] 410 | public SwitchStateBlindRequestOption[] Options { get; set; } 411 | } 412 | 413 | internal class SwitchStateRequestOption 414 | { 415 | [JsonProperty("5850")] 416 | public int? IsOn { get; set; } 417 | } 418 | 419 | internal class SwitchStateLightRequestOption : SwitchStateRequestOption 420 | { 421 | [JsonProperty("5851")] //TradfriConstAttr.LightDimmer 422 | public int? LightIntensity { get; set; } 423 | 424 | [JsonProperty("5706")] 425 | public string Color { get; set; } 426 | 427 | [JsonProperty("5712")] 428 | public int? TransitionTime { get; set; } 429 | 430 | [JsonProperty("9039")] //TradfriConstAttr.Mood 431 | public long? Mood { get; set; } 432 | } 433 | 434 | internal class SwitchStateLightXYRequestOption : SwitchStateRequestOption 435 | { 436 | [JsonProperty("5851")] //TradfriConstAttr.LightDimmer 437 | public int? LightIntensity { get; set; } 438 | 439 | [JsonProperty("5709")] 440 | public int ColorX { get; set; } 441 | 442 | [JsonProperty("5710")] 443 | public int ColorY { get; set; } 444 | 445 | [JsonProperty("5712")] 446 | public int? TransitionTime { get; set; } 447 | } 448 | 449 | internal class SwitchStateLightHSRequestOption : SwitchStateRequestOption 450 | { 451 | [JsonProperty("5851")] //TradfriConstAttr.LightDimmer 452 | public int? LightIntensity { get; set; } 453 | 454 | [JsonProperty("5707")] 455 | public int ColorHue { get; set; } 456 | 457 | [JsonProperty("5708")] 458 | public int ColorSaturation { get; set; } 459 | 460 | [JsonProperty("5712")] 461 | public int? TransitionTime { get; set; } 462 | } 463 | 464 | internal class SwitchStateBlindRequestOption 465 | { 466 | [JsonProperty("5536")] 467 | public int? Position { get; set; } 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /Tradfri/Controllers/GatewayController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs; 2 | using ApiLibs.General; 3 | using Newtonsoft.Json; 4 | using Org.BouncyCastle.Asn1.Cms; 5 | using System.Collections.Generic; 6 | using System.Threading.Tasks; 7 | using Tomidix.NetStandard.Tradfri.Models; 8 | 9 | namespace Tomidix.NetStandard.Tradfri.Controllers 10 | { 11 | public class GatewayController : SubService 12 | { 13 | private TradfriController mainController; 14 | 15 | public GatewayController(TradfriController controller) : base(controller) 16 | { 17 | this.mainController = controller; 18 | } 19 | 20 | #region Functions regarding Gateway 21 | 22 | /// 23 | /// Acquires GatewayInfo object 24 | /// 25 | /// If set to true, than it will ignore existing cached value and ask the gateway for the object 26 | /// 27 | public async Task GetGatewayInfo() 28 | { 29 | return await MakeRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.GatewayInfo}"); 30 | } 31 | 32 | /// 33 | /// Reboot the gateway 34 | /// 35 | /// 36 | public async Task Reboot() 37 | { 38 | await MakeRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.GatewayReboot}", Call.POST); 39 | } 40 | 41 | public async Task ForceCheckOTAUpdate() 42 | { 43 | AddDeviceRequest set = new AddDeviceRequest() 44 | { 45 | CommissioningMode = 5000 46 | }; 47 | await MakeRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.GatewayInfo}", Call.PUT, content: set); 48 | } 49 | 50 | /// 51 | /// Put gateway into pairing mode so you can pair the device 52 | /// 53 | /// Set the duration of the pairing process for gateway to wait 54 | /// 55 | public async Task AddDevice(int timeout = 60) 56 | { 57 | AddDeviceRequest set = new AddDeviceRequest() 58 | { 59 | CommissioningMode = timeout 60 | }; 61 | await MakeRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.OtaForceCheck}", Call.PUT, content: set); 62 | } 63 | 64 | /// 65 | /// Put gateway into pairing mode so you can pair the device 66 | /// 67 | /// Id of the group 68 | /// Set the duration of the pairing process for gateway to wait 69 | /// 70 | public async Task AddDevice(long groupId, int timeout) 71 | { 72 | AddDeviceGroupRequest set = new AddDeviceGroupRequest() 73 | { 74 | CommissioningGroupID = groupId, 75 | CommissioningMode = timeout 76 | }; 77 | await MakeRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.GatewayInfo}", Call.PUT, content: set); 78 | } 79 | 80 | #endregion Functions regarding Gateway 81 | 82 | /// 83 | /// Acquire IDs of connected devices 84 | /// 85 | /// 86 | public Task> GetDevices() 87 | { 88 | return GetEntityCollectionIDs(TradfriConstRoot.Devices); 89 | } 90 | 91 | /// 92 | /// Acquire all groups 93 | /// 94 | /// 95 | public async Task> GetDeviceObjects() 96 | { 97 | List devices = new List(); 98 | foreach (long item in await GetEntityCollectionIDs(TradfriConstRoot.Devices)) 99 | { 100 | devices.Add(await mainController.DeviceController.GetTradfriDevice(item)); 101 | } 102 | 103 | return devices; 104 | } 105 | 106 | /// 107 | /// Acquire IDs of groups 108 | /// 109 | /// 110 | public Task> GetGroups() 111 | { 112 | return GetEntityCollectionIDs(TradfriConstRoot.Groups); 113 | } 114 | 115 | /// 116 | /// Acquire all groups 117 | /// 118 | /// 119 | public async Task> GetGroupObjects() 120 | { 121 | List groups = new List(); 122 | foreach (long item in await GetEntityCollectionIDs(TradfriConstRoot.Groups)) 123 | { 124 | groups.Add(await mainController.GroupController.GetTradfriGroup(item)); 125 | } 126 | return groups; 127 | } 128 | 129 | public Task> GetSmartTasks() 130 | { 131 | return GetEntityCollectionIDs(TradfriConstRoot.SmartTasks); 132 | } 133 | 134 | public async Task> GetSmartTaskObjects() 135 | { 136 | List smartTasks = new List(); 137 | foreach (long item in await GetEntityCollectionIDs(TradfriConstRoot.SmartTasks)) 138 | { 139 | smartTasks.Add(await mainController.SmartTasksController.GetTradfriSmartTask(item)); 140 | } 141 | return smartTasks; 142 | } 143 | 144 | /// 145 | /// Acquire TradfriMoods by groups 146 | /// 147 | /// 148 | public async Task> GetMoods() 149 | { 150 | List moods = new List(); 151 | foreach (int groupID in await MakeRequest>($"/{(int)TradfriConstRoot.Moods}")) 152 | { 153 | foreach (int moodID in await MakeRequest>($"/{(int)TradfriConstRoot.Moods}/{groupID}")) 154 | { 155 | TradfriMood mood = await MakeRequest($"/{(int)TradfriConstRoot.Moods}/{groupID}/{moodID}"); 156 | mood.GroupID = groupID; 157 | moods.Add(mood); 158 | } 159 | } 160 | return moods; 161 | } 162 | 163 | public void FactoryReset() 164 | { 165 | throw new System.NotImplementedException(); 166 | } 167 | 168 | private Task> GetEntityCollectionIDs(TradfriConstRoot rootConst) 169 | { 170 | return MakeRequest>($"/{(int)rootConst}"); 171 | } 172 | } 173 | internal class AddDeviceRequest 174 | { 175 | [JsonProperty("9061")] 176 | public int CommissioningMode { get; set; } 177 | } 178 | internal class AddDeviceGroupRequest : AddDeviceRequest 179 | { 180 | [JsonProperty("9064")] 181 | public long CommissioningGroupID { get; set; } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Tradfri/Controllers/GroupController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs; 2 | using ApiLibs.General; 3 | using System; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using Tomidix.NetStandard.Tradfri.Models; 7 | 8 | namespace Tomidix.NetStandard.Tradfri.Controllers 9 | { 10 | public class GroupController : SubService 11 | { 12 | //private readonly CoapClient cc; 13 | //private long id { get; } 14 | //private TradfriGroup group { get; set; } 15 | /// 16 | /// ctor 17 | /// 18 | /// group id 19 | /// existing coap client 20 | /// Load group object automatically (default: true) 21 | public GroupController(TradfriController controller) : base(controller) { } 22 | 23 | /// 24 | /// Acquires TradfriGroup object 25 | /// 26 | /// Id of the group 27 | /// 28 | public async Task GetTradfriGroup(long id) 29 | { 30 | return await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}"); 31 | } 32 | 33 | /// 34 | /// Renames TradfriGroup object 35 | /// 36 | /// 37 | /// 38 | public Task RenameTradfriGroup(TradfriGroup group) 39 | { 40 | return RenameTradfriGroup(group.ID, group.Name); 41 | } 42 | 43 | /// 44 | /// Renames TradfriGroup by id 45 | /// 46 | /// 47 | /// 48 | /// 49 | public Task RenameTradfriGroup(long id, string newName) 50 | { 51 | if (!string.IsNullOrWhiteSpace(newName)) 52 | { 53 | RenameRequest set = new RenameRequest 54 | { 55 | Name = newName 56 | }; 57 | return MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", Call.PUT, content: set); 58 | } 59 | else 60 | { 61 | throw new Exception("Group cannot be renamed to empty string."); 62 | } 63 | } 64 | 65 | /// 66 | /// Sets a mood for the group 67 | /// 68 | /// A 69 | /// TradfriMood object which needs to be set 70 | /// 71 | public async Task SetMood(TradfriGroup group, TradfriMood mood) 72 | { 73 | await SetMood(group.ID, mood.ID); 74 | group.ActiveMood = mood.ID; 75 | } 76 | 77 | /// 78 | /// Set Dimmer for Light Devices in Group 79 | /// 80 | /// A 81 | /// Dimmer intensity (0-255) 82 | /// 83 | public async Task SetDimmer(TradfriGroup group, int value) 84 | { 85 | await SetDimmer(group.ID, value); 86 | group.LightDimmer = value; 87 | } 88 | 89 | /// 90 | /// Sets a mood for the group 91 | /// 92 | /// Id of the group 93 | /// TradfriMood ID which needs to be activated for group 94 | /// 95 | public async Task SetMood(long id, long moodId) 96 | { 97 | SwitchStateLightRequestOption set = new SwitchStateLightRequestOption() 98 | { 99 | IsOn = 1, 100 | Mood = moodId 101 | }; 102 | await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", Call.PUT, content: set); 103 | } 104 | 105 | /// 106 | /// Sets a custom moodProperties for the group 107 | /// 108 | /// Id of the group 109 | /// custom TradfriMoodProperties object which will be applied to all group bulbs 110 | /// 111 | public async Task SetMood(long id, TradfriMoodProperties moodProperties) 112 | { 113 | SwitchStateLightRequestOption set = new SwitchStateLightRequestOption() 114 | { 115 | IsOn = 1, 116 | Mood = 1 //hardcoded non-existing moodId 117 | }; 118 | await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", 119 | Call.PUT, 120 | content: moodProperties); 121 | 122 | await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", Call.PUT, content: set); 123 | } 124 | 125 | /// 126 | /// Set Dimmer for Light Devices in Group 127 | /// 128 | /// Id of the group 129 | /// Dimmer intensity (0-255) 130 | /// 131 | public async Task SetDimmer(long id, int value) 132 | { 133 | SwitchStateLightRequestOption set = new SwitchStateLightRequestOption() 134 | { 135 | LightIntensity = value 136 | }; 137 | await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", Call.PUT, content: set); 138 | } 139 | 140 | /// 141 | /// Turns a group of lights on or off 142 | /// 143 | /// A 144 | /// On (True) or Off(false) 145 | /// 146 | public async Task SetLight(TradfriGroup group, bool state) 147 | { 148 | await SetLight(group.ID, state); 149 | group.LightState = 1; 150 | } 151 | 152 | /// 153 | /// Turns a group of lights on or off 154 | /// 155 | /// Id of the group 156 | /// On (True) or Off(false) 157 | /// 158 | public async Task SetLight(long id, bool state) 159 | { 160 | SwitchStateLightRequestOption set = new SwitchStateLightRequestOption() 161 | { 162 | IsOn = state ? 1 : 0 163 | }; 164 | 165 | await MakeRequest($"/{(int)TradfriConstRoot.Groups}/{id}", Call.PUT, content: set); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Tradfri/Controllers/SmartTaskController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs.General; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Tomidix.NetStandard.Tradfri.Models; 6 | 7 | namespace Tomidix.NetStandard.Tradfri.Controllers 8 | { 9 | public class SmartTaskController : SubService 10 | { 11 | public SmartTaskController(TradfriController controller) : base(controller) 12 | { 13 | } 14 | 15 | /// 16 | /// Acquires TradfriGroup object 17 | /// 18 | /// Id of the group 19 | /// 20 | public Task GetTradfriSmartTask(long id) 21 | { 22 | return MakeRequest($"/{(int)TradfriConstRoot.SmartTasks}/{id}"); 23 | } 24 | 25 | public List GetSelectedRepeatDays(TradfriSmartTask task) 26 | { 27 | return GetSelectedRepeatDays(task.RepeatDays); 28 | } 29 | 30 | public List GetSelectedRepeatDays(long repeatDays) 31 | { 32 | List days = new List(); 33 | if (repeatDays > 0) 34 | { 35 | int tempDaysVariable = (int)repeatDays; 36 | Array daysArray = Enum.GetValues(typeof(Days)); 37 | 38 | //Array.Reverse(daysArray); 39 | string selectedDaysBinary = Convert.ToString(repeatDays, 2); 40 | int currentSign = selectedDaysBinary.Length - 1; 41 | if (currentSign >= 0) 42 | { 43 | foreach (Days currentDay in daysArray) 44 | { 45 | if (selectedDaysBinary[currentSign].Equals('1')) 46 | { 47 | days.Add(currentDay.ToString()); 48 | } 49 | currentSign--; 50 | if (currentSign < 0) 51 | break; 52 | } 53 | } 54 | } 55 | return days; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tradfri/Extensions/CoapClientExtension.cs: -------------------------------------------------------------------------------- 1 | using Com.AugustCellars.CoAP; 2 | using System; 3 | 4 | namespace Tomidix.NetStandard.Tradfri.Extensions 5 | { 6 | public static class CoapClientExtension 7 | { 8 | public static CoapObserveRelation Observe(this CoapClient _client, string url, Action callback, Action error = null) 9 | { 10 | _client.UriPath = url; 11 | return _client.Observe(callback, error); 12 | } 13 | 14 | public static CoapObserveRelation Observe2(this CoapClient _client, string url, Action callback, Action error = null) 15 | { 16 | _client.UriPath = url; 17 | return _client.Observe(callback, error); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tradfri/Extensions/CoapService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using ApiLibs; 4 | using Com.AugustCellars.CoAP; 5 | using Com.AugustCellars.CoAP.DTLS; 6 | using Newtonsoft.Json; 7 | 8 | namespace Tomidix.NetStandard.Tradfri.Extensions 9 | { 10 | public class CoapImplementation : ICallImplementation 11 | { 12 | internal CoapClient _coapClient; 13 | 14 | 15 | public override async Task ExecuteRequest(Service service2, ApiLibs.Request request) 16 | { 17 | var coapRequest = new Com.AugustCellars.CoAP.Request(ConvertToMethod(request.Method)) 18 | { 19 | UriPath = request.EndPoint 20 | }; 21 | 22 | if (request.Content != null) 23 | { 24 | coapRequest.SetPayload(AddBody(request.Content)); 25 | } 26 | 27 | // this is done on purpose to handle ObserveDevice from DeviceController 28 | if (request is WatchRequest watch) 29 | { 30 | coapRequest.MarkObserve(); 31 | Action cancelWatch = () => coapRequest.MarkObserveCancel(); 32 | coapRequest.Respond += (object sender, ResponseEventArgs e) => 33 | { 34 | watch.EventHandler?.Invoke(coapRequest.Response, cancelWatch); 35 | }; 36 | } 37 | 38 | if(request is EndPointRequest a) 39 | { 40 | coapRequest.EndPoint = a.DTLSEndPoint; 41 | } 42 | 43 | Task requestTask = new Task(() => 44 | { 45 | return _coapClient.Send(coapRequest); 46 | }); 47 | 48 | requestTask.Start(); 49 | 50 | Response resp = await requestTask; 51 | 52 | if(resp == null) 53 | { 54 | throw new Exception("Request timed out"); 55 | } 56 | 57 | var responseApiLibs = new RequestResponse((System.Net.HttpStatusCode)MapToHttpStatusCode(resp.StatusCode), resp.StatusCode.ToString(), resp.UriQuery, "", resp.ResponseText, resp, request, service2); 58 | 59 | if (resp.IsTimedOut) 60 | { 61 | throw new RequestTimeoutResponse(responseApiLibs).ToException(); 62 | } 63 | 64 | return responseApiLibs; 65 | } 66 | 67 | private static string AddBody(object content) 68 | { 69 | return content switch 70 | { 71 | string text => text, 72 | var randomObject => JsonConvert.SerializeObject(randomObject, new JsonSerializerSettings() 73 | { 74 | NullValueHandling = NullValueHandling.Ignore 75 | }) 76 | }; 77 | } 78 | 79 | private int MapToHttpStatusCode(StatusCode statusCode) => 80 | statusCode switch 81 | { 82 | StatusCode.Created => 201, 83 | StatusCode.Deleted => 200, 84 | StatusCode.Valid => 200, 85 | StatusCode.Changed => 200, 86 | StatusCode.Content => 200, 87 | StatusCode.BadRequest => 400, 88 | StatusCode.BadOption => 400, 89 | StatusCode.Forbidden => 403, 90 | StatusCode.NotFound => 404, 91 | StatusCode.MethodNotAllowed => 405, 92 | StatusCode.NotAcceptable => 406, 93 | StatusCode.PreconditionFailed => 412, 94 | StatusCode.RequestEntityTooLarge => 413, 95 | StatusCode.UnsupportedMediaType => 414, 96 | StatusCode.InternalServerError => 500, 97 | StatusCode.NotImplemented => 501, 98 | StatusCode.ServiceUnavailable => 503, 99 | StatusCode.GatewayTimeout => 504, 100 | StatusCode.ProxyingNotSupported => 500, 101 | _ => 400, 102 | }; 103 | 104 | private Method ConvertToMethod(Call call) 105 | { 106 | return call switch 107 | { 108 | Call.GET => Method.GET, 109 | Call.POST => Method.POST, 110 | Call.DELETE => Method.DELETE, 111 | Call.PUT => Method.PUT, 112 | _ => throw new System.NotSupportedException("This is not supported for coap"), 113 | }; 114 | } 115 | } 116 | 117 | public class WatchRequest : ApiLibs.Request 118 | { 119 | public WatchRequest(string endPoint) : base(endPoint) 120 | { 121 | } 122 | 123 | public Action EventHandler { get; internal set; } 124 | } 125 | 126 | public class EndPointRequest : Request 127 | { 128 | public EndPointRequest(string endPoint) : base(endPoint) 129 | { 130 | } 131 | 132 | public DTLSClientEndPoint DTLSEndPoint { get; set; } 133 | } 134 | } -------------------------------------------------------------------------------- /Tradfri/Extensions/ColorExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Tomidix.NetStandard.Tradfri.Extensions 4 | { 5 | internal static class ColorExtension 6 | { 7 | internal static (int, int) CalculateCIEFromRGB(int r, int g, int b) 8 | { 9 | double red = GammaCorrection(r); 10 | double green = GammaCorrection(g); 11 | double blue = GammaCorrection(b); 12 | 13 | // Wide RGB D65 conversion 14 | // math inspiration: http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html 15 | double X = (red * 0.664511) + (green * 0.154324) + (blue * 0.162028); 16 | double Y = (red * 0.283881) + (green * 0.668433) + (blue * 0.047685); 17 | double Z = (red * 0.000088) + (green * 0.072310) + (blue * 0.986039); 18 | 19 | // calculate the xy values from XYZ 20 | double x = (X / (X + Y + Z)); 21 | double y = (Y / (X + Y + Z)); 22 | 23 | int xyX = (int)((x * 65535) + 0.5); 24 | int xyY = (int)((y * 65535) + 0.5); 25 | 26 | return (xyX, xyY); 27 | } 28 | 29 | internal static int CalculateIntensity(int r, int g, int b) 30 | { 31 | return (int)(GetValue(r, g, b) * 254); 32 | } 33 | 34 | private static double GammaCorrection(double colorTone) 35 | { 36 | // gamma correction 37 | return (colorTone > 0.04045) ? Math.Pow((colorTone + 0.055) / (1.0 + 0.055), 2.4) : (colorTone / 12.92); 38 | } 39 | 40 | private static double GetValue(int r, int g, int b) 41 | { 42 | return Math.Max(r / 255.0, Math.Max(g / 255.0, b / 255.0)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tradfri/Extensions/EnumExtension.cs: -------------------------------------------------------------------------------- 1 | using Tomidix.NetStandard.Tradfri.Models; 2 | 3 | namespace Tomidix.NetStandard.Tradfri.Extensions 4 | { 5 | public static class EnumExtension 6 | { 7 | public static string ValueAsString(this TradfriConstRoot enumerator) 8 | { 9 | return ((int)enumerator).ToString(); 10 | } 11 | 12 | public static string ValueAsString(this TradfriConstAttr enumerator) 13 | { 14 | return ((int)enumerator).ToString(); 15 | } 16 | 17 | public static string ValueAsString(this TradfriConstMireds enumerator) 18 | { 19 | return ((int)enumerator).ToString(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tradfri/Extensions/MicrosecondEpochConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System; 4 | 5 | namespace Tomidix.NetStandard.Tradfri.Extensions 6 | { 7 | public class MicrosecondEpochConverter : DateTimeConverterBase 8 | { 9 | private static readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); 10 | 11 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 12 | { 13 | writer.WriteRawValue(((DateTime)value - _epoch).TotalMilliseconds + "000"); 14 | } 15 | 16 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 17 | { 18 | if (reader.Value == null) 19 | return DateTime.MinValue; 20 | else if (CheckNumberOfDigits((long)reader.Value) == 16) 21 | return _epoch.AddMilliseconds((long)reader.Value / 1000); 22 | return _epoch.AddMilliseconds((long)reader.Value * 1000); 23 | } 24 | 25 | 26 | 27 | private static int CheckNumberOfDigits(long n) 28 | { 29 | return Math.Abs(n).ToString().Length; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tradfri/Models/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Tomidix.NetStandard.Tradfri.Models 2 | { 3 | //... more values may be found according to https://github.com/ggravlingen/pytradfri/blob/master/pytradfri/const.py 4 | 5 | public enum TradfriConstRoot 6 | { 7 | Devices = 15001, 8 | Groups = 15004, 9 | Moods = 15005, 10 | Notification = 15006, // speculative name 11 | Switch = 15009, // remote control 12 | SmartTasks = 15010, 13 | Gateway = 15011, 14 | StartAction = 15013, 15 | SignalRepeater = 15014, 16 | StartBlinds = 15015 17 | } 18 | 19 | public enum TradfriConstAttr 20 | { 21 | Auth = 9063, 22 | Psk = 9091, 23 | Identity = 9090, 24 | 25 | ApplicationType = 5750, 26 | 27 | CreatedAt = 9002, 28 | 29 | CognitoId = 9101, 30 | CommissioningMode = 9061, 31 | 32 | CurrentTimeUnix = 9061, 33 | CurrentTimeISO8601 = 9060, 34 | 35 | DeviceInfo = 3, 36 | 37 | GatewayTimeSource = 9071, 38 | GatewayUpdateProgress = 9055, 39 | 40 | HomekitID = 9083, 41 | 42 | ID = 9003, 43 | LastSeen = 9020, 44 | LightControl = 3311, 45 | 46 | Name = 9001, 47 | NTP = 9023, 48 | FirmwareVersion = 9029, 49 | FirstSetup = 9069, 50 | 51 | GatewayInfo = 15012, 52 | GatewayID = 9081, 53 | GatewayReboot = 9030, 54 | GatewayFactoryDefaults = 9031, //gw to factory defaults 55 | GatewayFactoryDefaultsMinMaxMSR = 5605, 56 | 57 | LightState = 5850, // 0 or 1 58 | LightDimmer = 5851, // Dimmer, not following spec: 0..255 59 | LightColorHex = 5706, // string representing a value in hex 60 | LightColorX = 5709, 61 | LightColorY = 5710, 62 | LightColorSaturation = 5707, //guess 63 | LightColorHue = 5708, //guess 64 | LightMireds = 5711, 65 | 66 | MasterTokenTag = 9036, 67 | 68 | Mood = 9039, 69 | 70 | OtaType = 9066, 71 | OtaUpdateState = 9054, 72 | OtaUpdate = 9037, 73 | OtaForceCheck = 9032, 74 | 75 | ReachableState = 9019, 76 | 77 | RepeatDays = 9041, 78 | 79 | Sensor = 3300, 80 | SensorMinRangeValue = 5603, 81 | SensorMaxRangeValue = 5604, 82 | SensorMinMeasuredValue = 5601, 83 | SensorMaxMeasuredValue = 5602, 84 | SensorType = 5751, 85 | SensorUnit = 5701, 86 | SensorValue = 5700, 87 | 88 | StartAction = 9042, // array 89 | 90 | SmartTaskType = 9040, // 4 = transition | 1 = not home | 2 = on/off 91 | SmartTaskNotAtHome = 1, 92 | SmartTaskLightsOff = 2, 93 | SmartTaskWakeUp = 4, 94 | 95 | SmartTaskTriggerTimeInterval = 9044, 96 | SmartTaskTriggerTimeStartHour = 9046, 97 | SmartTaskTriggerTimeStartMin = 9047, 98 | 99 | SwitchPlug = 3312, 100 | SwitchPowerFactor = 5820, 101 | 102 | TransitionTime = 5712 103 | } 104 | 105 | public enum TradfriConstMireds 106 | { 107 | Min = 40, 108 | Max = 600, 109 | 110 | MinWS = 250, 111 | MaxWS = 454 112 | } 113 | 114 | public enum TradfriSupport 115 | { 116 | Brightness = 1, 117 | ColorTemp = 2, 118 | HexColor = 4, 119 | RGBColor = 8, 120 | XYColor = 16 121 | } 122 | 123 | public static class TradfriColors 124 | { 125 | public const string Blue = "4a418a"; 126 | public const string LightBlue = "6c83ba"; 127 | public const string SaturatedPurple = "8f2686"; 128 | public const string Lime = "a9d62b"; 129 | public const string LightPurple = "c984bb"; 130 | public const string Yellow = "d6e44b"; 131 | public const string SaturatedPink = "d9337c"; 132 | public const string DarkPeach = "da5d41"; 133 | public const string SaturatedRed = "dc4b31"; 134 | public const string ColdSky = "dcf0f8"; 135 | public const string Pink = "e491af"; 136 | public const string Peach = "e57345"; 137 | public const string WarmAmber = "e78834"; 138 | public const string LightPink = "e8bedd"; 139 | public const string CoolDaylight = "eaf6fb"; 140 | public const string CandleLight = "ebb63e"; 141 | public const string WarmGlow = "efd275"; 142 | public const string WarmWhite = "f1e0b5"; 143 | public const string Sunrise = "f2eccf"; 144 | public const string CoolWhite = "f5faf6"; 145 | } 146 | 147 | public enum Days 148 | { 149 | Monday = 1, 150 | Tuesday = 2, 151 | Wednesday = 4, 152 | Thursday = 8, 153 | Friday = 16, 154 | Saturday = 32, 155 | Sunday = 64 156 | } 157 | 158 | public enum Bool 159 | { 160 | True = 1, 161 | False = 0 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Tradfri/Models/GatewayInfo.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using Tomidix.NetStandard.Tradfri.Extensions; 4 | 5 | namespace Tomidix.NetStandard.Tradfri.Models 6 | { 7 | public class GatewayInfo 8 | { 9 | //properties needs naming schema mapping according to https://github.com/ggravlingen/pytradfri/blob/master/pytradfri/const.py 10 | [JsonProperty("9023")] 11 | public string NTP { get; set; } 12 | 13 | [JsonProperty("9029")] 14 | public string Firmware { get; set; } 15 | 16 | [JsonProperty("9054")] 17 | public long OtaUpdateState { get; set; } 18 | 19 | [JsonProperty("9055")] 20 | public long GatewayUpdateProgress { get; set; } 21 | 22 | [JsonProperty("9059")] 23 | public long CurrentTimeUnix { get; set; } 24 | 25 | [JsonProperty("9060")] 26 | public string CurrentTimeISO8601 { get; set; } 27 | 28 | [JsonProperty("9061")] 29 | public long CommissioningMode { get; set; } 30 | 31 | [JsonProperty("9062")] 32 | public long The9062 { get; set; } 33 | 34 | [JsonProperty("9066")] 35 | public long OtaType { get; set; } 36 | 37 | [JsonProperty("9069")] 38 | [JsonConverter(typeof(MicrosecondEpochConverter))] 39 | public DateTime FirstSetup { get; set; } 40 | 41 | [JsonProperty("9071")] 42 | public long GatewayTimeSource { get; set; } 43 | 44 | [JsonProperty("9072")] 45 | public long DST_StartMonth { get; set; } 46 | 47 | [JsonProperty("9073")] 48 | public long DST_StartDay { get; set; } 49 | 50 | [JsonProperty("9074")] 51 | public long DST_StartHour { get; set; } 52 | 53 | [JsonProperty("9075")] 54 | public long DST_StartMinute { get; set; } 55 | 56 | [JsonProperty("9076")] 57 | public long DST_EndMonth { get; set; } 58 | 59 | [JsonProperty("9077")] 60 | public long DST_EndDay { get; set; } 61 | 62 | [JsonProperty("9078")] 63 | public long DST_EndHour { get; set; } 64 | 65 | [JsonProperty("9079")] 66 | public long DST_EndMinute { get; set; } 67 | 68 | [JsonProperty("9080")] 69 | public long DST_TimeOffset { get; set; } 70 | 71 | [JsonProperty("9081")] 72 | public string GatewayID { get; set; } 73 | 74 | [JsonProperty("9082")] 75 | public bool The9082 { get; set; } 76 | 77 | [JsonProperty("9083")] 78 | public string HomekitID { get; set; } 79 | 80 | [JsonProperty("9092")] 81 | public long CertificateProv { get; set; } 82 | 83 | [JsonProperty("9093")] 84 | public long AlexaPairStatus { get; set; } 85 | 86 | [JsonProperty("9106")] 87 | public long The9106 { get; set; } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tradfri/Models/TradfriAuth.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Tomidix.NetStandard.Tradfri.Models 4 | { 5 | public class TradfriAuth 6 | { 7 | [JsonProperty("9091")] 8 | public string PSK { get; set; } 9 | 10 | [JsonProperty("9029")] 11 | public string FirmwareVersion { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tradfri/Models/TradfriDevice.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using Tomidix.NetStandard.Tradfri.Extensions; 5 | 6 | namespace Tomidix.NetStandard.Tradfri.Models 7 | { 8 | public enum DeviceType 9 | { 10 | Remote = 0, 11 | Unknown_1 = 1, 12 | Light = 2, 13 | ControlOutlet = 3, 14 | MotionSensor = 4, 15 | Unknown_3 = 5, 16 | Unknown_4 = 6, 17 | Blind = 7, 18 | Unknown_6 = 8, 19 | Unknown_7 = 9, 20 | Unknown_8 = 10, 21 | Unknown_9 = 11 22 | } 23 | 24 | public enum PowerSource 25 | { 26 | DCPower = 0, 27 | InternalBattery = 1, 28 | ExternalBattery = 2, 29 | Battery = 3, //used by motion sensor 30 | POE = 4, //power over ethernet 31 | USB = 5, 32 | ACPower = 6, 33 | Solar = 7, 34 | Unknown_1 = 8, 35 | Unknown_2 = 9, 36 | } 37 | 38 | public class TradfriDevice 39 | { 40 | [JsonProperty("15009")] 41 | public List RootSwitch { get; set; } 42 | 43 | [JsonProperty("3")] 44 | public DeviceInfo Info { get; set; } 45 | 46 | //0 - remote, 2 - light 47 | [JsonProperty("5750")] 48 | public DeviceType DeviceType { get; set; } 49 | 50 | [JsonProperty("9001")] 51 | public string Name { get; set; } 52 | 53 | [JsonProperty("9002")] 54 | [JsonConverter(typeof(MicrosecondEpochConverter))] 55 | public DateTime CreatedAt { get; set; } 56 | 57 | [JsonProperty("9003")] 58 | public long ID { get; set; } 59 | 60 | [JsonProperty("9019")] 61 | public long ReachableState { get; set; } 62 | 63 | [JsonProperty("9020")] 64 | [JsonConverter(typeof(MicrosecondEpochConverter))] 65 | public DateTime LastSeen { get; set; } 66 | 67 | [JsonProperty("9054")] 68 | public long OtaUpdateState { get; set; } 69 | 70 | [JsonProperty("3311")] 71 | public List LightControl { get; set; } 72 | 73 | [JsonProperty("3312")] 74 | public List Control { get; set; } 75 | 76 | [JsonProperty("15015")] 77 | public List Blind { get; set; } 78 | } 79 | 80 | public class DeviceInfo 81 | { 82 | [JsonProperty("0")] 83 | public string Manufacturer { get; set; } 84 | 85 | [JsonProperty("1")] 86 | public string DeviceType { get; set; } 87 | 88 | [JsonProperty("2")] 89 | public string Serial { get; set; } 90 | 91 | [JsonProperty("3")] 92 | public string FirmwareVersion { get; set; } 93 | 94 | [JsonProperty("6")] 95 | public PowerSource PowerSource { get; set; } 96 | 97 | [JsonProperty("9")] 98 | public long Battery { get; set; } 99 | } 100 | 101 | public class RootSwitch 102 | { 103 | [JsonProperty("9003")] 104 | public long RootSwitchID { get; set; } 105 | } 106 | 107 | public class Control 108 | { 109 | [JsonProperty("5850")] 110 | public Bool State { get; set; } 111 | 112 | [JsonProperty("5851")] 113 | public long Dimmer { get; set; } 114 | 115 | [JsonProperty("9003")] 116 | public long ID { get; set; } 117 | } 118 | 119 | public class LightControl : Control 120 | { 121 | [JsonProperty("5706")] 122 | public string ColorHex { get; set; } 123 | 124 | [JsonProperty("5707")] 125 | public long ColorHue { get; set; } 126 | 127 | [JsonProperty("5708")] 128 | public long ColorSaturation { get; set; } 129 | 130 | [JsonProperty("5709")] 131 | public long ColorX { get; set; } 132 | 133 | [JsonProperty("5710")] 134 | public long ColorY { get; set; } 135 | 136 | [JsonProperty("5711")] 137 | public long Mireds { get; set; } 138 | } 139 | 140 | public class Blind 141 | { 142 | [JsonProperty("9003")] 143 | public long ID { get; set; } 144 | 145 | [JsonProperty("5536")] 146 | public long Position { get; set; } 147 | } 148 | 149 | public class DeviceList 150 | { 151 | [JsonProperty("9003")] 152 | public List DevicesIds { get; set; } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Tradfri/Models/TradfriGroup.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using Tomidix.NetStandard.Tradfri.Extensions; 5 | 6 | namespace Tomidix.NetStandard.Tradfri.Models 7 | { 8 | public class TradfriGroup 9 | { 10 | [JsonProperty("5850")] 11 | public long LightState { get; set; } 12 | 13 | [JsonProperty("5851")] 14 | public long LightDimmer { get; set; } 15 | 16 | [JsonProperty("9001")] 17 | public string Name { get; set; } 18 | 19 | [JsonProperty("9002")] 20 | [JsonConverter(typeof(MicrosecondEpochConverter))] 21 | public DateTime CreatedAt { get; set; } 22 | 23 | [JsonProperty("9003")] 24 | public long ID { get; set; } 25 | 26 | [JsonProperty("9018")] 27 | public The9018 Devices { get; set; } 28 | 29 | [JsonProperty("9039")] 30 | public long ActiveMood { get; set; } 31 | } 32 | 33 | public class The9018 34 | { 35 | [JsonProperty("15002")] 36 | public The15002 The15002 { get; set; } 37 | } 38 | 39 | public class The15002 40 | { 41 | [JsonProperty("9003")] 42 | public List ID { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tradfri/Models/TradfriMood.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using Tomidix.NetStandard.Tradfri.Extensions; 4 | 5 | namespace Tomidix.NetStandard.Tradfri.Models 6 | { 7 | public class TradfriMood 8 | { 9 | [JsonProperty("15013")] 10 | public TradfriMoodProperties[] MoodProperties { get; set; } 11 | 12 | [JsonProperty("9001")] 13 | public string Name { get; set; } 14 | 15 | [JsonProperty("9002")] 16 | [JsonConverter(typeof(MicrosecondEpochConverter))] 17 | public DateTime CreatedAt { get; set; } 18 | 19 | [JsonProperty("9003")] 20 | public long ID { get; set; } 21 | 22 | [JsonProperty("9057")] 23 | public long The9057 { get; set; } 24 | 25 | [JsonProperty("9068")] 26 | public long The9068 { get; set; } 27 | 28 | public long GroupID { get; set; } 29 | } 30 | 31 | public class TradfriMoodProperties 32 | { 33 | [JsonProperty("5706")] 34 | public string ColorHex { get; set; } 35 | 36 | [JsonProperty("5707")] 37 | public long ColorSaturation { get; set; } 38 | 39 | [JsonProperty("5708")] 40 | public long ColorHue { get; set; } 41 | 42 | [JsonProperty("5709")] 43 | public long ColorX { get; set; } 44 | 45 | [JsonProperty("5710")] 46 | public long ColorY { get; set; } 47 | 48 | [JsonProperty("5711")] 49 | public long Mireds { get; set; } 50 | 51 | [JsonProperty("5712")] 52 | public long TransitionTime { get; set; } 53 | 54 | [JsonProperty("5850")] 55 | public long LightState { get; set; } 56 | 57 | [JsonProperty("5851")] 58 | public long Dimmer { get; set; } 59 | 60 | [JsonProperty("9003")] 61 | public long ID { get; set; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tradfri/Models/TradfriSmartTask.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using Tomidix.NetStandard.Tradfri.Extensions; 4 | 5 | namespace Tomidix.NetStandard.Tradfri.Models 6 | { 7 | public class TradfriSmartTask 8 | { 9 | [JsonProperty("5850")] 10 | public long LightState { get; set; } 11 | 12 | [JsonProperty("9002")] 13 | [JsonConverter(typeof(MicrosecondEpochConverter))] 14 | public DateTime CreatedAt { get; set; } 15 | 16 | [JsonProperty("9003")] 17 | public long ID { get; set; } 18 | 19 | [JsonProperty("9040")] 20 | public TradfriConstAttr TaskType { get; set; } 21 | 22 | [JsonProperty("9041")] 23 | public long RepeatDays { get; set; } 24 | 25 | [JsonProperty("9042")] 26 | public TradfriAction ActionTurnOn { get; set; } 27 | 28 | [JsonProperty("9043")] 29 | public TradfriAction ActionTurnOff { get; set; } 30 | 31 | [JsonProperty("9044")] 32 | public SmartTaskTrigger[] TriggerTimeInterval { get; set; } 33 | } 34 | 35 | public class SmartTaskTrigger 36 | { 37 | [JsonProperty("9046")] 38 | public long StartHour { get; set; } 39 | 40 | [JsonProperty("9047")] 41 | public long StartMin { get; set; } 42 | 43 | [JsonProperty("9048")] 44 | public long EndHour { get; set; } 45 | 46 | [JsonProperty("9049")] 47 | public long EndMin { get; set; } 48 | } 49 | 50 | public class TradfriAction 51 | { 52 | [JsonProperty("15013")] 53 | public TradfriDevice[] Devices { get; set; } 54 | 55 | [JsonProperty("5850")] 56 | public long LightState { get; set; } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tradfri/Tradfri.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | CSharpTradfriLibrary 6 | 8.0 7 | Tomidix.CSharpTradFriLibrary 8 | https://github.com/dominicusmento/CSharpTradFriLibrary 9 | https://github.com/dominicusmento/CSharpTradFriLibrary/blob/master/LICENSE 10 | https://github.com/dominicusmento/CSharpTradFriLibrary.git 11 | git 12 | ikea;tradfri;home;zigbee;csharp;netstandard 13 | This is an open source helper library which you can use to communicate with your ZigBee-based Ikea TradFri gateway 14 | tomidix 15 | 1.6.0.0 16 | Tomidix.NetStandard.Tradfri 17 | Tomidix.NetStandard.Tradfri 18 | 1.6.0.0 19 | 1.6.0.0 20 | false 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Tradfri/TradfriController.cs: -------------------------------------------------------------------------------- 1 | using ApiLibs; 2 | using ApiLibs.General; 3 | using Com.AugustCellars.CoAP; 4 | using Com.AugustCellars.CoAP.DTLS; 5 | using Com.AugustCellars.COSE; 6 | using Newtonsoft.Json; 7 | using PeterO.Cbor; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Net; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | using Tomidix.NetStandard.Tradfri.Controllers; 15 | using Tomidix.NetStandard.Tradfri.Extensions; 16 | using Tomidix.NetStandard.Tradfri.Models; 17 | using Method = Com.AugustCellars.CoAP.Method; 18 | 19 | namespace Tomidix.NetStandard.Tradfri 20 | { 21 | public class TradfriController : Service 22 | { 23 | private CoapClient _coapClient; 24 | 25 | public DeviceController DeviceController { get; set; } 26 | public GatewayController GatewayController { get; set; } 27 | public GroupController GroupController { get; set; } 28 | public SmartTaskController SmartTasksController { get; set; } 29 | private readonly string _gatewayIp; 30 | 31 | public string GateWayName { get; } 32 | 33 | public TradfriController(string gatewayName, string gatewayIp) : base(new CoapImplementation()) //Implementation of the COAP model 34 | { 35 | this.GateWayName = gatewayName; 36 | _gatewayIp = gatewayIp; 37 | 38 | DeviceController = new DeviceController(this); 39 | GatewayController = new GatewayController(this); 40 | GroupController = new GroupController(this); 41 | SmartTasksController = new SmartTaskController(this); 42 | } 43 | 44 | [Obsolete("This is an old way of connecting to Gateway. You should use with two parameters, then generate AppKey and use it in. Usefull for Unit Testing.", false)] 45 | public TradfriController(string gatewayName, string gatewayIp, string PSK) : this(gatewayName, gatewayIp) 46 | { 47 | ConnectPSK(PSK); 48 | } 49 | 50 | public void ConnectPSK(string GatewaySecret) 51 | { 52 | OneKey authKey = new OneKey(); 53 | authKey.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_Octet); 54 | authKey.Add(CoseKeyParameterKeys.Octet_k, CBORObject.FromObject(Encoding.UTF8.GetBytes(GatewaySecret))); 55 | 56 | DTLSClientEndPoint ep = new DTLSClientEndPoint(authKey); 57 | 58 | (Implementation as CoapImplementation)._coapClient = new CoapClient(new Uri($"coaps://{_gatewayIp}")) 59 | { 60 | EndPoint = ep 61 | }; 62 | 63 | ep.Start(); 64 | } 65 | 66 | public void ConnectAppKey(string appKey, string applicationName) 67 | { 68 | OneKey authKey = new OneKey(); 69 | authKey.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_Octet); 70 | authKey.Add(CoseKeyParameterKeys.Octet_k, CBORObject.FromObject(Encoding.UTF8.GetBytes(appKey))); 71 | authKey.Add(CoseKeyKeys.KeyIdentifier, CBORObject.FromObject(Encoding.UTF8.GetBytes(applicationName))); 72 | 73 | DTLSClientEndPoint ep = new DTLSClientEndPoint(authKey); 74 | (Implementation as CoapImplementation)._coapClient = new CoapClient(new Uri($"coaps://{_gatewayIp}")) 75 | { 76 | EndPoint = ep 77 | }; 78 | 79 | ep.Start(); 80 | } 81 | 82 | private Method ConvertToMethod(Call call) 83 | { 84 | switch (call) 85 | { 86 | case Call.GET: 87 | return Method.GET; 88 | case Call.POST: 89 | return Method.POST; 90 | case Call.DELETE: 91 | return Method.DELETE; 92 | case Call.PUT: 93 | return Method.PUT; 94 | default: 95 | throw new NotSupportedException("This is not supported for coap"); 96 | } 97 | } 98 | 99 | public Task GenerateAppSecret(string GatewaySecret, string applicationName) 100 | { 101 | OneKey authKey = new OneKey(); 102 | authKey.Add(CoseKeyKeys.KeyType, GeneralValues.KeyType_Octet); 103 | authKey.Add(CoseKeyParameterKeys.Octet_k, CBORObject.FromObject(Encoding.UTF8.GetBytes(GatewaySecret))); 104 | authKey.Add(CoseKeyKeys.KeyIdentifier, CBORObject.FromObject(Encoding.UTF8.GetBytes("Client_identity"))); 105 | 106 | var ep = new DTLSClientEndPoint(authKey); 107 | ep.Start(); 108 | 109 | return MakeRequest(new EndPointRequest($"/{(int)TradfriConstRoot.Gateway}/{(int)TradfriConstAttr.Auth}/") 110 | { 111 | Content = $"{{\"{(int)TradfriConstAttr.Identity}\":\"{applicationName}\"}}", 112 | Method = Call.POST, 113 | DTLSEndPoint = ep, 114 | RequestHandler = (resp) => resp switch 115 | { 116 | CreatedResponse crea => crea.Convert(), 117 | var other => throw other.ToException() 118 | } 119 | }); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /TradfriTerminalUI/MainLoop.cs: -------------------------------------------------------------------------------- 1 | using Martijn.Extensions.Linq; 2 | using Spectre.Console; 3 | using Spectre.Console.Json; 4 | using Tomidix.NetStandard.Dirigera.Devices; 5 | 6 | namespace TradfriTerminalUI 7 | { 8 | public class Details 9 | { 10 | public static async Task DetailView(Device? d) 11 | { 12 | AnsiConsole.Clear(); 13 | 14 | return d switch 15 | { 16 | EnvironmentSensor environmentSensor => EnvironmentDetailView(environmentSensor), 17 | Light light => await LightDetailView(light), 18 | _ => DetailViewFallback(d) 19 | }; 20 | } 21 | 22 | public static int DetailViewFallback(Device? device) 23 | { 24 | AnsiConsole.Confirm("The following value does not have a detail view: " + device?.Type, true); 25 | return 1; 26 | } 27 | 28 | public static int EnvironmentDetailView(EnvironmentSensor sensor) 29 | { 30 | var table = new Table(); 31 | table.AddColumns(new TableColumn("key"), new TableColumn("value")); 32 | 33 | var dict = new Dictionary { 34 | { "Temperature", sensor.Attributes.CurrentTemperature.ToString() }, 35 | { "PM25", sensor.Attributes.CurrentPM25.ToString() }, 36 | { "VOCIndex", sensor.Attributes.VocIndex.ToString() }, 37 | { "Humidity", sensor.Attributes.CurrentRH.ToString() }, 38 | }; 39 | 40 | dict.Foreach(item => 41 | { 42 | table.AddRow(item.Key, item.Value); 43 | }); 44 | 45 | AnsiConsole.Write(table); 46 | AnsiConsole.WriteLine(""); 47 | 48 | while (true) 49 | { 50 | string choice = AnsiConsole.Prompt( 51 | new SelectionPrompt() 52 | .MoreChoicesText("[grey](Move up and down to reveal more devices)[/]") 53 | .AddChoices([ 54 | "Exit" 55 | ])); 56 | 57 | 58 | if (choice == "Exit") 59 | { 60 | break; 61 | } 62 | } 63 | 64 | return 1; 65 | 66 | } 67 | 68 | public static async Task LightDetailView(Light light) 69 | { 70 | var table = new Table(); 71 | table.AddColumns(new TableColumn("key"), new TableColumn("value")); 72 | 73 | var dict = new Dictionary { 74 | { "Lightlevel", light.Attributes.LightLevel.ToString() }, 75 | { "IsOn", light.Attributes.IsOn.ToString() }, 76 | { "ColorTemperature", light.Attributes.ColorTemperature.ToString() }, 77 | { "ColorTemperatureMax", light.Attributes.ColorTemperatureMax.ToString() }, 78 | { "ColorTemperatureMin", light.Attributes.ColorTemperatureMin.ToString() }, 79 | }; 80 | 81 | dict.Foreach(item => 82 | { 83 | table.AddRow(item.Key, item.Value); 84 | }); 85 | 86 | AnsiConsole.Write(table); 87 | AnsiConsole.WriteLine(""); 88 | 89 | while (true) 90 | { 91 | string choice = AnsiConsole.Prompt( 92 | new SelectionPrompt() 93 | .MoreChoicesText("[grey](Move up and down to reveal more devices)[/]") 94 | .AddChoices([ 95 | "Set Temperature", 96 | "Set Lightlevel", 97 | "Toggle", 98 | "Exit" 99 | ])); 100 | if (choice == "Set Temperature") 101 | { 102 | var response = AnsiConsole.Ask("What should the temperature be?"); 103 | await light.SetLightTemperature(response); 104 | } 105 | 106 | if (choice == "Set Lightlevel") 107 | { 108 | var response = AnsiConsole.Ask("What should the lightlevel be?"); 109 | await light.SetLightLevel(response); 110 | } 111 | 112 | if (choice == "Toggle") 113 | { 114 | await light.Toggle(); 115 | } 116 | 117 | if (choice == "Exit") 118 | { 119 | break; 120 | } 121 | } 122 | 123 | return 1; 124 | 125 | } 126 | 127 | public static int JsonView(string input, string? name = null) 128 | { 129 | var json = new JsonText(input); 130 | 131 | AnsiConsole.Clear(); 132 | AnsiConsole.Write( 133 | new Panel(json) 134 | .Header(name) 135 | .RoundedBorder() 136 | .BorderColor(Color.Yellow)); 137 | 138 | AnsiConsole.Confirm("Press enter to go back", true); 139 | 140 | return 0; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /TradfriTerminalUI/Program.cs: -------------------------------------------------------------------------------- 1 |  2 | using Newtonsoft.Json; 3 | using Spectre.Console; 4 | using Tomidix.NetStandard.Dirigera; 5 | using Martijn.Extensions.Memory; 6 | using Martijn.Extensions.Time; 7 | using System.Net; 8 | using Tomidix.NetStandard.Dirigera.Devices; 9 | 10 | namespace TradfriTerminalUI 11 | { 12 | public class UserData 13 | { 14 | [JsonProperty("ip")] 15 | public string? IPAddress { get; set; } 16 | 17 | [JsonProperty("access_token")] 18 | public string? AccessToken { get; set; } 19 | 20 | [JsonProperty("hub_name")] 21 | public string? HubName { get; set; } 22 | } 23 | 24 | public partial class Program 25 | { 26 | private static readonly string SettingsFile = "userdata.json"; 27 | 28 | public static async Task Main(string[] args) 29 | { 30 | // We need to disable some server certification for these calls 31 | ServicePointManager.ServerCertificateValidationCallback += (o, c, ch, er) => true; 32 | 33 | Memory memory = new Memory(AppContext.BaseDirectory); 34 | var userData = await memory.ReadOrCalculate(SettingsFile, () => new UserData()); 35 | try 36 | { 37 | if (string.IsNullOrEmpty(userData.AccessToken)) 38 | { 39 | await Authenticate(userData); 40 | } 41 | else 42 | { 43 | await MainMenu(userData); 44 | } 45 | } 46 | catch (Exception e) 47 | { 48 | AnsiConsole.MarkupLine("[red]Encountered fatal error. Exiting...[/]"); 49 | AnsiConsole.WriteException(e); 50 | } 51 | await memory.Write(SettingsFile, userData); 52 | } 53 | 54 | public static async Task MainMenu(UserData data) 55 | { 56 | var controller = new DirigeraController(data.IPAddress!, data.AccessToken!); 57 | 58 | var me = await controller.UserController.GetMe(); 59 | 60 | AnsiConsole.MarkupLine("Hi, " + me.Name); 61 | 62 | // var devices = await controller.DeviceController.GetDevices(); 63 | await MainLoop(controller, new List()); 64 | 65 | // AnsiConsole.MarkupLine(Markup.Escape(devices)); 66 | 67 | 68 | 69 | } 70 | 71 | public static async Task MainLoop(DirigeraController controller, List devices) 72 | { 73 | while (true) 74 | { 75 | AnsiConsole.Clear(); 76 | var mapDeviceToString = (Device i) => 77 | { 78 | var text = i switch 79 | { 80 | Gateway g => g.ToString(), 81 | EnvironmentSensor environmentSensor => environmentSensor.ToString(), 82 | Light light => light.ToString(), 83 | Device d => "Unknown device:" + d.Type, 84 | _ => "Unknown value" 85 | }; 86 | return Markup.Escape(text); 87 | }; 88 | 89 | string chosenDevice = AnsiConsole.Prompt( 90 | new SelectionPrompt() 91 | .Title("Which device would you like to look into?") 92 | .EnableSearch() 93 | .MoreChoicesText("[grey](Move up and down to reveal more devices)[/]") 94 | .AddChoiceGroup("Devices", devices.Select(i => mapDeviceToString(i))) 95 | .AddChoiceGroup("System", ["Update devices", "Json Dump of devices", "Exit"])); 96 | if (chosenDevice == "Update devices") 97 | { 98 | devices = await controller.DeviceController.GetDevices(); 99 | continue; 100 | } 101 | 102 | if (chosenDevice == "Json Dump of devices") 103 | { 104 | Details.JsonView(await controller.DeviceController.GetDevicesJson(), "All devices"); 105 | continue; 106 | } 107 | 108 | if (chosenDevice == "Exit") 109 | { 110 | break; 111 | } 112 | 113 | await Details.DetailView(devices.FirstOrDefault(i => mapDeviceToString(i) == chosenDevice)); 114 | } 115 | } 116 | 117 | public static async Task Authenticate(UserData userData) 118 | { 119 | AnsiConsole.MarkupLine("Did not find a connection token. Starting authorization flow"); 120 | 121 | string code = DirigeraController.generateCodeVerifier(); 122 | // string code = "Hello"; 123 | AnsiConsole.MarkupLine("Generated code: [bold]{0}[/]", Markup.Escape(code)); 124 | 125 | string challenge = DirigeraController.calculateCodeChallenge(code); 126 | Console.WriteLine(challenge); 127 | AnsiConsole.MarkupLine("Using challenge: [bold]{0}[/]", Markup.Escape(challenge)); 128 | 129 | /// IP Address 130 | if (!string.IsNullOrEmpty(userData.IPAddress) && !AnsiConsole.Confirm($"Use {userData.IPAddress} to connect?")) 131 | { 132 | userData.IPAddress = null; 133 | } 134 | if (string.IsNullOrEmpty(userData.IPAddress)) 135 | { 136 | userData.IPAddress = AnsiConsole.Ask("What's the ip of the app? (it needs to be https://)"); 137 | } 138 | 139 | 140 | /// Hub Name 141 | if (!string.IsNullOrEmpty(userData.HubName) && !AnsiConsole.Confirm($"Use {userData.HubName} to connect?")) 142 | { 143 | userData.HubName = null; 144 | } 145 | if (string.IsNullOrEmpty(userData.HubName)) 146 | { 147 | userData.HubName = AnsiConsole.Ask("How would you like to name the hub?", "Debug Application"); 148 | } 149 | 150 | /// Connecting 151 | 152 | DirigeraController controller = new DirigeraController(userData.IPAddress); 153 | string? dirigeraCode = null; 154 | 155 | await AnsiConsole.Status() 156 | .Spinner(Spinner.Known.Dots) 157 | .StartAsync("Connecting to Dirigera Hub", async ctx => 158 | { 159 | dirigeraCode = (await controller.Authorize(challenge)).Code; 160 | }); 161 | 162 | 163 | 164 | await AnsiConsole.Status() 165 | .Spinner(Spinner.Known.Dots) 166 | .StartAsync("Trying to get an access token", async ctx => 167 | { 168 | AnsiConsole.MarkupLine("Press the Action Button on the bottom of your Dirigera Hub within 60 seconds."); 169 | 170 | 171 | for (int i = 0; i < 30; i++) 172 | { 173 | try 174 | { 175 | Thread.Sleep(TimeSpan.FromSeconds(3)); 176 | userData.AccessToken = (await controller.Pair(dirigeraCode!, code, userData.HubName)).AccessToken; 177 | break; 178 | } 179 | catch (Exception e) 180 | { 181 | AnsiConsole.MarkupLine("Got an exception: " + e.Message); 182 | ctx.Status = $"Trying to get an access token [[{i}/30]]"; 183 | } 184 | } 185 | 186 | 187 | }); 188 | 189 | AnsiConsole.MarkupLine("🎉 Successfully got the code 🎉"); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /TradfriTerminalUI/TradfriTerminalUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {6b39a6d0-adf6-4c55-9aef-00efe63141c0} 18 | Tradfri 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /TradfriTest/BaseTradfriTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using NUnit.Framework; 3 | using Tomidix.NetStandard.Tradfri; 4 | using Tomidix.NetStandard.Tradfri.Models; 5 | 6 | namespace Tomidix.NetCore.TradfriTest 7 | { 8 | public abstract class BaseTradfriTest 9 | { 10 | internal TradfriController tradfriController; 11 | 12 | [SetUp] 13 | public virtual void BaseSetup() 14 | { 15 | string applicationName = "UnitTestApp"; 16 | tradfriController = new TradfriController("GatewayName", "IP"); 17 | tradfriController.ConnectAppKey("PSK", applicationName); 18 | } 19 | 20 | // Real usage example 21 | public virtual async Task BaseSetupRecommendation() 22 | { 23 | // Unique name for the application which communicates with Tradfri gateway 24 | string applicationName = "UnitTestApp"; 25 | TradfriController controller = new TradfriController("GatewayName", "IP"); 26 | 27 | // This line should only be called once per applicationName 28 | // Gateway generates one appSecret key per applicationName 29 | TradfriAuth appSecret = await controller.GenerateAppSecret("PSK", applicationName); 30 | 31 | // You should now save programatically appSecret.PSK value and use it 32 | // when connection to your gateway every other time 33 | controller.ConnectAppKey(appSecret.PSK, applicationName); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /TradfriTest/ConnectTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Tomidix.NetCore.TradfriTest 4 | { 5 | internal class ConnectTest 6 | { 7 | [Test] 8 | public void SetColorTest() 9 | { 10 | //Fill in the following values 11 | string ip = null; 12 | string secret = null; 13 | 14 | if (ip == null || secret == null) 15 | { 16 | throw new InconclusiveException("You did not fill in anything"); 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TradfriTest/Controllers/DeviceControllerTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Tomidix.NetStandard.Tradfri.Controllers; 6 | using Tomidix.NetStandard.Tradfri.Models; 7 | 8 | namespace Tomidix.NetCore.TradfriTest.Controllers 9 | { 10 | internal class DeviceControllerTest : BaseTradfriTest 11 | { 12 | private DeviceController controller; 13 | private List devices; 14 | private List lights; 15 | private TradfriDevice colorLight; 16 | private TradfriDevice light; 17 | private TradfriDevice controlOutlet; 18 | 19 | [OneTimeSetUp] 20 | public async Task Setup() 21 | { 22 | BaseSetup(); 23 | controller = tradfriController.DeviceController; 24 | devices = new List(await tradfriController.GatewayController.GetDeviceObjects()); 25 | lights = devices.Where(i => i.DeviceType.Equals(DeviceType.Light)).ToList(); 26 | light = lights.FirstOrDefault(); 27 | colorLight = lights.FirstOrDefault(i => i.LightControl != null && i.LightControl[0]?.ColorHex != null); 28 | controlOutlet = devices.FirstOrDefault(i => i.DeviceType == DeviceType.ControlOutlet); 29 | } 30 | 31 | 32 | [Test] 33 | public async Task SetColorTest() 34 | { 35 | if (colorLight == null) 36 | { 37 | throw new InconclusiveException("There is no light with colors"); 38 | } 39 | await controller.SetColor(colorLight, TradfriColors.CoolWhite); 40 | } 41 | 42 | [Test] 43 | public async Task SetDimmerTest() 44 | { 45 | if (light == null) 46 | { 47 | throw new InconclusiveException("You have no lights"); 48 | } 49 | await controller.SetDimmer(light, 125); 50 | } 51 | 52 | [Test] 53 | public async Task SetLightTest() 54 | { 55 | if (light == null) 56 | { 57 | throw new InconclusiveException("You have no lights"); 58 | } 59 | await controller.SetLight(light, false); 60 | } 61 | 62 | [Test] 63 | public async Task SetOutletTest() 64 | { 65 | if (controlOutlet == null) 66 | { 67 | throw new InconclusiveException("You have no outlets"); 68 | } 69 | await controller.SetOutlet(controlOutlet, false); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /TradfriTest/Controllers/GatewayControllerTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Tomidix.NetStandard.Tradfri.Controllers; 5 | using Tomidix.NetStandard.Tradfri.Models; 6 | 7 | namespace Tomidix.NetCore.TradfriTest.Controllers 8 | { 9 | internal class GatewayControllerTest : BaseTradfriTest 10 | { 11 | private GatewayController controller; 12 | 13 | [SetUp] 14 | public void Setup() 15 | { 16 | BaseSetup(); 17 | controller = tradfriController.GatewayController; 18 | } 19 | 20 | 21 | [Test] 22 | public async Task GetGatewayInfo() 23 | { 24 | GatewayInfo obj = await controller.GetGatewayInfo(); 25 | Assert.NotNull(obj); 26 | Assert.Greater(obj.CurrentTimeUnix, 1546807755); 27 | } 28 | 29 | [Test] 30 | public async Task GetDevicesTest() 31 | { 32 | List obj = await controller.GetDevices(); 33 | Assert.NotNull(obj); 34 | Assert.Greater(obj.Count, 0); 35 | } 36 | 37 | [Test] 38 | public async Task GetDeviceObjectsTest() 39 | { 40 | List obj = await controller.GetDeviceObjects(); 41 | Assert.NotNull(obj); 42 | Assert.Greater(obj.Count, 0); 43 | } 44 | 45 | [Test] 46 | public async Task GetGroupsTest() 47 | { 48 | List obj = await controller.GetGroups(); 49 | Assert.NotNull(obj); 50 | Assert.Greater(obj.Count, 0); 51 | } 52 | 53 | [Test] 54 | public async Task GetGroupObjectsTest() 55 | { 56 | List obj = await controller.GetGroupObjects(); 57 | Assert.NotNull(obj); 58 | Assert.Greater(obj.Count, 0); 59 | } 60 | 61 | [Test] 62 | public async Task GetMoodsTest() 63 | { 64 | List obj = await controller.GetMoods(); 65 | Assert.NotNull(obj); 66 | Assert.Greater(obj.Count, 0); 67 | } 68 | 69 | [Test] 70 | public async Task GetSmartTasksTest() 71 | { 72 | List obj = await controller.GetSmartTasks(); 73 | Assert.NotNull(obj); 74 | Assert.Greater(obj.Count, 0); 75 | 76 | } 77 | 78 | [Test] 79 | public async Task GetSmartTaskObjectsTest() 80 | { 81 | List obj = await controller.GetSmartTaskObjects(); 82 | Assert.NotNull(obj); 83 | Assert.Greater(obj.Count, 0); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /TradfriTest/Controllers/GroupControllerTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Tomidix.NetStandard.Tradfri.Controllers; 6 | using Tomidix.NetStandard.Tradfri.Models; 7 | 8 | namespace Tomidix.NetCore.TradfriTest.Controllers 9 | { 10 | class GroupControllerTest : BaseTradfriTest 11 | { 12 | 13 | GroupController controller; 14 | List groups; 15 | TradfriGroup group; 16 | TradfriMood mood; 17 | 18 | [OneTimeSetUp] 19 | public async Task Setup() 20 | { 21 | BaseSetup(); 22 | controller = tradfriController.GroupController; 23 | groups = new List(await tradfriController.GatewayController.GetGroupObjects()); 24 | group = groups.FirstOrDefault(); 25 | mood = (await tradfriController.GatewayController.GetMoods()).FirstOrDefault(); 26 | } 27 | 28 | 29 | [Test] 30 | public async Task SetMoodTest() 31 | { 32 | if (group == null || mood == null) 33 | { 34 | throw new InconclusiveException("You have no groups"); 35 | } 36 | await controller.SetMood(group, mood); 37 | } 38 | 39 | [Test] 40 | public async Task SetDimmerTest() 41 | { 42 | if (group == null) 43 | { 44 | throw new InconclusiveException("You have no groups"); 45 | } 46 | await controller.SetDimmer(group, 125); 47 | } 48 | 49 | [Test] 50 | public async Task SetLightTest() 51 | { 52 | if (group == null) 53 | { 54 | throw new InconclusiveException("You have no groups"); 55 | } 56 | await controller.SetLight(group, true); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /TradfriTest/TradfriControllerTest.cs: -------------------------------------------------------------------------------- 1 | namespace Tomidix.NetCore.TradfriTest 2 | { 3 | internal class TradfriControllerTest : BaseTradfriTest 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /TradfriTest/TradfriTest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp2.2 4 | false 5 | Tomidix.NetCore.TradfriTest 6 | Tomidix.NetCore.TradfriTest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /TradfriUI/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | GW-Name 43 | 44 | 45 | GW-IP 46 | 47 | 48 | TradfriUI 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /TradfriUI/EnterGatewayPSK.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TradfriUI 2 | { 3 | partial class EnterGatewayPSK 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EnterGatewayPSK)); 32 | this.btnOK = new System.Windows.Forms.Button(); 33 | this.txtAppSecret = new System.Windows.Forms.TextBox(); 34 | this.lblAppSecret = new System.Windows.Forms.Label(); 35 | this.txtAppName = new System.Windows.Forms.TextBox(); 36 | this.lblAppName = new System.Windows.Forms.Label(); 37 | this.label1 = new System.Windows.Forms.Label(); 38 | this.btnSave = new System.Windows.Forms.Button(); 39 | this.label2 = new System.Windows.Forms.Label(); 40 | this.lblVersion = new System.Windows.Forms.Label(); 41 | this.label3 = new System.Windows.Forms.Label(); 42 | this.txtGatewayIP = new System.Windows.Forms.TextBox(); 43 | this.lblGatewayIP = new System.Windows.Forms.Label(); 44 | this.label5 = new System.Windows.Forms.Label(); 45 | this.txtGatewayName = new System.Windows.Forms.TextBox(); 46 | this.lblGatewayName = new System.Windows.Forms.Label(); 47 | this.SuspendLayout(); 48 | // 49 | // btnOK 50 | // 51 | this.btnOK.Location = new System.Drawing.Point(372, 382); 52 | this.btnOK.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 53 | this.btnOK.Name = "btnOK"; 54 | this.btnOK.Size = new System.Drawing.Size(185, 36); 55 | this.btnOK.TabIndex = 5; 56 | this.btnOK.Text = "Generate and Save"; 57 | this.btnOK.UseVisualStyleBackColor = true; 58 | this.btnOK.Click += new System.EventHandler(this.btnOK_Click); 59 | // 60 | // txtAppSecret 61 | // 62 | this.txtAppSecret.Location = new System.Drawing.Point(292, 304); 63 | this.txtAppSecret.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 64 | this.txtAppSecret.Name = "txtAppSecret"; 65 | this.txtAppSecret.Size = new System.Drawing.Size(265, 22); 66 | this.txtAppSecret.TabIndex = 3; 67 | // 68 | // lblAppSecret 69 | // 70 | this.lblAppSecret.AutoSize = true; 71 | this.lblAppSecret.Location = new System.Drawing.Point(153, 307); 72 | this.lblAppSecret.Name = "lblAppSecret"; 73 | this.lblAppSecret.Size = new System.Drawing.Size(138, 17); 74 | this.lblAppSecret.TabIndex = 12; 75 | this.lblAppSecret.Text = "Gateway/app secret:"; 76 | // 77 | // txtAppName 78 | // 79 | this.txtAppName.Enabled = false; 80 | this.txtAppName.Location = new System.Drawing.Point(292, 236); 81 | this.txtAppName.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 82 | this.txtAppName.Name = "txtAppName"; 83 | this.txtAppName.Size = new System.Drawing.Size(265, 22); 84 | this.txtAppName.TabIndex = 2; 85 | // 86 | // lblAppName 87 | // 88 | this.lblAppName.AutoSize = true; 89 | this.lblAppName.Location = new System.Drawing.Point(153, 239); 90 | this.lblAppName.Name = "lblAppName"; 91 | this.lblAppName.Size = new System.Drawing.Size(120, 17); 92 | this.lblAppName.TabIndex = 10; 93 | this.lblAppName.Text = "Application name:"; 94 | // 95 | // label1 96 | // 97 | this.label1.AutoSize = true; 98 | this.label1.Location = new System.Drawing.Point(230, 264); 99 | this.label1.Name = "label1"; 100 | this.label1.Size = new System.Drawing.Size(332, 17); 101 | this.label1.TabIndex = 11; 102 | this.label1.Text = "Your application name which is written in app.config"; 103 | // 104 | // btnSave 105 | // 106 | this.btnSave.Location = new System.Drawing.Point(156, 382); 107 | this.btnSave.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 108 | this.btnSave.Name = "btnSave"; 109 | this.btnSave.Size = new System.Drawing.Size(170, 36); 110 | this.btnSave.TabIndex = 4; 111 | this.btnSave.Text = "Save already generated"; 112 | this.btnSave.UseVisualStyleBackColor = true; 113 | this.btnSave.Click += new System.EventHandler(this.btnSave_Click); 114 | // 115 | // label2 116 | // 117 | this.label2.AutoSize = true; 118 | this.label2.Location = new System.Drawing.Point(239, 332); 119 | this.label2.Name = "label2"; 120 | this.label2.Size = new System.Drawing.Size(324, 17); 121 | this.label2.TabIndex = 13; 122 | this.label2.Text = "Original Gateway or already generated app secret"; 123 | // 124 | // lblVersion 125 | // 126 | this.lblVersion.AutoSize = true; 127 | this.lblVersion.Location = new System.Drawing.Point(560, 32); 128 | this.lblVersion.Name = "lblVersion"; 129 | this.lblVersion.Size = new System.Drawing.Size(118, 17); 130 | this.lblVersion.TabIndex = 14; 131 | this.lblVersion.Text = "Unknown Version"; 132 | // 133 | // label3 134 | // 135 | this.label3.AutoSize = true; 136 | this.label3.Location = new System.Drawing.Point(448, 195); 137 | this.label3.Name = "label3"; 138 | this.label3.Size = new System.Drawing.Size(109, 17); 139 | this.label3.TabIndex = 9; 140 | this.label3.Text = "Your gateway ip"; 141 | // 142 | // txtGatewayIP 143 | // 144 | this.txtGatewayIP.Location = new System.Drawing.Point(292, 167); 145 | this.txtGatewayIP.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 146 | this.txtGatewayIP.Name = "txtGatewayIP"; 147 | this.txtGatewayIP.Size = new System.Drawing.Size(265, 22); 148 | this.txtGatewayIP.TabIndex = 1; 149 | // 150 | // lblGatewayIP 151 | // 152 | this.lblGatewayIP.AutoSize = true; 153 | this.lblGatewayIP.Location = new System.Drawing.Point(153, 170); 154 | this.lblGatewayIP.Name = "lblGatewayIP"; 155 | this.lblGatewayIP.Size = new System.Drawing.Size(83, 17); 156 | this.lblGatewayIP.TabIndex = 8; 157 | this.lblGatewayIP.Text = "Gateway IP:"; 158 | // 159 | // label5 160 | // 161 | this.label5.AutoSize = true; 162 | this.label5.Location = new System.Drawing.Point(298, 120); 163 | this.label5.Name = "label5"; 164 | this.label5.Size = new System.Drawing.Size(264, 17); 165 | this.label5.TabIndex = 7; 166 | this.label5.Text = "Gateway name - just for your information"; 167 | // 168 | // txtGatewayName 169 | // 170 | this.txtGatewayName.Location = new System.Drawing.Point(292, 92); 171 | this.txtGatewayName.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 172 | this.txtGatewayName.Name = "txtGatewayName"; 173 | this.txtGatewayName.Size = new System.Drawing.Size(265, 22); 174 | this.txtGatewayName.TabIndex = 0; 175 | // 176 | // lblGatewayName 177 | // 178 | this.lblGatewayName.AutoSize = true; 179 | this.lblGatewayName.Location = new System.Drawing.Point(153, 95); 180 | this.lblGatewayName.Name = "lblGatewayName"; 181 | this.lblGatewayName.Size = new System.Drawing.Size(106, 17); 182 | this.lblGatewayName.TabIndex = 6; 183 | this.lblGatewayName.Text = "Gateway name:"; 184 | // 185 | // EnterGatewayPSK 186 | // 187 | this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); 188 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 189 | this.ClientSize = new System.Drawing.Size(711, 508); 190 | this.Controls.Add(this.label5); 191 | this.Controls.Add(this.txtGatewayName); 192 | this.Controls.Add(this.lblGatewayName); 193 | this.Controls.Add(this.label3); 194 | this.Controls.Add(this.txtGatewayIP); 195 | this.Controls.Add(this.lblGatewayIP); 196 | this.Controls.Add(this.lblVersion); 197 | this.Controls.Add(this.label2); 198 | this.Controls.Add(this.btnSave); 199 | this.Controls.Add(this.label1); 200 | this.Controls.Add(this.btnOK); 201 | this.Controls.Add(this.txtAppSecret); 202 | this.Controls.Add(this.lblAppSecret); 203 | this.Controls.Add(this.txtAppName); 204 | this.Controls.Add(this.lblAppName); 205 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 206 | this.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); 207 | this.Name = "EnterGatewayPSK"; 208 | this.Text = "Enter Gateway Settings"; 209 | this.Load += new System.EventHandler(this.EnterGatewayPSK_Load); 210 | this.ResumeLayout(false); 211 | this.PerformLayout(); 212 | 213 | } 214 | 215 | #endregion 216 | 217 | private System.Windows.Forms.Button btnOK; 218 | private System.Windows.Forms.TextBox txtAppSecret; 219 | private System.Windows.Forms.Label lblAppSecret; 220 | private System.Windows.Forms.TextBox txtAppName; 221 | private System.Windows.Forms.Label lblAppName; 222 | private System.Windows.Forms.Label label1; 223 | private System.Windows.Forms.Button btnSave; 224 | private System.Windows.Forms.Label label2; 225 | private System.Windows.Forms.Label lblVersion; 226 | private System.Windows.Forms.Label label3; 227 | private System.Windows.Forms.TextBox txtGatewayIP; 228 | private System.Windows.Forms.Label lblGatewayIP; 229 | private System.Windows.Forms.Label label5; 230 | private System.Windows.Forms.TextBox txtGatewayName; 231 | private System.Windows.Forms.Label lblGatewayName; 232 | } 233 | } -------------------------------------------------------------------------------- /TradfriUI/EnterGatewayPSK.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Deployment.Application; 3 | using System.Windows.Forms; 4 | 5 | namespace TradfriUI 6 | { 7 | public partial class EnterGatewayPSK : Form 8 | { 9 | public string GatewayName { get; set; } 10 | public string AppSecret { get; set; } 11 | public string IP { get; set; } 12 | 13 | public EnterGatewayPSK(string gwName, string appName, string ip) 14 | { 15 | InitializeComponent(); 16 | txtGatewayName.Text = gwName; 17 | txtAppName.Text = appName; 18 | txtGatewayIP.Text = ip; 19 | } 20 | 21 | 22 | 23 | private void btnOK_Click(object sender, EventArgs e) 24 | { 25 | if (!string.IsNullOrWhiteSpace(txtGatewayName.Text) && !string.IsNullOrWhiteSpace(txtAppSecret.Text) && !string.IsNullOrWhiteSpace(txtGatewayIP.Text)) 26 | { 27 | GatewayName = txtGatewayName.Text; 28 | AppSecret = txtAppSecret.Text; 29 | IP = txtGatewayIP.Text; 30 | this.DialogResult = DialogResult.OK; 31 | this.Close(); 32 | } 33 | } 34 | 35 | private void btnSave_Click(object sender, EventArgs e) 36 | { 37 | if (!string.IsNullOrWhiteSpace(txtGatewayName.Text) && !string.IsNullOrWhiteSpace(txtAppSecret.Text) && !string.IsNullOrWhiteSpace(txtGatewayIP.Text)) 38 | { 39 | GatewayName = txtGatewayName.Text; 40 | AppSecret = txtAppSecret.Text; 41 | IP = txtGatewayIP.Text; 42 | this.DialogResult = DialogResult.Yes; 43 | this.Close(); 44 | } 45 | } 46 | 47 | private void EnterGatewayPSK_Load(object sender, EventArgs e) 48 | { 49 | if (ApplicationDeployment.IsNetworkDeployed) 50 | { 51 | try 52 | { 53 | lblVersion.Text = $"Version: {ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString(4)}"; 54 | } 55 | catch { } 56 | } 57 | else 58 | { 59 | lblVersion.Text = $"Version: {Application.ProductVersion}"; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TradfriUI/Main.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace TradfriUI 2 | { 3 | partial class Main 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Main)); 32 | this.btnOff = new System.Windows.Forms.Button(); 33 | this.dgvDevices = new System.Windows.Forms.DataGridView(); 34 | this.btnOn = new System.Windows.Forms.Button(); 35 | this.btnGWReboot = new System.Windows.Forms.Button(); 36 | this.trbBrightness = new System.Windows.Forms.TrackBar(); 37 | this.btnBrightness = new System.Windows.Forms.Button(); 38 | this.lbxLog = new System.Windows.Forms.ListBox(); 39 | this.btnColor = new System.Windows.Forms.Button(); 40 | this.cmbColors = new System.Windows.Forms.ComboBox(); 41 | this.lblVersion = new System.Windows.Forms.Label(); 42 | this.btnMood = new System.Windows.Forms.Button(); 43 | this.btnRGBColor = new System.Windows.Forms.Button(); 44 | this.colorDlg = new System.Windows.Forms.ColorDialog(); 45 | this.btnAddDevice = new System.Windows.Forms.Button(); 46 | this.dgvGroups = new System.Windows.Forms.DataGridView(); 47 | this.btnAddToGroup = new System.Windows.Forms.Button(); 48 | this.lblGroups = new System.Windows.Forms.Label(); 49 | this.btnRenameDevice = new System.Windows.Forms.Button(); 50 | this.lblDevices = new System.Windows.Forms.Label(); 51 | this.lblGateway = new System.Windows.Forms.Label(); 52 | this.lblGatewayVersion = new System.Windows.Forms.Label(); 53 | this.btnRenameGroup = new System.Windows.Forms.Button(); 54 | this.btnCleanup = new System.Windows.Forms.Button(); 55 | this.lblSettingsPath = new System.Windows.Forms.Label(); 56 | this.lblSettingsPathValue = new System.Windows.Forms.Label(); 57 | ((System.ComponentModel.ISupportInitialize)(this.dgvDevices)).BeginInit(); 58 | ((System.ComponentModel.ISupportInitialize)(this.trbBrightness)).BeginInit(); 59 | ((System.ComponentModel.ISupportInitialize)(this.dgvGroups)).BeginInit(); 60 | this.SuspendLayout(); 61 | // 62 | // btnOff 63 | // 64 | this.btnOff.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 65 | this.btnOff.Location = new System.Drawing.Point(9, 450); 66 | this.btnOff.Margin = new System.Windows.Forms.Padding(2); 67 | this.btnOff.Name = "btnOff"; 68 | this.btnOff.Size = new System.Drawing.Size(76, 24); 69 | this.btnOff.TabIndex = 0; 70 | this.btnOff.Text = "Turn Off"; 71 | this.btnOff.UseVisualStyleBackColor = true; 72 | this.btnOff.Click += new System.EventHandler(this.btnOff_Click); 73 | // 74 | // dgvDevices 75 | // 76 | this.dgvDevices.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 77 | this.dgvDevices.Location = new System.Drawing.Point(8, 123); 78 | this.dgvDevices.Margin = new System.Windows.Forms.Padding(2); 79 | this.dgvDevices.Name = "dgvDevices"; 80 | this.dgvDevices.RowHeadersWidth = 51; 81 | this.dgvDevices.RowTemplate.Height = 28; 82 | this.dgvDevices.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 83 | this.dgvDevices.Size = new System.Drawing.Size(979, 309); 84 | this.dgvDevices.TabIndex = 1; 85 | this.dgvDevices.SelectionChanged += new System.EventHandler(this.dgvDevices_SelectionChanged); 86 | // 87 | // btnOn 88 | // 89 | this.btnOn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 90 | this.btnOn.Location = new System.Drawing.Point(881, 445); 91 | this.btnOn.Margin = new System.Windows.Forms.Padding(2); 92 | this.btnOn.Name = "btnOn"; 93 | this.btnOn.Size = new System.Drawing.Size(106, 24); 94 | this.btnOn.TabIndex = 2; 95 | this.btnOn.Text = "Turn On"; 96 | this.btnOn.UseVisualStyleBackColor = true; 97 | this.btnOn.Click += new System.EventHandler(this.btnOn_Click); 98 | // 99 | // btnGWReboot 100 | // 101 | this.btnGWReboot.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 102 | this.btnGWReboot.Location = new System.Drawing.Point(845, 45); 103 | this.btnGWReboot.Margin = new System.Windows.Forms.Padding(2); 104 | this.btnGWReboot.Name = "btnGWReboot"; 105 | this.btnGWReboot.Size = new System.Drawing.Size(141, 24); 106 | this.btnGWReboot.TabIndex = 3; 107 | this.btnGWReboot.Text = "Reboot Gateway"; 108 | this.btnGWReboot.UseVisualStyleBackColor = true; 109 | this.btnGWReboot.Click += new System.EventHandler(this.btnGWReboot_Click); 110 | // 111 | // trbBrightness 112 | // 113 | this.trbBrightness.Location = new System.Drawing.Point(118, 450); 114 | this.trbBrightness.Margin = new System.Windows.Forms.Padding(2); 115 | this.trbBrightness.Name = "trbBrightness"; 116 | this.trbBrightness.Size = new System.Drawing.Size(262, 56); 117 | this.trbBrightness.TabIndex = 4; 118 | this.trbBrightness.Value = 7; 119 | // 120 | // btnBrightness 121 | // 122 | this.btnBrightness.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 123 | this.btnBrightness.Location = new System.Drawing.Point(118, 484); 124 | this.btnBrightness.Margin = new System.Windows.Forms.Padding(2); 125 | this.btnBrightness.Name = "btnBrightness"; 126 | this.btnBrightness.Size = new System.Drawing.Size(262, 24); 127 | this.btnBrightness.TabIndex = 5; 128 | this.btnBrightness.Text = "Apply Brightness"; 129 | this.btnBrightness.UseVisualStyleBackColor = true; 130 | this.btnBrightness.Click += new System.EventHandler(this.btnBrightness_Click); 131 | // 132 | // lbxLog 133 | // 134 | this.lbxLog.FormattingEnabled = true; 135 | this.lbxLog.Location = new System.Drawing.Point(8, 794); 136 | this.lbxLog.Margin = new System.Windows.Forms.Padding(2); 137 | this.lbxLog.Name = "lbxLog"; 138 | this.lbxLog.Size = new System.Drawing.Size(978, 108); 139 | this.lbxLog.TabIndex = 6; 140 | // 141 | // btnColor 142 | // 143 | this.btnColor.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 144 | this.btnColor.Location = new System.Drawing.Point(421, 484); 145 | this.btnColor.Margin = new System.Windows.Forms.Padding(2); 146 | this.btnColor.Name = "btnColor"; 147 | this.btnColor.Size = new System.Drawing.Size(192, 24); 148 | this.btnColor.TabIndex = 7; 149 | this.btnColor.Text = "Apply Color"; 150 | this.btnColor.UseVisualStyleBackColor = true; 151 | this.btnColor.Click += new System.EventHandler(this.btnColor_Click); 152 | // 153 | // cmbColors 154 | // 155 | this.cmbColors.FormattingEnabled = true; 156 | this.cmbColors.Location = new System.Drawing.Point(421, 450); 157 | this.cmbColors.Margin = new System.Windows.Forms.Padding(2); 158 | this.cmbColors.Name = "cmbColors"; 159 | this.cmbColors.Size = new System.Drawing.Size(193, 21); 160 | this.cmbColors.TabIndex = 8; 161 | // 162 | // lblVersion 163 | // 164 | this.lblVersion.AutoSize = true; 165 | this.lblVersion.Location = new System.Drawing.Point(884, 17); 166 | this.lblVersion.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 167 | this.lblVersion.Name = "lblVersion"; 168 | this.lblVersion.Size = new System.Drawing.Size(103, 15); 169 | this.lblVersion.TabIndex = 10; 170 | this.lblVersion.Text = "Unknown Version"; 171 | // 172 | // btnMood 173 | // 174 | this.btnMood.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 175 | this.btnMood.Location = new System.Drawing.Point(881, 482); 176 | this.btnMood.Margin = new System.Windows.Forms.Padding(2); 177 | this.btnMood.Name = "btnMood"; 178 | this.btnMood.Size = new System.Drawing.Size(106, 24); 179 | this.btnMood.TabIndex = 11; 180 | this.btnMood.Text = "Set Mood Test"; 181 | this.btnMood.UseVisualStyleBackColor = true; 182 | this.btnMood.Click += new System.EventHandler(this.btnMood_ClickAsync); 183 | // 184 | // btnRGBColor 185 | // 186 | this.btnRGBColor.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 187 | this.btnRGBColor.Location = new System.Drawing.Point(644, 445); 188 | this.btnRGBColor.Margin = new System.Windows.Forms.Padding(2); 189 | this.btnRGBColor.Name = "btnRGBColor"; 190 | this.btnRGBColor.Size = new System.Drawing.Size(209, 24); 191 | this.btnRGBColor.TabIndex = 12; 192 | this.btnRGBColor.Text = "Set RGB Color"; 193 | this.btnRGBColor.UseVisualStyleBackColor = true; 194 | this.btnRGBColor.Click += new System.EventHandler(this.btnRGBColor_Click); 195 | // 196 | // colorDlg 197 | // 198 | this.colorDlg.SolidColorOnly = true; 199 | // 200 | // btnAddDevice 201 | // 202 | this.btnAddDevice.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 203 | this.btnAddDevice.Location = new System.Drawing.Point(832, 744); 204 | this.btnAddDevice.Margin = new System.Windows.Forms.Padding(2); 205 | this.btnAddDevice.Name = "btnAddDevice"; 206 | this.btnAddDevice.Size = new System.Drawing.Size(154, 24); 207 | this.btnAddDevice.TabIndex = 13; 208 | this.btnAddDevice.Text = "Add Device (Pair)"; 209 | this.btnAddDevice.UseVisualStyleBackColor = true; 210 | this.btnAddDevice.Click += new System.EventHandler(this.btnAddDevice_Click); 211 | // 212 | // dgvGroups 213 | // 214 | this.dgvGroups.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 215 | this.dgvGroups.Location = new System.Drawing.Point(9, 565); 216 | this.dgvGroups.Margin = new System.Windows.Forms.Padding(2); 217 | this.dgvGroups.MultiSelect = false; 218 | this.dgvGroups.Name = "dgvGroups"; 219 | this.dgvGroups.RowHeadersWidth = 51; 220 | this.dgvGroups.RowTemplate.Height = 28; 221 | this.dgvGroups.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect; 222 | this.dgvGroups.Size = new System.Drawing.Size(979, 160); 223 | this.dgvGroups.TabIndex = 14; 224 | // 225 | // btnAddToGroup 226 | // 227 | this.btnAddToGroup.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 228 | this.btnAddToGroup.Location = new System.Drawing.Point(8, 744); 229 | this.btnAddToGroup.Margin = new System.Windows.Forms.Padding(2); 230 | this.btnAddToGroup.Name = "btnAddToGroup"; 231 | this.btnAddToGroup.Size = new System.Drawing.Size(149, 24); 232 | this.btnAddToGroup.TabIndex = 15; 233 | this.btnAddToGroup.Text = "Add Device To Selected Group"; 234 | this.btnAddToGroup.UseVisualStyleBackColor = true; 235 | this.btnAddToGroup.Click += new System.EventHandler(this.btnAddToGroup_Click); 236 | // 237 | // lblGroups 238 | // 239 | this.lblGroups.AutoSize = true; 240 | this.lblGroups.Location = new System.Drawing.Point(11, 548); 241 | this.lblGroups.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 242 | this.lblGroups.Name = "lblGroups"; 243 | this.lblGroups.Size = new System.Drawing.Size(50, 15); 244 | this.lblGroups.TabIndex = 16; 245 | this.lblGroups.Text = "Groups:"; 246 | // 247 | // btnRenameDevice 248 | // 249 | this.btnRenameDevice.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 250 | this.btnRenameDevice.Location = new System.Drawing.Point(644, 482); 251 | this.btnRenameDevice.Margin = new System.Windows.Forms.Padding(2); 252 | this.btnRenameDevice.Name = "btnRenameDevice"; 253 | this.btnRenameDevice.Size = new System.Drawing.Size(209, 24); 254 | this.btnRenameDevice.TabIndex = 17; 255 | this.btnRenameDevice.Text = "Rename selected device"; 256 | this.btnRenameDevice.UseVisualStyleBackColor = true; 257 | this.btnRenameDevice.Click += new System.EventHandler(this.btnRenameDevice_Click); 258 | // 259 | // lblDevices 260 | // 261 | this.lblDevices.AutoSize = true; 262 | this.lblDevices.Location = new System.Drawing.Point(11, 106); 263 | this.lblDevices.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 264 | this.lblDevices.Name = "lblDevices"; 265 | this.lblDevices.Size = new System.Drawing.Size(53, 15); 266 | this.lblDevices.TabIndex = 18; 267 | this.lblDevices.Text = "Devices:"; 268 | // 269 | // lblGateway 270 | // 271 | this.lblGateway.AutoSize = true; 272 | this.lblGateway.Location = new System.Drawing.Point(11, 17); 273 | this.lblGateway.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 274 | this.lblGateway.Name = "lblGateway"; 275 | this.lblGateway.Size = new System.Drawing.Size(57, 15); 276 | this.lblGateway.TabIndex = 19; 277 | this.lblGateway.Text = "Gateway:"; 278 | // 279 | // lblGatewayVersion 280 | // 281 | this.lblGatewayVersion.AutoSize = true; 282 | this.lblGatewayVersion.Location = new System.Drawing.Point(11, 45); 283 | this.lblGatewayVersion.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 284 | this.lblGatewayVersion.Name = "lblGatewayVersion"; 285 | this.lblGatewayVersion.Size = new System.Drawing.Size(99, 15); 286 | this.lblGatewayVersion.TabIndex = 21; 287 | this.lblGatewayVersion.Text = "Gateway version:"; 288 | // 289 | // btnRenameGroup 290 | // 291 | this.btnRenameGroup.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 292 | this.btnRenameGroup.Location = new System.Drawing.Point(606, 744); 293 | this.btnRenameGroup.Margin = new System.Windows.Forms.Padding(2); 294 | this.btnRenameGroup.Name = "btnRenameGroup"; 295 | this.btnRenameGroup.Size = new System.Drawing.Size(209, 24); 296 | this.btnRenameGroup.TabIndex = 22; 297 | this.btnRenameGroup.Text = "Rename selected group"; 298 | this.btnRenameGroup.UseVisualStyleBackColor = true; 299 | this.btnRenameGroup.Click += new System.EventHandler(this.btnRenameGroup_Click); 300 | // 301 | // btnCleanup 302 | // 303 | this.btnCleanup.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 304 | this.btnCleanup.Location = new System.Drawing.Point(9, 62); 305 | this.btnCleanup.Margin = new System.Windows.Forms.Padding(2); 306 | this.btnCleanup.Name = "btnCleanup"; 307 | this.btnCleanup.Size = new System.Drawing.Size(141, 24); 308 | this.btnCleanup.TabIndex = 23; 309 | this.btnCleanup.Text = "Reset settings"; 310 | this.btnCleanup.UseVisualStyleBackColor = true; 311 | this.btnCleanup.Click += new System.EventHandler(this.btnCleanup_Click); 312 | // 313 | // lblSettingsPath 314 | // 315 | this.lblSettingsPath.AutoSize = true; 316 | this.lblSettingsPath.Location = new System.Drawing.Point(11, 88); 317 | this.lblSettingsPath.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 318 | this.lblSettingsPath.Name = "lblSettingsPath"; 319 | this.lblSettingsPath.Size = new System.Drawing.Size(81, 15); 320 | this.lblSettingsPath.TabIndex = 24; 321 | this.lblSettingsPath.Text = "Settings path:"; 322 | // 323 | // lblSettingsPathValue 324 | // 325 | this.lblSettingsPathValue.AutoSize = true; 326 | this.lblSettingsPathValue.Location = new System.Drawing.Point(115, 88); 327 | this.lblSettingsPathValue.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); 328 | this.lblSettingsPathValue.Name = "lblSettingsPathValue"; 329 | this.lblSettingsPathValue.Size = new System.Drawing.Size(11, 15); 330 | this.lblSettingsPathValue.TabIndex = 25; 331 | this.lblSettingsPathValue.Text = "-"; 332 | this.lblSettingsPathValue.DoubleClick += new System.EventHandler(this.lblSettingsPathValue_DoubleClick); 333 | // 334 | // Main 335 | // 336 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 337 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 338 | this.ClientSize = new System.Drawing.Size(1004, 922); 339 | this.Controls.Add(this.lblSettingsPathValue); 340 | this.Controls.Add(this.lblSettingsPath); 341 | this.Controls.Add(this.btnCleanup); 342 | this.Controls.Add(this.btnRenameGroup); 343 | this.Controls.Add(this.lblGatewayVersion); 344 | this.Controls.Add(this.lblGateway); 345 | this.Controls.Add(this.lblDevices); 346 | this.Controls.Add(this.btnRenameDevice); 347 | this.Controls.Add(this.lblGroups); 348 | this.Controls.Add(this.btnAddToGroup); 349 | this.Controls.Add(this.dgvGroups); 350 | this.Controls.Add(this.btnAddDevice); 351 | this.Controls.Add(this.btnRGBColor); 352 | this.Controls.Add(this.btnMood); 353 | this.Controls.Add(this.lblVersion); 354 | this.Controls.Add(this.cmbColors); 355 | this.Controls.Add(this.btnColor); 356 | this.Controls.Add(this.lbxLog); 357 | this.Controls.Add(this.btnBrightness); 358 | this.Controls.Add(this.trbBrightness); 359 | this.Controls.Add(this.btnGWReboot); 360 | this.Controls.Add(this.btnOn); 361 | this.Controls.Add(this.dgvDevices); 362 | this.Controls.Add(this.btnOff); 363 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 364 | this.Margin = new System.Windows.Forms.Padding(2); 365 | this.Name = "Main"; 366 | this.Text = "Main"; 367 | this.Load += new System.EventHandler(this.Main_Load); 368 | ((System.ComponentModel.ISupportInitialize)(this.dgvDevices)).EndInit(); 369 | ((System.ComponentModel.ISupportInitialize)(this.trbBrightness)).EndInit(); 370 | ((System.ComponentModel.ISupportInitialize)(this.dgvGroups)).EndInit(); 371 | this.ResumeLayout(false); 372 | this.PerformLayout(); 373 | 374 | } 375 | 376 | #endregion 377 | 378 | private System.Windows.Forms.Button btnOff; 379 | private System.Windows.Forms.DataGridView dgvDevices; 380 | private System.Windows.Forms.Button btnOn; 381 | private System.Windows.Forms.Button btnGWReboot; 382 | private System.Windows.Forms.TrackBar trbBrightness; 383 | private System.Windows.Forms.Button btnBrightness; 384 | private System.Windows.Forms.ListBox lbxLog; 385 | private System.Windows.Forms.Button btnColor; 386 | private System.Windows.Forms.ComboBox cmbColors; 387 | private System.Windows.Forms.Label lblVersion; 388 | private System.Windows.Forms.Button btnMood; 389 | private System.Windows.Forms.Button btnRGBColor; 390 | private System.Windows.Forms.ColorDialog colorDlg; 391 | private System.Windows.Forms.Button btnAddDevice; 392 | private System.Windows.Forms.DataGridView dgvGroups; 393 | private System.Windows.Forms.Button btnAddToGroup; 394 | private System.Windows.Forms.Label lblGroups; 395 | private System.Windows.Forms.Button btnRenameDevice; 396 | private System.Windows.Forms.Label lblDevices; 397 | private System.Windows.Forms.Label lblGateway; 398 | private System.Windows.Forms.Label lblGatewayVersion; 399 | private System.Windows.Forms.Button btnRenameGroup; 400 | private System.Windows.Forms.Button btnCleanup; 401 | private System.Windows.Forms.Label lblSettingsPath; 402 | private System.Windows.Forms.Label lblSettingsPathValue; 403 | } 404 | } 405 | 406 | -------------------------------------------------------------------------------- /TradfriUI/Main.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Deployment.Application; 6 | using System.Diagnostics; 7 | using System.Drawing; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Reflection; 11 | using System.Text.RegularExpressions; 12 | using System.Windows.Forms; 13 | using Tomidix.NetStandard.Tradfri; 14 | using Tomidix.NetStandard.Tradfri.Models; 15 | using TradfriUI.Settings; 16 | 17 | namespace TradfriUI 18 | { 19 | public partial class Main : Form 20 | { 21 | private string UserAppDataFilePath = Application.UserAppDataPath + "\\userdata.json"; 22 | private bool loadingSelectedRows = true; 23 | 24 | public delegate void Print(int value); 25 | 26 | private UserData userData; 27 | private TradfriController tradfriController; 28 | 29 | private delegate void ObserveLightDelegate(TradfriDevice observableDevice); 30 | 31 | public Main() 32 | { 33 | InitializeComponent(); 34 | } 35 | 36 | private async void Main_Load(object sender, EventArgs e) 37 | { 38 | if (ApplicationDeployment.IsNetworkDeployed) 39 | { 40 | try 41 | { 42 | lblVersion.Text = $"App Version: {ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString(4)}"; 43 | } 44 | catch { } 45 | } 46 | else 47 | { 48 | lblVersion.Text = $"App Version: {Application.ProductVersion}"; 49 | } 50 | lblSettingsPathValue.Text = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath.ToString(); 51 | 52 | //preload colors combobox 53 | cmbColors.DisplayMember = "ColorName"; 54 | //cmbColors.SelectedValueChanged += (s, ev) => cmbColors.SelectedText = s.ToString(); 55 | cmbColors.ValueMember = "ColorId"; 56 | //cmbColors.SelectedValueChanged += (s, ev) => cmbColors.SelectedText = s.ToString(); 57 | foreach (FieldInfo p in typeof(TradfriColors).GetFields()) 58 | { 59 | object v = p.GetValue(null); // static classes cannot be instanced, so use null... 60 | cmbColors.Items.Add(new { ColorName = p.Name, ColorId = v }); 61 | } 62 | 63 | // prepare/load settings 64 | userData = loadUserData(); 65 | 66 | if (string.IsNullOrWhiteSpace(Properties.Settings.Default.appSecret)) 67 | { 68 | using (EnterGatewayPSK form = new EnterGatewayPSK(Properties.Settings.Default.gatewayName, Properties.Settings.Default.appName, Properties.Settings.Default.gatewayIp)) 69 | { 70 | DialogResult result = form.ShowDialog(); 71 | if (result == DialogResult.OK) 72 | { 73 | tradfriController = new TradfriController(form.GatewayName, form.IP); 74 | // generating appSecret on gateway - appSecret is connected with the appName so you must use a combination 75 | // Gateway generates one appSecret key per applicationName 76 | TradfriAuth appSecret = tradfriController.GenerateAppSecret(form.AppSecret, Properties.Settings.Default.appName); 77 | // saving programatically appSecret.PSK value to settings 78 | Properties.Settings.Default.appSecret = appSecret.PSK; 79 | Properties.Settings.Default.gatewayName = form.GatewayName; 80 | Properties.Settings.Default.gatewayIp = form.IP; 81 | Properties.Settings.Default.Save(); 82 | 83 | } 84 | else if (result == DialogResult.Yes) 85 | { 86 | tradfriController = new TradfriController(form.GatewayName, form.IP); 87 | // saving programatically appSecret.PSK value to settings 88 | Properties.Settings.Default.appSecret = form.AppSecret; 89 | Properties.Settings.Default.gatewayName = form.GatewayName; 90 | Properties.Settings.Default.gatewayIp = form.IP; 91 | Properties.Settings.Default.Save(); 92 | } 93 | else 94 | { 95 | MessageBox.Show("You haven't entered your Gateway PSK so applicationSecret can't be generated on gateway." + Environment.NewLine + 96 | "You can also enter it manually into app.config if you have one for your app already.", 97 | "Error acquiring appSecret", 98 | MessageBoxButtons.OK, 99 | MessageBoxIcon.Error); 100 | this.Close(); 101 | } 102 | } 103 | } 104 | // initialize controller based on settings 105 | else 106 | { 107 | tradfriController = new TradfriController(Properties.Settings.Default.gatewayName, Properties.Settings.Default.gatewayIp); 108 | } 109 | 110 | // connection to gateway 111 | tradfriController.ConnectAppKey(Properties.Settings.Default.appSecret, Properties.Settings.Default.appName); 112 | GatewayInfo g = await tradfriController.GatewayController.GetGatewayInfo(); 113 | lblGatewayVersion.Text += g.Firmware; 114 | lblGateway.Text += Properties.Settings.Default.gatewayIp; 115 | 116 | List devices = new List(await tradfriController.GatewayController.GetDeviceObjects()).OrderBy(x => x.DeviceType.ToString()).ToList(); 117 | List lights = devices.Where(i => i.DeviceType.Equals(DeviceType.Light)).ToList(); 118 | List groups = new List(await tradfriController.GatewayController.GetGroupObjects()).OrderBy(x => x.Name).ToList(); 119 | 120 | // set datasource for dgv 121 | dgvDevices.DataSource = devices; 122 | dgvDevices.AutoGenerateColumns = true; 123 | 124 | // set datasource for dgv 125 | dgvGroups.DataSource = groups; 126 | dgvGroups.AutoGenerateColumns = true; 127 | 128 | // temporary disable autosave on rowSelectionChange 129 | loadingSelectedRows = true; 130 | // clear default selection 131 | dgvDevices.ClearSelection(); 132 | //reselect rows from settings 133 | if (userData.SelectedDevices.Count > 0 && devices.Count > 0) 134 | { 135 | foreach (DataGridViewRow row in dgvDevices.Rows) 136 | { 137 | if (userData.SelectedDevices.Any(x => x == (long)row.Cells["ID"].Value)) 138 | { 139 | row.Selected = true; 140 | } 141 | } 142 | } 143 | // re-enable autosave on rowSelectionChange 144 | loadingSelectedRows = false; 145 | 146 | if (lights.Count > 0) 147 | { 148 | // acquire first and last lights from grid 149 | TradfriDevice firstLight = lights.FirstOrDefault(); 150 | TradfriDevice lastLight = lights.LastOrDefault(); 151 | // function handler for observe events 152 | void ObserveLightEvent(TradfriDevice x) 153 | { 154 | TradfriDevice eventDevice = devices.Where(d => d.ID.Equals(x.ID)).SingleOrDefault(); 155 | eventDevice = x; 156 | if (dgvDevices.SelectedRows.Count > 0) 157 | { 158 | foreach (object item in dgvDevices.SelectedRows) 159 | { 160 | if (((TradfriDevice)((DataGridViewRow)item).DataBoundItem).ID.Equals(eventDevice.ID)) 161 | { 162 | this.Invoke((MethodInvoker)delegate 163 | { 164 | Debug.WriteLine($"{DateTime.Now} - triggered: {x.DeviceType}, {x.Name}, {x.LightControl[0].State}, {x.LightControl[0].Dimmer}"); 165 | lbxLog.Items.Add($"{DateTime.Now} - triggered: {x.DeviceType}, {x.Name}, {x.LightControl[0].State}, {x.LightControl[0].Dimmer}"); 166 | lbxLog.SelectedIndex = lbxLog.Items.Count - 1; 167 | trbBrightness.Value = Convert.ToInt16(eventDevice.LightControl[0].Dimmer * (double)10 / 254); 168 | }); 169 | } 170 | } 171 | } 172 | }; 173 | 174 | ObserveLightDelegate lightDelegate = new ObserveLightDelegate(ObserveLightEvent); 175 | // observe first light from grid 176 | tradfriController.DeviceController.ObserveDevice(firstLight, x => lightDelegate(x)); 177 | // observe last light from grid if the bulbs are different 178 | if (firstLight.ID != lastLight.ID) 179 | { 180 | tradfriController.DeviceController.ObserveDevice(lastLight, x => lightDelegate(x)); 181 | } 182 | } 183 | } 184 | 185 | private void btnOff_Click(object sender, EventArgs e) 186 | { 187 | SetSelectedLights(false); 188 | } 189 | 190 | private void btnOn_Click(object sender, EventArgs e) 191 | { 192 | SetSelectedLights(true); 193 | } 194 | 195 | private void SetSelectedLights(bool state) 196 | { 197 | // turn off the lights for selected rows in grid (rows, not cells) 198 | for (int index = 0; index < dgvDevices.SelectedRows.Count; index++) 199 | { 200 | 201 | TradfriDevice currentSelectedDevice = (TradfriDevice)(dgvDevices.SelectedRows[index]).DataBoundItem; 202 | if (currentSelectedDevice.DeviceType.Equals(DeviceType.Light)) 203 | { 204 | tradfriController.DeviceController.SetLight(currentSelectedDevice, state); 205 | } 206 | } 207 | } 208 | 209 | private void btnGWReboot_Click(object sender, EventArgs e) 210 | { 211 | tradfriController.GatewayController.Reboot(); 212 | } 213 | 214 | private void dgvDevices_SelectionChanged(object sender, EventArgs e) 215 | { 216 | if (dgvDevices.SelectedRows.Count > 0) 217 | { 218 | TradfriDevice firstSelectedLight = (TradfriDevice)dgvDevices.SelectedRows[0].DataBoundItem; 219 | if (firstSelectedLight.DeviceType.Equals(DeviceType.Light)) 220 | { 221 | trbBrightness.Value = Convert.ToInt16(firstSelectedLight.LightControl[0].Dimmer * (double)10 / 254); 222 | } 223 | if (!loadingSelectedRows) 224 | { 225 | userData.SelectedDevices = new List(); 226 | foreach (object item in dgvDevices.SelectedRows) 227 | { 228 | userData.SelectedDevices.Add(((TradfriDevice)((DataGridViewRow)item).DataBoundItem).ID); 229 | } 230 | File.WriteAllText(UserAppDataFilePath, JsonConvert.SerializeObject(userData)); 231 | } 232 | } 233 | } 234 | 235 | private UserData loadUserData() 236 | { 237 | if (File.Exists(UserAppDataFilePath)) 238 | { 239 | return JsonConvert.DeserializeObject(File.ReadAllText(UserAppDataFilePath)); 240 | } 241 | else 242 | { 243 | return new UserData(); 244 | } 245 | } 246 | 247 | private void btnBrightness_Click(object sender, EventArgs e) 248 | { 249 | // set brightness of the lights for selected rows in grid (rows, not cells) 250 | for (int index = 0; index < dgvDevices.SelectedRows.Count; index++) 251 | { 252 | TradfriDevice currentSelectedDevice = (TradfriDevice)(dgvDevices.SelectedRows[index]).DataBoundItem; 253 | if (currentSelectedDevice.DeviceType.Equals(DeviceType.Light)) 254 | { 255 | tradfriController.DeviceController.SetDimmer(currentSelectedDevice, trbBrightness.Value * 10 * 254 / 100); 256 | } 257 | } 258 | } 259 | 260 | private void btnColor_Click(object sender, EventArgs e) 261 | { 262 | PropertyInfo propertyInfo = cmbColors.Items[0].GetType().GetProperty("ColorId"); 263 | // set color of the lights for selected rows in grid (rows, not cells) 264 | for (int index = 0; index < dgvDevices.SelectedRows.Count; index++) 265 | { 266 | TradfriDevice currentSelectedDevice = (TradfriDevice)(dgvDevices.SelectedRows[index]).DataBoundItem; 267 | if (currentSelectedDevice.DeviceType.Equals(DeviceType.Light)) 268 | { 269 | tradfriController.DeviceController.SetColor(currentSelectedDevice, (string)propertyInfo.GetValue(cmbColors.SelectedItem, null)); 270 | } 271 | } 272 | } 273 | 274 | private async void btnMood_ClickAsync(object sender, EventArgs e) 275 | { 276 | TradfriGroup group = (TradfriGroup)dgvGroups.Rows[0].DataBoundItem; 277 | List moods = new List(await tradfriController.GatewayController.GetMoods()).OrderBy(x => x.Name).ToList(); 278 | 279 | TradfriMood relaxMood = moods.First(m => m.Name.Equals("RELAX", StringComparison.OrdinalIgnoreCase) && m.GroupID.Equals(group.ID)); 280 | 281 | // recommended if you want to use group instance later on 282 | tradfriController.GroupController.SetMood(group, relaxMood); 283 | 284 | // just change the mood 285 | //tradfriController.GroupController.SetMood(relaxMood.GroupID, relaxMood.ID); 286 | 287 | /* 288 | * set custom tradfri mood properties to every bulb in a group 289 | tradfriController.GroupController.SetMood(groups[0].ID, new TradfriMoodProperties 290 | { 291 | ColorHex = "f1e0b5", 292 | ColorHue = 24394, 293 | ColorSaturation = 5800, 294 | ColorX = 65535, 295 | ColorY = 65535, 296 | Dimmer = 254, 297 | ID = 1, 298 | LightState = 1, 299 | Mireds = 0 300 | }); 301 | */ 302 | 303 | } 304 | 305 | private void btnRGBColor_Click(object sender, EventArgs e) 306 | { 307 | if (colorDlg.ShowDialog() == DialogResult.OK) 308 | { 309 | Color clr = colorDlg.Color; 310 | 311 | // set color of the lights for selected rows in grid (rows, not cells) 312 | for (int index = 0; index < dgvDevices.SelectedRows.Count; index++) 313 | { 314 | TradfriDevice currentSelectedDevice = (TradfriDevice)(dgvDevices.SelectedRows[index]).DataBoundItem; 315 | if (currentSelectedDevice.DeviceType.Equals(DeviceType.Light)) 316 | { 317 | tradfriController.DeviceController.SetColor(currentSelectedDevice, clr.R, clr.G, clr.B); 318 | } 319 | } 320 | } 321 | 322 | } 323 | 324 | private async void btnAddDevice_Click(object sender, EventArgs e) 325 | { 326 | tradfriController.GatewayController.AddDevice(); 327 | } 328 | 329 | private async void btnAddToGroup_Click(object sender, EventArgs e) 330 | { 331 | var selectedGroup = (TradfriGroup)dgvGroups.SelectedRows[0].DataBoundItem; 332 | 333 | tradfriController.GatewayController.AddDevice(selectedGroup.ID, 60); 334 | } 335 | 336 | private void btnRenameDevice_Click(object sender, EventArgs e) 337 | { 338 | try 339 | { 340 | TradfriDevice currentSelectedDevice = (TradfriDevice)dgvDevices.SelectedRows[0].DataBoundItem; 341 | tradfriController.DeviceController.RenameTradfriDevice(currentSelectedDevice).Wait(); 342 | MessageBox.Show("Done", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); 343 | } 344 | catch (Exception ex) 345 | { 346 | MessageBox.Show(ex.InnerException.Message 347 | + Environment.NewLine 348 | + "First change the Name in column, leave the row selected and press the 'Rename' button." 349 | , "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 350 | } 351 | } 352 | 353 | private void btnRenameGroup_Click(object sender, EventArgs e) 354 | { 355 | try 356 | { 357 | TradfriGroup currentSelectedGroup = (TradfriGroup)dgvGroups.SelectedRows[0].DataBoundItem; 358 | tradfriController.GroupController.RenameTradfriGroup(currentSelectedGroup).Wait(); 359 | MessageBox.Show("Done", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information); 360 | } 361 | catch (Exception ex) 362 | { 363 | MessageBox.Show(ex.InnerException.Message 364 | + Environment.NewLine 365 | + "First change the Name in column, leave the row selected and press the 'Rename' button." 366 | , "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 367 | } 368 | } 369 | 370 | private void btnCleanup_Click(object sender, EventArgs e) 371 | { 372 | Properties.Settings.Default.Reset(); 373 | } 374 | 375 | private void lblSettingsPathValue_DoubleClick(object sender, EventArgs e) 376 | { 377 | Clipboard.SetText(lblSettingsPathValue.Text); 378 | MessageBox.Show("Path value copied to clipboard."); 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /TradfriUI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace TradfriUI 5 | { 6 | static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | static void Main() 13 | { 14 | Application.EnableVisualStyles(); 15 | Application.SetCompatibleTextRenderingDefault(false); 16 | Application.Run(new Main()); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TradfriUI/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("TradfriUI")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("TradfriUI")] 12 | [assembly: AssemblyCopyright("Copyright © 2019")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("e74f7b9b-80b3-443b-a34f-eb4a859dd0ff")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("2.1.0.5")] 35 | [assembly: AssemblyFileVersion("2.1.0.5")] 36 | -------------------------------------------------------------------------------- /TradfriUI/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TradfriUI.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TradfriUI.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /TradfriUI/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /TradfriUI/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace TradfriUI.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("TradfriUI")] 29 | public string appName { 30 | get { 31 | return ((string)(this["appName"])); 32 | } 33 | set { 34 | this["appName"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("")] 41 | public string appSecret { 42 | get { 43 | return ((string)(this["appSecret"])); 44 | } 45 | set { 46 | this["appSecret"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("GW-Name")] 53 | public string gatewayName { 54 | get { 55 | return ((string)(this["gatewayName"])); 56 | } 57 | set { 58 | this["gatewayName"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("GW-IP")] 65 | public string gatewayIp { 66 | get { 67 | return ((string)(this["gatewayIp"])); 68 | } 69 | set { 70 | this["gatewayIp"] = value; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /TradfriUI/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | GW-Name 7 | 8 | 9 | GW-IP 10 | 11 | 12 | TradfriUI 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /TradfriUI/Settings/UserData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace TradfriUI.Settings 4 | { 5 | public class UserData 6 | { 7 | public UserData() 8 | { 9 | SelectedDevices = new List(); 10 | } 11 | 12 | public UserData(List selectedDevices) 13 | { 14 | SelectedDevices = selectedDevices; 15 | } 16 | 17 | public List SelectedDevices { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TradfriUI/TradfriUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E74F7B9B-80B3-443B-A34F-EB4A859DD0FF} 8 | WinExe 9 | TradfriUI 10 | TradfriUI 11 | v4.7.2 12 | 512 13 | true 14 | true 15 | PackageReference 16 | 17 | 18 | AnyCPU 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | AnyCPU 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | bulbIcon.ico 38 | 39 | 40 | 41 | ..\packages\ApiLibs.1.36.0\lib\netstandard2.0\ApiLibs.dll 42 | 43 | 44 | ..\packages\Portable.BouncyCastle.1.8.5\lib\net40\BouncyCastle.Crypto.dll 45 | 46 | 47 | ..\packages\PeterO.Cbor.3.4.0\lib\net40\CBOR.dll 48 | 49 | 50 | ..\packages\Com.AugustCellars.CoAP.1.2.0\lib\net462\CoAP.dll 51 | 52 | 53 | ..\packages\Com.AugustCellars.COSE.1.4.0\lib\net462\COSE.dll 54 | 55 | 56 | ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll 57 | 58 | 59 | ..\packages\PeterO.Numbers.1.2.2\lib\net40\Numbers.dll 60 | 61 | 62 | ..\packages\RestSharp.106.6.9\lib\net452\RestSharp.dll 63 | 64 | 65 | 66 | ..\packages\System.Collections.Specialized.4.3.0\lib\net46\System.Collections.Specialized.dll 67 | True 68 | True 69 | 70 | 71 | 72 | 73 | ..\packages\System.Net.NameResolution.4.3.0\lib\net46\System.Net.NameResolution.dll 74 | True 75 | True 76 | 77 | 78 | ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll 79 | True 80 | True 81 | 82 | 83 | ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll 84 | True 85 | True 86 | 87 | 88 | ..\packages\System.Threading.ThreadPool.4.3.0\lib\net46\System.Threading.ThreadPool.dll 89 | True 90 | True 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | Form 106 | 107 | 108 | EnterGatewayPSK.cs 109 | 110 | 111 | Form 112 | 113 | 114 | Main.cs 115 | 116 | 117 | 118 | 119 | 120 | EnterGatewayPSK.cs 121 | 122 | 123 | Main.cs 124 | 125 | 126 | ResXFileCodeGenerator 127 | Resources.Designer.cs 128 | Designer 129 | 130 | 131 | True 132 | Resources.resx 133 | 134 | 135 | 136 | SettingsSingleFileGenerator 137 | Settings.Designer.cs 138 | 139 | 140 | True 141 | Settings.settings 142 | True 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | {6b39a6d0-adf6-4c55-9aef-00efe63141c0} 151 | Tradfri 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /TradfriUI/bulbIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dominicusmento/CSharpTradFriLibrary/981d27f3783f24be797c005cb0f9168b7b8ac4ee/TradfriUI/bulbIcon.ico -------------------------------------------------------------------------------- /TradfriUI/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /roslynator.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------