├── .gitattributes ├── .gitignore ├── JSON_Decoder.cpp ├── JSON_Decoder.h ├── JSON_Listener.cpp ├── JSON_Listener.h ├── LICENSE ├── README.md ├── examples ├── JsonStreamingParser │ ├── ExampleParser.cpp │ ├── ExampleParser.h │ └── JsonStreamingParser.ino └── Space_Station │ ├── ISS_API_Class.cpp │ ├── ISS_API_Class.h │ └── Space_Station.ino ├── keywords.txt ├── library.json └── library.properties /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .clang_complete 3 | .gcc-flags.json 4 | /.project 5 | -------------------------------------------------------------------------------- /JSON_Decoder.cpp: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2015 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-parser 24 | */ 25 | 26 | #include "JSON_Decoder.h" 27 | 28 | #if defined(ARDUINO_ARCH_MBED) || defined(ARDUINO_ARCH_RP2040) 29 | // Avoid errors with RP2040 Nano Connect 30 | #define sprintf_P sprintf 31 | #endif 32 | 33 | #ifdef USE_LONG_ERRORS 34 | const char PROGMEM_ERR0[] PROGMEM = "Unescaped control character encountered: %c at position: %d"; 35 | const char PROGMEM_ERR1[] PROGMEM = "Start of string expected for object key. Instead got: %c at position: %d"; 36 | const char PROGMEM_ERR2[] PROGMEM = "Expected ':' after key. Instead got %c at position %d"; 37 | const char PROGMEM_ERR3[] PROGMEM = "Expected ',' or '}' while parsing object. Got: %c at position %d"; 38 | const char PROGMEM_ERR4[] PROGMEM = "Expected ',' or ']' while parsing array. Got: %c at position: %d"; 39 | const char PROGMEM_ERR5[] PROGMEM = "Finished a literal, but unclear what state to move to. Last state: %d"; 40 | const char PROGMEM_ERR6[] PROGMEM = "Cannot have multiple decimal points in a number at: %d"; 41 | const char PROGMEM_ERR7[] PROGMEM = "Cannot have a decimal point in an exponent at: %d"; 42 | const char PROGMEM_ERR8[] PROGMEM = "Cannot have multiple exponents in a number at: %d"; 43 | const char PROGMEM_ERR9[] PROGMEM = "Can only have '+' or '-' after the 'e' or 'E' in a number at: %d"; 44 | const char PROGMEM_ERR10[] PROGMEM = "Document must start with object or array"; 45 | const char PROGMEM_ERR11[] PROGMEM = "Expected end of document"; 46 | const char PROGMEM_ERR12[] PROGMEM = "Internal error. Reached an unknown state at: %d"; 47 | const char PROGMEM_ERR13[] PROGMEM = "Unexpected end of string"; 48 | const char PROGMEM_ERR14[] PROGMEM = "Unexpected character for value"; 49 | const char PROGMEM_ERR15[] PROGMEM = "Unexpected end of array encountered"; 50 | const char PROGMEM_ERR16[] PROGMEM = "Unexpected end of object encountered"; 51 | const char PROGMEM_ERR17[] PROGMEM = "Expected escaped character after backslash"; 52 | const char PROGMEM_ERR18[] PROGMEM = "Expected hex character for escaped Unicode character"; 53 | const char PROGMEM_ERR19[] PROGMEM = "Expected '\\u' following a Unicode high surrogate"; 54 | const char PROGMEM_ERR20[] PROGMEM = "Expected 'true'"; 55 | const char PROGMEM_ERR21[] PROGMEM = "Expected 'false'"; 56 | const char PROGMEM_ERR22[] PROGMEM = "Expected 'null'"; 57 | #else 58 | const char PROGMEM_ERR0[] PROGMEM = "err0: %c at: %d"; 59 | const char PROGMEM_ERR1[] PROGMEM = "err1: %c at: %d"; 60 | const char PROGMEM_ERR2[] PROGMEM = "err2: %c at: %d"; 61 | const char PROGMEM_ERR3[] PROGMEM = "err3: %c at: %d"; 62 | const char PROGMEM_ERR4[] PROGMEM = "err4: %c at: %d"; 63 | const char PROGMEM_ERR5[] PROGMEM = "err5: %d"; 64 | const char PROGMEM_ERR6[] PROGMEM = "err6: at: %d"; 65 | const char PROGMEM_ERR7[] PROGMEM = "err7: at: %d"; 66 | const char PROGMEM_ERR8[] PROGMEM = "err8: at: %d"; 67 | const char PROGMEM_ERR9[] PROGMEM = "err9: at: %d"; 68 | const char PROGMEM_ERR10[] PROGMEM = "err10"; 69 | const char PROGMEM_ERR11[] PROGMEM = "err11"; 70 | const char PROGMEM_ERR12[] PROGMEM = "err12: %d"; 71 | const char PROGMEM_ERR13[] PROGMEM = "err13"; 72 | const char PROGMEM_ERR14[] PROGMEM = "err14"; 73 | const char PROGMEM_ERR15[] PROGMEM = "err15"; 74 | const char PROGMEM_ERR16[] PROGMEM = "err16"; 75 | const char PROGMEM_ERR17[] PROGMEM = "err17"; 76 | const char PROGMEM_ERR18[] PROGMEM = "err18"; 77 | const char PROGMEM_ERR19[] PROGMEM = "err19"; 78 | const char PROGMEM_ERR20[] PROGMEM = "err20"; 79 | const char PROGMEM_ERR21[] PROGMEM = "err21"; 80 | const char PROGMEM_ERR22[] PROGMEM = "err22"; 81 | #endif 82 | 83 | JSON_Decoder::JSON_Decoder() { 84 | reset(); 85 | } 86 | 87 | void JSON_Decoder::reset() { 88 | state = STATE_START_DOCUMENT; 89 | stackPos = 0; 90 | bufferPos = 0; 91 | unicodeEscapeBufferPos = 0; 92 | unicodeBufferPos = 0; 93 | characterCounter = 0; 94 | } 95 | 96 | void JSON_Decoder::setListener(JsonListener* listener) { 97 | myListener = listener; 98 | } 99 | 100 | bool JSON_Decoder::parse(char c) { 101 | // Serial.println(c); 102 | // valid whitespace characters in JSON (from RFC4627 for JSON) include: 103 | // space, horizontal tab, line feed or new line, and carriage return. 104 | // thanks: 105 | // http://stackoverflow.com/questions/16042274/definition-of-whitespace-in-json 106 | 107 | if ((c == ' ' || c == '\t' || c == '\n' || c == '\r') 108 | && !(state == STATE_IN_STRING || state == STATE_UNICODE || state == STATE_START_ESCAPE || state == STATE_IN_NUMBER 109 | || state == STATE_START_DOCUMENT)) { 110 | return true; 111 | } 112 | 113 | switch (state) { 114 | case STATE_IN_STRING: 115 | if (c == '"') { 116 | if (!endString()) { 117 | return false; 118 | } 119 | } else if (c == '\\') { 120 | state = STATE_START_ESCAPE; 121 | } else if ((c < 0x1f) || (c == 0x7f)) { 122 | sprintf_P(errorMessage, PROGMEM_ERR0, c, characterCounter); 123 | myListener->error(errorMessage); 124 | return false; 125 | } else { 126 | buffer[bufferPos] = c; 127 | increaseBufferPointer(); 128 | } 129 | break; 130 | case STATE_IN_ARRAY: 131 | if (c == ']') { 132 | if (!endArray()) { 133 | return false; 134 | } 135 | } else { 136 | if (!startValue(c)) { 137 | return false; 138 | } 139 | } 140 | break; 141 | case STATE_IN_OBJECT: 142 | if (c == '}') { 143 | if (!endObject()) { 144 | return false; 145 | } 146 | } else if (c == '"') { 147 | startKey(); 148 | } else { 149 | sprintf_P(errorMessage, PROGMEM_ERR1, c, characterCounter); 150 | myListener->error(errorMessage); 151 | return false; 152 | } 153 | break; 154 | case STATE_END_KEY: 155 | if (c != ':') { 156 | sprintf_P(errorMessage, PROGMEM_ERR2, c, characterCounter); 157 | myListener->error(errorMessage); 158 | return false; 159 | } 160 | state = STATE_AFTER_KEY; 161 | break; 162 | case STATE_AFTER_KEY: 163 | if (!startValue(c)) { 164 | return false; 165 | } 166 | break; 167 | case STATE_START_ESCAPE: 168 | if (!processEscapeCharacters(c)) { 169 | return false; 170 | } 171 | break; 172 | case STATE_UNICODE: 173 | if (!processUnicodeCharacter(c)) { 174 | return false; 175 | } 176 | break; 177 | case STATE_UNICODE_SURROGATE: 178 | unicodeEscapeBuffer[unicodeEscapeBufferPos] = c; 179 | unicodeEscapeBufferPos++; 180 | if (unicodeEscapeBufferPos == 2) { 181 | if (!endUnicodeSurrogateInterstitial()) { 182 | return false; 183 | } 184 | } 185 | break; 186 | case STATE_AFTER_VALUE: { 187 | // not safe for size == 0!!! 188 | int within = peek(); 189 | if (within == STACK_OBJECT) { 190 | if (c == '}') { 191 | if (!endObject()) { 192 | return false; 193 | } 194 | } else if (c == ',') { 195 | state = STATE_IN_OBJECT; 196 | } else { 197 | sprintf_P(errorMessage, PROGMEM_ERR3, c, characterCounter); 198 | myListener->error(errorMessage); 199 | return false; 200 | } 201 | } else if (within == STACK_ARRAY) { 202 | if (c == ']') { 203 | if (!endArray()) { 204 | return false; 205 | } 206 | } else if (c == ',') { 207 | state = STATE_IN_ARRAY; 208 | } else { 209 | sprintf_P(errorMessage, PROGMEM_ERR4, c, characterCounter); 210 | myListener->error(errorMessage); 211 | return false; 212 | } 213 | } else { 214 | sprintf_P(errorMessage, PROGMEM_ERR5, characterCounter); 215 | myListener->error(errorMessage); 216 | return false; 217 | } 218 | } 219 | break; 220 | case STATE_IN_NUMBER: 221 | if (c >= '0' && c <= '9') { 222 | buffer[bufferPos] = c; 223 | increaseBufferPointer(); 224 | } else if (c == '.') { 225 | if (doesCharArrayContain(buffer, bufferPos, '.')) { 226 | sprintf_P(errorMessage, PROGMEM_ERR6, characterCounter); 227 | myListener->error(errorMessage); 228 | return false; 229 | } else if (doesCharArrayContain(buffer, bufferPos, 'e')) { 230 | sprintf_P(errorMessage, PROGMEM_ERR7, characterCounter); 231 | myListener->error(errorMessage); 232 | return false; 233 | } 234 | buffer[bufferPos] = c; 235 | increaseBufferPointer(); 236 | } else if (c == 'e' || c == 'E') { 237 | if (doesCharArrayContain(buffer, bufferPos, 'e')) { 238 | sprintf_P(errorMessage, PROGMEM_ERR8, characterCounter); 239 | myListener->error(errorMessage); 240 | return false; 241 | } 242 | buffer[bufferPos] = c; 243 | increaseBufferPointer(); 244 | } else if (c == '+' || c == '-') { 245 | char last = buffer[bufferPos - 1]; 246 | if (!(last == 'e' || last == 'E')) { 247 | sprintf_P(errorMessage, PROGMEM_ERR9, characterCounter); 248 | myListener->error(errorMessage); 249 | return false; 250 | } 251 | buffer[bufferPos] = c; 252 | increaseBufferPointer(); 253 | } else { 254 | endNumber(); 255 | // we have consumed one beyond the end of the number 256 | parse(c); 257 | } 258 | break; 259 | case STATE_IN_TRUE: 260 | buffer[bufferPos] = c; 261 | increaseBufferPointer(); 262 | if (bufferPos == 4) { 263 | if (!endTrue()) { 264 | return false; 265 | } 266 | } 267 | break; 268 | case STATE_IN_FALSE: 269 | buffer[bufferPos] = c; 270 | increaseBufferPointer(); 271 | if (bufferPos == 5) { 272 | if (!endFalse()) { 273 | return false; 274 | } 275 | } 276 | break; 277 | case STATE_IN_NULL: 278 | buffer[bufferPos] = c; 279 | increaseBufferPointer(); 280 | if (bufferPos == 4) { 281 | if (!endNull()) { 282 | return false; 283 | } 284 | } 285 | break; 286 | case STATE_START_DOCUMENT: { 287 | if (c != '{') return false; // To wait for start character 288 | myListener->startDocument(); 289 | if (c == '[') { 290 | startArray(); 291 | } else if (c == '{') { 292 | startObject(); 293 | } else { 294 | sprintf_P(errorMessage, PROGMEM_ERR10); 295 | myListener->error(errorMessage); 296 | return false; 297 | } 298 | break; 299 | } 300 | case STATE_DONE: { 301 | sprintf_P(errorMessage, PROGMEM_ERR11); 302 | myListener->error(errorMessage); 303 | return false; 304 | } 305 | default: { 306 | sprintf_P(errorMessage, PROGMEM_ERR12, characterCounter); 307 | myListener->error(errorMessage); 308 | return false; 309 | } 310 | } 311 | 312 | characterCounter++; 313 | 314 | return true; 315 | } 316 | 317 | void JSON_Decoder::increaseBufferPointer() { 318 | bufferPos = min(bufferPos + 1, BUFFER_MAX_LENGTH - 1); 319 | } 320 | 321 | void JSON_Decoder::push(int value) { 322 | stack[stackPos] = value; 323 | stackPos++; 324 | } 325 | 326 | int JSON_Decoder::pop() { 327 | stackPos--; 328 | return stack[stackPos]; 329 | } 330 | 331 | int JSON_Decoder::peek() { 332 | return stack[stackPos - 1]; 333 | } 334 | 335 | void JSON_Decoder::startKey() { 336 | push(STACK_KEY); 337 | state = STATE_IN_STRING; 338 | } 339 | 340 | boolean JSON_Decoder::startValue(char c) { 341 | if (c == '[') { 342 | startArray(); 343 | } else if (c == '{') { 344 | startObject(); 345 | } else if (c == '"') { 346 | startString(); 347 | } else if (isDigit(c)) { 348 | startNumber(c); 349 | } else if (c == 't') { 350 | state = STATE_IN_TRUE; 351 | buffer[bufferPos] = c; 352 | increaseBufferPointer(); 353 | } else if (c == 'f') { 354 | state = STATE_IN_FALSE; 355 | buffer[bufferPos] = c; 356 | increaseBufferPointer(); 357 | } else if (c == 'n') { 358 | state = STATE_IN_NULL; 359 | buffer[bufferPos] = c; 360 | increaseBufferPointer(); 361 | } else { 362 | sprintf_P(errorMessage, PROGMEM_ERR14); 363 | myListener->error(errorMessage); 364 | return false; 365 | } 366 | return true; 367 | } 368 | 369 | void JSON_Decoder::startObject() { 370 | myListener->startObject(); 371 | state = STATE_IN_OBJECT; 372 | push(STACK_OBJECT); 373 | } 374 | 375 | boolean JSON_Decoder::endObject() { 376 | if (pop() != STACK_OBJECT) { 377 | sprintf_P(errorMessage, PROGMEM_ERR16); 378 | myListener->error(errorMessage); 379 | return false; 380 | } 381 | myListener->endObject(); 382 | state = STATE_AFTER_VALUE; 383 | if (stackPos == 0) { 384 | endDocument(); 385 | } 386 | return true; 387 | } 388 | 389 | void JSON_Decoder::startArray() { 390 | myListener->startArray(); 391 | state = STATE_IN_ARRAY; 392 | push(STACK_ARRAY); 393 | } 394 | 395 | boolean JSON_Decoder::endArray() { 396 | if (pop() != STACK_ARRAY) { 397 | sprintf_P(errorMessage, PROGMEM_ERR15); 398 | myListener->error(errorMessage); 399 | return false; 400 | } 401 | myListener->endArray(); 402 | state = STATE_AFTER_VALUE; 403 | if (stackPos == 0) { 404 | endDocument(); 405 | } 406 | return true; 407 | } 408 | 409 | void JSON_Decoder::startString() { 410 | push(STACK_STRING); 411 | state = STATE_IN_STRING; 412 | } 413 | 414 | boolean JSON_Decoder::endString() { 415 | int popped = pop(); 416 | if (popped == STACK_KEY) { 417 | buffer[bufferPos] = '\0'; 418 | myListener->key(buffer); 419 | state = STATE_END_KEY; 420 | } else if (popped == STACK_STRING) { 421 | buffer[bufferPos] = '\0'; 422 | myListener->value(buffer); 423 | state = STATE_AFTER_VALUE; 424 | } else { 425 | sprintf_P(errorMessage, PROGMEM_ERR13); 426 | myListener->error(errorMessage); 427 | return false; 428 | } 429 | bufferPos = 0; 430 | return true; 431 | } 432 | 433 | void JSON_Decoder::startNumber(char c) { 434 | state = STATE_IN_NUMBER; 435 | buffer[bufferPos] = c; 436 | increaseBufferPointer(); 437 | } 438 | 439 | void JSON_Decoder::endNumber() { 440 | buffer[bufferPos] = '\0'; 441 | myListener->value(buffer); 442 | bufferPos = 0; 443 | state = STATE_AFTER_VALUE; 444 | } 445 | 446 | boolean JSON_Decoder::endTrue() { 447 | buffer[bufferPos] = '\0'; 448 | // String value = String(buffer); 449 | if (strncmp(buffer, "true", 4) == 0) { 450 | myListener->value("true"); 451 | } else { 452 | sprintf_P(errorMessage, PROGMEM_ERR20); 453 | myListener->error(errorMessage); 454 | return false; 455 | } 456 | bufferPos = 0; 457 | state = STATE_AFTER_VALUE; 458 | return true; 459 | } 460 | 461 | boolean JSON_Decoder::endFalse() { 462 | buffer[bufferPos] = '\0'; 463 | // String value = String(buffer); 464 | if (strncmp(buffer, "false", 5) == 0) { 465 | myListener->value("false"); 466 | } else { 467 | sprintf_P(errorMessage, PROGMEM_ERR21); 468 | myListener->error(errorMessage); 469 | return false; 470 | } 471 | bufferPos = 0; 472 | state = STATE_AFTER_VALUE; 473 | return true; 474 | } 475 | 476 | boolean JSON_Decoder::endNull() { 477 | buffer[bufferPos] = '\0'; 478 | // String value = String(buffer); 479 | if (strncmp(buffer, "null", 4) == 0) { 480 | myListener->value("null"); 481 | } else { 482 | sprintf_P(errorMessage, PROGMEM_ERR22); 483 | myListener->error(errorMessage); 484 | return false; 485 | } 486 | bufferPos = 0; 487 | state = STATE_AFTER_VALUE; 488 | return true; 489 | } 490 | 491 | void JSON_Decoder::endDocument() { 492 | myListener->endDocument(); 493 | reset(); 494 | } 495 | 496 | boolean JSON_Decoder::isDigit(char c) { 497 | // Only concerned with the first character in a number. 498 | return (c >= '0' && c <= '9') || c == '-'; 499 | } 500 | 501 | boolean JSON_Decoder::isHexCharacter(char c) { 502 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); 503 | } 504 | 505 | boolean JSON_Decoder::doesCharArrayContain(char myArray[], int length, char c) { 506 | for (int i = 0; i < length; i++) { 507 | if (myArray[i] == c) { 508 | return true; 509 | } 510 | } 511 | return false; 512 | } 513 | 514 | boolean JSON_Decoder::processEscapeCharacters(char c) { 515 | if (c == '"') { 516 | buffer[bufferPos] = '"'; 517 | increaseBufferPointer(); 518 | } else if (c == '\\') { 519 | buffer[bufferPos] = '\\'; 520 | increaseBufferPointer(); 521 | } else if (c == '/') { 522 | buffer[bufferPos] = '/'; 523 | increaseBufferPointer(); 524 | } else if (c == 'b') { 525 | buffer[bufferPos] = 0x08; 526 | increaseBufferPointer(); 527 | } else if (c == 'f') { 528 | buffer[bufferPos] = '\f'; 529 | increaseBufferPointer(); 530 | } else if (c == 'n') { 531 | buffer[bufferPos] = '\n'; 532 | increaseBufferPointer(); 533 | } else if (c == 'r') { 534 | buffer[bufferPos] = '\r'; 535 | increaseBufferPointer(); 536 | } else if (c == 't') { 537 | buffer[bufferPos] = '\t'; 538 | increaseBufferPointer(); 539 | } else if (c == 'u') { 540 | state = STATE_UNICODE; 541 | } else { 542 | sprintf_P(errorMessage, PROGMEM_ERR17); 543 | myListener->error(errorMessage); 544 | return false; 545 | } 546 | if (state != STATE_UNICODE) { 547 | state = STATE_IN_STRING; 548 | } 549 | return true; 550 | } 551 | 552 | boolean JSON_Decoder::processUnicodeCharacter(char c) { 553 | if (!isHexCharacter(c)) { 554 | sprintf_P(errorMessage, PROGMEM_ERR18); 555 | myListener->error(errorMessage); 556 | return false; 557 | } 558 | 559 | unicodeBuffer[unicodeBufferPos] = c; 560 | unicodeBufferPos++; 561 | 562 | if (unicodeBufferPos == 4) { 563 | int codepoint = getHexArrayAsDecimal(unicodeBuffer, unicodeBufferPos); 564 | endUnicodeCharacter(codepoint); 565 | } 566 | return true; 567 | } 568 | 569 | int JSON_Decoder::getHexArrayAsDecimal(char hexArray[], int length) { 570 | int result = 0; 571 | for (int i = 0; i < length; i++) { 572 | char current = hexArray[length - i - 1]; 573 | int value = 0; 574 | if (current >= 'a' && current <= 'f') { 575 | value = current - 'a' + 10; 576 | } else if (current >= 'A' && current <= 'F') { 577 | value = current - 'A' + 10; 578 | } else if (current >= '0' && current <= '9') { 579 | value = current - '0'; 580 | } 581 | result += value * 16 ^ i; 582 | } 583 | return result; 584 | } 585 | 586 | boolean JSON_Decoder::endUnicodeSurrogateInterstitial() { 587 | char unicodeEscape = unicodeEscapeBuffer[unicodeEscapeBufferPos - 1]; 588 | if (unicodeEscape != 'u') { 589 | sprintf_P(errorMessage, PROGMEM_ERR19); 590 | myListener->error(errorMessage); 591 | return false; 592 | } 593 | unicodeBufferPos = 0; 594 | unicodeEscapeBufferPos = 0; 595 | state = STATE_UNICODE; 596 | return true; 597 | } 598 | 599 | int JSON_Decoder::convertDecimalBufferToInt(char myArray[], int length) { 600 | int result = 0; 601 | for (int i = 0; i < length; i++) { 602 | char current = myArray[length - i - 1]; 603 | result += (current - '0') * 10; 604 | } 605 | return result; 606 | } 607 | 608 | void JSON_Decoder::endUnicodeCharacter(int codepoint) { 609 | buffer[bufferPos] = convertCodepointToCharacter(codepoint); 610 | increaseBufferPointer(); 611 | unicodeBufferPos = 0; 612 | unicodeHighSurrogate = -1; 613 | state = STATE_IN_STRING; 614 | } 615 | 616 | char JSON_Decoder::convertCodepointToCharacter(int num) { 617 | if (num <= 0x7F) { 618 | return (char) (num); 619 | } 620 | return ' '; 621 | } 622 | -------------------------------------------------------------------------------- /JSON_Decoder.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2015 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-parser 24 | */ 25 | 26 | #pragma once 27 | 28 | #ifdef ARDUINO 29 | #include 30 | #else 31 | #include "MockArduino.h" 32 | #endif 33 | #include "JSON_Listener.h" 34 | 35 | /** Define this to enable verbose erroring. You may not want this on flash-constrained platforms */ 36 | #define USE_LONG_ERRORS 1 37 | 38 | #define STATE_START_DOCUMENT 0 39 | #define STATE_DONE -1 40 | #define STATE_IN_ARRAY 1 41 | #define STATE_IN_OBJECT 2 42 | #define STATE_END_KEY 3 43 | #define STATE_AFTER_KEY 4 44 | #define STATE_IN_STRING 5 45 | #define STATE_START_ESCAPE 6 46 | #define STATE_UNICODE 7 47 | #define STATE_IN_NUMBER 8 48 | #define STATE_IN_TRUE 9 49 | #define STATE_IN_FALSE 10 50 | #define STATE_IN_NULL 11 51 | #define STATE_AFTER_VALUE 12 52 | #define STATE_UNICODE_SURROGATE 13 53 | 54 | #define STACK_OBJECT 0 55 | #define STACK_ARRAY 1 56 | #define STACK_KEY 2 57 | #define STACK_STRING 3 58 | 59 | #define BUFFER_MAX_LENGTH 512 60 | 61 | class JSON_Decoder { 62 | private: 63 | 64 | 65 | int state; 66 | int stack[20]; 67 | int stackPos = 0; 68 | JsonListener* myListener; 69 | 70 | boolean doEmitWhitespace = false; 71 | // fixed length buffer array to prepare for c code 72 | char buffer[BUFFER_MAX_LENGTH]; 73 | int bufferPos = 0; 74 | 75 | char unicodeEscapeBuffer[10]; 76 | int unicodeEscapeBufferPos = 0; 77 | 78 | char unicodeBuffer[10]; 79 | int unicodeBufferPos = 0; 80 | 81 | int characterCounter = 0; 82 | 83 | int unicodeHighSurrogate = 0; 84 | 85 | char errorMessage[128]; 86 | 87 | void increaseBufferPointer(); 88 | 89 | void push(int value); 90 | int pop(); 91 | int peek(); 92 | 93 | void endDocument(); 94 | 95 | void startKey(); 96 | 97 | void startObject(); 98 | boolean endObject(); 99 | 100 | void startArray(); 101 | boolean endArray(); 102 | 103 | boolean startValue(char c); 104 | boolean endNull(); 105 | boolean endFalse(); 106 | boolean endTrue(); 107 | void endUnicodeCharacter(int codepoint); 108 | boolean endUnicodeSurrogateInterstitial(); 109 | 110 | void startString(); 111 | boolean endString(); 112 | 113 | void startNumber(char c); 114 | void endNumber(); 115 | 116 | boolean processUnicodeCharacter(char c); 117 | boolean processEscapeCharacters(char c); 118 | 119 | boolean isDigit(char c); 120 | boolean isHexCharacter(char c); 121 | boolean doesCharArrayContain(char myArray[], int length, char c); 122 | 123 | char convertCodepointToCharacter(int num); 124 | int convertDecimalBufferToInt(char myArray[], int length); 125 | int getHexArrayAsDecimal(char hexArray[], int length); 126 | 127 | 128 | public: 129 | JSON_Decoder(); 130 | bool parse(char c); 131 | void setListener(JsonListener* listener); 132 | void reset(); 133 | }; 134 | -------------------------------------------------------------------------------- /JSON_Listener.cpp: -------------------------------------------------------------------------------- 1 | // Dummy set so class user does not have to implement them all and 2 | // if I add more functions then legacy sketches will still work... 3 | 4 | // These are over-ridden by the users equivalent functions 5 | 6 | #include "JSON_Listener.h" 7 | 8 | //JsonListener::JsonListener() {} implicitly declared 9 | 10 | // Called when JSON document start detected e.g. "{" 11 | void JsonListener::startDocument() {}; 12 | 13 | // Called at end of JSON document 14 | void JsonListener::endDocument() {}; 15 | 16 | // Called at the start of each object, objects can be nested 17 | void JsonListener::startObject() {}; 18 | 19 | // Called at the end of each object, objects can be nested 20 | // Increment array index here 21 | void JsonListener::endObject() {}; 22 | 23 | // Called at begining of an array of N items 24 | // Reset array index to 0 here 25 | // Array path is currentObject:currentKey 26 | void JsonListener::startArray() {}; 27 | 28 | // Called when all N items have ended 29 | void JsonListener::endArray() {}; 30 | 31 | // Current key 32 | void JsonListener::key(const char *key) { key = key;}; 33 | 34 | // Value associated with current key 35 | void JsonListener::value(const char *value) {value = value;}; 36 | 37 | // Called when whitespace characters detected in stream 38 | void JsonListener::whitespace(char c) {c = c;}; 39 | 40 | // Called when an error occurs during parsing - typically indicates bad JSON 41 | void JsonListener::error( const char *message ) {message = message;}; 42 | -------------------------------------------------------------------------------- /JSON_Listener.h: -------------------------------------------------------------------------------- 1 | /**The MIT License (MIT) 2 | 3 | Copyright (c) 2015 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | See more at http://blog.squix.ch and https://github.com/squix78/json-streaming-parser 25 | 26 | */ 27 | 28 | // altrunner forker and refactored to use char arrays and add error reporting: 29 | // https://github.com/altrunner/json-streaming-parser 30 | 31 | // altrunner version of library forked and name changed by Bodmer to avoid IDE 32 | // name conflicts with Arduino IDE Library manager version: 33 | // https://github.com/Bodmer/JSON_Decoder 34 | 35 | #pragma once 36 | 37 | #ifdef ARDUINO 38 | #include 39 | #else 40 | #include "MockArduino.h" 41 | #endif 42 | 43 | class JsonListener { 44 | private: 45 | 46 | public: 47 | 48 | virtual ~JsonListener() {} 49 | 50 | // Member functions changed to a more logical order 51 | virtual void startDocument(); 52 | 53 | virtual void endDocument(); 54 | 55 | 56 | virtual void startObject(); 57 | 58 | virtual void endObject(); 59 | 60 | 61 | virtual void startArray(); 62 | 63 | virtual void endArray(); 64 | 65 | 66 | virtual void key(const char *key); 67 | 68 | virtual void value(const char *value); 69 | 70 | 71 | virtual void whitespace(char c); 72 | 73 | 74 | virtual void error( const char *message ); 75 | 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 by Daniel Eichhorn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON_Decoder 2 | 3 | This is a fork of altrunner's variant of Daniel Eichhorn's library. altrunner converted the library to use char arrays instead of the String class. The update has the potential to reduce the parser memory footprint during decoding and I like the error reporting added in altrunner's variant. 4 | 5 | This fork here has been renamed JSON_Decoder to avoid conflicts in the Arduino IDE with Daniel's original "json-streaming-parser" library: 6 | 7 | https://github.com/squix78/json-streaming-parser 8 | 9 | Thus the two libraries can happily co-exist. The example sketch has been corrected to work with an update that added error reporting in altrunners fork. 10 | 11 | This version of the library will also throw away characters in the GET response until the first '{' is found, which marks the start of a JSON object. This means you do not need to discard the response header yourself. 12 | 13 | The SpaceStation example has been added to show how to fetch and decode the JSON messages from a website. This is about as simple as I could make it and the example includes comments that explain what the various bits of the example code do. 14 | 15 | The streaming parser is very efficient on memory use compared to the Arduino JSON library because space only needs to be reserved temporarily to store the elements from the message that you need, for example the Arduino library required 55kbytes of RAM to decode a 28kbyte JSON message which made it unusable on an ESP8266. The price paid for this memory efficiency is that you must write the code to pull out the values you need. The SpaceStation example includes links so you can see the JSON message that is being parsed in your web browser. 16 | 17 | The following Weather forecast libraries use this JSON_Decoder version of the streaming parser: 18 | 19 | https://github.com/Bodmer/ApixuWeather 20 | 21 | https://github.com/Bodmer/DarkSkyWeather 22 | 23 | This library is based on all the hard work by Daniel Eichhorn: https://github.com/squix78 24 | 25 | The original ReadMe for his source library is below. 26 | 27 | # json-streaming-parser 28 | Arduino library for parsing potentially huge json streams on devices with scarce memory. 29 | 30 | This library is a port of Salsify's PHP based json streaming parser (https://github.com/salsify/jsonstreamingparser). 31 | 32 | ## Why yet another JSON parser? 33 | 34 | When working with small (connected) devices you might quickly get to the point where you need to process potentially huge JSON object received from a REST interface. 35 | All libraries for processing JSON objects on the Arduino platform had some deficiencies which led me to create one on my own. 36 | Not really being an expert in writing parsers I miserably failed the first time. 37 | 38 | This is the second attempt and I took Salsify's PHP based parser and ported it to C++. 39 | This library has been tested on the Arduino/ESP8266 target platform but should work in theory on all platforms available for the Arduino environment 40 | 41 | ## Why a streaming parser? 42 | 43 | Generally speaking when parsing data you have two options to make sense of this data: 44 | you either create a document object model (DOM) which contains the whole information of that document and lets you retrieve the 45 | nodes with random access. A DOM parser needs the whole document to start parsing and only lets you access the data 46 | after it has finished the process. 47 | The other option you have when parsing a document is to process it char by char (or byte by byte) while it becomes 48 | available to your client code. Compared to the DOM parser this has two advantages: a) you can react 49 | the data as soon as meaningful parts are available and b) you can drop information as soon as the parser has processed 50 | it. This reduces the memory consumption a lot, especially if you retrieve huge documents when only being interested 51 | by a small subset of it. But this efficiency comes at a price: your code will have to do more "magic" than with a 52 | DOM parser, the business logic becomes part of the parser. 53 | 54 | ## How to install 55 | 56 | Until the library becomes available in the Arduino IDE library manager you'll have to do a bit more work by hand. 57 | 1) Download this library: https://github.com/squix78/json-streaming-parser/archive/master.zip 58 | 2) Rename master.zip to json-streaming-parser.zip 59 | 3) Open the zip file in the Arduino IDE from menu Sketch > Include Library > Add ZIP Library... 60 | 61 | ## How to use 62 | 63 | This is a streaming parser, which means that you feed a stream of chars into the parser and you take out from that 64 | stream whatever you are interested in. In order to do that you will create a subclass of JsonListener class and 65 | implement methods which will be notified in case of certain events in the feed occure. Available events are: 66 | 67 | * startDocument() 68 | * key(const char *key) 69 | * value(const char *value) 70 | * endArray() 71 | * endObject() 72 | * endDocument() 73 | * startArray() 74 | * startObject() 75 | * error(const char *message) 76 | 77 | In your implementation of these methods you will have to write problem specific code to find the parts of the document that you are interested in. Please see the example to understand what that means. In the example the ExampleListener implements the event methods declared in the JsonListener interface and prints to the serial console when they are called. 78 | 79 | ## License 80 | 81 | This code is available under the MIT license, which basically means that you can use, modify the distribute the code as long as you give credits to me (and Salsify) and add a reference back to this repository. Please read https://github.com/squix78/json-streaming-parser/blob/master/LICENSE for more detail... 82 | 83 | ## Credits 84 | 85 | First of all I'd like to thank Salsify for making their PHP parser available to the public. You find their repository here: https://github.com/salsify/jsonstreamingparser 86 | 87 | Then I'd like to thank my employer Netcetera (https://github.com/netceteragroup) to let us hackers go twice a year to the CodeCamp and work on software projects like this one. 88 | 89 | And last but not least I'd like to thank my wife that she led me spend three days away from the family hacking in the wonderful mountains of Berne. 90 | -------------------------------------------------------------------------------- /examples/JsonStreamingParser/ExampleParser.cpp: -------------------------------------------------------------------------------- 1 | #include "ExampleParser.h" 2 | #include "JsonListener.h" 3 | 4 | 5 | void ExampleListener::whitespace(char c) { 6 | Serial.println("whitespace"); 7 | } 8 | 9 | void ExampleListener::startDocument() { 10 | Serial.println("\nstart document"); 11 | } 12 | 13 | void ExampleListener::key(const char *key) { 14 | Serial.print("key: "); 15 | Serial.println(key); 16 | } 17 | 18 | void ExampleListener::value(const char *value) { 19 | Serial.print("value: "); 20 | Serial.println(value); 21 | } 22 | 23 | void ExampleListener::endArray() { 24 | Serial.println("end array. "); 25 | } 26 | 27 | void ExampleListener::endObject() { 28 | Serial.println("end object. "); 29 | } 30 | 31 | void ExampleListener::endDocument() { 32 | Serial.println("end document. "); 33 | } 34 | 35 | void ExampleListener::startArray() { 36 | Serial.println("start array. "); 37 | } 38 | 39 | void ExampleListener::startObject() { 40 | Serial.println("start object. "); 41 | } 42 | 43 | void ExampleListener::error( const char *message ) { 44 | Serial.print("\nError message: "); 45 | Serial.println(message); 46 | } 47 | -------------------------------------------------------------------------------- /examples/JsonStreamingParser/ExampleParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "JsonListener.h" 4 | 5 | class ExampleListener: public JsonListener { 6 | 7 | public: 8 | virtual void whitespace(char c); 9 | 10 | virtual void startDocument(); 11 | 12 | virtual void key(const char *key); 13 | 14 | virtual void value(const char *value); 15 | 16 | virtual void endArray(); 17 | 18 | virtual void endObject(); 19 | 20 | virtual void endDocument(); 21 | 22 | virtual void startArray(); 23 | 24 | virtual void startObject(); 25 | 26 | virtual void error( const char *message ); 27 | }; 28 | -------------------------------------------------------------------------------- /examples/JsonStreamingParser/JsonStreamingParser.ino: -------------------------------------------------------------------------------- 1 | #include "JSON_Decoder.h" 2 | #include "JsonListener.h" 3 | #include "ExampleParser.h" 4 | 5 | JSON_Decoder parser; 6 | ExampleListener listener; 7 | 8 | void setup() { 9 | Serial.begin(115200); 10 | #ifdef ARDUINO_ARCH_ESP8266 11 | Serial.println(String(ESP.getFreeHeap())); 12 | #endif 13 | parser.setListener(&listener); 14 | // put your setup code here, to run once: 15 | char json[] = "{\"a\":3, \"b\":{\"c\":\"d\"}}"; 16 | for (int i = 0; i < sizeof(json); i++) { 17 | parser.parse(json[i]); 18 | } 19 | #ifdef ARDUINO_ARCH_ESP8266 20 | Serial.println(String(ESP.getFreeHeap())); 21 | #endif 22 | } 23 | 24 | void loop() { 25 | // put your main code here, to run repeatedly: 26 | 27 | } 28 | -------------------------------------------------------------------------------- /examples/Space_Station/ISS_API_Class.cpp: -------------------------------------------------------------------------------- 1 | // Space Station class functions which supports the main sketch 2 | 3 | #include "ISS_API_Class.h" // Include the header with the function prototypes etc 4 | 5 | /*************************************************************************************** 6 | ** Connect to website and get the International Space Station pass times 7 | ***************************************************************************************/ 8 | void SpaceStation::getPasses(String latitude, String longitude, ISS_pass* passData) 9 | { 10 | this->passData = passData; // Make a copy of the pointer for this class 11 | 12 | JSON_Decoder json; // Create an instance of the parser 13 | json.setListener(this); // Pass pointer to "this" SpaceStation class to the listener 14 | // so it can call the support functions in this class 15 | 16 | // Use WiFiClient class to create TCP connections 17 | WiFiClient client; 18 | 19 | // URL and port of the server 20 | const char* host = "api.open-notify.org"; 21 | const int httpPort = 80; 22 | 23 | // Connect as a client to the server 24 | if (!client.connect(host, httpPort)) { 25 | Serial.println("connection failed"); 26 | return; 27 | } 28 | 29 | // Built up the GET request 30 | String url = "http://api.open-notify.org/iss-pass.json?lat=" + latitude + "&lon=" + longitude + "&n=" + PASSES; 31 | 32 | // Send GET request 33 | Serial.println("\nSending GET request to api.open-notify.org...\n"); 34 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); 35 | 36 | // Local variables for time-out etc 37 | uint32_t timeout = millis(); 38 | char c = 0; 39 | uint16_t ccount = 0; 40 | 41 | Serial.println("=====> Header start <====="); 42 | 43 | // Read the header that precedes the JSON, ends with \r\n 44 | while ( client.available() > 0 || client.connected()) 45 | { 46 | String line = client.readStringUntil('\n'); 47 | if (line == "\r") { 48 | Serial.println("=====> Header end <====="); 49 | break; 50 | } 51 | 52 | Serial.println(line); // Print the header to serial monitor for viewing 53 | 54 | // Check for timeout 55 | if ((millis() - timeout) > 5000UL) 56 | { 57 | Serial.println ("HTTP header timeout"); 58 | client.stop(); 59 | return; 60 | } 61 | } 62 | 63 | // The JSON message should now be in the buffer for reading 64 | 65 | // Read the JSON character by character and pass it to the JSON decoder 66 | // The decoder will call the SpaceStation class during decoding, so we can 67 | // save the decoded values 68 | 69 | // Use OR since data may still be in the buffer when the client has disconnected! 70 | while ( client.available() > 0 || client.connected()) 71 | { 72 | while(client.available() > 0) 73 | { 74 | c = client.read(); // Read a received character 75 | 76 | #ifdef SHOW_JSON // Optionally show the message with a simple formatter 77 | Serial.print(c); 78 | #endif 79 | 80 | json.parse(c); // Pass to the parser, parser will call listener support functions as needed 81 | 82 | // Check for timeout 83 | if ((millis() - timeout) > 8000UL) 84 | { 85 | Serial.println ("JSON parse client timeout"); 86 | json.reset(); 87 | client.stop(); 88 | return; 89 | } 90 | yield(); 91 | } 92 | } 93 | 94 | json.reset(); 95 | 96 | client.stop(); 97 | 98 | } 99 | 100 | /*************************************************************************************** 101 | ** JSON Decoder library calls this when a key has been read 102 | ***************************************************************************************/ 103 | void SpaceStation::key(const char *key) { 104 | currentKey = key; 105 | 106 | //Serial.print("key: "); 107 | //Serial.println(key); 108 | } 109 | 110 | /*************************************************************************************** 111 | ** JSON Decoder library calls this when a value has been read 112 | ***************************************************************************************/ 113 | void SpaceStation::value(const char *value) { 114 | 115 | String val = value; 116 | 117 | // Test only: 118 | //Serial.print("\nvaluePath :"); Serial.println(valuePath); 119 | //Serial.print("currentParent :"); Serial.println(currentParent); 120 | //Serial.print("currentKey :"); Serial.println(currentKey); 121 | //Serial.print("arrayIndex :"); Serial.println(arrayIndex); 122 | //Serial.print("Value :"); Serial.println(val); 123 | 124 | if (currentParent == "") 125 | { 126 | if (currentKey == "message") passData->message = val; 127 | return; 128 | } 129 | 130 | else 131 | if (currentParent == "request") 132 | { 133 | if (currentKey == "datetime") passData->datetime = (uint32_t)val.toInt(); 134 | } 135 | 136 | else 137 | if (valuePath == "/response") 138 | { 139 | if (currentKey == "duration") passData->passDuration[arrayIndex] = (uint16_t)val.toInt(); 140 | else 141 | if (currentKey == "risetime") passData->passRiseTime[arrayIndex] = (uint32_t)val.toInt(); 142 | } 143 | } 144 | 145 | /*************************************************************************************** 146 | ** JSON Decoder library calls this when a start of document decoded 147 | ***************************************************************************************/ 148 | void SpaceStation::startDocument() { 149 | currentParent = currentKey = ""; 150 | arrayIndex = 0; 151 | ended = false; 152 | 153 | //Serial.println("\nstart document"); 154 | } 155 | 156 | /*************************************************************************************** 157 | ** JSON Decoder library calls this when a end of document decoded 158 | ***************************************************************************************/ 159 | void SpaceStation::endDocument() { 160 | ended = true; 161 | 162 | //Serial.println("end document. "); 163 | } 164 | 165 | /*************************************************************************************** 166 | ** JSON Decoder library calls this when a start of object decoded 167 | ***************************************************************************************/ 168 | void SpaceStation::startObject() { 169 | currentParent = currentKey; 170 | 171 | //Serial.println("start object. "); 172 | } 173 | 174 | /*************************************************************************************** 175 | ** JSON Decoder library calls this when a end of object decoded 176 | ***************************************************************************************/ 177 | void SpaceStation::endObject() { 178 | currentParent = ""; 179 | arrayIndex++; 180 | 181 | //Serial.println("end object. "); 182 | } 183 | 184 | /*************************************************************************************** 185 | ** JSON Decoder library calls this when an array of values has started 186 | ***************************************************************************************/ 187 | void SpaceStation::startArray() { 188 | arrayIndex = 0; 189 | valuePath = currentParent + "/" + currentKey; 190 | 191 | //Serial.println("start array. "); 192 | } 193 | 194 | /*************************************************************************************** 195 | ** JSON Decoder library calls this when an array of values has ended 196 | ***************************************************************************************/ 197 | void SpaceStation::endArray() { 198 | valuePath = ""; 199 | 200 | //Serial.println("end array. "); 201 | } 202 | 203 | /*************************************************************************************** 204 | ** JSON Decoder library calls this when a character is whitespace (not used here) 205 | ***************************************************************************************/ 206 | // whitespace(char c) not used, JSON Listener substitutes a dummy function 207 | //void SpaceStation::whitespace(char c) { 208 | 209 | //Serial.println("whitespace"); 210 | //} 211 | 212 | /*************************************************************************************** 213 | ** JSON Decoder library calls this when a decoding error occurs 214 | ***************************************************************************************/ 215 | void SpaceStation::error( const char *message ) { 216 | if (ended == false) // Only report errors in a document 217 | { 218 | Serial.print("\nError message: "); 219 | Serial.println(message); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /examples/Space_Station/ISS_API_Class.h: -------------------------------------------------------------------------------- 1 | // Header for Space Station class which supports the main sketch 2 | 3 | #define SHOW_JSON // Print the JSON to the serial monitor 4 | #define PASSES 7 // Number of ISS passes to fetch 5 | 6 | // Choose the WiFi library to load 7 | #ifdef ESP8266 8 | #include // Built in library for ESP8266 9 | #else // ESP32 10 | #include // Built in library for ESP32 11 | #endif 12 | 13 | #include // Built in library 14 | 15 | #include // Decoder library https://github.com/Bodmer/JSON_Decoder 16 | 17 | #include // This is part of the JSON_Decoder library 18 | 19 | // Structure to hold the parsed values 20 | typedef struct ISS_pass { 21 | String message; // Message, usually "sucess" 22 | uint32_t datetime = 0; // Time of request, UTC unix time in seconds since Jan 01 1970 23 | uint16_t passDuration[PASSES] = { 0 }; // Pass duration in seconds 24 | uint32_t passRiseTime[PASSES] = { 0 }; // Time of pass, UTC unix time 25 | } ISS_pass; 26 | 27 | class SpaceStation: public JsonListener { 28 | 29 | public: 30 | 31 | void getPasses(String latitude, String longitude, ISS_pass* passData); 32 | 33 | // Start of Listener support functions 34 | void key(const char *key); 35 | 36 | void value(const char *value); 37 | 38 | void startDocument(); 39 | 40 | void endDocument(); 41 | 42 | void startObject(); 43 | 44 | void endObject(); 45 | 46 | void startArray(); 47 | 48 | void endArray(); 49 | 50 | // void whitespace(char c); // No need to include functions if not used 51 | 52 | void error( const char *message ); 53 | // End of Listener support functions 54 | 55 | private: 56 | 57 | bool ended = true; // Flag to indicate document has ended 58 | 59 | String currentParent; // Current object e.g. "request" 60 | 61 | String currentKey; // Name key of the name:value pair e.g "temperature" 62 | 63 | uint16_t arrayIndex; // Array index 0-N e.g. 4 for 5th pass, qualify with valuePath 64 | 65 | String valuePath; // object (i.e. sequential key) path (like a "file path") 66 | // taken to the name:value pair in the form "/response" 67 | // so values can be pulled from the correct array. 68 | // Needed since different objects contain "data" arrays. 69 | 70 | ISS_pass *passData; // pointer provided by sketch 71 | }; 72 | -------------------------------------------------------------------------------- /examples/Space_Station/Space_Station.ino: -------------------------------------------------------------------------------- 1 | // This is an example sketch for the JSON_Decoder library: 2 | // https://github.com/Bodmer/JSON_Decoder 3 | 4 | // It connects to a website, requests a list of International Space Station passes that might 5 | // be visible, decodes (parses) the returned JSON message and prints the results to the 6 | // serial monitor window. 7 | 8 | // The sketch uses WiFi to connect to your router and sends a GET request to this 9 | // website which responds with pass times and duration: 10 | // http://open-notify.org/Open-Notify-API/ISS-Pass-Times/ 11 | // This site is convenient for use in an example becuase you do not need to setup an 12 | // account and the API requests per day is not limited. The website also outputs quite a 13 | // simple JSON message with line feeds etc, so it prints nicely. 14 | 15 | // Click the following link to fetch an example JSON message and see it in a browser: 16 | // http://api.open-notify.org/iss-pass.json?lat=27.9881&lon=86.9250 17 | 18 | // An alternative ISS pass time API website is here is you wish to adapt the sketch: 19 | // https://wheretheiss.at/w/developer 20 | 21 | // To support the example a C++ class is used, this could be in a library of it's own but 22 | // it is attached in the ISS_API_Class tabs of this sketch. 23 | 24 | // Request every 5 minutes for demonstration only, ISS pass times are not updated often 25 | const int UPDATE_INTERVAL_SECS = 5 * 60UL; // 5 minutes 26 | 27 | // >>>>>>>>>>>> Change to suit your WiFi router <<<<<<<<<<<< NOTE 28 | #define WIFI_SSID "Your_SSID" 29 | #define SSID_PASSWORD "Your_password" 30 | 31 | // >>>>>>>>>>>> Change to your location <<<<<<<<<<<< NOTE 32 | // Set the latitude and longitude to at least 4 decimal places, website server bug means it 33 | // will not accept 0.0000 but 0.0001 is OK, try 0.0000 to show error handling! 34 | //const String latitude = "27.9881"; // 90.0000 to -90.0000 negative for Southern hemisphere 35 | //const String longitude = "86.9250"; // 180.000 to -180.000 negative for West 36 | const String latitude = "0.0000001"; // near Null Island 37 | const String longitude = "0.0000001"; // 38 | 39 | #include // https://github.com/PaulStoffregen/Time/blob/master/TimeLib.h 40 | 41 | // Choose the WiFi library to load 42 | #ifdef ESP8266 43 | #include 44 | #else // ESP32 45 | #include 46 | #endif 47 | 48 | #include 49 | 50 | #include "ISS_API_Class.h" // Local sketch functions 51 | 52 | SpaceStation api; // Create an instance of this sketches support class 53 | 54 | WiFiUDP udp; // A UDP instance to send and receive packets 55 | 56 | ISS_pass *pass_data; // Pointer to struct that will contain decoded values 57 | 58 | uint32_t nextUpdate = 0; // Time of next update request 59 | 60 | /*************************************************************************************** 61 | ** Setup 62 | ***************************************************************************************/ 63 | void setup() { 64 | Serial.begin(115200); 65 | 66 | WiFi.begin(WIFI_SSID, SSID_PASSWORD); 67 | 68 | while (WiFi.status() != WL_CONNECTED) { 69 | delay(1000); 70 | Serial.print("."); 71 | } 72 | Serial.println(); 73 | 74 | unsigned int localPort = 2390; // local port to listen to for UDP packets 75 | 76 | udp.begin(localPort); 77 | 78 | nextUpdate = millis(); // Set so next update happens immediately 79 | } 80 | 81 | /*************************************************************************************** 82 | ** Loop 83 | ***************************************************************************************/ 84 | void loop() { 85 | 86 | // Check if it is time for an update 87 | if (millis() >= nextUpdate) 88 | { 89 | // Set next update time 90 | nextUpdate = millis() + 1000UL * UPDATE_INTERVAL_SECS; 91 | 92 | // Connect to server and fetch ISS pass time 93 | getPassTimes(latitude, longitude); 94 | } 95 | 96 | } 97 | 98 | /*************************************************************************************** 99 | ** Get the International Space Station pass times 100 | ***************************************************************************************/ 101 | void getPassTimes(String latitude, String longitude) 102 | { 103 | // Create a new struct to contain the decoded values 104 | pass_data = new ISS_pass; 105 | 106 | uint32_t dt = millis(); // Time how long it takes out of interest only 107 | 108 | // Request passes from website API, provide location and pointer to struct 109 | api.getPasses(latitude, longitude, pass_data); 110 | 111 | Serial.print("\nJSON response parsed in "); 112 | Serial.print(millis()-dt); 113 | Serial.println(" ms\n"); 114 | 115 | // Print pass details while struct still exists 116 | printPasses(pass_data); 117 | 118 | // Delete the struct to recover the memory used 119 | delete pass_data; 120 | } 121 | 122 | /*************************************************************************************** 123 | ** Retrieve data from struct and print to serial monitor 124 | ***************************************************************************************/ 125 | void printPasses(ISS_pass* pass_data) 126 | { 127 | Serial.print("Message = "); Serial.println(pass_data->message); 128 | Serial.print("Date = "); Serial.println(strDate(pass_data->datetime)); 129 | Serial.println(); 130 | 131 | for (int i = 0; i < PASSES; i++ ) 132 | { 133 | Serial.print("Pass = "); Serial.println( i+1 ); 134 | Serial.print("Duration = "); Serial.print(pass_data->passDuration[i]); Serial.println( " s" ); 135 | Serial.print("Rise time = "); Serial.println(strDate(pass_data->passRiseTime[i])); 136 | } 137 | } 138 | 139 | /*************************************************************************************** 140 | ** Convert unix time to a local date + time string "Oct 16 17:18", ends with newline 141 | ***************************************************************************************/ 142 | String strDate(time_t utc) 143 | { 144 | setTime(utc); // Set system clock to utc time (not time zone compensated) 145 | 146 | String localDate = ""; 147 | 148 | localDate += monthShortStr(month(utc)); 149 | localDate += " "; 150 | localDate += day(utc); 151 | localDate += " " + strTime(utc); 152 | 153 | return localDate; 154 | } 155 | 156 | /*************************************************************************************** 157 | ** Convert unix time to a "local time" time string "12:34" 158 | ***************************************************************************************/ 159 | String strTime(time_t unixTime) 160 | { 161 | String localTime = ""; 162 | 163 | if (hour(unixTime) < 10) localTime += "0"; 164 | localTime += hour(unixTime); 165 | localTime += ":"; 166 | if (minute(unixTime) < 10) localTime += "0"; 167 | localTime += minute(unixTime); 168 | 169 | return localTime; 170 | } 171 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | JSON_Decoder KEYWORD1 2 | 3 | init KEYWORD2 4 | parse KEYWORD2 5 | setListener KEYWORD2 6 | reset KEYWORD2 7 | whitespace KEYWORD2 8 | startDocument KEYWORD2 9 | key KEYWORD2 10 | value KEYWORD2 11 | endArray KEYWORD2 12 | endObject KEYWORD2 13 | endDocument KEYWORD2 14 | startArray KEYWORD2 15 | startObject KEYWORD2 16 | error KEYWORD2 17 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JSON_Decoder", 3 | "version": "0.1.1", 4 | "keywords": "json, streaming, parser, decoder", 5 | "description": "A memory efficient library to parse (large) JSON objects", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Bodmer/JSON_Decoder" 9 | }, 10 | "authors": [{ 11 | "name": "Bodmer", 12 | "email": "bodmer@anola.net", 13 | "maintainer": true 14 | }], 15 | "frameworks": "arduino", 16 | "platforms": "*", 17 | "headers": "JSON_Decoder.h" 18 | } 19 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=JSON_Decoder 2 | version=0.1.1 3 | author=Bodmer, original by Daniel Eichhorn 4 | maintainer=Bodmer 5 | sentence=A memory efficient library to parse (large) JSON objects 6 | paragraph=A memory efficient library to parse (large) JSON objects 7 | category=Data Processing 8 | url=https://github.com/Bodmer/JSON_Decoder 9 | architectures=* 10 | --------------------------------------------------------------------------------