├── 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 | --------------------------------------------------------------------------------