├── .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 | 
2 |
3 | # Particle Windows Cloud SDK
4 |
5 | [](https://github.com/spark/particle-windows-sdk/blob/master/LICENSE)
6 | [](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 |
--------------------------------------------------------------------------------