├── LICENSE
├── README.md
├── examples
├── CheerLights
│ └── CheerLights.ino
├── ReadLastTemperature
│ └── ReadLastTemperature.ino
├── ReadPrivateChannel
│ └── ReadPrivateChannel.ino
├── ReadWeatherStation
│ └── ReadWeatherStation.ino
├── WriteMultipleVoltages
│ └── WriteMultipleVoltages.ino
└── WriteVoltage
│ └── WriteVoltage.ino
├── library.properties
└── src
├── ThingSpeak.cpp
├── ThingSpeak.h
└── ThingSpeak
└── ThingSpeak.h
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright :copyright: 2017, The MathWorks, Inc.
2 |
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10 | 4. In all cases, the software is, and all modifications and derivatives of the software shall be, licensed to you solely for use in conjunction with MathWorks products and service offerings.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ThingSpeak Communication Library for Particle
2 |
3 | This library enables Particle hardware to write or read data to or from ThingSpeak, an open data platform for the Internet of Things with MATLAB analytics and visualization.
4 |
5 | ThingSpeak offers free data storage and analysis of time-stamped numeric or alphanumeric data. Users can access ThingSpeak by visiting https://thingspeak.com and creating a ThingSpeak user account.
6 |
7 | ThingSpeak stores data in channels. Channels support an unlimited number of timestamped observations (think of these as rows in a spreadsheet). Each channel has up to 8 fields (think of these as columns in a speadsheet). Check out this [video](https://www.mathworks.com/videos/introduction-to-thingspeak-107749.html) for an overview.
8 |
9 | Channels may be public, where anyone can see the data, or private, where only the owner and select users can read the data. Each channel has an associated Write API Key that is used to control who can write to a channel. In addition, private channels have one or more Read API Keys to control who can read from private channel. An API Key is not required to read from public channels. Each channel can have up to 8 fields. One field is created by default.
10 |
11 | You can visualize and do online analytics of your data on ThingSpeak using the built-in version of MATLAB, or use the desktop version of MATLAB to get deeper historical insight. Visit https://www.mathworks.com/hardware-support/thingspeak.html to learn more.
12 |
13 | #### Particle Web IDE
14 | In the Particle Web IDE, click the libraries tab, find ThingSpeak, and choose "Include in App"
15 |
16 | ## Compatible Hardware:
17 | * Particle (Formally Spark) Core, [Photon](https://www.particle.io/prototype#photon), [Electron](https://www.particle.io/prototype#electron) and [P1](https://www.particle.io/prototype#p0-and-p1).
18 |
19 | # Some Quick Examples
20 |
21 | ## Write to a Channel Field
22 | ```
23 | #include "ThingSpeak.h"
24 |
25 | TCPClient client;
26 |
27 | unsigned long myChannelNumber = 31461; // change this to your channel number
28 | const char * myWriteAPIKey = "LD79EOAAWRVYF04Y"; // change this to your channels write API key
29 |
30 | void setup() {
31 | ThingSpeak.begin(client);
32 | }
33 |
34 | void loop() {
35 | // read the input on analog pin 0:
36 | int sensorValue = analogRead(A0);
37 |
38 | // Write to ThingSpeak, field 1, immediately
39 | ThingSpeak.writeField(myChannelNumber, 1, sensorValue, myWriteAPIKey);
40 | delay(20000); // ThingSpeak will only accept updates every 15 seconds.
41 | }
42 |
43 | ```
44 | ## Write to a Multiple Channel fields at once
45 | ```
46 | #include "ThingSpeak.h"
47 |
48 | TCPClient client;
49 |
50 | unsigned long myChannelNumber = 31461; // change this to your channel number
51 | const char * myWriteAPIKey = "LD79EOAAWRVYF04Y"; // change this to your channel write API key
52 |
53 | void setup() {
54 | ThingSpeak.begin(client);
55 | }
56 |
57 | void loop(){
58 | // read the input on analog pins 1, 2 and 3:
59 | int sensorValue1 = analogRead(A1);
60 | int sensorValue2 = analogRead(A2);
61 | int sensorValue3 = analogRead(A3);
62 |
63 | // set fields one at a time
64 | ThingSpeak.setField(1,sensorValue1);
65 | ThingSpeak.setField(2,sensorValue2);
66 | ThingSpeak.setField(3,sensorValue3);
67 |
68 | // set the status if over the threshold
69 | if(sensorValue1 > 100){
70 | ThingSpeak.setStatus("ALERT! HIGH VALUE");
71 | }
72 |
73 | // Write the fields that you've set all at once.
74 | ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
75 |
76 | delay(20000); // ThingSpeak will only accept updates every 15 seconds.
77 | }
78 |
79 | ```
80 | ## Read from a Public Channel
81 | ```
82 | #include "ThingSpeak.h"
83 |
84 | TCPClient client;
85 |
86 | unsigned long weatherStationChannelNumber = 12397;
87 |
88 | void setup() {
89 | ThingSpeak.begin(client);
90 | }
91 |
92 | void loop(){
93 |
94 | // Read latest measurements from the weather station in Natick, MA
95 | float temperature = ThingSpeak.readFloatField(weatherStationChannelNumber,4);
96 | float humidity = ThingSpeak.readFloatField(weatherStationChannelNumber,3);
97 |
98 | Particle.publish("thingspeak-weather", "Current weather conditions in Natick: ",60,PRIVATE);
99 | Particle.publish("thingspeak-weather", String(temperature) + " degrees F, " + String(humidity) + "% humidity",60,PRIVATE);
100 |
101 | delay(60000); // Note that the weather station only updates once a minute
102 |
103 | }
104 | ```
105 | ## Read from a Private Channel
106 | ```
107 | #include "ThingSpeak.h"
108 |
109 | TCPClient client;
110 |
111 | unsigned long myChannelNumber = 31461;
112 | const char * myReadAPIKey = "NKX4Z5JGO4M5I18A";
113 |
114 | void setup() {
115 | ThingSpeak.begin(client);
116 | }
117 |
118 | void loop(){
119 |
120 | // Read the latest value from field 1 of channel 31461
121 | float value = ThingSpeak.readFloatField(myChannelNumber, 1, myReadAPIKey);
122 |
123 | Particle.publish("thingspeak-value", "Latest value is: " + String(value),60,PRIVATE);
124 | delay(30000);
125 |
126 | }
127 | ```
128 |
129 | ## Read multiple fields from last feed ingested in a Channel
130 | ```
131 | #include "ThingSpeak.h"
132 |
133 | TCPClient client;
134 |
135 | unsigned long weatherStationChannelNumber = 12397;
136 |
137 | void setup() {
138 | ThingSpeak.begin(client);
139 | }
140 |
141 | void loop(){
142 |
143 | // Read latest measurements from the weather station in Natick, MA
144 | // when reading from a private channel, pass the channel ReadApi key
145 | statusCodeRead = ThingSpeak.readMultipleFields(weatherStationChannelNumber);
146 |
147 | // Wind Direction (North = 0 degrees)
148 | float windDirection = ThingSpeak.getFieldAsFloat(1);
149 |
150 | // Wind Speed (mph)
151 | float windSpeed = ThingSpeak.getFieldAsFloat(2);
152 |
153 | // Humidity (%)
154 | float humidity = ThingSpeak.getFieldAsFloat(3);
155 |
156 | // Temperature (F)
157 | float temperature = ThingSpeak.getFieldAsFloat(4);
158 |
159 | // Rain (Inches/minute)
160 | float rain = ThingSpeak.getFieldAsFloat(5);
161 |
162 | // Pressure ("Hg)
163 | float pressure = ThingSpeak.getFieldAsFloat(6);
164 |
165 | // Power Level (V)
166 | float powerLevel = ThingSpeak.getFieldAsFloat(7);
167 |
168 | // Light Intensity
169 | float pressure = ThingSpeak.getFieldAsFloat(8);
170 |
171 | Particle.publish("thingspeak-weather", "Current weather conditions in Natick: ",60,PRIVATE);
172 | Particle.publish("thingspeak-weather", String(temperature) + " degrees F, " + String(humidity) + "% humidity",60,PRIVATE);
173 |
174 | delay(60000); // Note that the weather station only updates once a minute
175 |
176 | }
177 | ```
178 |
179 | # Documentation
180 |
181 | ## begin
182 | Initializes the ThingSpeak library and network settings.
183 | ```
184 | bool begin (client) // defaults to port 80
185 | ```
186 | ```
187 | bool begin (client, port)
188 | ```
189 | | Parameter | Type | Description |
190 | |----------------|:-------------|:-------------------------------------------------------|
191 | | client | Client & | TCPClient created earlier in the sketch |
192 |
193 | | port | unsigned int | Specific port number to use |
194 |
195 | ### Returns
196 | Always returns true. This does not validate the information passed in, or generate any calls to ThingSpeak.
197 |
198 | ## writeField
199 | Write a value to a single field in a ThingSpeak channel.
200 | ```
201 | int writeField(channelNumber, field, value, writeAPIKey)
202 | ```
203 | | Parameter | Type | Description |
204 | |---------------|:--------------|:------------------------------------------------------------------------------------------------|
205 | | channelNumber | unsigned long | Channel number |
206 | | field | unsigned int | Field number (1-8) within the channel to write to. |
207 | | value | int | Integer value (from -32,768 to 32,767) to write. |
208 | | | long | Long value (from -2,147,483,648 to 2,147,483,647) to write. |
209 | | | float | Floating point value (from -999999000000 to 999999000000) to write. |
210 | | | String | String to write (UTF8 string). ThingSpeak limits this field to 255 bytes. |
211 | | | const char * | Character array (zero terminated) to write (UTF8). ThingSpeak limits this field to 255 bytes. |
212 | | writeAPIKey | const char * | Write API key associated with the channel. If you share code with others, do not share this key |
213 |
214 | ### Returns
215 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
216 |
217 | ### Remarks
218 | Special characters will be automatically encoded by this method. See the note regarding special characters below.
219 |
220 | ## writeFields
221 | Write a multi-field update. Call setField() for each of the fields you want to write first.
222 | ```
223 | int writeFields (channelNumber, writeAPIKey)
224 | ```
225 | | Parameter | Type | Description |
226 | |---------------|:--------------|:------------------------------------------------------------------------------------------------|
227 | | channelNumber | unsigned long | Channel number |
228 | | writeAPIKey | const char * | Write API key associated with the channel. If you share code with others, do not share this key |
229 |
230 | ### Returns
231 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
232 |
233 | ### Remarks
234 | Special characters will be automatically encoded by this method. See the note regarding special characters below.
235 |
236 | ## writeRaw
237 | Write a raw POST to a ThingSpeak channel.
238 | ```
239 | int writeRaw (channelNumber, postMessage, writeAPIKey)
240 | ```
241 |
242 | | Parameter | Type | Description |
243 | |---------------|:--------------|:--------------------------------------------------------------------------------------------------------------------------------------------------|
244 | | channelNumber | unsigned long | Channel number |
245 | | postMessage | const char * | Raw URL to write to ThingSpeak as a String. See the documentation at https://thingspeak.com/docs/channels#update_feed. |
246 | | | String | Raw URL to write to ThingSpeak as a character array (zero terminated). See the documentation at https://thingspeak.com/docs/channels#update_feed. |
247 | | writeAPIKey | const char * | Write API key associated with the channel. If you share code with others, do not share this key |
248 |
249 | ### Returns
250 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
251 |
252 | ### Remarks
253 | This method will not encode special characters in the post message. Use '%XX' URL encoding to send special characters. See the note regarding special characters below.
254 |
255 | ## setField
256 | Set the value of a single field that will be part of a multi-field update.
257 | ```
258 | int setField (field, value)
259 | ```
260 |
261 | | Parameter | Type | Description |
262 | |-----------|:-------------|:----------------------------------------------------------------------------------------------|
263 | | field | unsigned int | Field number (1-8) within the channel to set |
264 | | value | int | Integer value (from -32,768 to 32,767) to write. |
265 | | | long | Long value (from -2,147,483,648 to 2,147,483,647) to write. |
266 | | | float | Floating point value (from -999999000000 to 999999000000) to write. |
267 | | | String | String to write (UTF8 string). ThingSpeak limits this field to 255 bytes. |
268 | | | const char * | Character array (zero terminated) to write (UTF8). ThingSpeak limits this field to 255 bytes. |
269 |
270 | ### Returns
271 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
272 |
273 | ## setStatus
274 | Set the status of a multi-field update. Use status to provide additonal details when writing a channel update.
275 | ```
276 | int setStatus (status)
277 | ```
278 |
279 | | Parameter | Type | Description |
280 | |--------|:-------------|:------------------------------------------------------------------------------|
281 | | status | const char * | String to write (UTF8). ThingSpeak limits this to 255 bytes. |
282 | | | String | const character array (zero terminated). ThingSpeak limits this to 255 bytes. |
283 |
284 | ### Returns
285 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
286 |
287 | ## setLatitude
288 | Set the latitude of a multi-field update.
289 | ```
290 | int setLatitude (latitude)
291 | ```
292 |
293 | | Parameter | Type | Description |
294 | |-----------|:------|:---------------------------------------------------------------------------|
295 | | latitude | float | Latitude of the measurement (degrees N, use negative values for degrees S) |
296 |
297 | ### Returns
298 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
299 |
300 | ## setLongitude
301 | Set the longitude of a multi-field update.
302 | ```
303 | int setLongitude (longitude)
304 | ```
305 |
306 | | Parameter | Type | Description |
307 | |-----------|:------|:----------------------------------------------------------------------------|
308 | | longitude | float | Longitude of the measurement (degrees E, use negative values for degrees W) |
309 |
310 | ### Returns
311 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
312 |
313 | ## setElevation
314 | Set the elevation of a multi-field update.
315 | ```
316 | int setElevation (elevation)
317 | ```
318 |
319 | | Parameter | Type | Description |
320 | |-----------|:------|:--------------------------------------------------------|
321 | | elevation | float | Elevation of the measurement (meters above sea level) |
322 |
323 | ### Returns
324 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
325 |
326 | ## setCreatedAt
327 | Set the created-at date of a multi-field update. The timestamp string must be in the ISO 8601 format. Example "2017-01-12 13:22:54"
328 | ```
329 | int setCreatedAt (createdAt)
330 | ```
331 |
332 | | Parameter | Type | Description |
333 | |-----------|:-------------|:-------------------------------------------------------------------------------------------------|
334 | | createdAt | String | Desired timestamp to be included with the channel update as a String. |
335 | | | const char * | Desired timestamp to be included with the channel update as a character array (zero terminated). |
336 |
337 | ### Returns
338 | HTTP status code of 200 if successful. See Return Codes below for other possible return values.
339 |
340 | ### Remarks
341 | Timezones can be set using the timezone hour offset parameter. For example, a timestamp for Eastern Standard Time is: "2017-01-12 13:22:54-05". If no timezone hour offset parameter is used, UTC time is assumed.
342 |
343 | ## readStringField
344 | Read the latest string from a channel. Include the readAPIKey to read a private channel.
345 | ```
346 | String readStringField (channelNumber, field, readAPIKey)
347 | ```
348 | ```
349 | String readStringField (channelNumber, field)
350 | ```
351 |
352 | | Parameter | Type | Description |
353 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
354 | | channelNumber | unsigned long | Channel number |
355 | | field | unsigned int | Field number (1-8) within the channel to read from. |
356 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
357 |
358 | ### Returns
359 | Value read (UTF8 string), or empty string if there is an error.
360 |
361 | ## readFloatField
362 | Read the latest float from a channel. Include the readAPIKey to read a private channel.
363 | ```
364 | float readFloatField (channelNumber, field, readAPIKey)
365 | ```
366 | ```
367 | float readFloatField (channelNumber, field)
368 | ```
369 |
370 | | Parameter | Type | Description |
371 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
372 | | channelNumber | unsigned long | Channel number |
373 | | field | unsigned int | Field number (1-8) within the channel to read from. |
374 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
375 |
376 | ### Returns
377 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
378 |
379 | ## readLongField
380 | Read the latest long from a channel. Include the readAPIKey to read a private channel.
381 | ```
382 | long readLongField (channelNumber, field, readAPIKey)
383 | ```
384 | ```
385 | long readLongField (channelNumber, field)
386 | ```
387 |
388 | | Parameter | Type | Description |
389 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
390 | | channelNumber | unsigned long | Channel number |
391 | | field | unsigned int | Field number (1-8) within the channel to read from. |
392 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
393 |
394 | ### Returns
395 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information.
396 |
397 | ## readIntField
398 | Read the latest int from a channel. Include the readAPIKey to read a private channel.
399 | ```
400 | int readIntField (channelNumber, field, readAPIKey)
401 | ```
402 | ```
403 | int readIntField (channelNumber, field)
404 | ```
405 |
406 | | Parameter | Type | Description |
407 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
408 | | channelNumber | unsigned long | Channel number |
409 | | field | unsigned int | Field number (1-8) within the channel to read from. |
410 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
411 |
412 | ### Returns
413 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. If the value returned is out of range for an int, the result is undefined.
414 |
415 | ## readStatus
416 | Read the latest status from a channel. Include the readAPIKey to read a private channel.
417 | ```
418 | String readStatus (channelNumber, readAPIKey)
419 | ```
420 | ```
421 | String readStatus (channelNumber)
422 | ```
423 |
424 | | Parameter | Type | Description |
425 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
426 | | channelNumber | unsigned long | Channel number |
427 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
428 |
429 | ### Returns
430 | Returns the status field as a String.
431 |
432 | ## String readCreatedAt()
433 | Read the created-at timestamp associated with the latest update to a channel. Include the readAPIKey to read a private channel.
434 | ```
435 | String readCreatedAt (channelNumber, readAPIKey)
436 | ```
437 | ```
438 | String readCreatedAt (channelNumber)
439 | ```
440 |
441 | | channelNumber | unsigned long | Channel number |
442 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
443 |
444 | ### Returns
445 | Returns the created-at timestamp as a String.
446 |
447 | ## readRaw
448 | Read a raw response from a channel. Include the readAPIKey to read a private channel.
449 | ```
450 | String readRaw (channelNumber, URLSuffix, readAPIKey)
451 | ```
452 | ```
453 | String readRaw (channelNumber, URLSuffix)
454 | ```
455 |
456 | | Parameter | Type | Description |
457 | |---------------|:--------------|:-------------------------------------------------------------------------------------------------------------------|
458 | | channelNumber | unsigned long | Channel number |
459 | | URLSuffix | String | Raw URL to write to ThingSpeak as a String. See the documentation at https://thingspeak.com/docs/channels#get_feed |
460 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key. |
461 |
462 | ### Returns
463 | Returns the raw response from a HTTP request as a String.
464 |
465 | ## getLastReadStatus
466 | Get the status of the previous read.
467 | ```
468 | int getLastReadStatus ()
469 | ```
470 |
471 | ## readMultipleFields
472 | Read all the field values, status message, location coordinates, and created-at timestamp associated with the latest feed to a ThingSpeak channel.
473 | The values are stored in a struct, which holds all the 8 fields data, along with status, latitude, longitude, elevation and createdAt associated with the latest field.
474 | To retrieve all the values, invoke these functions in order:
475 | 1. readMultipleFields
476 | 2. readMultipleFields helper functions
477 |
478 | ### 1. readMultipleFields
479 |
480 | ```
481 | int readMultipleFields (channelNumber, readAPIKey)
482 | ```
483 | ```
484 | int readMultipleFields (channelNumber)
485 | ```
486 |
487 | | Parameter | Type | Description |
488 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
489 | | channelNumber | unsigned long | Channel number |
490 | | readAPIKey | const char * | Read API key associated with the channel. If you share code with others, do not share this key |
491 |
492 | #### Returns
493 | HTTP status code of 200 if successful
494 |
495 |
496 | #### 2. readMultipleFields helper functions
497 |
498 | #### a. getFieldAsString
499 |
500 | ```
501 | String getFieldAsString (field)
502 | ```
503 |
504 | | Parameter | Type | Description |
505 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
506 | | field | unsigned int | Field number (1-8) within the channel to read from.
507 |
508 | #### Returns
509 | Value read (UTF8 string), empty string if there is an error, or old value read (UTF8 string) if invoked before readMultipleFields().
510 |
511 |
512 | #### b. getFieldAsFloat
513 |
514 | ```
515 | float getFieldAsFloat (field)
516 | ```
517 |
518 | | Parameter | Type | Description |
519 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
520 | | field | unsigned int | Field number (1-8) within the channel to read from.
521 |
522 | #### Returns
523 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields().
524 |
525 | #### c. getFieldAsLong
526 |
527 | ```
528 | long getFieldAsLong (field)
529 | ```
530 |
531 | | Parameter | Type | Description |
532 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
533 | | field | unsigned int | Field number (1-8) within the channel to read from.
534 |
535 | #### Returns
536 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields().
537 |
538 | #### d. getFieldAsInt
539 |
540 | ```
541 | int getFieldAsInt (field)
542 | ```
543 |
544 | | Parameter | Type | Description |
545 | |---------------|:--------------|:-----------------------------------------------------------------------------------------------|
546 | | field | unsigned int | Field number (1-8) within the channel to read from.
547 |
548 | #### Returns
549 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields().
550 |
551 | #### e. getStatus
552 |
553 | ```
554 | String getStatus ()
555 | ```
556 |
557 | #### Returns
558 | Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error.
559 |
560 | #### f. getLatitude
561 |
562 | ```
563 | String getLatitude ()
564 | ```
565 |
566 | #### Returns
567 | Value read (UTF8 string). An empty string is returned if there was no latitude written to the channel or in case of an error.
568 |
569 | #### g. getLongitude
570 |
571 | ```
572 | String getLongitude ()
573 | ```
574 |
575 | #### Returns
576 | Value read (UTF8 string). An empty string is returned if there was no longitude written to the channel or in case of an error.
577 |
578 | #### h. getElevation
579 |
580 | ```
581 | String getElevation ()
582 | ```
583 |
584 | #### Returns
585 | Value read (UTF8 string). An empty string is returned if there was no elevation written to the channel or in case of an error.
586 |
587 | #### i. getCreatedAt
588 |
589 | ```
590 | String getCreatedAt ()
591 | ```
592 |
593 | #### Returns
594 | Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error.
595 |
596 |
597 |
598 | ## Return Codes
599 | | Value | Meaning |
600 | |-------|:----------------------------------------------------------------------------------------|
601 | | 200 | OK / Success |
602 | | 404 | Incorrect API key (or invalid ThingSpeak server address) |
603 | | -101 | Value is out of range or string is too long (> 255 characters) |
604 | | -201 | Invalid field number specified |
605 | | -210 | setField() was not called before writeFields() |
606 | | -301 | Failed to connect to ThingSpeak |
607 | | -302 | Unexpected failure during write to ThingSpeak |
608 | | -303 | Unable to parse response |
609 | | -304 | Timeout waiting for server to respond |
610 | | -401 | Point was not inserted (most probable cause is the rate limit of once every 15 seconds) |
611 | | 0 | Other error |
612 |
613 | ## Special Characters
614 | Some characters require '%XX' style URL encoding before sending to ThingSpeak. The writeField() and writeFields() methods will perform the encoding automatically. The writeRaw() method will not.
615 |
616 | | Character | Encoding |
617 | |------------|:---------|
618 | | " | %22 |
619 | | % | %25 |
620 | | & | %26 |
621 | | + | %2B |
622 | | ; | %3B |
623 |
624 | Control characters, ASCII values 0 though 31, are not accepted by ThingSpeak and will be ignored. Extended ASCII characters with values above 127 will also be ignored.
625 |
626 | # Additional Examples
627 |
628 | The library source includes several examples to help you get started. These are accessible in ThingSpeak library section of the Particle Web IDE.
629 |
630 | * **CheerLights:** Reads the latest CheerLights color on ThingSpeak, and sets an RGB LED.
631 | * **ReadLastTemperature:** Reads the latest temperature from the public MathWorks weather station in Natick, MA on ThingSpeak.
632 | * **ReadPrivateChannel:** Reads the latest voltage value from a private channel on ThingSpeak.
633 | * **ReadWeatherStation:** Reads the latest weather data from the public MathWorks weather station in Natick, MA on ThingSpeak.
634 | * **WriteMultipleVoltages:** Reads analog voltages from pins A1-A6 and writes them to the fields of a channel on ThingSpeak.
635 | * **WriteVoltage:** Reads an analog voltage from pin 0, converts to a voltage, and writes it to a channel on ThingSpeak.
636 |
--------------------------------------------------------------------------------
/examples/CheerLights/CheerLights.ino:
--------------------------------------------------------------------------------
1 | /*
2 | CheerLights
3 |
4 | Reads the latest CheerLights color on ThingSpeak, and sets a common anode RGB LED on digital pins 5, 6, and 9.
5 | On Spark core, the built in RGB LED is used
6 | Visit http://www.cheerlights.com for more info.
7 |
8 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
9 |
10 | Copyright 2017, The MathWorks, Inc.
11 |
12 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
13 | See the accompanying license file for licensing information.
14 | */
15 |
16 | #include "ThingSpeak.h"
17 |
18 | // Make sure that you put a 330 ohm resistor between the Particle
19 | // pins and each of the color pins on the LED.
20 | int pinRed = 9;
21 | int pinGreen = 6;
22 | int pinBlue = 5;
23 |
24 | TCPClient client;
25 |
26 |
27 | /*
28 | This is the ThingSpeak channel number for CheerLights
29 | https://thingspeak.com/channels/1417. Field 1 contains a string with
30 | the latest CheerLights color.
31 | */
32 | unsigned long cheerLightsChannelNumber = 1417;
33 |
34 | void setup() {
35 | ThingSpeak.begin(client);
36 | }
37 |
38 | void loop() {
39 | // Read the latest value from field 1 of channel 1417
40 | String color = ThingSpeak.readStringField(cheerLightsChannelNumber, 1);
41 | setColor(color);
42 |
43 | // Check again in 5 seconds
44 | delay(5000);
45 | }
46 |
47 | // List of CheerLights color names
48 | String colorName[] = {"none","red","pink","green","blue","cyan","white","warmwhite","oldlace","purple","magenta","yellow","orange"};
49 |
50 | // Map of RGB values for each of the Cheerlight color names
51 | int colorRGB[][3] = { 0, 0, 0, // "none"
52 | 255, 0, 0, // "red"
53 | 255,192,203, // "pink"
54 | 0,255, 0, // "green"
55 | 0, 0,255, // "blue"
56 | 0,255,255, // "cyan",
57 | 255,255,255, // "white",
58 | 255,223,223, // "warmwhite",
59 | 255,223,223, // "oldlace",
60 | 128, 0,128, // "purple",
61 | 255, 0,255, // "magenta",
62 | 255,255, 0, // "yellow",
63 | 255,165, 0}; // "orange"};
64 |
65 | void setColor(String color)
66 | {
67 | // Look through the list of colors to find the one that was requested
68 | for(int iColor = 0; iColor <= 12; iColor++)
69 | {
70 | if(color == colorName[iColor])
71 | {
72 | // When it matches, look up the RGB values for that color in the table,
73 | // and write the red, green, and blue values.
74 |
75 | RGB.control(true);
76 | RGB.color(colorRGB[iColor][0], colorRGB[iColor][1], colorRGB[iColor][2]);
77 |
78 | return;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/examples/ReadLastTemperature/ReadLastTemperature.ino:
--------------------------------------------------------------------------------
1 | /*
2 | ReadLastTemperature
3 |
4 | Reads the latest temperature from the MathWorks weather station in Natick, MA
5 | https://thingspeak.com/channels/12397 on ThingSpeak, and prints to
6 | the serial port debug window every 30 seconds.
7 |
8 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
9 |
10 | Copyright 2017, The MathWorks, Inc.
11 |
12 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
13 | See the accompanying license file for licensing information.
14 | */
15 |
16 | #include "ThingSpeak.h"
17 |
18 | // On Particle Core, Photon, and Electron the results are published to the Particle dashboard using events.
19 | // Go to http://dashboard.particle.io, click on the logs tab, and you'll see the events coming in.
20 | TCPClient client;
21 |
22 | /*
23 | This is the ThingSpeak channel number for the MathwWorks weather station
24 | https://thingspeak.com/channels/12397. It senses a number of things, including
25 | Wind Direction, Wind Speed, Humidity, Temperature, Rainfall, and Atmospheric Pressure.
26 |
27 | Temperature is stored in field 4
28 | */
29 |
30 | unsigned long weatherStationChannelNumber = 12397;
31 | unsigned int temperatureFieldNumber = 4;
32 |
33 | void setup() {
34 | ThingSpeak.begin(client);
35 | }
36 |
37 | void loop() {
38 | // Read the latest value from field 4 of channel 12397
39 | float temperatureInF = ThingSpeak.readFloatField(weatherStationChannelNumber, temperatureFieldNumber);
40 |
41 | Particle.publish("thingspeak-lasttemp", String::format("Current temp %.1f degrees F",temperatureInF),60,PRIVATE);
42 | delay(30000); // Note that the weather station only updates once a minute
43 | }
44 |
--------------------------------------------------------------------------------
/examples/ReadPrivateChannel/ReadPrivateChannel.ino:
--------------------------------------------------------------------------------
1 | /*
2 | ReadPrivateChannel
3 |
4 | Reads the latest voltage value from a private channel on ThingSpeak every 30 seconds
5 | and prints to the serial port debug window.
6 |
7 | For an example of how to read from a public channel, see ReadChannel example
8 |
9 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
10 |
11 | Copyright 2017, The MathWorks, Inc.
12 |
13 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
14 | See the accompanying license file for licensing information.
15 | */
16 |
17 | #include "ThingSpeak.h"
18 |
19 | // Particle Core, Photon, and Electron the results are published to the Particle dashboard using events.
20 | // Go to http://dashboard.particle.io, click on the logs tab, and you'll see the events coming in.
21 | TCPClient client;
22 |
23 | /*
24 | *****************************************************************************************
25 | **** Visit https://www.thingspeak.com to sign up for a free account and create
26 | **** a channel. The video tutorial http://community.thingspeak.com/tutorials/thingspeak-channels/
27 | **** has more information. You need to change this to your channel, and your read API key
28 | **** IF YOU SHARE YOUR CODE WITH OTHERS, MAKE SURE YOU REMOVE YOUR READ API KEY!!
29 | *****************************************************************************************
30 |
31 | This is the ThingSpeak channel used in the write examples (31461). It is private, and requires a
32 | read API key to access it. We'll read from the first field.
33 | */
34 | unsigned long myChannelNumber = 31461;
35 | const char * myReadAPIKey = "NKX4Z5JGO4M5I18A";
36 |
37 | void setup() {
38 | ThingSpeak.begin(client);
39 | }
40 |
41 | void loop() {
42 | // Read the latest value from field 1 of channel 31461
43 | float voltage = ThingSpeak.readFloatField(myChannelNumber, 1, myReadAPIKey);
44 |
45 | Particle.publish("thingspeak-readvoltage", "Latest voltage is: " + String(voltage) + "V",60,PRIVATE);
46 | delay(30000);
47 | }
48 |
--------------------------------------------------------------------------------
/examples/ReadWeatherStation/ReadWeatherStation.ino:
--------------------------------------------------------------------------------
1 | /*
2 | ReadWeatherStation
3 |
4 | Reads the latest weather data every 60 seconds from the public MathWorks
5 | weather station in Natick, MA https://thingspeak.com/channels/12397 on ThingSpeak.
6 |
7 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
8 |
9 | Copyright 2017, The MathWorks, Inc.
10 |
11 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
12 | See the accompanying license file for licensing information.
13 | */
14 |
15 | #include "ThingSpeak.h"
16 |
17 | // On Particle Core, Photon, and Electron the results are published to the Particle dashboard using events.
18 | // Go to http://dashboard.particle.io, click on the logs tab, and you'll see the events coming in.
19 | TCPClient client;
20 |
21 | /*
22 | This is the ThingSpeak channel number for the MathwWorks weather station
23 | https://thingspeak.com/channels/12397. It senses a number of things and puts them in the eight
24 | field of the channel:
25 | Field 1 - Wind Direction (degrees where 0 is North)
26 | Field 2 - Wind Speed (MPH)
27 | Field 3 - Humidity (%RH)
28 | Field 4 - Temperature (Degrees F)
29 | Field 5 - Rainfall (inches since last measurement)
30 | Field 6 - Atmospheric Pressure (inHg)
31 | */
32 | unsigned long weatherStationChannelNumber = 12397;
33 |
34 | void setup() {
35 | ThingSpeak.begin(client);
36 | }
37 |
38 | void loop() {
39 | float windDirection = ThingSpeak.readFloatField(weatherStationChannelNumber,1);
40 | float windSpeed = ThingSpeak.readFloatField(weatherStationChannelNumber,2);
41 | float humidity = ThingSpeak.readFloatField(weatherStationChannelNumber,3);
42 | float temperature = ThingSpeak.readFloatField(weatherStationChannelNumber,4);
43 | float rainfall = ThingSpeak.readFloatField(weatherStationChannelNumber,5);
44 | float pressure = ThingSpeak.readFloatField(weatherStationChannelNumber,6);
45 |
46 | Particle.publish("thingspeak-weather", "Current weather conditions in Natick: ",60,PRIVATE);
47 | Particle.publish("thingspeak-weather", String(temperature) + " degrees F, " + String(humidity) + "% humidity",60,PRIVATE);
48 | Particle.publish("thingspeak-weather", "Wind at " + String(windSpeed) + " MPH at " + String (windDirection) + " degrees",60,PRIVATE);
49 | if(rainfall > 0)
50 | {
51 | Particle.publish("thingspeak-weather", "Pressure is " + String(pressure) + " inHg, and it's raining",60,PRIVATE);
52 | }
53 | else
54 | {
55 | Particle.publish("thingspeak-weather", "Pressure is " + String(pressure) + " inHg",60,PRIVATE);
56 | }
57 |
58 | delay(60000); // Note that the weather station only updates once a minute
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/examples/WriteMultipleVoltages/WriteMultipleVoltages.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WriteMultipleVoltages
3 |
4 | Reads analog voltages from pins A1-A6 and writes them to the 8 fields of a channel on ThingSpeak every 20 seconds.
5 |
6 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
7 |
8 | Copyright 2018, The MathWorks, Inc.
9 |
10 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
11 | See the accompanying license file for licensing information.
12 | */
13 |
14 | #include "ThingSpeak.h"
15 |
16 | TCPClient client;
17 |
18 | /*
19 | *****************************************************************************************
20 | **** Visit https://www.thingspeak.com to sign up for a free account and create
21 | **** a channel. The video tutorial http://community.thingspeak.com/tutorials/thingspeak-channels/
22 | **** has more information. You need to change this to your channel, and your write API key
23 | **** IF YOU SHARE YOUR CODE WITH OTHERS, MAKE SURE YOU REMOVE YOUR WRITE API KEY!!
24 | *****************************************************************************************/
25 | unsigned long myChannelNumber = 31461;
26 | const char * myWriteAPIKey = "LD79EOAAWRVYF04Y";
27 |
28 | void setup() {
29 | ThingSpeak.begin(client);
30 | }
31 |
32 | void loop() {
33 | // Read the input on each pin, convert the reading, and set each field to be sent to ThingSpeak.
34 | // On Particle: 0 - 4095 maps to 0 - 3.3 volts
35 | float pinVoltage = analogRead(A0) * (3.3 / 4095.0);
36 |
37 | ThingSpeak.setField(1,pinVoltage);
38 | pinVoltage = analogRead(A1) * (3.3 / 4095.0);
39 | ThingSpeak.setField(2,pinVoltage);
40 | pinVoltage = analogRead(A2) * (3.3 / 4095.0);
41 | ThingSpeak.setField(3,pinVoltage);
42 | pinVoltage = analogRead(A3) * (3.3 / 4095.0);
43 | ThingSpeak.setField(4,pinVoltage);
44 | pinVoltage = analogRead(A4) * (3.3 / 4095.0);
45 | ThingSpeak.setField(5,pinVoltage);
46 | pinVoltage = analogRead(A5) * (3.3 / 4095.0);
47 | ThingSpeak.setField(6,pinVoltage);
48 | pinVoltage = analogRead(A6) * (3.3 / 4095.0);
49 | ThingSpeak.setField(7,pinVoltage);
50 |
51 | // Write the fields that you've set all at once.
52 | ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
53 |
54 | delay(20000); // ThingSpeak will only accept updates every 15 seconds.
55 | }
56 |
--------------------------------------------------------------------------------
/examples/WriteVoltage/WriteVoltage.ino:
--------------------------------------------------------------------------------
1 | /*
2 | WriteVoltage
3 |
4 | Reads an analog voltage from pin 0, and writes it to a channel on ThingSpeak every 20 seconds.
5 |
6 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
7 |
8 | Copyright 2017, The MathWorks, Inc.
9 |
10 | Documentation for the ThingSpeak Communication Library for Particle is in the README.md file where the library was installed.
11 | See the accompanying license file for licensing information.
12 | */
13 |
14 | #include "ThingSpeak.h"
15 |
16 | TCPClient client;
17 |
18 | /*
19 | *****************************************************************************************
20 | **** Visit https://www.thingspeak.com to sign up for a free account and create
21 | **** a channel. The video tutorial http://community.thingspeak.com/tutorials/thingspeak-channels/
22 | **** has more information. You need to change this to your channel, and your write API key
23 | **** IF YOU SHARE YOUR CODE WITH OTHERS, MAKE SURE YOU REMOVE YOUR WRITE API KEY!!
24 | *****************************************************************************************/
25 | unsigned long myChannelNumber = 31461;
26 | const char * myWriteAPIKey = "LD79EOAAWRVYF04Y";
27 |
28 | void setup() {
29 | ThingSpeak.begin(client);
30 | }
31 |
32 | void loop() {
33 | // read the input on analog pin 0:
34 | int sensorValue = analogRead(A0);
35 | // Convert the analog reading
36 | // On Particle: 0 - 4095 maps to 0 - 3.3 volts
37 | float voltage = sensorValue * (3.3 / 4095.0);
38 |
39 | // Write to ThingSpeak. There are up to 8 fields in a channel, allowing you to store up to 8 different
40 | // pieces of information in a channel. Here, we write to field 1.
41 | ThingSpeak.writeField(myChannelNumber, 1, voltage, myWriteAPIKey);
42 | delay(20000); // ThingSpeak will only accept updates every 15 seconds.
43 | }
44 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=ThingSpeak
2 | version=1.6.0
3 | author=MathWorks, Inc
4 | license=See associated license file
5 | sentence=ThingSpeak Communication Library for Particle-based devices written by the ThingSpeak development team.
6 | url=https://thingspeak.com
7 | repository=https://github.com/mathworks/thingspeak-particle.git
8 | architectures=*
9 |
--------------------------------------------------------------------------------
/src/ThingSpeak.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ThingSpeak Communication Library For Particle
3 |
4 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and
5 | analyze live data streams in the cloud.
6 |
7 | Copyright 2017-2025, The MathWorks, Inc.
8 |
9 | See the accompanying license file for licensing information.
10 | */
11 |
12 | #include "ThingSpeak.h"
13 | ThingSpeakClass ThingSpeak;
14 |
--------------------------------------------------------------------------------
/src/ThingSpeak.h:
--------------------------------------------------------------------------------
1 | /*
2 | ThingSpeak(TM) Communication Library For Particle
3 |
4 | Enables Particle hardware to write or read data to or from ThingSpeak,
5 | an open data platform for the Internet of Things with MATLAB analytics and visualization.
6 |
7 | ThingSpeak ( https://www.thingspeak.com ) is an analytic IoT platform service that allows you to aggregate, visualize and analyze live data streams in the cloud.
8 |
9 | Copyright 2020-2025, The MathWorks, Inc.
10 |
11 | See the accompanying license file for licensing information.
12 | */
13 |
14 | //#define PRINT_DEBUG_MESSAGES
15 | //#define PRINT_HTTP
16 |
17 | #ifndef ThingSpeak_h
18 | #define ThingSpeak_h
19 |
20 | #define TS_VER "1.6.0"
21 |
22 |
23 | // Create platform defines for Particle devices
24 | #if PLATFORM_ID == 0
25 | #define PARTICLE_CORE
26 | #elif PLATFORM_ID == 6
27 | #define PARTICLE_PHOTON
28 | #define PARTICLE_PHOTONELECTRON
29 | #elif PLATFORM_ID == 8
30 | #define PARTICLE_P1
31 | #define PARTICLE_PHOTONELECTRON
32 | #elif PLATFORM_ID == 10
33 | #define PARTICLE_ELECTRON
34 | #define PARTICLE_PHOTONELECTRON
35 | #elif PLATFORM_ID == 12
36 | #define PARTICLE_ARGON
37 | #define PARTICLE_PHOTONELECTRON
38 | #elif PLATFORM_ID == 13
39 | #define PARTICLE_BORON
40 | #define PARTICLE_PHOTONELECTRON
41 | #elif PLATFORM_ID == 14
42 | #error TCP connection are not supported on mesh nodes (Xenon), only mesh gateways (Argon, Boron)
43 | #else
44 | #error Only Core/Photon/Electron/P1/Argon/Boron are supported.
45 | #endif
46 |
47 |
48 | #include "math.h"
49 | #include "application.h"
50 | #ifdef PARTICLE_PHOTONELECTRON
51 | extern char* dtoa(double val, unsigned char prec, char *sout);
52 | // On spark photon, There is no itoa, so map to ltoa.
53 | #include "string_convert.h"
54 | #define itoa ltoa
55 | #else
56 | // On spark core, a long and an int are equivalent, and so there's no "ltoa" function defined. Map it to itoa.
57 | extern char * itoa(int a, char* buffer, unsigned char radix);
58 | #define ltoa itoa
59 | extern char *dtostrf (double val, signed char width, unsigned char prec, char *sout);
60 | #endif
61 |
62 |
63 | #define THINGSPEAK_URL "api.thingspeak.com"
64 | #define THINGSPEAK_PORT_NUMBER 80
65 |
66 |
67 | #ifdef PARTICLE_CORE
68 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle core)"
69 | #elif defined(PARTICLE_PHOTON)
70 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle photon)"
71 | #elif defined(PARTICLE_ELECTRON)
72 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle electron)"
73 | #elif defined(PARTICLE_P1)
74 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle p1)"
75 | #elif defined(PARTICLE_ARGON)
76 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle argon)"
77 | #elif defined(PARTICLE_BORON)
78 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle boron)"
79 | #else
80 | #define TS_USER_AGENT "tslib-arduino/" TS_VER " (particle unknown)"
81 | #endif
82 | #define SPARK_PUBLISH_TTL 60 // Spark "time to live" for published messages
83 | #define SPARK_PUBLISH_TOPIC "thingspeak-debug"
84 |
85 |
86 | #define FIELDNUM_MIN 1
87 | #define FIELDNUM_MAX 8
88 | #define FIELDLENGTH_MAX 255 // Max length for a field in ThingSpeak is 255 bytes (UTF-8)
89 |
90 | #define TIMEOUT_MS_SERVERRESPONSE 5000 // Wait up to five seconds for server to respond
91 |
92 | #define TS_OK_SUCCESS 200 // OK / Success
93 | #define TS_ERR_BADAPIKEY 400 // Incorrect API key (or invalid ThingSpeak server address)
94 | #define TS_ERR_BADURL 404 // Incorrect API key (or invalid ThingSpeak server address)
95 | #define TS_ERR_OUT_OF_RANGE -101 // Value is out of range or string is too long (> 255 bytes)
96 | #define TS_ERR_INVALID_FIELD_NUM -201 // Invalid field number specified
97 | #define TS_ERR_SETFIELD_NOT_CALLED -210 // setField() was not called before writeFields()
98 | #define TS_ERR_CONNECT_FAILED -301 // Failed to connect to ThingSpeak
99 | #define TS_ERR_UNEXPECTED_FAIL -302 // Unexpected failure during write to ThingSpeak
100 | #define TS_ERR_BAD_RESPONSE -303 // Unable to parse response
101 | #define TS_ERR_TIMEOUT -304 // Timeout waiting for server to respond
102 | #define TS_ERR_NOT_INSERTED -401 // Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
103 |
104 |
105 | // variables to store the values from the readMultipleFields functionality
106 | typedef struct feedRecord
107 | {
108 | String nextReadField[8];
109 | String nextReadStatus;
110 | String nextReadLatitude;
111 | String nextReadLongitude;
112 | String nextReadElevation;
113 | String nextReadCreatedAt;
114 | }feed;
115 |
116 |
117 | // Enables Particle hardware to write or read data to or from ThingSpeak, an open data platform for the Internet of Things with MATLAB analytics and visualization.
118 | class ThingSpeakClass
119 | {
120 | public:
121 | ThingSpeakClass()
122 | {
123 | resetWriteFields();
124 | this->lastReadStatus = TS_OK_SUCCESS;
125 | };
126 |
127 | /*
128 | Function: begin
129 |
130 | Summary:
131 | Initializes the ThingSpeak library and network settings using the ThingSpeak.com service.
132 |
133 | Parameters:
134 | client - TCPClient created earlier in the sketch
135 |
136 | Returns:
137 | Always returns true
138 |
139 | Notes:
140 | This does not validate the information passed in, or generate any calls to ThingSpeak.
141 | */
142 | bool begin(Client & client)
143 | {
144 | #ifdef PRINT_DEBUG_MESSAGES
145 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::tsBegin", SPARK_PUBLISH_TTL, PRIVATE);
146 | #endif
147 | this->setClient(&client);
148 | this->setPort(THINGSPEAK_PORT_NUMBER);
149 | resetWriteFields();
150 | this->lastReadStatus = TS_OK_SUCCESS;
151 | return true;
152 | }
153 |
154 |
155 | /*
156 | Function: writeField
157 |
158 | Summary:
159 | Write an integer value to a single field in a ThingSpeak channel
160 |
161 | Parameters:
162 | channelNumber - Channel number
163 | field - Field number (1-8) within the channel to write to.
164 | value - Integer value (from -32,768 to 32,767) to write.
165 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
166 |
167 | Returns:
168 | HTTP status code of 200 if successful.
169 |
170 | Notes:
171 | See getLastReadStatus() for other possible return values.
172 | */
173 | int writeField(unsigned long channelNumber, unsigned int field, int value, const char * writeAPIKey)
174 | {
175 | // On Spark, int and long are the same, so map to the long version
176 | return writeField(channelNumber, field, (long)value, writeAPIKey);
177 | }
178 |
179 |
180 | /*
181 | Function: writeField
182 |
183 | Summary:
184 | Write a long value to a single field in a ThingSpeak channel
185 |
186 | Parameters:
187 | channelNumber - Channel number
188 | field - Field number (1-8) within the channel to write to.
189 | value - Long value (from -2,147,483,648 to 2,147,483,647) to write.
190 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
191 |
192 | Returns:
193 | HTTP status code of 200 if successful.
194 |
195 | Notes:
196 | See getLastReadStatus() for other possible return values.
197 | */
198 | int writeField(unsigned long channelNumber, unsigned int field, long value, const char * writeAPIKey)
199 | {
200 | char valueString[15]; // long range is -2147483648 to 2147483647, so 12 bytes including terminator
201 | ltoa(value, valueString, 10);
202 | return writeField(channelNumber, field, valueString, writeAPIKey);
203 | }
204 |
205 |
206 | /*
207 | Function: writeField
208 |
209 | Summary:
210 | Write a floating point value to a single field in a ThingSpeak channel
211 |
212 | Parameters:
213 | channelNumber - Channel number
214 | field - Field number (1-8) within the channel to write to.
215 | value - Floating point value (from -999999000000 to 999999000000) to write. If you need more accuracy, or a wider range, you should format the number using dtostrf and writeField().
216 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
217 |
218 | Returns:
219 | HTTP status code of 200 if successful.
220 |
221 | Notes:
222 | See getLastReadStatus() for other possible return values.
223 | */
224 | int writeField(unsigned long channelNumber, unsigned int field, float value, const char * writeAPIKey)
225 | {
226 | #ifdef PRINT_DEBUG_MESSAGES
227 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::writeField (channelNumber: " + String(channelNumber) + " writeAPIKey: " + String(writeAPIKey) + " field: " + String(field) + " value: " + String(value,5) + ")" , SPARK_PUBLISH_TTL, PRIVATE);
228 | #endif
229 | char valueString[20]; // range is -999999000000.00000 to 999999000000.00000, so 19 + 1 for the terminator
230 | int status = convertFloatToChar(value, valueString);
231 | if(status != TS_OK_SUCCESS) return status;
232 |
233 | return writeField(channelNumber, field, valueString, writeAPIKey);
234 | }
235 |
236 |
237 | /*
238 | Function: writeField
239 |
240 | Summary:
241 | Write a string to a single field in a ThingSpeak channel
242 |
243 | Parameters:
244 | channelNumber - Channel number
245 | field - Field number (1-8) within the channel to write to.
246 | value - String to write (UTF8 string). ThingSpeak limits this field to 255 bytes.
247 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
248 |
249 | Returns:
250 | HTTP status code of 200 if successful.
251 |
252 | Notes:
253 | See getLastReadStatus() for other possible return values.
254 | */
255 | int writeField(unsigned long channelNumber, unsigned int field, String value, const char * writeAPIKey)
256 | {
257 | // Invalid field number specified
258 | if(field < FIELDNUM_MIN || field > FIELDNUM_MAX) return TS_ERR_INVALID_FIELD_NUM;
259 | // Max # bytes for ThingSpeak field is 255
260 | if(value.length() > FIELDLENGTH_MAX) return TS_ERR_OUT_OF_RANGE;
261 |
262 | #ifdef PRINT_DEBUG_MESSAGES
263 | Particle.publish(SPARK_PUBLISH_TOPIC, "writeField (" + String(channelNumber) + ", " + String(writeAPIKey) + ", " + String(field) + ", " + escapeUrl(value) + ")", SPARK_PUBLISH_TTL, PRIVATE);
264 | #endif
265 | String postMessage = String("field") + String(field) + "=" + escapeUrl(value);
266 | return writeRaw(channelNumber, postMessage, writeAPIKey);
267 | }
268 |
269 |
270 | /*
271 | Function: setField
272 |
273 | Summary:
274 | Set the value of a single field that will be part of a multi-field update.
275 |
276 | Parameters:
277 | field - Field number (1-8) within the channel to set.
278 | value - Integer value (from -32,768 to 32,767) to set.
279 |
280 | Returns:
281 | Code of 200 if successful.
282 | Code of -101 if value is out of range or string is too long (> 255 bytes)
283 | */
284 | int setField(unsigned int field, int value)
285 | {
286 | // On Spark, int and long are the same, so map to the long version
287 | return setField(field, (long)value);
288 | }
289 |
290 |
291 | /*
292 | Function: setField
293 |
294 | Summary:
295 | Set the value of a single field that will be part of a multi-field update.
296 |
297 | Parameters:
298 | field - Field number (1-8) within the channel to set.
299 | value - Long value (from -2,147,483,648 to 2,147,483,647) to write.
300 |
301 | Returns:
302 | Code of 200 if successful.
303 | Code of -101 if value is out of range or string is too long (> 255 bytes)
304 | */
305 | int setField(unsigned int field, long value)
306 | {
307 | char valueString[15]; // long range is -2147483648 to 2147483647, so 12 bytes including terminator
308 | ltoa(value, valueString, 10);
309 | return setField(field, valueString);
310 | }
311 |
312 |
313 | /*
314 | Function: setField
315 |
316 | Summary:
317 | Set the value of a single field that will be part of a multi-field update.
318 |
319 | Parameters:
320 | field - Field number (1-8) within the channel to set.
321 | value - Floating point value (from -999999000000 to 999999000000) to write. If you need more accuracy, or a wider range, you should format the number yourself (using dtostrf) and setField() using the resulting string.
322 |
323 | Returns:
324 | Code of 200 if successful.
325 | Code of -101 if value is out of range or string is too long (> 255 bytes)
326 | */
327 | int setField(unsigned int field, float value)
328 | {
329 | char valueString[20]; // range is -999999000000.00000 to 999999000000.00000, so 19 + 1 for the terminator
330 | int status = convertFloatToChar(value, valueString);
331 | if(status != TS_OK_SUCCESS) return status;
332 |
333 | return setField(field, valueString);
334 | }
335 |
336 |
337 | /*
338 | Function: setField
339 |
340 | Summary:
341 | Set the value of a single field that will be part of a multi-field update.
342 |
343 | Parameters:
344 | field - Field number (1-8) within the channel to set.
345 | value - String to write (UTF8). ThingSpeak limits this to 255 bytes.
346 |
347 | Returns:
348 | Code of 200 if successful.
349 | Code of -101 if value is out of range or string is too long (> 255 bytes)
350 | */
351 | int setField(unsigned int field, String value)
352 | {
353 | #ifdef PRINT_DEBUG_MESSAGES
354 | Particle.publish(SPARK_PUBLISH_TOPIC, "setField " + String(field) + " to " + String(value), SPARK_PUBLISH_TTL, PRIVATE);
355 | #endif
356 | if(field < FIELDNUM_MIN || field > FIELDNUM_MAX) return TS_ERR_INVALID_FIELD_NUM;
357 | // Max # bytes for ThingSpeak field is 255 (UTF-8)
358 | if(value.length() > FIELDLENGTH_MAX) return TS_ERR_OUT_OF_RANGE;
359 | this->nextWriteField[field - 1] = value;
360 | return TS_OK_SUCCESS;
361 | }
362 |
363 |
364 | /*
365 | Function: setLatitude
366 |
367 | Summary:
368 | Set the latitude of a multi-field update.
369 |
370 | Parameters:
371 | latitude - Latitude of the measurement as a floating point value (degrees N, use negative values for degrees S)
372 |
373 | Returns:
374 | Always return 200
375 |
376 | Notes:
377 | To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
378 | */
379 | int setLatitude(float latitude)
380 | {
381 | #ifdef PRINT_DEBUG_MESSAGES
382 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::setLatitude(latitude: " + String(latitude,3) + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
383 | #endif
384 | this->nextWriteLatitude = latitude;
385 | return TS_OK_SUCCESS;
386 | }
387 |
388 |
389 | /*
390 | Function: setLongitude
391 |
392 | Summary:
393 | Set the longitude of a multi-field update.
394 |
395 | Parameters:
396 | longitude - Longitude of the measurement as a floating point value (degrees E, use negative values for degrees W)
397 |
398 | Returns:
399 | Always return 200
400 |
401 | Notes:
402 | To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
403 | */
404 | int setLongitude(float longitude)
405 | {
406 | #ifdef PRINT_DEBUG_MESSAGES
407 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::setLongitude(longitude: " + String(longitude,3) + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
408 | #endif
409 | this->nextWriteLongitude = longitude;
410 | return TS_OK_SUCCESS;
411 | }
412 |
413 |
414 | /*
415 | Function: setElevation
416 |
417 | Summary:
418 | Set the elevation of a multi-field update.
419 |
420 | Parameters:
421 | elevation - Elevation of the measurement as a floating point value (meters above sea level)
422 |
423 | Returns:
424 | Always return 200
425 |
426 | Notes:
427 | To record latitude, longitude and elevation of a write, call setField() for each of the fields you want to write. Then setLatitude(), setLongitude(), setElevation() and then call writeFields()
428 | */
429 | int setElevation(float elevation)
430 | {
431 | #ifdef PRINT_DEBUG_MESSAGES
432 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::setElevation(elevation: " + String(elevation,3) + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
433 | #endif
434 | this->nextWriteElevation = elevation;
435 | return TS_OK_SUCCESS;
436 | }
437 |
438 |
439 | /*
440 | Function: setStatus
441 |
442 | Summary:
443 | Set the status field of a multi-field update.
444 |
445 | Parameters:
446 | status - String to write (UTF8). ThingSpeak limits this to 255 bytes.
447 |
448 | Returns:
449 | Code of 200 if successful.
450 | Code of -101 if string is too long (> 255 bytes)
451 |
452 | Notes:
453 | To record a status message on a write, call setStatus() then call writeFields().
454 | Use status to provide additonal details when writing a channel update.
455 | */
456 | int setStatus(String status)
457 | {
458 | #ifdef PRINT_DEBUG_MESSAGES
459 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::setStatus(status: " + status + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
460 | #endif
461 | // Max # bytes for ThingSpeak field is 255 (UTF-8)
462 | if(status.length() > FIELDLENGTH_MAX) return TS_ERR_OUT_OF_RANGE;
463 | this->nextWriteStatus = status;
464 | return TS_OK_SUCCESS;
465 | }
466 |
467 |
468 | /*
469 | Function: setCreatedAt
470 |
471 | Summary:
472 | Set the created-at date of a multi-field update.
473 |
474 | Parameters:
475 | createdAt - Desired timestamp to be included with the channel update as a String. The timestamp string must be in the ISO 8601 format. Example "2017-01-12 13:22:54"
476 |
477 | Returns:
478 | Code of 200 if successful.
479 | Code of -101 if string is too long (> 255 bytes)
480 |
481 | Notes:
482 | Timezones can be set using the timezone hour offset parameter. For example, a timestamp for Eastern Standard Time is: "2017-01-12 13:22:54-05".
483 | If no timezone hour offset parameter is used, UTC time is assumed.
484 | */
485 | int setCreatedAt(String createdAt)
486 | {
487 | #ifdef PRINT_DEBUG_MESSAGES
488 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::setCreatedAt(createdAt: " + createdAt + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
489 | #endif
490 |
491 | // the ISO 8601 format is too complicated to check for valid timestamps here
492 | // we'll need to reply on the api to tell us if there is a problem
493 | // Max # bytes for ThingSpeak field is 255 (UTF-8)
494 | if(createdAt.length() > FIELDLENGTH_MAX) return TS_ERR_OUT_OF_RANGE;
495 | this->nextWriteCreatedAt = createdAt;
496 |
497 | return TS_OK_SUCCESS;
498 | }
499 |
500 |
501 | /*
502 | Function: writeFields
503 |
504 | Summary:
505 | Write a multi-field update.
506 |
507 | Parameters:
508 | channelNumber - Channel number
509 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
510 |
511 | Returns:
512 | 200 - successful.
513 | 404 - Incorrect API key (or invalid ThingSpeak server address)
514 | -101 - Value is out of range or string is too long (> 255 characters)
515 | -201 - Invalid field number specified
516 | -210 - setField() was not called before writeFields()
517 | -301 - Failed to connect to ThingSpeak
518 | -302 - Unexpected failure during write to ThingSpeak
519 | -303 - Unable to parse response
520 | -304 - Timeout waiting for server to respond
521 | -401 - Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
522 |
523 |
524 | Notes:
525 | Call setField(), setLatitude(), setLongitude(), setElevation() and/or setStatus() and then call writeFields()
526 | */
527 | int writeFields(unsigned long channelNumber, const char * writeAPIKey)
528 | {
529 | String postMessage = String("");
530 | bool fFirstItem = true;
531 | for(size_t iField = 0; iField < 8; iField++)
532 | {
533 | if(this->nextWriteField[iField].length() > 0)
534 | {
535 | if(!fFirstItem)
536 | {
537 | postMessage = postMessage + String("&");
538 | }
539 | postMessage = postMessage + String("field") + String(iField + 1) + String("=") + escapeUrl(this->nextWriteField[iField]);
540 | fFirstItem = false;
541 | this->nextWriteField[iField] = "";
542 | }
543 | }
544 |
545 | if(!isnan(nextWriteLatitude))
546 | {
547 | if(!fFirstItem)
548 | {
549 | postMessage = postMessage + String("&");
550 | }
551 | postMessage = postMessage + String("lat=") + String(this->nextWriteLatitude);
552 | fFirstItem = false;
553 | this->nextWriteLatitude = NAN;
554 | }
555 |
556 | if(!isnan(this->nextWriteLongitude))
557 | {
558 | if(!fFirstItem)
559 | {
560 | postMessage = postMessage + String("&");
561 | }
562 | postMessage = postMessage + String("long=") + String(this->nextWriteLongitude);
563 | fFirstItem = false;
564 | this->nextWriteLongitude = NAN;
565 | }
566 |
567 |
568 | if(!isnan(this->nextWriteElevation))
569 | {
570 | if(!fFirstItem)
571 | {
572 | postMessage = postMessage + String("&");
573 | }
574 | postMessage = postMessage + String("elevation=") + String(this->nextWriteElevation);
575 | fFirstItem = false;
576 | this->nextWriteElevation = NAN;
577 | }
578 |
579 | if(this->nextWriteStatus.length() > 0)
580 | {
581 | if(!fFirstItem)
582 | {
583 | postMessage = postMessage + String("&");
584 | }
585 | postMessage = postMessage + String("status=") + escapeUrl(this->nextWriteStatus);
586 | fFirstItem = false;
587 | this->nextWriteStatus = "";
588 | }
589 |
590 | if(this->nextWriteCreatedAt.length() > 0)
591 | {
592 | if(!fFirstItem)
593 | {
594 | postMessage = postMessage + String("&");
595 | }
596 | postMessage = postMessage + String("created_at=") + String(this->nextWriteCreatedAt);
597 | fFirstItem = false;
598 | this->nextWriteCreatedAt = "";
599 | }
600 |
601 |
602 | if(fFirstItem)
603 | {
604 | // setField was not called before writeFields
605 | return TS_ERR_SETFIELD_NOT_CALLED;
606 | }
607 |
608 | return writeRaw(channelNumber, postMessage, writeAPIKey);
609 | }
610 |
611 |
612 | /*
613 | Function: writeRaw
614 |
615 | Summary:
616 | Write a raw POST to a ThingSpeak channel
617 |
618 | Parameters:
619 | channelNumber - Channel number
620 | postMessage - Raw URL to write to ThingSpeak as a string. See the documentation at https://thingspeak.com/docs/channels#update_feed.
621 | writeAPIKey - Write API key associated with the channel. *If you share code with others, do _not_ share this key*
622 |
623 | Returns:
624 | 200 - successful.
625 | 404 - Incorrect API key (or invalid ThingSpeak server address)
626 | -101 - Value is out of range or string is too long (> 255 characters)
627 | -201 - Invalid field number specified
628 | -210 - setField() was not called before writeFields()
629 | -301 - Failed to connect to ThingSpeak
630 | -302 - Unexpected failure during write to ThingSpeak
631 | -303 - Unable to parse response
632 | -304 - Timeout waiting for server to respond
633 | -401 - Point was not inserted (most probable cause is the rate limit of once every 15 seconds)
634 |
635 | Notes:
636 | This is low level functionality that will not be required by most users.
637 | */
638 | int writeRaw(unsigned long channelNumber, String postMessage, const char * writeAPIKey)
639 | {
640 | #ifdef PRINT_DEBUG_MESSAGES
641 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::writeRaw (channelNumber: " + String(channelNumber) + " writeAPIKey: " + String(writeAPIKey) + " postMessage: \"" + postMessage + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
642 | #endif
643 |
644 | if(!connectThingSpeak())
645 | {
646 | // Failed to connect to ThingSpeak
647 | return TS_ERR_CONNECT_FAILED;
648 | }
649 |
650 | postMessage = postMessage + String("&headers=false");
651 |
652 | #ifdef PRINT_DEBUG_MESSAGES
653 | Particle.publish(SPARK_PUBLISH_TOPIC, "Post " + postMessage, SPARK_PUBLISH_TTL, PRIVATE);
654 | #endif
655 |
656 |
657 | // Post data to thingspeak
658 | if(!this->client->print("POST /update HTTP/1.1\r\n")) return abortWriteRaw();
659 | if(!writeHTTPHeader(writeAPIKey)) return abortWriteRaw();
660 | if(!this->client->print("Content-Type: application/x-www-form-urlencoded\r\n")) return abortWriteRaw();
661 | if(!this->client->print("Content-Length: ")) return abortWriteRaw();
662 | if(!this->client->print(postMessage.length())) return abortWriteRaw();
663 | if(!this->client->print("\r\n\r\n")) return abortWriteRaw();
664 | if(!this->client->print(postMessage)) return abortWriteRaw();
665 |
666 | String entryIDText = String();
667 | int status = getHTTPResponse(entryIDText);
668 | if(status != TS_OK_SUCCESS)
669 | {
670 | client->stop();
671 | return status;
672 | }
673 | long entryID = entryIDText.toInt();
674 |
675 | #ifdef PRINT_DEBUG_MESSAGES
676 | Particle.publish(SPARK_PUBLISH_TOPIC, " Entry ID \"" + entryIDText + "\" (" + String(entryID) + ")" , SPARK_PUBLISH_TTL, PRIVATE);
677 | #endif
678 |
679 | client->stop();
680 |
681 | #ifdef PRINT_DEBUG_MESSAGES
682 | Particle.publish(SPARK_PUBLISH_TOPIC, "disconnected.", SPARK_PUBLISH_TTL, PRIVATE);
683 | #endif
684 | if(entryID == 0)
685 | {
686 | // ThingSpeak did not accept the write
687 | status = TS_ERR_NOT_INSERTED;
688 | }
689 | return status;
690 | }
691 |
692 |
693 | /*
694 | Function: readStringField
695 |
696 | Summary:
697 | Read the latest string from a private ThingSpeak channel
698 |
699 | Parameters:
700 | channelNumber - Channel number
701 | field - Field number (1-8) within the channel to read from.
702 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
703 |
704 | Returns:
705 | Value read (UTF8 string), or empty string if there is an error. Use getLastReadStatus() to get more specific information.
706 | */
707 | String readStringField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
708 | {
709 | if(field < FIELDNUM_MIN || field > FIELDNUM_MAX)
710 | {
711 | this->lastReadStatus = TS_ERR_INVALID_FIELD_NUM;
712 | return("");
713 | }
714 | #ifdef PRINT_DEBUG_MESSAGES
715 |
716 | if(NULL != readAPIKey)
717 | {
718 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::readStringField(channelNumber: " + String(channelNumber) + " readAPIKey: " + String(readAPIKey) + " field: " + String(field) +")", SPARK_PUBLISH_TTL, PRIVATE);
719 | }
720 | else{
721 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::readStringField(channelNumber: " + String(channelNumber) + " field: " + String(field) + ")", SPARK_PUBLISH_TTL, PRIVATE);
722 | }
723 | #endif
724 | return readRaw(channelNumber, String(String("/fields/") + String(field) + String("/last")), readAPIKey);
725 | }
726 |
727 |
728 | /*
729 | Function: readStringField
730 |
731 | Summary:
732 | Read the latest string from a private ThingSpeak channel
733 |
734 | Parameters:
735 | channelNumber - Channel number
736 | field - Field number (1-8) within the channel to read from.
737 |
738 | Returns:
739 | Value read (UTF8 string), or empty string if there is an error. Use getLastReadStatus() to get more specific information.
740 | */
741 | String readStringField(unsigned long channelNumber, unsigned int field)
742 | {
743 | return readStringField(channelNumber, field, NULL);
744 | }
745 |
746 |
747 | /*
748 | Function: readFloatField
749 |
750 | Summary:
751 | ead the latest floating point value from a private ThingSpeak channel
752 |
753 | Parameters:
754 | channelNumber - Channel number
755 | field - Field number (1-8) within the channel to read from.
756 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
757 |
758 | Returns:
759 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
760 | */
761 | float readFloatField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
762 | {
763 | return convertStringToFloat(readStringField(channelNumber, field, readAPIKey));
764 | }
765 |
766 |
767 | /*
768 | Function: readFloatField
769 |
770 | Summary:
771 | Read the latest floating point value from a private ThingSpeak channel
772 |
773 | Parameters:
774 | channelNumber - Channel number
775 | field - Field number (1-8) within the channel to read from.
776 |
777 | Returns:
778 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
779 | */
780 | float readFloatField(unsigned long channelNumber, unsigned int field)
781 | {
782 | return readFloatField(channelNumber, field, NULL);
783 | }
784 |
785 |
786 | /*
787 | Function: readLongField
788 |
789 | Summary:
790 | Read the latest long value from a private ThingSpeak channel
791 |
792 | Parameters:
793 | channelNumber - Channel number
794 | field - Field number (1-8) within the channel to read from.
795 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
796 |
797 | Returns:
798 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
799 | */
800 | long readLongField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
801 | {
802 | // Note that although the function is called "toInt" it really returns a long.
803 | return readStringField(channelNumber, field, readAPIKey).toInt();
804 | }
805 |
806 |
807 | /*
808 | Function: readLongField
809 |
810 | Summary:
811 | Read the latest long value from a private ThingSpeak channel
812 |
813 | Parameters:
814 | channelNumber - Channel number
815 | field - Field number (1-8) within the channel to read from.
816 |
817 | Returns:
818 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
819 | */
820 | long readLongField(unsigned long channelNumber, unsigned int field)
821 | {
822 | return readLongField(channelNumber, field, NULL);
823 | }
824 |
825 |
826 | /*
827 | Function: readIntField
828 |
829 | Summary:
830 | Read the latest int value from a private ThingSpeak channel
831 |
832 | Parameters:
833 | channelNumber - Channel number
834 | field - Field number (1-8) within the channel to read from.
835 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
836 |
837 | Returns:
838 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
839 | */
840 | int readIntField(unsigned long channelNumber, unsigned int field, const char * readAPIKey)
841 | {
842 | return readLongField(channelNumber, field, readAPIKey);
843 | }
844 |
845 |
846 | /*
847 | Function: readIntField
848 |
849 | Summary:
850 | Read the latest int value from a private ThingSpeak channel
851 |
852 | Parameters:
853 | channelNumber - Channel number
854 | field - Field number (1-8) within the channel to read from.
855 |
856 | Returns:
857 | Value read, or 0 if the field is text or there is an error. Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
858 | */
859 | int readIntField(unsigned long channelNumber, unsigned int field)
860 | {
861 | return readLongField(channelNumber, field, NULL);
862 | }
863 |
864 |
865 | /*
866 | Function: readStatus
867 |
868 | Summary:
869 | Read the latest status from a private ThingSpeak channel
870 |
871 | Parameters:
872 | channelNumber - Channel number
873 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
874 |
875 | Results:
876 | Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
877 | */
878 | String readStatus(unsigned long channelNumber, const char * readAPIKey)
879 | {
880 | String content = readRaw(channelNumber, "/feeds/last.txt?status=true", readAPIKey);
881 |
882 | if(getLastReadStatus() != TS_OK_SUCCESS){
883 | return String("");
884 | }
885 |
886 | return getJSONValueByKey(content, "status");
887 | }
888 |
889 |
890 | /*
891 | Function: readStatus
892 |
893 | Summary:
894 | Read the latest status from a private ThingSpeak channel
895 |
896 | Parameters:
897 | channelNumber - Channel number
898 |
899 | Results:
900 | Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
901 | */
902 | String readStatus(unsigned long channelNumber)
903 | {
904 | return readStatus(channelNumber, NULL);
905 | }
906 |
907 |
908 | /*
909 | Function: readCreatedAt
910 |
911 | Summary:
912 | Read the created-at timestamp associated with the latest update to a private ThingSpeak channel
913 |
914 | Parameters:
915 | channelNumber - Channel number
916 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
917 |
918 | Results:
919 | Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
920 | */
921 | String readCreatedAt(unsigned long channelNumber, const char * readAPIKey)
922 | {
923 | String content = readRaw(channelNumber, "/feeds/last.txt", readAPIKey);
924 |
925 | if(getLastReadStatus() != TS_OK_SUCCESS){
926 | return String("");
927 | }
928 |
929 | return getJSONValueByKey(content, "created_at");
930 | }
931 |
932 |
933 | /*
934 | Function: readCreatedAt
935 |
936 | Summary:
937 | Read the created-at timestamp associated with the latest update to a private ThingSpeak channel
938 |
939 | Parameters:
940 | channelNumber - Channel number
941 |
942 | Results:
943 | Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
944 | */
945 | String readCreatedAt(unsigned long channelNumber)
946 | {
947 | return readCreatedAt(channelNumber, NULL);
948 | }
949 |
950 |
951 | /*
952 | Function: readRaw
953 |
954 | Summary:
955 | Read a raw response from a public ThingSpeak channel
956 |
957 | Parameters:
958 | channelNumber - Channel number
959 | URLSuffix - Raw URL to write to ThingSpeak as a String. See the documentation at https://thingspeak.com/docs/channels#get_feed
960 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
961 |
962 | Returns:
963 | Response if successful, or empty string. Use getLastReadStatus() to get more specific information.
964 |
965 | Notes:
966 | This is low level functionality that will not be required by most users.
967 | */
968 | String readRaw(unsigned long channelNumber, String URLSuffix, const char * readAPIKey)
969 | {
970 | #ifdef PRINT_DEBUG_MESSAGES
971 | if(NULL != readAPIKey)
972 | {
973 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::readRaw (channelNumber: " + String(channelNumber) + " readAPIKey: " + String(readAPIKey) + " URLSuffix: \"" + URLSuffix + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
974 | }
975 | else
976 | {
977 | Particle.publish(SPARK_PUBLISH_TOPIC, "ts::readRaw (channelNumber: " + String(channelNumber) + " URLSuffix: \"" + URLSuffix + "\")" , SPARK_PUBLISH_TTL, PRIVATE);
978 | }
979 | #endif
980 |
981 | if(!connectThingSpeak())
982 | {
983 | this->lastReadStatus = TS_ERR_CONNECT_FAILED;
984 | return String("");
985 | }
986 |
987 | String URL = String("/channels/") + String(channelNumber) + URLSuffix;
988 |
989 | #ifdef PRINT_DEBUG_MESSAGES
990 | Particle.publish(SPARK_PUBLISH_TOPIC," GET \"" + URL + "\"" , SPARK_PUBLISH_TTL, PRIVATE);
991 | #endif
992 |
993 | // Post data to thingspeak
994 | if(!this->client->print("GET ")) return abortReadRaw();
995 | if(!this->client->print(URL)) return abortReadRaw();
996 | if(!this->client->print(" HTTP/1.1\r\n")) return abortReadRaw();
997 | if(!writeHTTPHeader(readAPIKey)) return abortReadRaw();
998 | if(!this->client->print("\r\n")) return abortReadRaw();
999 |
1000 | String content = String();
1001 | int status = getHTTPResponse(content);
1002 | this->lastReadStatus = status;
1003 |
1004 |
1005 | #ifdef PRINT_DEBUG_MESSAGES
1006 | if(status == TS_OK_SUCCESS)
1007 | {
1008 | Particle.publish(SPARK_PUBLISH_TOPIC, "Read: \"" + content + "\"" , SPARK_PUBLISH_TTL, PRIVATE);
1009 | }
1010 | #endif
1011 |
1012 | client->stop();
1013 | #ifdef PRINT_DEBUG_MESSAGES
1014 | Particle.publish(SPARK_PUBLISH_TOPIC, "disconnected." , SPARK_PUBLISH_TTL, PRIVATE);
1015 | #endif
1016 |
1017 | if(status != TS_OK_SUCCESS)
1018 | {
1019 | // return status;
1020 | return String("");
1021 | }
1022 |
1023 | // This is a workaround to a bug in the Spark implementation of String
1024 | return String("") + content;
1025 | }
1026 |
1027 |
1028 | /*
1029 | Function: readRaw
1030 |
1031 | Summary:
1032 | Read a raw response from a public ThingSpeak channel
1033 |
1034 | Parameters:
1035 | channelNumber - Channel number
1036 | URLSuffix - Raw URL to write to ThingSpeak as a String. See the documentation at https://thingspeak.com/docs/channels#get_feed
1037 |
1038 | Returns:
1039 | Response if successful, or empty string. Use getLastReadStatus() to get more specific information.
1040 |
1041 | Notes:
1042 | This is low level functionality that will not be required by most users.
1043 | */
1044 | String readRaw(unsigned long channelNumber, String URLSuffix)
1045 | {
1046 | return readRaw(channelNumber, URLSuffix, NULL);
1047 | }
1048 |
1049 |
1050 | /*
1051 | Function: readMultipleFields
1052 |
1053 | Summary:
1054 | Read all the field values, status message, location coordinates, and created-at timestamp associated with the latest feed to a private ThingSpeak channel and store the values locally in variables within a struct.
1055 |
1056 | Parameters:
1057 | channelNumber - Channel number
1058 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
1059 |
1060 | Returns:
1061 | HTTP status code of 200 if successful.
1062 |
1063 | Notes:
1064 | See getLastReadStatus() for other possible return values.
1065 | */
1066 | int readMultipleFields(unsigned long channelNumber, const char * readAPIKey)
1067 | {
1068 | String readCondition = "/feeds/last.txt?status=true&location=true";
1069 |
1070 | String multiContent = readRaw(channelNumber, readCondition, readAPIKey);
1071 |
1072 | if(getLastReadStatus() != TS_OK_SUCCESS){
1073 | return getLastReadStatus();
1074 | }
1075 |
1076 | this->lastFeed.nextReadField[0] = parseValues(multiContent, "field1");
1077 | this->lastFeed.nextReadField[1] = parseValues(multiContent, "field2");
1078 | this->lastFeed.nextReadField[2] = parseValues(multiContent, "field3");
1079 | this->lastFeed.nextReadField[3] = parseValues(multiContent, "field4");
1080 | this->lastFeed.nextReadField[4] = parseValues(multiContent, "field5");
1081 | this->lastFeed.nextReadField[5] = parseValues(multiContent, "field6");
1082 | this->lastFeed.nextReadField[6] = parseValues(multiContent, "field7");
1083 | this->lastFeed.nextReadField[7] = parseValues(multiContent, "field8");
1084 | this->lastFeed.nextReadCreatedAt = parseValues(multiContent, "created_at");
1085 | this->lastFeed.nextReadLatitude = parseValues(multiContent, "latitude");
1086 | this->lastFeed.nextReadLongitude = parseValues(multiContent, "longitude");
1087 | this->lastFeed.nextReadElevation = parseValues(multiContent, "elevation");
1088 | this->lastFeed.nextReadStatus = parseValues(multiContent, "status");
1089 |
1090 | return TS_OK_SUCCESS;
1091 | }
1092 |
1093 |
1094 | /*
1095 | Function: readMultipleFields
1096 |
1097 | Summary:
1098 | Read all the field values, status message, location coordinates, and created-at timestamp associated with the latest update to a private ThingSpeak channel and store the values locally in variables within a struct.
1099 |
1100 | Parameters:
1101 | channelNumber - Channel number
1102 | readAPIKey - Read API key associated with the channel. *If you share code with others, do _not_ share this key*
1103 |
1104 | Returns:
1105 | HTTP status code of 200 if successful.
1106 |
1107 | Notes:
1108 | See getLastReadStatus() for other possible return values.
1109 | */
1110 | int readMultipleFields(unsigned long channelNumber)
1111 | {
1112 | return readMultipleFields(channelNumber, NULL);
1113 | }
1114 |
1115 |
1116 | /*
1117 | Function: getFieldAsString
1118 |
1119 | Summary:
1120 | Fetch the value as string from the latest stored feed record.
1121 |
1122 | Parameters:
1123 | field - Field number (1-8) within the channel to read from.
1124 |
1125 | Returns:
1126 | Value read (UTF8 string), empty string if there is an error, or old value read (UTF8 string) if invoked before readMultipleFields(). Use getLastReadStatus() to get more specific information.
1127 | */
1128 | String getFieldAsString(unsigned int field)
1129 | {
1130 | if(field < FIELDNUM_MIN || field > FIELDNUM_MAX)
1131 | {
1132 | this->lastReadStatus = TS_ERR_INVALID_FIELD_NUM;
1133 | return("");
1134 | }
1135 |
1136 | this->lastReadStatus = TS_OK_SUCCESS;
1137 | return this->lastFeed.nextReadField[field-1];
1138 | }
1139 |
1140 |
1141 | /*
1142 | Function: getFieldAsFloat
1143 |
1144 | Summary:
1145 | Fetch the value as float from the latest stored feed record.
1146 |
1147 | Parameters:
1148 | field - Field number (1-8) within the channel to read from.
1149 |
1150 | Returns:
1151 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields(). Use getLastReadStatus() to get more specific information. Note that NAN, INFINITY, and -INFINITY are valid results.
1152 | */
1153 | float getFieldAsFloat(unsigned int field)
1154 | {
1155 | return convertStringToFloat(getFieldAsString(field));
1156 | }
1157 |
1158 |
1159 | /*
1160 | Function: getFieldAsLong
1161 |
1162 | Summary:
1163 | Fetch the value as long from the latest stored feed record.
1164 |
1165 | Parameters:
1166 | field - Field number (1-8) within the channel to read from.
1167 |
1168 | Returns:
1169 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields(). Use getLastReadStatus() to get more specific information.
1170 | */
1171 | long getFieldAsLong(unsigned int field)
1172 | {
1173 | // Note that although the function is called "toInt" it really returns a long.
1174 | return getFieldAsString(field).toInt();
1175 | }
1176 |
1177 |
1178 | /*
1179 | Function: getFieldAsInt
1180 |
1181 | Summary:
1182 | Fetch the value as int from the latest stored feed record.
1183 |
1184 | Parameters:
1185 | field - Field number (1-8) within the channel to read from.
1186 |
1187 | Returns:
1188 | Value read, 0 if the field is text or there is an error, or old value read if invoked before readMultipleFields(). Use getLastReadStatus() to get more specific information.
1189 | */
1190 | int getFieldAsInt(unsigned int field)
1191 | {
1192 | // int and long are same
1193 | return getFieldAsLong(field);
1194 | }
1195 |
1196 |
1197 | /*
1198 | Function: getStatus
1199 |
1200 | Summary:
1201 | Fetch the status message associated with the latest stored feed record.
1202 |
1203 | Results:
1204 | Value read (UTF8 string). An empty string is returned if there was no status written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
1205 | */
1206 | String getStatus()
1207 | {
1208 | return this->lastFeed.nextReadStatus;
1209 | }
1210 |
1211 |
1212 | /*
1213 | Function: getLatitude
1214 |
1215 | Summary:
1216 | Fetch the latitude associated with the latest stored feed record.
1217 |
1218 | Results:
1219 | Value read (UTF8 string). An empty string is returned if there was no latitude written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
1220 | */
1221 | String getLatitude()
1222 | {
1223 | return this->lastFeed.nextReadLatitude;
1224 | }
1225 |
1226 |
1227 | /*
1228 | Function: getLongitude
1229 |
1230 | Summary:
1231 | Fetch the longitude associated with the latest stored feed record.
1232 |
1233 | Results:
1234 | Value read (UTF8 string). An empty string is returned if there was no longitude written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
1235 | */
1236 | String getLongitude()
1237 | {
1238 | return this->lastFeed.nextReadLongitude;
1239 | }
1240 |
1241 |
1242 | /*
1243 | Function: getElevation
1244 |
1245 | Summary:
1246 | Fetch the longitude associated with the latest stored feed record.
1247 |
1248 | Results:
1249 | Value read (UTF8 string). An empty string is returned if there was no elevation written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
1250 | */
1251 | String getElevation()
1252 | {
1253 | return this->lastFeed.nextReadElevation;
1254 | }
1255 |
1256 |
1257 | /*
1258 | Function: getCreatedAt
1259 |
1260 | Summary:
1261 | Fetch the created-at timestamp associated with the latest stored feed record.
1262 |
1263 | Results:
1264 | Value read (UTF8 string). An empty string is returned if there was no created-at timestamp written to the channel or in case of an error. Use getLastReadStatus() to get more specific information.
1265 | */
1266 | String getCreatedAt()
1267 | {
1268 | return this->lastFeed.nextReadCreatedAt;
1269 | }
1270 |
1271 |
1272 | /*
1273 | Function: getLastReadStatus
1274 |
1275 | Summary:
1276 | Get the status of the previous read.
1277 |
1278 | Returns:
1279 | Generally, these are HTTP status codes. Negative values indicate an error generated by the library.
1280 | Possible response codes...
1281 | 200 - OK / Success
1282 | 404 - Incorrect API key (or invalid ThingSpeak server address)
1283 | -101 - Value is out of range or string is too long (> 255 characters)
1284 | -201 - Invalid field number specified
1285 | -210 - setField() was not called before writeFields()
1286 | -301 - Failed to connect to ThingSpeak
1287 | -302 - Unexpected failure during write to ThingSpeak
1288 | -303 - Unable to parse response
1289 | -304 - Timeout waiting for server to respond
1290 | -401 - Point was not inserted (most probable cause is exceeding the rate limit)
1291 |
1292 | Notes:
1293 | The read functions will return zero or empty if there is an error. Use this function to retrieve the details.
1294 | */
1295 | int getLastReadStatus()
1296 | {
1297 | return this->lastReadStatus;
1298 | }
1299 |
1300 |
1301 | private:
1302 |
1303 | // Creates a new String
1304 | String escapeUrl(String message){
1305 | char t;
1306 | char ch[] = " ";
1307 | char temp[4];
1308 | char *encoded;
1309 | String result = "";
1310 | unsigned int i;
1311 | unsigned int n = message.length() + 1; // add an extra for the null
1312 |
1313 | // figure out the length of the char array
1314 | for(i = 0; i < message.length(); i++){
1315 | t = message.charAt(i);
1316 | if( (t >= 0x00 && t <= 0x1F) || t >= 0x80 ){
1317 | n--;
1318 | continue;
1319 | }
1320 | if(t == 0x22 || t == 0x25 || t == 0x26 || t == 0x2B || t == 0x3B){
1321 | n = n + 2;
1322 | }
1323 | }
1324 |
1325 | // create the char array
1326 | encoded = (char *)malloc(sizeof(char) * n);
1327 | if(encoded == NULL){
1328 | return result;
1329 | }
1330 | encoded[0] = 0;
1331 |
1332 | // build the char array
1333 | for(i = 0; i < message.length(); i++){
1334 | t = message.charAt(i);
1335 | // don't include non-printable or anything about 127
1336 | if( (t >= 0x00 && t <= 0x1F) || t >= 0x80 ){
1337 | continue;
1338 | }
1339 | // encode the special characters
1340 | if(t == 0x22 || t == 0x25 || t == 0x26 || t == 0x2B || t == 0x3B){
1341 | sprintf(temp, "%%%02X", t);
1342 | strcat(encoded, temp);
1343 | continue;
1344 | }
1345 | // add the regular characters
1346 | ch[0] = t;
1347 | strcat(encoded, ch);
1348 | }
1349 |
1350 | result = String(encoded);
1351 | free(encoded);
1352 |
1353 | return result;
1354 | }
1355 |
1356 | String getJSONValueByKey(String textToSearch, String key)
1357 | {
1358 | if(textToSearch.length() == 0){
1359 | return String("");
1360 | }
1361 |
1362 | String searchPhrase = String("\"") + key + String("\":\"");
1363 |
1364 | int fromPosition = textToSearch.indexOf(searchPhrase,0);
1365 |
1366 | if(fromPosition == -1){
1367 | // return because there is no status or it's null
1368 | return String("");
1369 | }
1370 |
1371 | fromPosition = fromPosition + searchPhrase.length();
1372 |
1373 | int toPosition = textToSearch.indexOf("\"", fromPosition);
1374 |
1375 |
1376 | if(toPosition == -1){
1377 | // return because there is no end quote
1378 | return String("");
1379 | }
1380 |
1381 | textToSearch.remove(toPosition);
1382 |
1383 | return textToSearch.substring(fromPosition);
1384 | }
1385 |
1386 | String parseValues(String & multiContent, String key)
1387 | {
1388 | if(multiContent.length() == 0){
1389 | return String("");
1390 | }
1391 |
1392 | String searchPhrase = String("\"") + key + String("\":\"");
1393 |
1394 | int fromPosition = multiContent.indexOf(searchPhrase,0);
1395 |
1396 | if(fromPosition == -1){
1397 | // return because there is no status or it's null
1398 | return String("");
1399 | }
1400 |
1401 | fromPosition = fromPosition + searchPhrase.length();
1402 |
1403 | int toPosition = multiContent.indexOf("\"", fromPosition);
1404 |
1405 |
1406 | if(toPosition == -1){
1407 | // return because there is no end quote
1408 | return String("");
1409 | }
1410 |
1411 | return multiContent.substring(fromPosition, toPosition);
1412 | }
1413 |
1414 | int abortWriteRaw()
1415 | {
1416 | this->client->stop();
1417 | return TS_ERR_UNEXPECTED_FAIL;
1418 | }
1419 |
1420 | String abortReadRaw()
1421 | {
1422 | this->client->stop();
1423 | #ifdef PRINT_DEBUG_MESSAGES
1424 | Particle.publish(SPARK_PUBLISH_TOPIC, "ReadRaw abort - disconnected." , SPARK_PUBLISH_TTL, PRIVATE);
1425 | #endif
1426 | this->lastReadStatus = TS_ERR_UNEXPECTED_FAIL;
1427 | return String("");
1428 | }
1429 |
1430 | void setPort(unsigned int port)
1431 | {
1432 | this->port = port;
1433 | }
1434 |
1435 |
1436 | void setClient(Client * client)
1437 | {
1438 | this->client = client;
1439 | }
1440 |
1441 | Client * client = NULL;
1442 | unsigned int port = THINGSPEAK_PORT_NUMBER;
1443 | String nextWriteField[8];
1444 | float nextWriteLatitude;
1445 | float nextWriteLongitude;
1446 | float nextWriteElevation;
1447 | int lastReadStatus;
1448 | String nextWriteStatus;
1449 | String nextWriteCreatedAt;
1450 | feed lastFeed;
1451 |
1452 | bool connectThingSpeak()
1453 | {
1454 | bool connectSuccess = false;
1455 |
1456 | #ifdef PRINT_DEBUG_MESSAGES
1457 | Particle.publish(SPARK_PUBLISH_TOPIC, String("Connect to ThingSpeak URL: ") + String(THINGSPEAK_URL) + String(":") + String(this->port) + "..." , SPARK_PUBLISH_TTL, PRIVATE);
1458 | Serial.print(THINGSPEAK_URL);
1459 | Serial.print(":");
1460 | Serial.print(this->port);
1461 | Serial.print("...");
1462 | #endif
1463 | connectSuccess = client->connect(THINGSPEAK_URL, this->port);
1464 |
1465 | #ifdef PRINT_DEBUG_MESSAGES
1466 | if (connectSuccess)
1467 | {
1468 | Particle.publish(SPARK_PUBLISH_TOPIC, "Connection Success", SPARK_PUBLISH_TTL, PRIVATE);
1469 | }
1470 | else
1471 | {
1472 | Particle.publish(SPARK_PUBLISH_TOPIC, "Connection Failure", SPARK_PUBLISH_TTL, PRIVATE);
1473 | }
1474 | #endif
1475 | return connectSuccess;
1476 |
1477 | };
1478 |
1479 | bool writeHTTPHeader(const char * APIKey)
1480 | {
1481 |
1482 | if (!this->client->print("Host: api.thingspeak.com\r\n")) return false;
1483 | if (!this->client->print("Connection: close\r\n")) return false;
1484 | if (!this->client->print("User-Agent: ")) return false;
1485 | if (!this->client->print(TS_USER_AGENT)) return false;
1486 | if (!this->client->print("\r\n")) return false;
1487 | if(NULL != APIKey)
1488 | {
1489 | if (!this->client->print("X-THINGSPEAKAPIKEY: ")) return false;
1490 | if (!this->client->print(APIKey)) return false;
1491 | if (!this->client->print("\r\n")) return false;
1492 | }
1493 | return true;
1494 | };
1495 |
1496 | int getHTTPResponse(String & response)
1497 | {
1498 | unsigned long startWaitForResponseAt = millis();
1499 | while(client->available() == 0 && millis() - startWaitForResponseAt < TIMEOUT_MS_SERVERRESPONSE)
1500 | {
1501 | delay(100);
1502 | }
1503 | if(client->available() == 0)
1504 | {
1505 | return TS_ERR_TIMEOUT; // Didn't get server response in time
1506 | }
1507 |
1508 | if(!client->find(const_cast("HTTP/1.1")))
1509 | {
1510 | #ifdef PRINT_HTTP
1511 | Particle.publish(SPARK_PUBLISH_TOPIC, "ERROR: Didn't find HTTP/1.1", SPARK_PUBLISH_TTL, PRIVATE);
1512 | #endif
1513 | return TS_ERR_BAD_RESPONSE; // Couldn't parse response (didn't find HTTP/1.1)
1514 | }
1515 | int status = client->parseInt();
1516 | #ifdef PRINT_HTTP
1517 | Particle.publish(SPARK_PUBLISH_TOPIC, "Got Status of " + String(status), SPARK_PUBLISH_TTL, PRIVATE);
1518 | #endif
1519 | if(status != TS_OK_SUCCESS)
1520 | {
1521 | return status;
1522 | }
1523 |
1524 | if(!client->find(const_cast("\r\n")))
1525 | {
1526 | #ifdef PRINT_HTTP
1527 | Particle.publish(SPARK_PUBLISH_TOPIC, "ERROR: Didn't find end of status line", SPARK_PUBLISH_TTL, PRIVATE);
1528 | #endif
1529 | return TS_ERR_BAD_RESPONSE;
1530 | }
1531 | #ifdef PRINT_HTTP
1532 | Particle.publish(SPARK_PUBLISH_TOPIC, "Found end of status line", SPARK_PUBLISH_TTL, PRIVATE);
1533 | #endif
1534 |
1535 | if(!client->find(const_cast("\n\r\n")))
1536 | {
1537 | #ifdef PRINT_HTTP
1538 | Particle.publish(SPARK_PUBLISH_TOPIC, "ERROR: Didn't find end of header", SPARK_PUBLISH_TTL, PRIVATE);
1539 | #endif
1540 | return TS_ERR_BAD_RESPONSE;
1541 | }
1542 | #ifdef PRINT_HTTP
1543 | Particle.publish(SPARK_PUBLISH_TOPIC, "Found end of header", SPARK_PUBLISH_TTL, PRIVATE);
1544 | #endif
1545 | // This is a workaround to a bug in the Spark implementation of String
1546 | String tempString = client->readString();
1547 | response = tempString;
1548 | #ifdef PRINT_HTTP
1549 | Particle.publish(SPARK_PUBLISH_TOPIC, "Response: \"" + tempString + "\"", SPARK_PUBLISH_TTL, PRIVATE);
1550 | #endif
1551 | return status;
1552 | };
1553 |
1554 | int convertFloatToChar(float value, char *valueString)
1555 | {
1556 | // Supported range is -999999000000 to 999999000000
1557 | if(0 == isinf(value) && (value > 999999000000 || value < -999999000000))
1558 | {
1559 | // Out of range
1560 | return TS_ERR_OUT_OF_RANGE;
1561 | }
1562 | // Given that the resolution of Spark is 1 / 2^12, or ~0.00024 volts, assume that 5 places right of decimal should be sufficient for most applications
1563 | sprintf(valueString, "%.5f", value);
1564 |
1565 | return TS_OK_SUCCESS;
1566 | };
1567 |
1568 | float convertStringToFloat(String value)
1569 | {
1570 | // There's a bug in the AVR function strtod that it doesn't decode -INF correctly (it maps it to INF)
1571 | float result = value.toFloat();
1572 | if(1 == isinf(result) && *value.c_str() == '-')
1573 | {
1574 | result = (float)-INFINITY;
1575 | }
1576 | return result;
1577 | };
1578 |
1579 | void resetWriteFields()
1580 | {
1581 | for(size_t iField = 0; iField < 8; iField++)
1582 | {
1583 | this->nextWriteField[iField] = "";
1584 | }
1585 | this->nextWriteLatitude = NAN;
1586 | this->nextWriteLongitude = NAN;
1587 | this->nextWriteElevation = NAN;
1588 | this->nextWriteStatus = "";
1589 | this->nextWriteCreatedAt = "";
1590 | };
1591 | };
1592 |
1593 | extern ThingSpeakClass ThingSpeak;
1594 |
1595 | #endif //ThingSpeak_h
1596 |
--------------------------------------------------------------------------------
/src/ThingSpeak/ThingSpeak.h:
--------------------------------------------------------------------------------
1 | #include "../ThingSpeak.h"
2 |
--------------------------------------------------------------------------------