├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── ParticleSDK.sln ├── ParticleSDK ├── Models │ ├── ParticleAuthenticationResponse.cs │ ├── ParticleDeviceResponse.cs │ ├── ParticleEventGroup.cs │ ├── ParticleEventHandlerData.cs │ ├── ParticleEventResponse.cs │ ├── ParticleFunctionResponse.cs │ ├── ParticleGenericResponse.cs │ └── ParticleVariableResponse.cs ├── Particle.SDK.netcorestandard.csproj ├── Particle.SDK.win81+wpa81.csproj ├── Particle.SDK.win81+wpa81.nuspec ├── ParticleCloud.cs ├── ParticleDevice.cs ├── ParticleExceptions.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── AssemblyInfo.net452.cs │ └── AssemblyInfo.win81+wpa81.cs ├── RestApi │ ├── ParticleRestApiBase.cs │ ├── ParticleRestApiSystemNetHttp.cs │ └── ParticleRestApiWindowsWebHttp.cs ├── Utils │ └── DeviceNameGenerator.cs └── packages.config └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Microsoft Azure ApplicationInsights config file 170 | ApplicationInsights.config 171 | 172 | # Windows Store app package directory 173 | AppPackages/ 174 | BundleArtifacts/ 175 | 176 | # Visual Studio cache files 177 | # files ending in .cache can be ignored 178 | *.[Cc]ache 179 | # but keep track of directories ending in .cache 180 | !*.[Cc]ache/ 181 | 182 | # Others 183 | ClientBin/ 184 | [Ss]tyle[Cc]op.* 185 | ~$* 186 | *~ 187 | *.dbmdl 188 | *.dbproj.schemaview 189 | *.pfx 190 | *.publishsettings 191 | node_modules/ 192 | orleans.codegen.cs 193 | 194 | # RIA/Silverlight projects 195 | Generated_Code/ 196 | 197 | # Backup & report files from converting an old project file 198 | # to a newer Visual Studio version. Backup files are not needed, 199 | # because we have git ;-) 200 | _UpgradeReport_Files/ 201 | Backup*/ 202 | UpgradeLog*.XML 203 | UpgradeLog*.htm 204 | 205 | # SQL Server files 206 | *.mdf 207 | *.ldf 208 | 209 | # Business Intelligence projects 210 | *.rdl.data 211 | *.bim.layout 212 | *.bim_*.settings 213 | 214 | # Microsoft Fakes 215 | FakesAssemblies/ 216 | 217 | # GhostDoc plugin setting file 218 | *.GhostDoc.xml 219 | 220 | # Node.js Tools for Visual Studio 221 | .ntvs_analysis.dat 222 | 223 | # Visual Studio 6 build log 224 | *.plg 225 | 226 | # Visual Studio 6 workspace options file 227 | *.opt 228 | 229 | # Visual Studio LightSwitch build output 230 | **/*.HTMLClient/GeneratedArtifacts 231 | **/*.DesktopClient/GeneratedArtifacts 232 | **/*.DesktopClient/ModelManifest.xml 233 | **/*.Server/GeneratedArtifacts 234 | **/*.Server/ModelManifest.xml 235 | _Pvt_Extensions 236 | 237 | # LightSwitch generated files 238 | GeneratedArtifacts/ 239 | ModelManifest.xml 240 | 241 | # Paket dependency manager 242 | .paket/paket.exe 243 | 244 | # FAKE - F# Make 245 | .fake/ 246 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | Particle Windows Cloud SDK adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | --- 7 | 8 | ## [0.1.0](https://github.com/spark/particle-windows-sdk/releases/tag/v0.1.0) (2016-05-16) 9 | 10 | * Add .NET Framework 4.5.2 library for use in non WinRT applications 11 | * `ParticleCloud` 12 | * Add public properties `OAuthClientId` and `OAuthClientSecret` for setting OAuth client tokens 13 | * Add `SignupWithCustomerAsync`, `RequestPasswordResetForCustomerAsync`, and `CreateClaimCodeForOrganizationAsync` 14 | * `ParticleDevice` 15 | * Add `SignalAsync` to send a signal to the device to shout rainbows 16 | * Add public properties `IsFlashing`, `KnownPlatformId`, and `KnownProductId` 17 | * Add **Digistump Oak**, **RedBear Duo**, and **Bluz DK** to the list of know devices 18 | 19 | ## [0.0.5](https://github.com/spark/particle-windows-sdk/releases/tag/v0.0.5) (2016-05-03) 20 | 21 | * Initial Release 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ParticleSDK.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2016 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Particle.SDK.win81+wpa81", "ParticleSDK\Particle.SDK.win81+wpa81.csproj", "{DC5F6004-31B8-4E9A-B034-4F163905E54E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Particle.SDK.netcorestandard", "ParticleSDK\Particle.SDK.netcorestandard.csproj", "{6CD84312-68C1-4293-9320-C8E83D410936}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {DC5F6004-31B8-4E9A-B034-4F163905E54E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {DC5F6004-31B8-4E9A-B034-4F163905E54E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {DC5F6004-31B8-4E9A-B034-4F163905E54E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {DC5F6004-31B8-4E9A-B034-4F163905E54E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {6CD84312-68C1-4293-9320-C8E83D410936}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {6CD84312-68C1-4293-9320-C8E83D410936}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {6CD84312-68C1-4293-9320-C8E83D410936}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {6CD84312-68C1-4293-9320-C8E83D410936}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D37C7006-732B-4C2D-B98C-75939D844F5A} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleAuthenticationResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Particle.SDK.Models 4 | { 5 | /// 6 | /// Helper class from Particle Cloud Authentication request 7 | /// 8 | public class ParticleAuthenticationResponse 9 | { 10 | [JsonProperty("token_type")] 11 | public string TokenType { get; set; } 12 | [JsonProperty("access_token")] 13 | public string AccessToken { get; set; } 14 | [JsonProperty("expires_in")] 15 | public int ExpiresIn { get; set; } 16 | [JsonProperty("refresh_token")] 17 | public string RefreshToken { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleDeviceResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Particle.SDK.Models 6 | { 7 | /// 8 | /// Helper class from Particle Cloud Device Information request 9 | /// 10 | public class ParticleDeviceResponse 11 | { 12 | [JsonProperty("id")] 13 | public string Id { get; internal set; } 14 | [JsonProperty("name")] 15 | public string Name { get; internal set; } 16 | [JsonProperty("last_app")] 17 | public string LastApp { get; internal set; } 18 | [JsonProperty("last_ip_address")] 19 | public string LastIPAddress { get; internal set; } 20 | [JsonProperty("last_heard")] 21 | public DateTime LastHeard { get; internal set; } 22 | [JsonProperty("requires_deep_update")] 23 | public bool RequiresDeepUpdate { get; internal set; } 24 | [JsonProperty("product_id")] 25 | public int ProductId { get; internal set; } 26 | [JsonProperty("connected")] 27 | public bool Connected { get; internal set; } 28 | [JsonProperty("platform_id")] 29 | public int PlatformId { get; internal set; } 30 | [JsonProperty("cellular")] 31 | public bool Cellular { get; internal set; } 32 | [JsonProperty("status")] 33 | public string Status { get; internal set; } 34 | [JsonProperty("last_iccid")] 35 | public string LastICCID { get; internal set; } 36 | [JsonProperty("imei")] 37 | public string IMEI { get; internal set; } 38 | [JsonProperty("current_build_target")] 39 | public string CurrentBuildTarget { get; internal set; } 40 | [JsonProperty("variables")] 41 | public Dictionary Variables { get; internal set; } 42 | [JsonProperty("functions")] 43 | public string[] Functions { get; internal set; } 44 | [JsonProperty("cc3000_patch_version")] 45 | public string CC3000PatchVersion { get; internal set; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleEventGroup.cs: -------------------------------------------------------------------------------- 1 | using Particle.SDK.Models; 2 | 3 | namespace Particle.SDK 4 | { 5 | /// 6 | /// Class of ParticleEventHandler for a specific relative path for events 7 | /// 8 | public class ParticleEventGroup 9 | { 10 | /// 11 | /// Global event called by ParticleEventGroup whenever a new message arrives 12 | /// 13 | public event ParticleEventHandler OnMessage; 14 | 15 | /// 16 | /// Whether or not this group has any handlers 17 | /// 18 | public bool HasHandlers 19 | { 20 | get { return OnMessage != null; } 21 | } 22 | 23 | /// 24 | /// Add new ParticleEventHandler 25 | /// 26 | /// ParticleEventHandler to add 27 | public void AddHandler(ParticleEventHandler eventHandler) 28 | { 29 | OnMessage += eventHandler; 30 | } 31 | 32 | /// 33 | /// New message for this group of event handlers 34 | /// 35 | /// Object sending request 36 | /// ParticleEventResponse 37 | public void NewMessage(object sender, ParticleEventResponse particeEvent) 38 | { 39 | try { 40 | if (HasHandlers) 41 | OnMessage(sender, particeEvent); 42 | } 43 | catch 44 | { 45 | } 46 | } 47 | 48 | /// 49 | /// Remove ParticleEventHandler 50 | /// 51 | /// ParticleEventHandler to remove 52 | public void RemoveHandler(ParticleEventHandler eventHandler) 53 | { 54 | OnMessage -= eventHandler; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleEventHandlerData.cs: -------------------------------------------------------------------------------- 1 | namespace Particle.SDK.Models 2 | { 3 | public delegate void ParticleEventHandler(object sender, ParticleEventResponse particeEvent); 4 | 5 | /// 6 | /// Class of relative path and ParticleEventHandler for tracking handlers for events 7 | /// 8 | public class ParticleEventHandlerData 9 | { 10 | public string Path { get; internal set; } 11 | public ParticleEventHandler EventHandler { get; internal set; } 12 | 13 | public ParticleEventHandlerData(string path, ParticleEventHandler eventHandler) 14 | { 15 | Path = path; 16 | EventHandler = eventHandler; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleEventResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Particle.SDK.Models 6 | { 7 | /// 8 | /// Helper class from Particle Cloud Event Stream request 9 | /// 10 | public class ParticleEventResponse 11 | { 12 | public string Name { get; set; } 13 | 14 | [JsonProperty("data")] 15 | public string Data { get; set; } 16 | [JsonProperty("ttl")] 17 | public int TTL { get; set; } 18 | [JsonProperty("published_at")] 19 | public DateTime PublishedAt { get; set; } 20 | [JsonProperty("coreid")] 21 | public string DeviceId { get; set; } 22 | } 23 | 24 | /// 25 | /// Collection class from Particle Cloud Event Stream request 26 | /// 27 | public class ParticleEventResponseCollection : List 28 | { 29 | public ParticleEventResponseCollection() 30 | { 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleFunctionResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Particle.SDK.Models 4 | { 5 | /// 6 | /// Helper class from Particle Cloud Function request 7 | /// 8 | public class ParticleFunctionResponse 9 | { 10 | [JsonProperty("id")] 11 | public string Id { get; set; } 12 | [JsonProperty("name")] 13 | public string Name { get; set; } 14 | [JsonProperty("last_app")] 15 | public string LastApp { get; set; } 16 | [JsonProperty("connected")] 17 | public bool Connected { get; set; } 18 | [JsonProperty("return_value")] 19 | public int ReturnValue { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleGenericResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Particle.SDK.Models 4 | { 5 | /// 6 | /// Helper class from general Particle Cloud request 7 | /// 8 | internal class ParticleGenericResponse 9 | { 10 | [JsonProperty("code")] 11 | public int Code { get; set; } 12 | [JsonProperty("ok")] 13 | public bool Ok { get; set; } 14 | [JsonProperty("message")] 15 | public string Message { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ParticleSDK/Models/ParticleVariableResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Particle.SDK.Models 5 | { 6 | /// 7 | /// Helper class from Particle Cloud Variable request coreInfo data 8 | /// 9 | public class CoreInfo 10 | { 11 | [JsonProperty("last_app")] 12 | public string LastApp { get; set; } 13 | [JsonProperty("last_heard")] 14 | public DateTime LastHeard { get; set; } 15 | [JsonProperty("connected")] 16 | public bool Connected { get; set; } 17 | [JsonProperty("last_handshake_at")] 18 | public DateTime LastHandshakeAt { get; set; } 19 | [JsonProperty("deviceID")] 20 | public string DeviceId { get; set; } 21 | [JsonProperty("product_id")] 22 | public int ProductId { get; set; } 23 | } 24 | 25 | /// 26 | /// Helper class from Particle Cloud Variable request 27 | /// 28 | public class ParticleVariableResponse 29 | { 30 | [JsonProperty("cmd")] 31 | public string Command { get; set; } 32 | [JsonProperty("name")] 33 | public string Name { get; set; } 34 | [JsonProperty("result")] 35 | public dynamic Result { get; set; } 36 | [JsonProperty("coreInfo")] 37 | public CoreInfo CoreInfo { get; set; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ParticleSDK/Particle.SDK.netcorestandard.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ParticleSDK/Particle.SDK.win81+wpa81.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 12.0 6 | Debug 7 | AnyCPU 8 | {DC5F6004-31B8-4E9A-B034-4F163905E54E} 9 | Library 10 | Properties 11 | Particle.SDK 12 | Particle.SDK 13 | en-US 14 | 512 15 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | Profile32 17 | v4.6 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\portable46-win81+wpa81\ 24 | obj\Debug\portable46-win81+wpa81\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | false 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\portable46-win81+wpa81\ 34 | obj\Release\portable46-win81+wpa81\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ..\packages\Newtonsoft.Json.8.0.3\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll 67 | True 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ParticleSDK/Particle.SDK.win81+wpa81.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $id$ 5 | $version$ 6 | $title$ 7 | $author$ 8 | $author$ 9 | https://github.com/spark/particle-windows-sdk/blob/master/LICENSE 10 | https://github.com/spark/particle-windows-sdk 11 | https://secure.gravatar.com/avatar/41d69da42a30b02153710eb33a186ad7?s=64 12 | false 13 | $description$ 14 | Initial Release 15 | Copyright 2016 16 | IoT Photon Particle Tinker connected Spark Core Electron Arduino 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ParticleSDK/ParticleCloud.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Particle.SDK.Models; 4 | using Particle.SDK.RestApi; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Threading.Tasks; 9 | 10 | namespace Particle.SDK 11 | { 12 | /// 13 | /// Class for communicating with Particle Cloud 14 | /// 15 | public class ParticleCloud : ParticleRestApi 16 | { 17 | #region Internal Constants 18 | 19 | internal static readonly string ParticleApiVersion = "v1"; 20 | 21 | internal static readonly string ParticleApiPahtLogin = "oauth/token"; 22 | internal static readonly string ParticleApiPathSignup = "users"; 23 | internal static readonly string ParticleApiPathPasswordReset = "user/password-reset"; 24 | internal static readonly string ParticleApiPathUser = "user"; 25 | internal static readonly string ParticleApiPathDevices = "devices"; 26 | internal static readonly string ParticleApiPathClaimCode = "device_claims"; 27 | internal static readonly string ParticleApiPathSimDataUsage = "sims/{0}/data_usage"; 28 | internal static readonly string ParticleApiPathEvents = "events"; 29 | internal static readonly string ParticleApiPathDevicesEvents = "devices/events"; 30 | internal static readonly string ParticleApiPathDeviceEvents = "devices/{0}/events"; 31 | internal static readonly string ParticleApiPathCustomerSignup = "orgs/{0}/customers"; 32 | internal static readonly string ParticleApiPathCustomerPasswordReset = "orgs/{0}/customers/reset_password"; 33 | internal static readonly string ParticleApiPathOrganizationClaimCode = "orgs/{0}/products/{1}/device_claims"; 34 | 35 | #endregion 36 | 37 | #region Private Members 38 | 39 | private Dictionary particleEventGroups = null; 40 | private Dictionary particleEventHandlerDatas = null; 41 | 42 | #endregion 43 | 44 | #region Private Static Members 45 | 46 | private static ParticleCloud particleCloud = null; 47 | 48 | #endregion 49 | 50 | #region Static Properties 51 | 52 | /// 53 | /// Instance of a static shared ParticleCloud for simplicity 54 | /// 55 | public static ParticleCloud SharedCloud 56 | { 57 | get 58 | { 59 | if (particleCloud == null) 60 | particleCloud = new ParticleCloud(); 61 | 62 | return particleCloud; 63 | } 64 | } 65 | 66 | #endregion 67 | 68 | #region Constructors 69 | 70 | /// 71 | /// Create a new instance of ParticleCloud 72 | /// 73 | /// A token from a prevous OAuth call 74 | public ParticleCloud(string accessToken = null) 75 | { 76 | SetOAuthClient(); 77 | 78 | if (accessToken != null) 79 | { 80 | particleAuthentication = new ParticleAuthenticationResponse(); 81 | particleAuthentication.AccessToken = accessToken; 82 | } 83 | } 84 | 85 | #endregion 86 | 87 | #region Public Auth Methods 88 | 89 | /// 90 | /// Create a token for a user via OAuth 91 | /// 92 | /// Username of the user 93 | /// Password for the user 94 | /// Seconds in which to expire the token. Zero for never 95 | /// Returns a ParticleAuthenticationResponse 96 | public async Task CreateTokenAsync(string username, string password, int expiresIn = 0) 97 | { 98 | if (string.IsNullOrWhiteSpace(username)) 99 | throw new ArgumentNullException(nameof(username)); 100 | if (string.IsNullOrWhiteSpace(password)) 101 | throw new ArgumentNullException(nameof(password)); 102 | 103 | var data = new Dictionary 104 | { 105 | {"grant_type", "password"}, 106 | {"username", username}, 107 | {"password", password}, 108 | {"expires_in", expiresIn.ToString()} 109 | }; 110 | 111 | try 112 | { 113 | var responseContent = await PostDataAsync(ParticleApiPahtLogin, data, true); 114 | var results = JsonConvert.DeserializeObject(responseContent); 115 | if (results != null && !string.IsNullOrWhiteSpace(results.AccessToken)) 116 | return results; 117 | 118 | return null; 119 | } 120 | catch 121 | { 122 | return null; 123 | } 124 | } 125 | 126 | /// 127 | /// Login a user and save the state if valid 128 | /// 129 | /// Username of the user 130 | /// Password for the user 131 | /// Seconds in which to expire the token. Zero for never 132 | /// Returns true if the user credentials are valid 133 | public async Task LoginAsync(string username, string password, int expiresIn = 0) 134 | { 135 | var results = await CreateTokenAsync(username, password, expiresIn); 136 | if (results != null) 137 | { 138 | particleAuthentication = results; 139 | logedInUsername = username; 140 | 141 | return true; 142 | } 143 | else 144 | { 145 | return false; 146 | } 147 | } 148 | 149 | /// 150 | /// Request a password reset email be sent to the user 151 | /// 152 | /// Username to request password reset for 153 | /// Returns true if the username exists 154 | public async Task RequestPasswordResetAsync(string username) 155 | { 156 | if (string.IsNullOrWhiteSpace(username)) 157 | throw new ArgumentNullException(nameof(username)); 158 | 159 | var data = new Dictionary 160 | { 161 | {"username", username} 162 | }; 163 | 164 | try 165 | { 166 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{ParticleApiPathPasswordReset}", data); 167 | var results = JsonConvert.DeserializeObject(responseContent); 168 | if (results != null && results.Ok) 169 | { 170 | return true; 171 | } 172 | 173 | return false; 174 | } 175 | catch 176 | { 177 | return false; 178 | } 179 | } 180 | 181 | /// 182 | /// Set the authentication values from previous values 183 | /// 184 | /// Previously saved access token 185 | /// Previously saved expires in value 186 | /// Previously saved refresh token 187 | public void SetAuthentication(string accessToken, int? expires_in = null, string refresh_token = null) 188 | { 189 | particleAuthentication = new ParticleAuthenticationResponse(); 190 | particleAuthentication.AccessToken = accessToken; 191 | if (expires_in != null) 192 | particleAuthentication.ExpiresIn = expires_in.Value; 193 | if (refresh_token != null) 194 | particleAuthentication.RefreshToken = refresh_token; 195 | } 196 | 197 | /// 198 | /// Create a new user and save the state 199 | /// 200 | /// Username of new user 201 | /// Password for new user 202 | /// Returns true if the new user is signed up 203 | public async Task SignupAsync(string username, string password) 204 | { 205 | if (string.IsNullOrWhiteSpace(username)) 206 | throw new ArgumentNullException(nameof(username)); 207 | if (string.IsNullOrWhiteSpace(password)) 208 | throw new ArgumentNullException(nameof(password)); 209 | 210 | var data = new Dictionary 211 | { 212 | {"username", username}, 213 | {"password", password}, 214 | }; 215 | 216 | try 217 | { 218 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{ParticleApiPathSignup}", data); 219 | var result = JToken.Parse(responseContent); 220 | if ((bool)result["ok"] == true) 221 | { 222 | return await LoginAsync(username, password); 223 | } 224 | else 225 | { 226 | return false; 227 | } 228 | } 229 | catch 230 | { 231 | return false; 232 | } 233 | } 234 | 235 | /// 236 | /// Validate a token and save the state if valid 237 | /// 238 | /// A token from a prevous OAuth call 239 | /// Returns true if the token is valid 240 | public async Task TokenLoginAsync(string token) 241 | { 242 | particleAuthentication = new ParticleAuthenticationResponse(); 243 | particleAuthentication.AccessToken = token; 244 | 245 | try 246 | { 247 | var responseContent = await GetDataAsync($"{ParticleApiVersion}/{ParticleApiPathUser}"); 248 | return true; 249 | } 250 | catch 251 | { 252 | Logout(); 253 | return false; 254 | } 255 | } 256 | 257 | #endregion 258 | 259 | #region Public Device Methods 260 | 261 | /// 262 | /// Claim a device 263 | /// 264 | /// Device ID to claim 265 | /// Returns true if a device could be claimed 266 | public async Task ClaimDeviceAsync(string deviceId) 267 | { 268 | if (string.IsNullOrWhiteSpace(deviceId)) 269 | throw new ArgumentNullException(nameof(deviceId)); 270 | 271 | var data = new Dictionary 272 | { 273 | {"id", deviceId} 274 | }; 275 | 276 | try 277 | { 278 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{ParticleApiPathDevices}", data); 279 | var result = JToken.Parse(responseContent); 280 | return true; 281 | } 282 | catch 283 | { 284 | return false; 285 | } 286 | } 287 | 288 | /// 289 | /// Create a claim code for sending to a device in listening mode 290 | /// 291 | /// Returns a claim code 292 | public async Task CreateClaimCodeAsync() 293 | { 294 | try 295 | { 296 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{ParticleApiPathClaimCode}"); 297 | var result = JToken.Parse(responseContent); 298 | return (string)result["claim_code"]; 299 | } 300 | catch 301 | { 302 | return null; 303 | } 304 | } 305 | 306 | /// 307 | /// Flash a compiled firmware to a device 308 | /// A return of true only means it was sent to the device, not that flash is successful 309 | /// 310 | /// ParticleDevice 311 | /// Stream of compiled binary 312 | /// Filename of compiled binary 313 | /// Returns true if binary is sent to device 314 | public async Task DeviceFlashBinaryAsync(ParticleDevice particeDevice, Stream firmwareStream, string filename) 315 | { 316 | return await DeviceFlashBinaryAsync(particeDevice.Id, firmwareStream, filename); 317 | } 318 | 319 | /// 320 | /// Gets a device 321 | /// 322 | /// Device ID 323 | /// Returns a ParticleDevice 324 | public async Task GetDeviceAsync(string deviceId) 325 | { 326 | try 327 | { 328 | ParticleDevice device = new ParticleDevice(deviceId, this); 329 | await device.RefreshAsync(); 330 | 331 | return device; 332 | } 333 | catch 334 | { 335 | return null; 336 | } 337 | } 338 | 339 | /// 340 | /// Gets a list of all users devices 341 | /// 342 | /// Returns a List of ParticleDevices 343 | public async Task> GetDevicesAsync() 344 | { 345 | try 346 | { 347 | var jsonSerializer = new JsonSerializer(); 348 | jsonSerializer.DateTimeZoneHandling = DateTimeZoneHandling.Local; 349 | 350 | var responseContent = await GetDataAsync($"{ParticleApiVersion}/{ParticleApiPathDevices}"); 351 | var result = JToken.Parse(responseContent); 352 | 353 | List devices = new List(); 354 | foreach (JObject device in (JArray)result) 355 | { 356 | ParticleDeviceResponse deviceState = device.ToObject(jsonSerializer); 357 | ParticleDevice particleDevice = new ParticleDevice(deviceState, this); 358 | devices.Add(particleDevice); 359 | } 360 | 361 | foreach (ParticleDevice device in devices) 362 | { 363 | if (device.Connected) 364 | await device.RefreshAsync(); 365 | } 366 | 367 | return new List(devices); 368 | } 369 | catch 370 | { 371 | return null; 372 | } 373 | } 374 | 375 | #endregion 376 | 377 | #region Public Organization Methods 378 | 379 | /// 380 | /// Create a new user in an organization 381 | /// 382 | /// Organization slug 383 | /// Email of new user 384 | /// Password for new user 385 | /// Returns true if the new user is signed up 386 | public async Task SignupWithCustomerAsync(string organizationSlug, string email, string password) 387 | { 388 | if (string.IsNullOrWhiteSpace(organizationSlug)) 389 | throw new ArgumentNullException(nameof(organizationSlug)); 390 | if (string.IsNullOrWhiteSpace(email)) 391 | throw new ArgumentNullException(nameof(email)); 392 | if (string.IsNullOrWhiteSpace(password)) 393 | throw new ArgumentNullException(nameof(password)); 394 | 395 | var data = new Dictionary 396 | { 397 | {"email", email}, 398 | {"password", password} 399 | }; 400 | 401 | try 402 | { 403 | string path = string.Format(ParticleApiPathCustomerSignup, organizationSlug); 404 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{path}", data, true); 405 | var results = JsonConvert.DeserializeObject(responseContent); 406 | if (!string.IsNullOrWhiteSpace(results.AccessToken)) 407 | { 408 | particleAuthentication = results; 409 | logedInUsername = email; 410 | 411 | return true; 412 | } 413 | else 414 | { 415 | return false; 416 | } 417 | } 418 | catch 419 | { 420 | return false; 421 | } 422 | } 423 | 424 | /// 425 | /// Request a password reset email be sent to the user of the organization 426 | /// 427 | /// Organization slug 428 | /// Username to request password reset for 429 | /// Returns true if the username exists 430 | public async Task RequestPasswordResetForCustomerAsync(string organizationSlug, string email) 431 | { 432 | if (string.IsNullOrWhiteSpace(organizationSlug)) 433 | throw new ArgumentNullException(nameof(organizationSlug)); 434 | if (string.IsNullOrWhiteSpace(email)) 435 | throw new ArgumentNullException(nameof(email)); 436 | 437 | var data = new Dictionary 438 | { 439 | {"email", email} 440 | }; 441 | 442 | try 443 | { 444 | string path = string.Format(ParticleApiPathCustomerPasswordReset, organizationSlug); 445 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{path}", data); 446 | var results = JsonConvert.DeserializeObject(responseContent); 447 | if (results != null && results.Code == 200) 448 | { 449 | return true; 450 | } 451 | 452 | return false; 453 | } 454 | catch 455 | { 456 | return false; 457 | } 458 | } 459 | 460 | /// 461 | /// Create a claim code for sending to a device in listening mode for products in an organization 462 | /// 463 | /// Organization slug 464 | /// Pruduct slug 465 | /// activation code 466 | /// Returns a claim code 467 | public async Task CreateClaimCodeForOrganizationAsync(string organizationSlug, string productSlug) 468 | { 469 | if (string.IsNullOrWhiteSpace(productSlug)) 470 | throw new ArgumentNullException(nameof(productSlug)); 471 | if (string.IsNullOrWhiteSpace(organizationSlug)) 472 | throw new ArgumentNullException(nameof(organizationSlug)); 473 | 474 | try 475 | { 476 | string path = string.Format(ParticleApiPathOrganizationClaimCode, organizationSlug, productSlug); 477 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{path}"); 478 | var result = JToken.Parse(responseContent); 479 | return (string)result["claim_code"]; 480 | } 481 | catch 482 | { 483 | return null; 484 | } 485 | } 486 | 487 | #endregion 488 | 489 | #region Public Event Methods 490 | 491 | /// 492 | /// Publish an event to the cloud 493 | /// 494 | /// Name of the event 495 | /// String data to send with the event 496 | /// Boolean boolean flag determining if this event is private or not 497 | /// Time To Live in seconds 498 | /// 499 | public async Task PublishEventAsync(string eventName, string eventData = "", bool isPrivate = true, int ttl = 60) 500 | { 501 | if (string.IsNullOrWhiteSpace(eventName)) 502 | throw new ArgumentNullException(nameof(eventName)); 503 | 504 | var data = new Dictionary 505 | { 506 | {"name", eventName}, 507 | {"data", eventData}, 508 | {"private", isPrivate.ToString().ToLower()}, 509 | {"ttl", ttl.ToString()}, 510 | }; 511 | 512 | try 513 | { 514 | var responseContent = await PostDataAsync($"{ParticleApiVersion}/{ParticleApiPathDevicesEvents}", data); 515 | var result = JToken.Parse(responseContent); 516 | if ((bool)result["ok"] == true) 517 | { 518 | return true; 519 | } 520 | 521 | else 522 | { 523 | return false; 524 | } 525 | } 526 | catch 527 | { 528 | return false; 529 | } 530 | } 531 | 532 | /// 533 | /// Creates a new long running task to listen for all events 534 | /// 535 | /// ParticleEventHandler to call when new event arrives 536 | /// Prefix to monitor on event stream 537 | /// Returns GUID reference to long running event task 538 | public async Task SubscribeToAllEventsWithPrefixAsync(ParticleEventHandler eventHandler, string eventNamePrefix = "") 539 | { 540 | if (string.IsNullOrWhiteSpace(eventNamePrefix)) 541 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{ParticleApiPathEvents}", eventHandler); 542 | else 543 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{ParticleApiPathEvents}/{eventNamePrefix}", eventHandler); 544 | } 545 | 546 | /// 547 | /// Creates a new long running task to listen for events on a specific users device 548 | /// 549 | /// ParticleEventHandler to call when new event arrives 550 | /// ParticleDevice to limit event stream to 551 | /// Prefix to monitor on event stream 552 | /// Returns GUID reference to long running event task 553 | public async Task SubscribeToDeviceEventsWithPrefixAsync(ParticleEventHandler eventHandler, ParticleDevice device, string eventNamePrefix = "") 554 | { 555 | return await SubscribeToDeviceEventsWithPrefixAsync(eventHandler, device.Id, eventNamePrefix); 556 | } 557 | 558 | /// 559 | /// Creates a new long running task to listen for events on a specific users device 560 | /// 561 | /// ParticleEventHandler to call when new event arrives 562 | /// ParticleDevice ID to limit event stream to 563 | /// Prefix to monitor on event stream 564 | /// Returns GUID reference to long running event task 565 | public async Task SubscribeToDeviceEventsWithPrefixAsync(ParticleEventHandler eventHandler, string deviceId, string eventNamePrefix = "") 566 | { 567 | string path = string.Format(ParticleApiPathDeviceEvents, deviceId); 568 | 569 | if (string.IsNullOrWhiteSpace(eventNamePrefix)) 570 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{path}", eventHandler); 571 | else 572 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{path}/{eventNamePrefix}", eventHandler); 573 | } 574 | 575 | /// 576 | /// Creates a new long running task to listen for events on users devices 577 | /// 578 | /// ParticleEventHandler to call when new event arrives 579 | /// Prefix to monitor on event stream 580 | /// Returns GUID reference to long running event task 581 | public async Task SubscribeToDevicesEventsWithPrefixAsync(ParticleEventHandler eventHandler, string eventNamePrefix = "") 582 | { 583 | if (string.IsNullOrWhiteSpace(eventNamePrefix)) 584 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{ParticleApiPathDevicesEvents}", eventHandler); 585 | else 586 | return await SubscribeToEventAsync($"{ParticleApiVersion}/{ParticleApiPathDevicesEvents}/{eventNamePrefix}", eventHandler); 587 | } 588 | 589 | /// 590 | /// Removes ParticleEventHandler linked to a specified GUID, stoping the handler from receiving events 591 | /// and if it's the last one shutting down the long running event 592 | /// 593 | /// GUID from a previous call to subscribe to an event 594 | public void UnsubscribeFromEvent(Guid eventListenerID) 595 | { 596 | if (particleEventHandlerDatas == null) 597 | return; 598 | 599 | if (!particleEventHandlerDatas.ContainsKey(eventListenerID)) 600 | return; 601 | 602 | var particleEventHandlerData = particleEventHandlerDatas[eventListenerID]; 603 | particleEventHandlerDatas.Remove(eventListenerID); 604 | 605 | var particleEventGroup = particleEventGroups[particleEventHandlerData.Path]; 606 | particleEventGroup.RemoveHandler(particleEventHandlerData.EventHandler); 607 | 608 | if (!particleEventGroup.HasHandlers) 609 | particleEventGroups.Remove(particleEventHandlerData.Path); 610 | } 611 | 612 | #endregion 613 | 614 | #region Private Event Methods 615 | 616 | /// 617 | /// Creates a new long running task to listen for events 618 | /// 619 | /// Relative path to Particle Cloud event endpoint 620 | /// ParticleEventHandler to call when new event arrives 621 | /// Returns GUID reference to long running event task 622 | private async Task SubscribeToEventAsync(string path, ParticleEventHandler eventHandler) 623 | { 624 | var guid = Guid.NewGuid(); 625 | bool newEvent = false; 626 | 627 | if (particleEventGroups == null) 628 | { 629 | particleEventGroups = new Dictionary(); 630 | particleEventHandlerDatas = new Dictionary(); 631 | } 632 | 633 | if (!particleEventGroups.ContainsKey(path)) 634 | { 635 | particleEventGroups.Add(path, new ParticleEventGroup()); 636 | newEvent = true; 637 | } 638 | 639 | particleEventHandlerDatas.Add(guid, new ParticleEventHandlerData(path, eventHandler)); 640 | 641 | var particleEventGroup = particleEventGroups[path]; 642 | particleEventGroup.AddHandler(eventHandler); 643 | 644 | if (newEvent) 645 | await Task.Factory.StartNew(() => ListenForEventAsync(path, particleEventGroup).ConfigureAwait(false), TaskCreationOptions.LongRunning); 646 | 647 | return guid; 648 | } 649 | 650 | #endregion 651 | } 652 | } 653 | -------------------------------------------------------------------------------- /ParticleSDK/ParticleDevice.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using Particle.SDK.Models; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Threading.Tasks; 11 | 12 | namespace Particle.SDK 13 | { 14 | #region Enums 15 | 16 | /// 17 | /// Enumeration for Particle Device types 18 | /// 19 | public enum ParticleDeviceType 20 | { 21 | Unknown = -1, 22 | ParticleCore = 0, 23 | ParticlePhoton = 6, 24 | ParticleP1 = 8, 25 | ParticleElectron = 10, 26 | 27 | // Non Particle Devices 28 | DigistumpOak = 82, 29 | RedBearDuo = 88, 30 | BluzDK = 103, 31 | } 32 | 33 | /// 34 | /// Enumeration for internally used Particle Device States 35 | /// 36 | public enum ParticleDeviceState 37 | { 38 | Unknown, 39 | Offline, 40 | Flashing, 41 | Online, 42 | Tinker 43 | } 44 | 45 | #endregion 46 | 47 | /// 48 | /// Class for using a device with Particle Cloud 49 | /// implements the IPropertyChange interface. 50 | /// 51 | public class ParticleDevice : INotifyPropertyChanged 52 | { 53 | #region Private Members 54 | 55 | private ParticleDeviceResponse deviceState = null; 56 | private ParticleCloud particleCloud = null; 57 | private ParticleDeviceState state = ParticleDeviceState.Unknown; 58 | private bool isFlashing = false; 59 | private double mbsUsed = 0; 60 | private Guid? onlineEventListenerID = null; 61 | 62 | #endregion 63 | 64 | #region Events 65 | 66 | /// 67 | /// Global event called by ParticleDevice when any property is changed 68 | /// 69 | public event PropertyChangedEventHandler PropertyChanged; 70 | 71 | #endregion 72 | 73 | #region Properties 74 | 75 | /// 76 | /// Readonly Id from ParticleDeviceResponse 77 | /// 78 | public string Id 79 | { 80 | get { return deviceState.Id; } 81 | } 82 | 83 | /// 84 | /// Readonly Name from ParticleDeviceResponse 85 | /// 86 | public string Name 87 | { 88 | get { return deviceState.Name; } 89 | } 90 | 91 | /// 92 | /// Readonly LastApp from ParticleDeviceResponse 93 | /// 94 | public string LastApp 95 | { 96 | get { return deviceState.LastApp; } 97 | } 98 | 99 | /// 100 | /// Readonly LastIPAddress from ParticleDeviceResponse 101 | /// 102 | public string LastIPAddress 103 | { 104 | get { return deviceState.LastIPAddress; } 105 | } 106 | 107 | /// 108 | /// Readonly LastHeard from ParticleDeviceResponse 109 | /// 110 | public DateTime LastHeard 111 | { 112 | get { return deviceState.LastHeard; } 113 | } 114 | 115 | /// 116 | /// Readonly RequiresDeepUpdate from ParticleDeviceResponse 117 | /// 118 | public bool RequiresDeepUpdate 119 | { 120 | get { return deviceState.RequiresDeepUpdate; } 121 | } 122 | 123 | /// 124 | /// Readonly ProductId from ParticleDeviceResponse 125 | /// 126 | public int ProductId 127 | { 128 | get 129 | { 130 | return deviceState.ProductId; 131 | } 132 | } 133 | 134 | /// 135 | /// Readonly Known ProductId from ParticleDeviceResponse 136 | /// 137 | public ParticleDeviceType KnownProductId 138 | { 139 | get 140 | { 141 | return IntToParticleDeviceType(deviceState.ProductId); 142 | } 143 | } 144 | 145 | /// 146 | /// Readonly Connected from ParticleDeviceResponse 147 | /// 148 | public bool Connected 149 | { 150 | get { return deviceState.Connected; } 151 | } 152 | 153 | /// 154 | /// Readonly PlatformId from ParticleDeviceResponse 155 | /// 156 | public int PlatformId 157 | { 158 | get 159 | { 160 | return deviceState.PlatformId; 161 | } 162 | } 163 | 164 | /// 165 | /// Readonly Known PlatformId from ParticleDeviceResponse 166 | /// 167 | public ParticleDeviceType KnownPlatformId 168 | { 169 | get 170 | { 171 | return IntToParticleDeviceType(deviceState.PlatformId); 172 | } 173 | } 174 | 175 | /// 176 | /// Readonly Cellular from ParticleDeviceResponse 177 | /// 178 | public bool Cellular 179 | { 180 | get { return deviceState.Cellular; } 181 | } 182 | 183 | /// 184 | /// Readonly Status from ParticleDeviceResponse 185 | /// 186 | public string Status 187 | { 188 | get { return deviceState.Status; } 189 | } 190 | 191 | /// 192 | /// Readonly LastICCID from ParticleDeviceResponse 193 | /// 194 | public string LastICCID 195 | { 196 | get { return deviceState.LastICCID; } 197 | } 198 | 199 | /// 200 | /// Readonly IMEI from ParticleDeviceResponse 201 | /// 202 | public string IMEI 203 | { 204 | get { return deviceState.IMEI; } 205 | } 206 | 207 | /// 208 | /// Readonly CurrentBuildTarget from ParticleDeviceResponse 209 | /// 210 | public string CurrentBuildTarget 211 | { 212 | get { return deviceState.CurrentBuildTarget; } 213 | } 214 | 215 | /// 216 | /// Readonly Variables from ParticleDeviceResponse 217 | /// 218 | public Dictionary Variables 219 | { 220 | get { return deviceState.Variables; } 221 | } 222 | 223 | /// 224 | /// Readonly Functions from ParticleDeviceResponse 225 | /// 226 | public string[] Functions 227 | { 228 | get { return deviceState.Functions; } 229 | } 230 | 231 | /// 232 | /// Readonly CC3000PatchVersion from ParticleDeviceResponse 233 | /// 234 | public string CC3000PatchVersion 235 | { 236 | get { return deviceState.CC3000PatchVersion; } 237 | } 238 | 239 | /// 240 | /// Readonly internally created State 241 | /// 242 | public ParticleDeviceState State 243 | { 244 | get { return state; } 245 | internal set 246 | { 247 | state = value; 248 | OnPropertyChanged("State"); 249 | } 250 | } 251 | 252 | /// 253 | /// Readonly value of flashing state 254 | /// 255 | public bool IsFlashing 256 | { 257 | get { return isFlashing; } 258 | internal set 259 | { 260 | isFlashing = value; 261 | OnPropertyChanged("IsFlashing"); 262 | } 263 | } 264 | 265 | /// 266 | /// Readonly amount of megabytes used in billing period for an Electron 267 | /// 268 | public double MbsUsed 269 | { 270 | get { return mbsUsed; } 271 | internal set 272 | { 273 | mbsUsed = value; 274 | OnPropertyChanged("MbsUsed"); 275 | } 276 | } 277 | 278 | #endregion 279 | 280 | #region Constructors 281 | 282 | /// 283 | /// Create a new instance of a ParticleDevice from a ParticleDeviceResponse 284 | /// 285 | /// ParticleDeviceResponse from Particle Cloud 286 | /// Authorized ParticleCloud 287 | public ParticleDevice(ParticleDeviceResponse deviceState, ParticleCloud particleCloud) 288 | { 289 | this.deviceState = deviceState; 290 | this.particleCloud = particleCloud; 291 | 292 | UpdateState(); 293 | } 294 | 295 | /// 296 | /// Create a new instance of a ParticleDevice with only an ID 297 | /// 298 | /// Device ID 299 | /// Authorized ParticleCloud 300 | public ParticleDevice(string deviceId, ParticleCloud particleCloud) 301 | { 302 | this.deviceState = new ParticleDeviceResponse(); 303 | this.deviceState.Id = deviceId; 304 | this.particleCloud = particleCloud; 305 | 306 | UpdateState(); 307 | } 308 | 309 | #endregion 310 | 311 | #region Public Methods 312 | 313 | /// 314 | /// Used when you detect the device is flashing by external means and wish to update the model 315 | /// 316 | /// True if the flash is completed 317 | public void FlagFlashStatusChange(bool completed = false) 318 | { 319 | IsFlashing = !completed; 320 | if (!completed) 321 | State = ParticleDeviceState.Flashing; 322 | } 323 | 324 | /// 325 | /// Flash a compiled firmware to a device 326 | /// A return of true only means it was sent to the device, not that flash is successful 327 | /// 328 | /// Stream of compiled binary 329 | /// Filename of compiled binary 330 | /// Whether or not to monitor status 331 | /// Returns true if binary is sent to device 332 | public async Task FlashBinaryAsync(Stream firmwareStream, string filename, bool monitorStatus = false) 333 | { 334 | if (firmwareStream == null) 335 | throw new ArgumentNullException(nameof(firmwareStream)); 336 | 337 | try 338 | { 339 | IsFlashing = true; 340 | State = ParticleDeviceState.Flashing; 341 | if (monitorStatus) 342 | MonitorForOnlineEvent(); 343 | await particleCloud.DeviceFlashBinaryAsync(this, firmwareStream, filename); 344 | return true; 345 | } 346 | catch 347 | { 348 | IsFlashing = false; 349 | State = ParticleDeviceState.Unknown; 350 | return false; 351 | } 352 | } 353 | 354 | /// 355 | /// Flash a known app to a device 356 | /// A return of true only means it was sent to the device, not that flash is successful 357 | /// 358 | /// Known app name by Particle Cloud 359 | /// Whether or not to monitor status 360 | /// Returns true if known app is sent to device 361 | public async Task FlashKnownAppAsync(string app, bool monitorStatus = false) 362 | { 363 | if (string.IsNullOrWhiteSpace(app)) 364 | throw new ArgumentNullException(nameof(app)); 365 | 366 | var data = new Dictionary 367 | { 368 | {"app", app} 369 | }; 370 | 371 | try 372 | { 373 | IsFlashing = true; 374 | State = ParticleDeviceState.Flashing; 375 | if (monitorStatus) 376 | MonitorForOnlineEvent(); 377 | var responseContent = await particleCloud.PutDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}", data); 378 | return true; 379 | } 380 | catch 381 | { 382 | IsFlashing = false; 383 | State = ParticleDeviceState.Unknown; 384 | return false; 385 | } 386 | } 387 | 388 | /// 389 | /// Retrieve a variable from a device 390 | /// 391 | /// Variable name 392 | /// Returns a ParticleVariableResponse 393 | public async Task GetVariableAsync(string variable) 394 | { 395 | if (string.IsNullOrWhiteSpace(variable)) 396 | throw new ArgumentNullException(nameof(variable)); 397 | 398 | try 399 | { 400 | var jsonSerializerSettings = new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Local }; 401 | 402 | var responseContent = await particleCloud.GetDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}/{variable}"); 403 | return JsonConvert.DeserializeObject(responseContent, jsonSerializerSettings); 404 | } 405 | catch 406 | { 407 | return null; 408 | } 409 | } 410 | 411 | /// 412 | /// Check device functions to see if it's compatible with tinker 413 | /// 414 | /// Returns true if device is compatible with tinker 415 | public bool IsRunningTinker() 416 | { 417 | var lowercaseFunctions = new List(); 418 | foreach (string function in Functions) 419 | { 420 | lowercaseFunctions.Add(function.ToLower()); 421 | } 422 | string[] tinkerFunctions = { "digitalread", "digitalwrite", "analogread", "analogwrite" }; 423 | 424 | return (Connected && !tinkerFunctions.Except(lowercaseFunctions).Any()); 425 | } 426 | 427 | /// 428 | /// Refreshes a devices properties with current information 429 | /// 430 | /// Returns true if the device is updated 431 | public async Task RefreshAsync() 432 | { 433 | try 434 | { 435 | var jsonSerializerSettings = new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Local }; 436 | 437 | var responseContent = await particleCloud.GetDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}"); 438 | ParticleDeviceResponse deviceState = JsonConvert.DeserializeObject(responseContent, jsonSerializerSettings); 439 | SetDeviceState(deviceState); 440 | 441 | if (Cellular) 442 | await UpdateMonthlyUssageAsync(); 443 | 444 | return true; 445 | } 446 | catch 447 | { 448 | return false; 449 | } 450 | } 451 | 452 | /// 453 | /// Rename a device 454 | /// 455 | /// New neame for device 456 | /// Returns true if device is renamed 457 | public async Task RenameAsync(string name) 458 | { 459 | var data = new Dictionary 460 | { 461 | {"name", name} 462 | }; 463 | 464 | try 465 | { 466 | var responseContent = await particleCloud.PutDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}", data); 467 | return await RefreshAsync(); 468 | } 469 | catch 470 | { 471 | return false; 472 | } 473 | } 474 | 475 | /// 476 | /// Call a function on a device 477 | /// 478 | /// Function to call 479 | /// Arguments to send to function 480 | /// Returns a ParticleFunctionResponse 481 | public async Task RunFunctionAsync(string function, string arg) 482 | { 483 | if (string.IsNullOrWhiteSpace(function)) 484 | throw new ArgumentNullException(nameof(function)); 485 | 486 | var data = new Dictionary 487 | { 488 | {"arg", arg} 489 | }; 490 | 491 | try 492 | { 493 | var responseContent = await particleCloud.PostDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}/{function}", data); 494 | var results = JsonConvert.DeserializeObject(responseContent); 495 | return results; 496 | } 497 | catch (ParticleUnauthorizedException) 498 | { 499 | throw; 500 | } 501 | catch (ParticleRequestBadRequestException) 502 | { 503 | throw; 504 | } 505 | catch 506 | { 507 | return null; 508 | } 509 | } 510 | 511 | /// 512 | /// Send a signal to the device to shout rainbows 513 | /// 514 | /// Turn signal on 515 | /// True on success 516 | public async Task SignalAsync(bool turnSignalOn) 517 | { 518 | var data = new Dictionary 519 | { 520 | {"signal", turnSignalOn ? "1" : "0"} 521 | }; 522 | 523 | try 524 | { 525 | var responseContent = await particleCloud.PutDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}", data); 526 | var result = JToken.Parse(responseContent); 527 | return true; 528 | } 529 | catch 530 | { 531 | return false; 532 | } 533 | } 534 | 535 | /// 536 | /// Unclaim a device from users account 537 | /// 538 | /// True if the device is successfully unbclaimed 539 | public async Task UnclaimAsync() 540 | { 541 | try 542 | { 543 | var responseContent = await particleCloud.DeleteDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{Id}"); 544 | var result = JToken.Parse(responseContent); 545 | return true; 546 | } 547 | catch 548 | { 549 | return false; 550 | } 551 | } 552 | 553 | /// 554 | /// Get the amount of megabytes used in billing period for an Electron 555 | /// 556 | /// Double value of amount of megabytes used in billing period 557 | public async Task UpdateMonthlyUssageAsync() 558 | { 559 | if (!Cellular) 560 | return 0; 561 | 562 | double mbsUsed = 0; 563 | 564 | try 565 | { 566 | var particleApiPathSimDataUsage = string.Format(ParticleCloud.ParticleApiPathSimDataUsage, LastICCID); 567 | var responseContent = await particleCloud.GetDataAsync($"{ParticleCloud.ParticleApiVersion}/{particleApiPathSimDataUsage}"); 568 | var result = JToken.Parse(responseContent); 569 | 570 | foreach (JObject entry in (JArray)result) 571 | mbsUsed = Math.Max(mbsUsed, (double)entry["mbs_used_cumulative"]); 572 | } 573 | catch 574 | { 575 | mbsUsed = -1; 576 | } 577 | 578 | MbsUsed = mbsUsed; 579 | return mbsUsed; 580 | } 581 | 582 | #endregion 583 | 584 | #region Public Static Methods 585 | 586 | /// 587 | /// Convert Value into known Particle Device Type 588 | /// 589 | /// Value to convert 590 | /// ParticleDeviceTypealid ParticleDeviceType or Unknown 591 | public static ParticleDeviceType IntToParticleDeviceType(int value) 592 | { 593 | if (Enum.IsDefined(typeof(ParticleDeviceType), value)) 594 | return (ParticleDeviceType)value; 595 | else 596 | return ParticleDeviceType.Unknown; 597 | } 598 | 599 | public static string ParticleDeviceTypeToString(int value) 600 | { 601 | return ParticleDeviceTypeToString((ParticleDeviceType)value); 602 | } 603 | 604 | public static string ParticleDeviceTypeToString(ParticleDeviceType value) 605 | { 606 | switch (value) 607 | { 608 | case ParticleDeviceType.ParticleCore: 609 | return "Core"; 610 | case ParticleDeviceType.ParticlePhoton: 611 | return "Photon"; 612 | case ParticleDeviceType.ParticleP1: 613 | return "P1"; 614 | case ParticleDeviceType.ParticleElectron: 615 | return "Electron"; 616 | case ParticleDeviceType.DigistumpOak: 617 | return "Digistump Oak"; 618 | case ParticleDeviceType.RedBearDuo: 619 | return "RedBear Duo"; 620 | case ParticleDeviceType.BluzDK: 621 | return "Bluz DK"; 622 | default: 623 | return "Unknown"; 624 | } 625 | } 626 | 627 | #endregion 628 | 629 | #region Public Event Methods 630 | 631 | /// 632 | /// Creates a new long running task to listen for events on this specific device 633 | /// 634 | /// ParticleEventHandler to call when new event arrives 635 | /// Prefix to monitor on event stream 636 | /// Returns GUID reference to long running event task 637 | public async Task SubscribeToDeviceEventsWithPrefixAsync(ParticleEventHandler eventHandler, string eventNamePrefix = "") 638 | { 639 | return await particleCloud.SubscribeToDeviceEventsWithPrefixAsync(eventHandler, this, eventNamePrefix); 640 | } 641 | 642 | /// 643 | /// Removes ParticleEventHandler linked to a specified GUID, stoping the handler from receiving events 644 | /// and if it's the last one shutting down the long running event 645 | /// 646 | /// GUID from a previous call to subscribe to an event 647 | public void UnsubscribeFromEvent(Guid eventListenerID) 648 | { 649 | particleCloud.UnsubscribeFromEvent(eventListenerID); 650 | } 651 | 652 | #endregion 653 | 654 | #region Private Methods 655 | 656 | /// 657 | /// Start monitoring for an Online event 658 | /// 659 | public async void MonitorForOnlineEvent() 660 | { 661 | if (onlineEventListenerID == null) 662 | onlineEventListenerID = await SubscribeToDeviceEventsWithPrefixAsync(CheckForOnlineEvent, "spark"); 663 | } 664 | 665 | /// 666 | /// Update this device with new values from a ParticleDeviceResponse 667 | /// 668 | /// Updated ParticleDeviceResponse 669 | private void SetDeviceState(ParticleDeviceResponse deviceState) 670 | { 671 | bool updateState = false; 672 | ParticleDeviceResponse oldDeviceState = this.deviceState; 673 | this.deviceState = deviceState; 674 | 675 | var properties = deviceState.GetType().GetRuntimeProperties(); 676 | foreach (PropertyInfo property in properties) 677 | { 678 | bool updateStateField = property.Name == "Connected" || property.Name == "Functions"; 679 | var valueA = property.GetValue(deviceState); 680 | var valueB = property.GetValue(oldDeviceState); 681 | 682 | if (valueA == null || valueB == null) 683 | { 684 | if (valueA != valueB) 685 | { 686 | OnPropertyChanged(property.Name); 687 | if (updateStateField) 688 | updateState = true; 689 | } 690 | } 691 | else if (!valueA.Equals(valueB)) 692 | { 693 | OnPropertyChanged(property.Name); 694 | if (updateStateField) 695 | updateState = true; 696 | } 697 | } 698 | 699 | if (updateState) 700 | UpdateState(); 701 | } 702 | 703 | /// 704 | /// A ParticleEventHandler to look for the specific event that this device is online 705 | /// 706 | /// Object sending request 707 | /// ParticleEventResponse 708 | private void CheckForOnlineEvent(object sender, ParticleEventResponse particeEvent) 709 | { 710 | if (particeEvent.Name.Equals("spark/status") && particeEvent.Data.Equals("online")) 711 | { 712 | Guid tempOnlineEventListenerID = onlineEventListenerID.Value; 713 | onlineEventListenerID = null; 714 | 715 | UnsubscribeFromEvent(tempOnlineEventListenerID); 716 | 717 | particleCloud.SynchronizationContextPost(async a => 718 | { 719 | IsFlashing = false; 720 | UpdateState(); 721 | await RefreshAsync(); 722 | }, null); 723 | } 724 | } 725 | 726 | /// 727 | /// Notifies clients that a property value has changed 728 | /// 729 | /// Property name that has changed 730 | private void OnPropertyChanged(string propertyName) 731 | { 732 | var handler = PropertyChanged; 733 | if (handler != null) 734 | handler(this, new PropertyChangedEventArgs(propertyName)); 735 | } 736 | 737 | /// 738 | /// Update the devices state based on flashing, connected and functions 739 | /// 740 | private void UpdateState() 741 | { 742 | if (IsFlashing) 743 | { 744 | State = ParticleDeviceState.Flashing; 745 | return; 746 | } 747 | 748 | if (!Connected) 749 | { 750 | State = ParticleDeviceState.Offline; 751 | } 752 | else if (Functions == null || Functions.Length < 4) 753 | { 754 | State = ParticleDeviceState.Online; 755 | } 756 | else 757 | { 758 | if (IsRunningTinker()) 759 | State = ParticleDeviceState.Tinker; 760 | else 761 | State = ParticleDeviceState.Online; 762 | } 763 | } 764 | 765 | #endregion 766 | } 767 | 768 | /// 769 | /// Collection class of ParticleDevics 770 | /// 771 | public class ParticleDeviceCollection : List 772 | { 773 | public ParticleDeviceCollection() 774 | { 775 | } 776 | } 777 | } 778 | -------------------------------------------------------------------------------- /ParticleSDK/ParticleExceptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Particle.SDK 4 | { 5 | /// 6 | /// Simple Exception class for particle cloud returning 404 Not Found 7 | /// 8 | public class ParticleNotFoundException : Exception 9 | { 10 | public ParticleNotFoundException() 11 | { 12 | } 13 | 14 | public ParticleNotFoundException(string message) 15 | : base(message) 16 | { 17 | } 18 | } 19 | 20 | /// 21 | /// Simple Exception class for particle cloud returning 400 Bad Request 22 | /// 23 | public class ParticleRequestBadRequestException : Exception 24 | { 25 | public ParticleRequestBadRequestException() 26 | { 27 | } 28 | 29 | public ParticleRequestBadRequestException(string message) 30 | : base(message) 31 | { 32 | } 33 | } 34 | 35 | /// 36 | /// Simple Exception class for particle cloud returning 401 Unauthorized 37 | /// 38 | public class ParticleUnauthorizedException : Exception 39 | { 40 | public ParticleUnauthorizedException() 41 | { 42 | } 43 | 44 | public ParticleUnauthorizedException(string message) 45 | : base(message) 46 | { 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ParticleSDK/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Particle Windows Cloud SDK")] 9 | [assembly: AssemblyDescription("Particle Windows Cloud SDK enables Windows apps to interact with Particle-powered connected products via the Particle Cloud. It’s an easy-to-use wrapper for Particle REST API")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Particle Industries, Inc")] 12 | [assembly: AssemblyProduct("Particle.SDK")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("0.1.0.0")] 28 | [assembly: AssemblyFileVersion("0.1.0.0")] 29 | -------------------------------------------------------------------------------- /ParticleSDK/Properties/AssemblyInfo.net452.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Setting ComVisible to false makes the types in this assembly not visible 6 | // to COM components. If you need to access a type in this assembly from 7 | // COM, set the ComVisible attribute to true on that type. 8 | [assembly: ComVisible(false)] 9 | 10 | // The following GUID is for the ID of the typelib if this project is exposed to COM 11 | [assembly: Guid("f9aeac94-1259-4c0d-8dd1-fc15457f9dd5")] -------------------------------------------------------------------------------- /ParticleSDK/Properties/AssemblyInfo.win81+wpa81.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: NeutralResourcesLanguage("en")] 10 | -------------------------------------------------------------------------------- /ParticleSDK/RestApi/ParticleRestApiBase.cs: -------------------------------------------------------------------------------- 1 | using Particle.SDK.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Particle.SDK.RestApi 9 | { 10 | #region Event Handlers 11 | 12 | /// 13 | /// Delegete define for callback on unauthorized event 14 | /// 15 | public delegate void ClientUnauthorizedEventHandler(); 16 | 17 | #endregion 18 | 19 | /// 20 | /// Base class for communicating with Particle Cloud via Windows.Web.Http 21 | /// 22 | public class ParticleRestApiBase 23 | { 24 | #region Internal Constants 25 | 26 | internal static readonly string ParticleApiUrl = "https://api.particle.io/"; 27 | 28 | #endregion 29 | 30 | #region Protected Members 31 | 32 | protected object clientUnauthorizedLock = new object(); 33 | protected string logedInUsername = ""; 34 | protected ParticleAuthenticationResponse particleAuthentication = null; 35 | 36 | #endregion 37 | 38 | #region Events 39 | 40 | /// 41 | /// Global event called by ParticleCloud when any requests returns 401 Unauthorized 42 | /// 43 | public event ClientUnauthorizedEventHandler ClientUnauthorized; 44 | 45 | #endregion 46 | 47 | #region Properties 48 | 49 | /// 50 | /// AccessToken from Login/Signup calls 51 | /// 52 | public string AccessToken 53 | { 54 | get 55 | { 56 | return particleAuthentication?.AccessToken; 57 | } 58 | } 59 | 60 | /// 61 | /// OAuth Client Id for creating tokens 62 | /// 63 | public string OAuthClientId { get; set; } = "particle"; 64 | 65 | /// 66 | /// OAuth Client Secret for creating tokens 67 | /// 68 | public string OAuthClientSecret { get; set; } = "particle"; 69 | 70 | /// 71 | /// SynchronizationContext for dispatching calls 72 | /// 73 | public SynchronizationContext SynchronizationContext { get; set; } 74 | 75 | /// 76 | /// Username from Login/Signup call 77 | /// 78 | public string Username 79 | { 80 | get 81 | { 82 | return logedInUsername; 83 | } 84 | } 85 | 86 | #endregion 87 | 88 | #region Public Auth Methods 89 | 90 | /// 91 | /// Clear local state. 92 | /// Does not destroy the token from Particle Cloud 93 | /// 94 | public void Logout() 95 | { 96 | particleAuthentication = null; 97 | logedInUsername = ""; 98 | } 99 | 100 | #endregion 101 | 102 | #region Public Virtual Device Methods 103 | 104 | /// 105 | /// Flash a compiled firmware to a device 106 | /// A return of true only means it was sent to the device, not that flash is successful 107 | /// 108 | /// Device ID 109 | /// Stream of compiled binary 110 | /// Filename of compiled binary 111 | /// Returns true if binary is sent to device 112 | public virtual Task DeviceFlashBinaryAsync(string deviceId, Stream firmwareStream, string filename) 113 | { 114 | throw new NotImplementedException(); 115 | } 116 | 117 | #endregion 118 | 119 | #region Protected Auth Methods 120 | 121 | /// 122 | /// The client unauthorized event-invoking method 123 | /// 124 | protected void OnClientUnauthorized() 125 | { 126 | bool loggedOut = false; 127 | 128 | lock (clientUnauthorizedLock) 129 | { 130 | if (particleAuthentication != null) 131 | { 132 | loggedOut = true; 133 | Logout(); 134 | } 135 | } 136 | 137 | if (loggedOut && ClientUnauthorized != null) 138 | ClientUnauthorized(); 139 | } 140 | 141 | #endregion 142 | 143 | #region Protected Virtual Auth Methods 144 | 145 | /// 146 | /// Set the OAuth client members 147 | /// 148 | protected virtual void SetOAuthClient() 149 | { 150 | throw new NotImplementedException(); 151 | } 152 | 153 | #endregion 154 | 155 | #region Protected Rest Methods 156 | 157 | /// 158 | /// Function to return full URI to Particle Cloud endpoint from relative path 159 | /// 160 | /// Relative path to Particle Cloud endpoint 161 | /// Returns full Uri given a partial path 162 | protected Uri CreateUriFromPathString(string path) 163 | { 164 | if (!string.IsNullOrWhiteSpace(AccessToken)) 165 | return new Uri($"{ParticleApiUrl}{path}?access_token={AccessToken}"); 166 | else 167 | return new Uri($"{ParticleApiUrl}{path}"); 168 | } 169 | 170 | #endregion 171 | 172 | #region Protected Virtual Rest Methods 173 | 174 | /// 175 | /// Genericized function to make requests to the Particle Cloud and throw exceptions on HTTP errors 176 | /// 177 | /// HttpRequestMessage with path and method 178 | /// Retuns string response from Particle Cloud request 179 | protected virtual Task SendAsync(object request, bool sendAuthHeader = false) 180 | { 181 | throw new NotImplementedException(); 182 | } 183 | 184 | #endregion 185 | 186 | #region Protected Virtual Event Methods 187 | 188 | /// 189 | /// Long running task to listen for events 190 | /// 191 | /// Relative path to Particle Cloud event endpoint 192 | /// ParticleEventGroup to send new events to 193 | /// Returns Task of long running event task 194 | protected virtual Task ListenForEventAsync(string path, ParticleEventGroup particleEventGroup) 195 | { 196 | throw new NotImplementedException(); 197 | } 198 | 199 | #endregion 200 | 201 | #region Internal Threading Methods 202 | 203 | /// 204 | /// Dispatches an asynchronous message to a synchronization if set, otherwise calls the callback 205 | /// 206 | /// The System.Threading.SendOrPostCallback delegate to call 207 | /// The object passed to the delegate 208 | internal void SynchronizationContextPost(SendOrPostCallback d, object state) 209 | { 210 | if (SynchronizationContext != null) 211 | SynchronizationContext.Post(d, state); 212 | else 213 | d(state); 214 | } 215 | 216 | #endregion 217 | 218 | #region Internal Virtual Rest Methods 219 | 220 | /// 221 | /// Make a DELETE requests to the Particle Cloud 222 | /// 223 | /// Relative path to Particle Cloud endpoint 224 | /// Retuns string response from Particle Cloud DELETE request 225 | internal virtual Task DeleteDataAsync(string path) 226 | { 227 | throw new NotImplementedException(); 228 | } 229 | 230 | /// 231 | /// Make a GET requests to the Particle Cloud 232 | /// 233 | /// Relative path to Particle Cloud endpoint 234 | /// Retuns string response from Particle Cloud GET request 235 | internal virtual Task GetDataAsync(string path) 236 | { 237 | throw new NotImplementedException(); 238 | } 239 | 240 | /// 241 | /// Make a POST requests to the Particle Cloud 242 | /// 243 | /// Relative path to Particle Cloud endpoint 244 | /// Retuns string response from Particle Cloud POST request 245 | internal virtual Task PostDataAsync(string path, bool sendAuthHeader = false) 246 | { 247 | throw new NotImplementedException(); 248 | } 249 | 250 | /// 251 | /// Make a POST requests to the Particle Cloud 252 | /// 253 | /// Relative path to Particle Cloud endpoint 254 | /// Dictonary of key/value pairs to convert to form url encoded content 255 | /// Retuns string response from Particle Cloud POST request 256 | internal virtual Task PostDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 257 | { 258 | throw new NotImplementedException(); 259 | } 260 | 261 | /// 262 | /// Make a POST requests to the Particle Cloud 263 | /// 264 | /// Relative path to Particle Cloud endpoint 265 | /// Content to send in request 266 | /// Retuns string response from Particle Cloud POST request 267 | internal virtual Task PostDataAsync(string path, object content = null, bool sendAuthHeader = false) 268 | { 269 | throw new NotImplementedException(); 270 | } 271 | 272 | /// 273 | /// Make a PUT requests to the Particle Cloud 274 | /// 275 | /// Relative path to Particle Cloud endpoint 276 | /// Retuns string response from Particle Cloud PUT request 277 | internal virtual Task PutDataAsync(string path, bool sendAuthHeader = false) 278 | { 279 | throw new NotImplementedException(); 280 | } 281 | 282 | /// 283 | /// Make a PUT requests to the Particle Cloud 284 | /// 285 | /// Relative path to Particle Cloud endpoint 286 | /// Dictonary of key/value pairs to convert to form url encoded content 287 | /// Retuns string response from Particle Cloud PUT request 288 | internal virtual Task PutDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 289 | { 290 | throw new NotImplementedException(); 291 | } 292 | 293 | /// 294 | /// Make a PUT requests to the Particle Cloud 295 | /// 296 | /// Relative path to Particle Cloud endpoint 297 | /// Content to send in request 298 | /// Retuns string response from Particle Cloud PUT request 299 | internal virtual Task PutDataAsync(string path, object content = null, bool sendAuthHeader = false) 300 | { 301 | throw new NotImplementedException(); 302 | } 303 | 304 | #endregion 305 | } 306 | } -------------------------------------------------------------------------------- /ParticleSDK/RestApi/ParticleRestApiSystemNetHttp.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Particle.SDK.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Net.Http.Headers; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Particle.SDK.RestApi 13 | { 14 | /// 15 | /// Base class for communicating with Particle Cloud via System.Nwt.Http 16 | /// 17 | public class ParticleRestApi : ParticleRestApiBase 18 | { 19 | #region Public Device Method Overrides 20 | 21 | /// 22 | /// Flash a compiled firmware to a device 23 | /// A return of true only means it was sent to the device, not that flash is successful 24 | /// 25 | /// Device ID 26 | /// Stream of compiled binary 27 | /// Filename of compiled binary 28 | /// Returns true if binary is sent to device 29 | public override async Task DeviceFlashBinaryAsync(string deviceId, Stream firmwareStream, string filename) 30 | { 31 | if (deviceId == null) 32 | throw new ArgumentNullException(nameof(deviceId)); 33 | if (firmwareStream == null) 34 | throw new ArgumentNullException(nameof(firmwareStream)); 35 | 36 | using (HttpContent file = new StreamContent(firmwareStream)) 37 | { 38 | file.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream"); 39 | 40 | var content = new MultipartFormDataContent(); 41 | content.Add(new StringContent("binary"), "\"file_type\""); 42 | content.Add(file, "\"file\"", filename); 43 | 44 | try 45 | { 46 | var responseContent = await PutDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{deviceId}", content); 47 | return true; 48 | } 49 | catch 50 | { 51 | return false; 52 | } 53 | } 54 | } 55 | 56 | #endregion 57 | 58 | #region Protected Auth Method Overrides 59 | 60 | /// 61 | /// Set the OAuth client members 62 | /// 63 | protected override void SetOAuthClient() 64 | { 65 | } 66 | 67 | #endregion 68 | 69 | #region Protected Rest Method Overrides 70 | 71 | /// 72 | /// Genericized function to make requests to the Particle Cloud and throw exceptions on HTTP errors 73 | /// 74 | /// HttpRequestMessage with path and method 75 | /// Retuns string response from Particle Cloud request 76 | protected async Task SendAsync(HttpRequestMessage request, bool sendAuthHeader = false) 77 | { 78 | using (HttpClient client = new HttpClient()) 79 | { 80 | if (sendAuthHeader) 81 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{OAuthClientId}:{OAuthClientSecret}"))); 82 | 83 | client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue() { NoCache = true }; 84 | var response = await client.SendAsync(request); 85 | var responseContent = await response.Content.ReadAsStringAsync(); 86 | 87 | switch (response.StatusCode) 88 | { 89 | case HttpStatusCode.OK: 90 | case HttpStatusCode.Created: 91 | return responseContent; 92 | 93 | case HttpStatusCode.Unauthorized: 94 | OnClientUnauthorized(); 95 | throw new ParticleUnauthorizedException(responseContent); 96 | 97 | case HttpStatusCode.NotFound: 98 | throw new ParticleNotFoundException(responseContent); 99 | 100 | case HttpStatusCode.BadRequest: 101 | throw new ParticleRequestBadRequestException(responseContent); 102 | 103 | default: 104 | throw new Exception(); 105 | } 106 | 107 | } 108 | } 109 | 110 | #endregion 111 | 112 | #region Protected Event Method Overrides 113 | 114 | /// 115 | /// Long running task to listen for events 116 | /// 117 | /// Relative path to Particle Cloud event endpoint 118 | /// ParticleEventGroup to send new events to 119 | /// Returns Task of long running event task 120 | protected override async Task ListenForEventAsync(string path, ParticleEventGroup particleEventGroup) 121 | { 122 | var httpListener = new HttpListener(); 123 | string eventName = ""; 124 | 125 | try 126 | { 127 | var url = new Uri($"https://api.particle.io/{path}/?access_token={AccessToken}"); 128 | var request = WebRequest.Create(url); 129 | request.Method = "GET"; 130 | 131 | var task = Task.Factory.StartNew(() => httpListener.Start()); 132 | 133 | using (var response = await request.GetResponseAsync()) 134 | { 135 | using (var stream = response.GetResponseStream()) 136 | using (var reader = new StreamReader(stream)) 137 | { 138 | while (!reader.EndOfStream && particleEventGroup.HasHandlers) 139 | { 140 | var outputString = reader.ReadLine(); 141 | 142 | if (outputString.StartsWith("event:")) 143 | { 144 | eventName = outputString.Substring(6).Trim(); 145 | } 146 | else if (outputString.StartsWith("data:") && !string.IsNullOrWhiteSpace(eventName)) 147 | { 148 | var jsonSerializerSettings = new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Local }; 149 | var particleEventResponse = JsonConvert.DeserializeObject(outputString.Substring(5), jsonSerializerSettings); 150 | particleEventResponse.Name = eventName; 151 | eventName = ""; 152 | 153 | SynchronizationContextPost(a => 154 | { 155 | particleEventGroup.NewMessage(this, particleEventResponse); 156 | }, null); 157 | } 158 | } 159 | } 160 | } 161 | } 162 | catch 163 | { 164 | } 165 | 166 | httpListener.Stop(); 167 | } 168 | 169 | #endregion 170 | 171 | #region Internal Rest Method Overrides 172 | 173 | /// 174 | /// Make a DELETE requests to the Particle Cloud 175 | /// 176 | /// Relative path to Particle Cloud endpoint 177 | /// Retuns string response from Particle Cloud DELETE request 178 | internal override async Task DeleteDataAsync(string path) 179 | { 180 | var request = new HttpRequestMessage(HttpMethod.Delete, CreateUriFromPathString(path)); 181 | return await SendAsync(request); 182 | } 183 | 184 | /// 185 | /// Make a GET requests to the Particle Cloud 186 | /// 187 | /// Relative path to Particle Cloud endpoint 188 | /// Retuns string response from Particle Cloud GET request 189 | internal override async Task GetDataAsync(string path) 190 | { 191 | var request = new HttpRequestMessage(HttpMethod.Get, CreateUriFromPathString(path)); 192 | return await SendAsync(request); 193 | } 194 | 195 | /// 196 | /// Make a POST requests to the Particle Cloud 197 | /// 198 | /// Relative path to Particle Cloud endpoint 199 | /// Retuns string response from Particle Cloud POST request 200 | internal override async Task PostDataAsync(string path, bool sendAuthHeader = false) 201 | { 202 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 203 | return await SendAsync(request, sendAuthHeader); 204 | } 205 | 206 | /// 207 | /// Make a POST requests to the Particle Cloud 208 | /// 209 | /// Relative path to Particle Cloud endpoint 210 | /// Dictonary of key/value pairs to convert to form url encoded content 211 | /// Retuns string response from Particle Cloud POST request 212 | internal override async Task PostDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 213 | { 214 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 215 | if (data != null) 216 | request.Content = new FormUrlEncodedContent(data); 217 | 218 | return await SendAsync(request, sendAuthHeader); 219 | } 220 | 221 | /// 222 | /// Make a POST requests to the Particle Cloud 223 | /// 224 | /// Relative path to Particle Cloud endpoint 225 | /// IHttpContent to send in request 226 | /// Retuns string response from Particle Cloud POST request 227 | internal async Task PostDataAsync(string path, HttpContent content = null, bool sendAuthHeader = false) 228 | { 229 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 230 | request.Content = content; 231 | return await SendAsync(request, sendAuthHeader); 232 | } 233 | 234 | /// 235 | /// Make a PUT requests to the Particle Cloud 236 | /// 237 | /// Relative path to Particle Cloud endpoint 238 | /// Retuns string response from Particle Cloud PUT request 239 | internal override async Task PutDataAsync(string path, bool sendAuthHeader = false) 240 | { 241 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 242 | return await SendAsync(request, sendAuthHeader); 243 | } 244 | 245 | /// 246 | /// Make a PUT requests to the Particle Cloud 247 | /// 248 | /// Relative path to Particle Cloud endpoint 249 | /// Dictonary of key/value pairs to convert to form url encoded content 250 | /// Retuns string response from Particle Cloud PUT request 251 | internal override async Task PutDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 252 | { 253 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 254 | if (data != null) 255 | request.Content = new FormUrlEncodedContent(data); 256 | 257 | return await SendAsync(request, sendAuthHeader); 258 | } 259 | 260 | /// 261 | /// Make a PUT requests to the Particle Cloud 262 | /// 263 | /// Relative path to Particle Cloud endpoint 264 | /// IHttpContent to send in request 265 | /// Retuns string response from Particle Cloud PUT request 266 | internal async Task PutDataAsync(string path, HttpContent content = null, bool sendAuthHeader = false) 267 | { 268 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 269 | request.Content = content; 270 | return await SendAsync(request, sendAuthHeader); 271 | } 272 | 273 | #endregion 274 | } 275 | } -------------------------------------------------------------------------------- /ParticleSDK/RestApi/ParticleRestApiWindowsWebHttp.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Particle.SDK.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Windows.ApplicationModel.Resources; 9 | using Windows.Web.Http; 10 | using Windows.Web.Http.Headers; 11 | 12 | namespace Particle.SDK.RestApi 13 | { 14 | /// 15 | /// Base class for communicating with Particle Cloud via Windows.Web.Http 16 | /// 17 | public class ParticleRestApi : ParticleRestApiBase 18 | { 19 | #region Public Device Method Overrides 20 | 21 | /// 22 | /// Flash a compiled firmware to a device 23 | /// A return of true only means it was sent to the device, not that flash is successful 24 | /// 25 | /// Device ID 26 | /// Stream of compiled binary 27 | /// Filename of compiled binary 28 | /// Returns true if binary is sent to device 29 | public override async Task DeviceFlashBinaryAsync(string deviceId, Stream firmwareStream, string filename) 30 | { 31 | if (deviceId == null) 32 | throw new ArgumentNullException(nameof(deviceId)); 33 | if (firmwareStream == null) 34 | throw new ArgumentNullException(nameof(firmwareStream)); 35 | 36 | using (IHttpContent file = new HttpStreamContent(firmwareStream.AsInputStream())) 37 | { 38 | file.Headers.ContentType = HttpMediaTypeHeaderValue.Parse("application/octet-stream"); 39 | 40 | var content = new HttpMultipartFormDataContent(); 41 | content.Add(new HttpStringContent("binary"), "file_type"); 42 | content.Add(file, "file", filename); 43 | 44 | try 45 | { 46 | var responseContent = await PutDataAsync($"{ParticleCloud.ParticleApiVersion}/{ParticleCloud.ParticleApiPathDevices}/{deviceId}", content); 47 | return true; 48 | } 49 | catch 50 | { 51 | return false; 52 | } 53 | } 54 | } 55 | 56 | #endregion 57 | 58 | #region Protected Auth Method Overrides 59 | 60 | /// 61 | /// Set the OAuth client members 62 | /// 63 | protected override void SetOAuthClient() 64 | { 65 | try 66 | { 67 | ResourceLoader resourceLoader = ResourceLoader.GetForCurrentView("OAuthClient"); 68 | string oauth_client_id = resourceLoader.GetString("OAuthClientID"); 69 | string oauth_client_secret = resourceLoader.GetString("OAuthClientSecret"); 70 | 71 | if (!string.IsNullOrWhiteSpace(oauth_client_id) && !string.IsNullOrWhiteSpace(oauth_client_secret)) 72 | { 73 | OAuthClientId = oauth_client_id; 74 | OAuthClientSecret = oauth_client_secret; 75 | } 76 | } 77 | catch 78 | { 79 | } 80 | } 81 | 82 | #endregion 83 | 84 | #region Protected Rest Method Overrides 85 | 86 | /// 87 | /// Genericized function to make requests to the Particle Cloud and throw exceptions on HTTP errors 88 | /// 89 | /// HttpRequestMessage with path and method 90 | /// Retuns string response from Particle Cloud request 91 | protected async Task SendAsync(HttpRequestMessage request, bool sendAuthHeader = false) 92 | { 93 | using (var filter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter()) 94 | { 95 | filter.CacheControl.ReadBehavior = Windows.Web.Http.Filters.HttpCacheReadBehavior.MostRecent; 96 | using (HttpClient client = new HttpClient(filter)) 97 | { 98 | if (sendAuthHeader) 99 | client.DefaultRequestHeaders.Authorization = new HttpCredentialsHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{OAuthClientId}:{OAuthClientSecret}"))); 100 | 101 | var response = await client.SendRequestAsync(request); 102 | var responseContent = await response.Content.ReadAsStringAsync(); 103 | 104 | switch (response.StatusCode) 105 | { 106 | case HttpStatusCode.Ok: 107 | case HttpStatusCode.Created: 108 | return responseContent; 109 | 110 | case HttpStatusCode.Unauthorized: 111 | OnClientUnauthorized(); 112 | throw new ParticleUnauthorizedException(responseContent); 113 | 114 | case HttpStatusCode.NotFound: 115 | throw new ParticleNotFoundException(responseContent); 116 | 117 | case HttpStatusCode.BadRequest: 118 | throw new ParticleRequestBadRequestException(responseContent); 119 | 120 | default: 121 | throw new Exception(); 122 | } 123 | 124 | } 125 | } 126 | } 127 | 128 | #endregion 129 | 130 | #region Protected Event Method Overrides 131 | 132 | /// 133 | /// Long running task to listen for events 134 | /// 135 | /// Relative path to Particle Cloud event endpoint 136 | /// ParticleEventGroup to send new events to 137 | /// Returns Task of long running event task 138 | protected override async Task ListenForEventAsync(string path, ParticleEventGroup particleEventGroup) 139 | { 140 | string eventName = ""; 141 | 142 | try 143 | { 144 | using (var client = new HttpClient()) 145 | { 146 | var url = new Uri($"https://api.particle.io/{path}/?access_token={AccessToken}"); 147 | var request = new HttpRequestMessage(HttpMethod.Get, url); 148 | 149 | using (var response = await client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead)) 150 | { 151 | using (var stream = await response.Content.ReadAsInputStreamAsync()) 152 | using (var reader = new StreamReader(stream.AsStreamForRead())) 153 | { 154 | while (!reader.EndOfStream && particleEventGroup.HasHandlers) 155 | { 156 | var outputString = reader.ReadLine(); 157 | 158 | if (outputString.StartsWith("event:")) 159 | { 160 | eventName = outputString.Substring(6).Trim(); 161 | } 162 | else if (outputString.StartsWith("data:") && !string.IsNullOrWhiteSpace(eventName)) 163 | { 164 | var jsonSerializerSettings = new JsonSerializerSettings() { DateTimeZoneHandling = DateTimeZoneHandling.Local }; 165 | var particleEventResponse = JsonConvert.DeserializeObject(outputString.Substring(5), jsonSerializerSettings); 166 | particleEventResponse.Name = eventName; 167 | eventName = ""; 168 | 169 | SynchronizationContextPost(a => 170 | { 171 | particleEventGroup.NewMessage(this, particleEventResponse); 172 | }, null); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | } 179 | catch 180 | { 181 | } 182 | } 183 | 184 | #endregion 185 | 186 | #region Internal Rest Method Overrides 187 | 188 | /// 189 | /// Make a DELETE requests to the Particle Cloud 190 | /// 191 | /// Relative path to Particle Cloud endpoint 192 | /// Retuns string response from Particle Cloud DELETE request 193 | internal override async Task DeleteDataAsync(string path) 194 | { 195 | var request = new HttpRequestMessage(HttpMethod.Delete, CreateUriFromPathString(path)); 196 | return await SendAsync(request); 197 | } 198 | 199 | /// 200 | /// Make a GET requests to the Particle Cloud 201 | /// 202 | /// Relative path to Particle Cloud endpoint 203 | /// Retuns string response from Particle Cloud GET request 204 | internal override async Task GetDataAsync(string path) 205 | { 206 | var request = new HttpRequestMessage(HttpMethod.Get, CreateUriFromPathString(path)); 207 | return await SendAsync(request); 208 | } 209 | 210 | /// 211 | /// Make a POST requests to the Particle Cloud 212 | /// 213 | /// Relative path to Particle Cloud endpoint 214 | /// Retuns string response from Particle Cloud POST request 215 | internal override async Task PostDataAsync(string path, bool sendAuthHeader = false) 216 | { 217 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 218 | return await SendAsync(request, sendAuthHeader); 219 | } 220 | 221 | /// 222 | /// Make a POST requests to the Particle Cloud 223 | /// 224 | /// Relative path to Particle Cloud endpoint 225 | /// Dictonary of key/value pairs to convert to form url encoded content 226 | /// Retuns string response from Particle Cloud POST request 227 | internal override async Task PostDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 228 | { 229 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 230 | if (data != null) 231 | request.Content = new HttpFormUrlEncodedContent(data); 232 | 233 | return await SendAsync(request, sendAuthHeader); 234 | } 235 | 236 | /// 237 | /// Make a POST requests to the Particle Cloud 238 | /// 239 | /// Relative path to Particle Cloud endpoint 240 | /// IHttpContent to send in request 241 | /// Retuns string response from Particle Cloud POST request 242 | internal async Task PostDataAsync(string path, IHttpContent content = null, bool sendAuthHeader = false) 243 | { 244 | var request = new HttpRequestMessage(HttpMethod.Post, CreateUriFromPathString(path)); 245 | request.Content = content; 246 | return await SendAsync(request, sendAuthHeader); 247 | } 248 | 249 | /// 250 | /// Make a PUT requests to the Particle Cloud 251 | /// 252 | /// Relative path to Particle Cloud endpoint 253 | /// Retuns string response from Particle Cloud PUT request 254 | internal override async Task PutDataAsync(string path, bool sendAuthHeader = false) 255 | { 256 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 257 | return await SendAsync(request, sendAuthHeader); 258 | } 259 | 260 | /// 261 | /// Make a PUT requests to the Particle Cloud 262 | /// 263 | /// Relative path to Particle Cloud endpoint 264 | /// Dictonary of key/value pairs to convert to form url encoded content 265 | /// Retuns string response from Particle Cloud PUT request 266 | internal override async Task PutDataAsync(string path, Dictionary data = null, bool sendAuthHeader = false) 267 | { 268 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 269 | if (data != null) 270 | request.Content = new HttpFormUrlEncodedContent(data); 271 | 272 | return await SendAsync(request, sendAuthHeader); 273 | } 274 | 275 | /// 276 | /// Make a PUT requests to the Particle Cloud 277 | /// 278 | /// Relative path to Particle Cloud endpoint 279 | /// IHttpContent to send in request 280 | /// Retuns string response from Particle Cloud PUT request 281 | internal async Task PutDataAsync(string path, IHttpContent content = null, bool sendAuthHeader = false) 282 | { 283 | var request = new HttpRequestMessage(HttpMethod.Put, CreateUriFromPathString(path)); 284 | request.Content = content; 285 | return await SendAsync(request, sendAuthHeader); 286 | } 287 | 288 | #endregion 289 | } 290 | } -------------------------------------------------------------------------------- /ParticleSDK/Utils/DeviceNameGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Particle.SDK.Utils 5 | { 6 | /// 7 | /// Simple class to generate unique names for Particle devices 8 | /// 9 | public class DeviceNameGenerator 10 | { 11 | private static Random random = new Random(); 12 | 13 | private static String[] trochees = new String[]{"aardvark", "bacon", "badger", "banjo", 14 | "bobcat", "boomer", "captain", "chicken", "cowboy", "maker", "splendid", "useful", 15 | "dentist", "doctor", "dozen", "easter", "ferret", "gerbil", "hacker", "hamster", 16 | "sparkling", "hobbit", "hoosier", "hunter", "jester", "jetpack", "kitty", "laser", "lawyer", 17 | "mighty", "monkey", "morphing", "mutant", "narwhal", "ninja", "normal", "penguin", 18 | "pirate", "pizza", "plumber", "power", "puppy", "ranger", "raptor", "robot", "scraper", 19 | "spark", "station", "tasty", "trochee", "turkey", "turtle", "vampire", "wombat", 20 | "zombie"}; 21 | 22 | public static string GenerateUniqueName(HashSet existingNames = null) 23 | { 24 | string uniqueName = null; 25 | while (uniqueName == null) 26 | { 27 | string part1 = GetRandomName(); 28 | string part2 = GetRandomName(); 29 | string candidate = part1 + "_" + part2; 30 | if (part1.Equals(part2)) 31 | continue; 32 | else if (existingNames == null) 33 | uniqueName = candidate; 34 | else if (!existingNames.Contains(candidate)) 35 | uniqueName = candidate; 36 | } 37 | return uniqueName; 38 | } 39 | 40 | private static String GetRandomName() 41 | { 42 | int randomIndex = random.Next(0, trochees.Length); 43 | return trochees[randomIndex]; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ParticleSDK/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Particle

2 | 3 | # Particle Windows Cloud SDK 4 | 5 | [![license](https://img.shields.io/hexpm/l/plug.svg)](https://github.com/spark/particle-windows-sdk/blob/master/LICENSE) 6 | [![NuGet Version](http://img.shields.io/nuget/v/Particle.SDK.svg?style=flat)](https://www.nuget.org/packages/Particle.SDK/) 7 | 8 | ## Introduction 9 | 10 | Particle Windows Cloud SDK enables Windows apps to interact with Particle-powered connected products via the Particle Cloud. It's an easy-to-use wrapper for Particle REST API. The Cloud SDK will allow you to: 11 | 12 | - Manage user sessions for the Particle Cloud (access tokens, encrypted session management) 13 | - Claim/Unclaim devices for a user account 14 | - Get a list of instances of user's Particle devices 15 | - Read variables from devices 16 | - Invoke functions on devices 17 | - Publish events and subscribe to events coming from devices 18 | 19 | All cloud operations take place asynchronously and return a *System.Threading.Tasks.Task* allowing you to build beautiful responsive apps for your Particle products and projects. Windows Cloud SDK is implemented as an open-source .NET Portable Class Library. See [Installation](#installation) section for more details. It works well for both C# and VB projects. 20 | 21 | ## Beta notice 22 | 23 | This SDK is still under development and is currently released as Beta and over the next few months may go under considerable changes. Although tested, bugs and issues may be present. Some code might require cleanup. In addition, until version 1.0 is released, we cannot guarantee that API calls will not break from one Cloud SDK version to the next. Be sure to consult the [Change Log](https://github.com/spark/particle-windows-sdk/blob/master/CHANGELOG.md) for any breaking changes / additions to the SDK. 24 | 25 | ## Getting started 26 | 27 | - Perform the installation step described under the [Installation](#installation) section below for integrating in your own project 28 | - Be sure to check [Usage](#usage) before you begin for some code examples 29 | 30 | ## Usage 31 | 32 | Cloud SDK usage involves two basic classes: first is [`ParticleCloud`](https://github.com/spark/particle-windows-sdk/blob/master/ParticleCloud.cs) which is an object that enables all basic cloud operations such as user authentication, device listing, claiming etc. Second class is [`ParticleDevice`](https://github.com/spark/particle-windows-sdk/blob/master/ParticleDevice.cs) which is an instance representing a claimed device in the current user session. Each object enables device-specific operation such as: getting its info, invoking functions and reading variables from it. 33 | 34 | ## SDK calls from the UI thread 35 | 36 | Some calls from the SDK can both update properties or run callbacks on a non UI thread (e.g. Events). If your application has a UI thread make sure to set the `SynchronizationContext`. 37 | 38 | ```cs 39 | ParticleCloud.SharedCloud.SynchronizationContext = System.Threading.SynchronizationContext.Current; 40 | ``` 41 | 42 | ## Common tasks 43 | 44 | Here are few examples for the most common use cases to get your started: 45 | 46 | ### Log in to Particle Cloud 47 | 48 | You don't need to worry about access tokens and session expiry, SDK takes care of that for you. 49 | 50 | ```cs 51 | var success = await ParticleCloud.SharedCloud.LoginAsync("user@example.com", "myl33tp4ssw0rd"); 52 | ``` 53 | 54 | ### Log in to Particle Cloud with a token and validate 55 | 56 | ```cs 57 | var success = await ParticleCloud.SharedCloud.TokenLoginAsync("d4f69e3a357f78316d50e76dbf10fe92364154bf"); 58 | ``` 59 | 60 | ### Injecting an access token (app utilizes two legged authentication) 61 | 62 | ```cs 63 | var success = await ParticleCloud.SharedCloud.SetAuthentication("d4f69e3a357f78316d50e76dbf10fe92364154bf"); 64 | ``` 65 | 66 | ### Get a list of all devices 67 | 68 | List the devices that belong to currently logged in user and find a specific device by name: 69 | 70 | ```cs 71 | ParticleDevice myDevice = null; 72 | List devices = ParticleCloud.SharedCloud.GetDevicesAsync(); 73 | foreach (ParticleDevice device in devices) 74 | { 75 | if (device.Name().equals("myDeviceName")) 76 | myDevice = device; 77 | } 78 | ``` 79 | 80 | ### Get device instance by its ID or name 81 | 82 | ```cs 83 | ParticleDevice device = ParticleCloud.SharedCloud.GetDeviceAsync("e9eb56e90e703f602d67ceb3"); 84 | ``` 85 | 86 | ### Read a variable from a Particle device 87 | 88 | Assuming here that `myDevice` is an active instance of `ParticleDevice` class which represents a device claimed to current user. 89 | 90 | ```cs 91 | var variableResponse = myDevice.GetVariableAsync("temperature"); 92 | int temperatureReading = (int)variableResponse.Result; 93 | ``` 94 | 95 | ### Call a function on a Particle device 96 | 97 | Invoke a function on the device and pass a parameter to it, the returning `ParticleFunctionResponse` will represent the returned result data of the function on the device. 98 | 99 | ```cs 100 | int functionResponse = myDevice.RunFunctionAsync("digitalwrite", "D7 HIGH")); 101 | int result = functionResponse.ReturnValue; 102 | ``` 103 | 104 | ### List device exposed functions and variables 105 | 106 | `ParticleDevice.Functions` returns a list of function names. `ParticleDevice.Variables` returns a dictionary of variable names to types. 107 | 108 | ```cs 109 | foreach (string functionName in myDevice.Functions) 110 | Debug.WriteLine($"Device has function: {functionName}"); 111 | 112 | foreach (varvariable in myDevice.Variables) 113 | Debug.WriteLine($"Device has variable: '{variable.Key}' of type '{variable.Value}'"); 114 | ``` 115 | 116 | ### Rename a device 117 | 118 | Set a new name for a claimed device: 119 | 120 | ```cs 121 | myDevice.RenameAsync("myDeviceNew"); 122 | ``` 123 | 124 | ### Refresh a device 125 | 126 | Refreshes all the locally stored properties from the cloud and physical device (if online): 127 | 128 | ```cs 129 | myDevice.RefreshAsync(); 130 | ``` 131 | 132 | ### Signal a device 133 | 134 | Send a signal to the device to shout rainbows: 135 | 136 | ```cs 137 | myDevice.SignalAsync(true); 138 | ``` 139 | 140 | ### Log out 141 | 142 | Log out the user, clearing their session and access token: 143 | 144 | ```cs 145 | ParticleCloud.SharedCloud.LogOut(); 146 | ``` 147 | 148 | ## Events sub-system 149 | You can make an API call that will open a stream of [Server-Sent Events (SSEs)](http://www.w3.org/TR/eventsource/). You will make one API call that opens a connection to the Particle Cloud. That connection will stay open, unlike normal HTTP calls which end quickly. Very little data will come to you across the connection unless your Particle device publishes an event, at which point you will be immediately notified. In each case, the event name filter is `eventNamePrefix` and is optional. When specifying an event name filter, published events will be limited to those events with names that begin with the specified string. For example, specifying an event name filter of 'temp' will return events with names 'temp' and 'temperature'. 150 | 151 | ### Subscribe to events 152 | 153 | Subscribe to the firehose of public events, plus private events published by devices one owns: 154 | 155 | ```cs 156 | private void onEvent(object sender, ParticleEventResponse particeEvent) 157 | { 158 | Debug.WriteLine($"Got Event {particeEvent.Name} with data {particeEvent.Data}"); 159 | } 160 | 161 | Guid eventListenerID = ParticleCloud.SharedCloud.SubscribeToAllEventsWithPrefixAsync(onEvent, "temp"); 162 | ``` 163 | 164 | *Note:* specifying null or empty string in the `eventNamePrefix` parameter will subscribe to ALL events (lots of data!) You can have multiple handlers per event name and/or same handler per multiple events names. 165 | 166 | Subscribe to all events, public and private, published by devices the user owns: 167 | 168 | ```cs 169 | Guid eventListenerID = ParticleCloud.SharedCloud.SubscribeToDevicesEventsWithPrefixAsync(handler, "temp"); 170 | ``` 171 | 172 | Subscribe to events from one specific device. Pass a `PaticleDevice` or `deviceId` string as a second parameter. If the API user owns the device, then all events, public and private, published by that device will be received. If the API user does not own the device only public events will be received. 173 | 174 | ```cs 175 | Guid eventListenerID = ParticleCloud.SharedCloud.SubscribeToDeviceEventsWithPrefixAsync(handler, myDevice); 176 | ``` 177 | 178 | ```cs 179 | Guid eventListenerID = ParticleCloud.SharedCloud.SubscribeToDeviceEventsWithPrefixAsync(handler, "e9eb56e90e703f602d67ceb3"); 180 | ``` 181 | 182 | The method `SubscribeToDeviceEventsWithPrefixAsync` can also be called on a `ParticleDevice` instance, guaranteeing that private events will be received since having access device instance in your app signifies that the user has this device claimed. 183 | 184 | ```cs 185 | Guid eventListenerID = myDevice.SubscribeToDeviceEventsWithPrefixAsync(handler, "temp"); 186 | ``` 187 | 188 | ### Unsubscribing from events 189 | 190 | Very straightforward. Keep the id object the subscribe method returned and use it as parameter to call the unsubscribe method: 191 | 192 | ```cs 193 | ParticleCloud.SharedCloud.UnsubscribeFromEvent(eventListenerID); 194 | ``` 195 | 196 | or via the `ParticleDevice` instance (if applicable): 197 | 198 | ```cs 199 | myDevice.UnsubscribeFromEvent(eventListenerID); 200 | ``` 201 | 202 | ### Publishing an event 203 | 204 | You can also publish an event from your app to the Particle Cloud: 205 | 206 | ```cs 207 | ParticleCloud.SharedCloud.PublishEventAsync("event_from_app", "event_payload", true, 60); 208 | ``` 209 | 210 | ## OAuth client configuration 211 | 212 | If you're creating an app you're required to provide the `ParticleCloud` class with OAuth clientId and secret. Those are used to identify users coming from your specific app to the Particle Cloud. Please follow the procedure described [in our guide](https://docs.particle.io/reference/api/#create-an-oauth-client) to create those strings. 213 | 214 | Once you've created your OAuth credentials, you can supply them to the SDK by providing them as string resources in a string resource file called "OAuthClient.resw", using the names `OAuthClientID` and `OAuthClientSecret` and they'll be picked up by the SDK automatically: 215 | 216 | ```xml 217 | 218 | (client ID string goes here) 219 | 220 | 221 | (client secret 40-char hex string goes here) 222 | 223 | ``` 224 | 225 | If you aren't creating a Windows Store app and/or not using string resources you can manually set the values. Make sure you do this before calling any other functions. 226 | 227 | ```cs 228 | ParticleCloud.SharedCloud.OAuthClientId = "(client ID string goes here)"; 229 | ParticleCloud.SharedCloud.OAuthClientSecret = "(client secret 40-char hex string goes here)"; 230 | ``` 231 | 232 | ## Installation 233 | 234 | - There are two versions of the library: 235 | - **portable46-win81+wpa81**: Portable .NET Framework 4.6 for use in Windows Runtime (WinRT) applications (Windows 8.1+ and Windows Phone 8.1+) 236 | - **netcorestandard**: .NET Core Standard (2.0) Framework for use in full .NetFramework, .Net Core, Mono, Xamarin, UWP (https://docs.microsoft.com/en-us/dotnet/standard/net-standard) 237 | - Any edition of Microsoft Visual Studio 2017 (Other build systems may also work, but are not officially supported.) 238 | - You can use either C# or VB 239 | 240 | You can either [download Particle Windows Cloud SDK](https://github.com/spark/particle-windows-sdk/archive/master.zip) or install using [NuGet](http://www.nuget.org/packages/Particle.SDK) 241 | 242 | `PM> Install-Package Particle.SDK` 243 | 244 | ## Communication 245 | 246 | - If you **need help**, use [Our community website](http://community.particle.io), use the `Mobile` category for discussion/troubleshooting Windows apps using the Particle Windows Cloud SDK. 247 | - If you are certain you **found a bug**, _and can provide steps to reliably reproduce it_, [open an issue on GitHub](https://github.com/spark/particle-windows-sdk/labels/bug). 248 | - If you **have a feature request**, [open an issue on GitHub](https://github.com/spark/particle-windows-sdk/labels/enhancement). 249 | - If you **want to contribute**, submit a pull request, be sure to check out spark.github.io for our contribution guidelines, and please sign the [CLA](https://docs.google.com/a/particle.io/forms/d/1_2P-vRKGUFg5bmpcKLHO_qNZWGi5HKYnfrrkd-sbZoA/viewform). 250 | 251 | ## Maintainers 252 | 253 | - Justin Myers [Github](https://github.com/justmobilize) 254 | - Ido Kleinman [Github](https://www.github.com/idokleinman) | [Twitter](https://www.twitter.com/idokleinman) 255 | 256 | ## License 257 | 258 | Particle Windows Cloud SDK is available under the Apache License 2.0. See the [LICENSE file](https://github.com/spark/particle-windows-sdk/blob/master/LICENSE) for more info. 259 | --------------------------------------------------------------------------------