├── .gitignore ├── project.properties ├── electron.bin ├── LICENSE ├── README.md └── clouddebug-electron.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | dependencies.CellularHelper=0.0.4 2 | -------------------------------------------------------------------------------- /electron.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rickkas7/electron-clouddebug/HEAD/electron.bin -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 rickkas7 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 | # Electron Cloud Debug 2 | 3 | *Special code for debugging cloud connection issues with the Particle Electron* 4 | 5 | ## Wait! 6 | 7 | There is a newer and better version of cloud debug in the Particle Github. You should use that version instead: 8 | 9 | [https://github.com/particle-iot/cloud-debug](https://github.com/particle-iot/cloud-debug) 10 | 11 | ## What is this? 12 | 13 | This is a tool to debug cloud connection issues. It: 14 | 15 | - Prints out cellular parameters (ICCID, etc.) 16 | - Prints out your cellular carrier and frequency band information 17 | - Pings your IP gateway 18 | - Pings the Google DNS (8.8.8.8) 19 | - Does a DNS server lookup of the device server (device.spark.io) 20 | - Makes an actual cloud connection 21 | - Acts like Tinker after connecting 22 | - Can print out information about nearby cellular towers 23 | 24 | It uses a special debug version of the system firmware, so there's additional debugging information generated as well. 25 | 26 | Here's a bit of the output log: 27 | 28 | ``` 29 | service rat=GSM mcc=310, mnc=11094, lac=2 ci=a782 band=GSM 850 bsic=3b arfcn=ed rxlev=40 30 | neighbor 0 rat=GSM mcc=310, mnc=11094, lac=80592df ci=a56f band=GSM 850 bsic=18 arfcn=eb rxlev=37 31 | neighbor 1 rat=GSM mcc=310, mnc=11094, lac=100 ci=a5f2 band=GSM 850 bsic=25 arfcn=b4 rxlev=22 32 | 15.443 AT send 20 "AT+UPING=\"8.8.8.8\"\r\n" 33 | 15.443 AT read + 14 "\r\n+CIEV: 2,2\r\n" 34 | 15.484 AT read OK 6 "\r\nOK\r\n" 35 | ping addr 8.8.8.8=1 36 | 15.484 AT send 31 "AT+UDNSRN=0,\"device.spark.io\"\r\n" 37 | 17.204 AT read + 67 "\r\n+UUPING: 1,32,\"google-public-dns-a.google.com\",\"8.8.8.8\",55,812\r\n" 38 | 17.865 AT read + 67 "\r\n+UUPING: 2,32,\"google-public-dns-a.google.com\",\"8.8.8.8\",55,651\r\n" 39 | 17.936 AT read + 27 "\r\n+UDNSRN: \"52.91.48.237\"\r\n" 40 | 17.946 AT read OK 6 "\r\nOK\r\n" 41 | ``` 42 | 43 | The source code is [here](https://github.com/rickkas7/electron-clouddebug/blob/master/clouddebug-electron.cpp) so you can see how it works. 44 | 45 | ## Prerequisites 46 | 47 | - You should have the [Particle CLI](https://docs.particle.io/guide/tools-and-features/cli/photon/) version 1.49.0 or later installed. 48 | - To update the CLI, use the command: `particle update-cli`. 49 | 50 | ## To Install - Electron or E Series: 51 | 52 | If your Electron or E Series is not running Device OS 1.4.2 or later you should upgrade. If you're not sure, it doesn't hurt to do this step: 53 | 54 | Put the device in DFU mode (blinking yellow) by holding down RESET and MODE, releasing RESET while continuing to hold down MODE. The main status LED will blink magenta (blue and red at the same time), then yellow. Once blinking yellow, release SETUP. 55 | 56 | From a Command Prompt or Terminal window: 57 | 58 | ``` 59 | particle update 60 | ``` 61 | 62 | Download the [electron.bin](https://github.com/rickkas7/electron-clouddebug/raw/master/electron.bin) file. Click on that link and then the Download button on the page that displays, don't just right click and Save Link As. 63 | 64 | If device is not blinking yellow, repeat the steps above to put it back in DFU mode, then: 65 | 66 | ``` 67 | particle flash --usb electron.bin 68 | ``` 69 | 70 | The Electron will restart. Immediately open a serial window. For example, using the CLI: 71 | 72 | ``` 73 | particle serial monitor 74 | ``` 75 | 76 | For Windows 10, to copy and paste out of the Command Prompt window, press Control-M (Mark), click and the end and drag to the beginning of where you want to copy. Then press Enter to copy the text. 77 | 78 | ## Doing a carrier scan 79 | 80 | If you are stuck at blinking green, it can be helpful to do a tower or carrier scan to see if any towers are visible. 81 | 82 | Reset the Electron and immediately open the particle serial monitor. 83 | 84 | The Electron will breathe white. Tap the MODE button once and a carrier scan will begin. You must do this within the first 10 seconds of startup otherwise the normal tests will be done. 85 | 86 | 87 | ## Running Advanced Tests 88 | 89 | If you connect using particle serial monitor, the default options are used. If you want to use a custom APN or keep-alive you need to use a terminal program that allows you to send USB serial data, such as: 90 | 91 | - PuTTY or CoolTerm (Windows) 92 | - screen (Mac or Linux) 93 | - Particle Dev (Atom IDE) Serial Monitor 94 | - Arduino serial monitor 95 | 96 | There's more information on using serial terminals [here](https://github.com/rickkas7/serial_tutorial). 97 | 98 | Once you connect to the serial terminal you can press Return or wait a few seconds and you should get a menu: 99 | 100 | ``` 101 | clouddebug: press letter corresponding to the command 102 | a - enter APN for 3rd-party SIM card 103 | k - set keep-alive value 104 | c - show carriers at this location 105 | t - run normal tests (occurs automatically after 10 seconds) 106 | or tap the MODE button once to show carriers 107 | ``` 108 | 109 | If you do nothing, the t option (run normal tests) is run, which behaves like the previous version of cloud debug. 110 | 111 | If you press c (show carriers at this location), the program will scan nearby towers and show the carriers, frequencies, and signal strength. This takes several minutes to run, which is why it's not done by default. 112 | 113 | If you are using a 3rd-party SIM card, you can set the APN and keep-alive values using the a and k options, respectively. 114 | 115 | 116 | ## To Remove 117 | 118 | Put the Electron in DFU mode (blinking yellow) by pressing RESET and SETUP. Release RESET and continue to hold down SETUP while the LED blinks magenta until it blinks yellow, then release SETUP. 119 | 120 | ``` 121 | particle flash --usb tinker 122 | ``` 123 | 124 | Or flash your own firmware instead. 125 | 126 | 127 | -------------------------------------------------------------------------------- /clouddebug-electron.cpp: -------------------------------------------------------------------------------- 1 | #include "Particle.h" 2 | 3 | #include "CellularHelper.h" 4 | 5 | // In order to use this with full debugging, you need a debug build of system firmware 6 | // as well as this app. 7 | 8 | // Adds debugging output over Serial USB 9 | // ALL_LEVEL, TRACE_LEVEL, DEBUG_LEVEL, INFO_LEVEL, WARN_LEVEL, ERROR_LEVEL, PANIC_LEVEL, NO_LOG_LEVEL 10 | SerialLogHandler logHandler(LOG_LEVEL_INFO); 11 | 12 | // STARTUP(cellular_credentials_set("broadband", "", "", NULL)); 13 | 14 | SYSTEM_MODE(SEMI_AUTOMATIC); 15 | // SYSTEM_THREAD(ENABLED); 16 | 17 | /* Function prototypes -------------------------------------------------------*/ 18 | int tinkerDigitalRead(String pin); 19 | int tinkerDigitalWrite(String command); 20 | int tinkerAnalogRead(String pin); 21 | int tinkerAnalogWrite(String command); 22 | void buttonHandler(); 23 | void enableTraceLogging(); 24 | void runModemTests(); 25 | void runCellularTests(); 26 | void runTowerTest(); 27 | void printCellData(CellularHelperEnvironmentCellData *data); 28 | 29 | 30 | /* Constants -----------------------------------------------------------------*/ 31 | const unsigned long STARTUP_WAIT_TIME_MS = 5000; 32 | const unsigned long COMMAND_WAIT_TIME_MS = 10000; 33 | const unsigned long MODEM_ON_WAIT_TIME_MS = 4000; 34 | const size_t INPUT_BUF_SIZE = 256; 35 | 36 | /* Global Variables-----------------------------------------------------------*/ 37 | 38 | enum State { STARTUP_STATE, 39 | COMMAND_STATE, COMMAND_WAIT_STATE, 40 | ENTER_APN_STATE, ENTER_KEEPALIVE_STATE, 41 | CELLULAR_ON_STATE, CELLULAR_ON_WAIT_STATE, MODEM_REPORT_STATE, 42 | CONNECT_WAIT_STATE, CONNECT_REPORT_STATE, 43 | CLOUD_CONNECT_WAIT_STATE, CLOUD_CONNECTED_STATE, 44 | TOWER_REPORT_STATE, 45 | DISCONNECT_STATE, IDLE_STATE }; 46 | State state = STARTUP_STATE; 47 | State postCellularOnState; 48 | unsigned long stateTime = 0; 49 | CellularHelperEnvironmentResponseStatic<32> envResp; 50 | size_t inputBufferOffset; 51 | char inputBuffer[INPUT_BUF_SIZE]; 52 | int keepAlive = 0; 53 | bool buttonClicked = false; 54 | 55 | /* This function is called once at start up ----------------------------------*/ 56 | void setup() 57 | { 58 | Serial.begin(9600); 59 | 60 | System.on(button_click, buttonHandler); 61 | 62 | // When using a 3rd-party SIM card, be sure to set the keepAlive 63 | //Particle.keepAlive(60); 64 | 65 | //Setup the Tinker application here 66 | 67 | //Register all the Tinker functions 68 | Particle.function("digitalread", tinkerDigitalRead); 69 | Particle.function("digitalwrite", tinkerDigitalWrite); 70 | 71 | Particle.function("analogread", tinkerAnalogRead); 72 | Particle.function("analogwrite", tinkerAnalogWrite); 73 | } 74 | 75 | /* This function loops forever --------------------------------------------*/ 76 | void loop() { 77 | int serialData; 78 | 79 | 80 | switch(state) { 81 | 82 | case STARTUP_STATE: 83 | if (millis() - stateTime >= STARTUP_WAIT_TIME_MS) { 84 | state = COMMAND_STATE; 85 | stateTime = millis(); 86 | } 87 | break; 88 | 89 | case COMMAND_STATE: 90 | Serial.println("clouddebug: press letter corresponding to the command"); 91 | Serial.println("a - enter APN for 3rd-party SIM card"); 92 | Serial.println("k - set keep-alive value"); 93 | Serial.println("c - show carriers at this location"); 94 | Serial.println("t - run normal tests (occurs automatically after 10 seconds)"); 95 | Serial.println("or tap the MODE button once to show carriers"); 96 | state = COMMAND_WAIT_STATE; 97 | stateTime = millis(); 98 | break; 99 | 100 | case COMMAND_WAIT_STATE: 101 | if (buttonClicked) { 102 | buttonClicked = false; 103 | 104 | Serial.printlnf("starting carrier report..."); 105 | postCellularOnState = TOWER_REPORT_STATE; 106 | state = CELLULAR_ON_STATE; 107 | stateTime = millis(); 108 | } 109 | else 110 | if (millis() - stateTime >= COMMAND_WAIT_TIME_MS) { 111 | // No command entered in the timeout time 112 | Serial.printlnf("starting tests..."); 113 | postCellularOnState = MODEM_REPORT_STATE; 114 | state = CELLULAR_ON_STATE; 115 | stateTime = millis(); 116 | break; 117 | } 118 | while(Serial.available()) { 119 | serialData = Serial.read(); 120 | switch(serialData) { 121 | case 'A': 122 | case 'a': 123 | Serial.print("enter APN: "); 124 | state = ENTER_APN_STATE; 125 | inputBufferOffset = 0; 126 | break; 127 | 128 | case 'K': 129 | case 'k': 130 | Serial.print("enter keep-alive value in seconds (typically 30 to 1380): "); 131 | state = ENTER_KEEPALIVE_STATE; 132 | inputBufferOffset = 0; 133 | break; 134 | 135 | case 'C': 136 | case 'c': 137 | Serial.printlnf("starting carrier report..."); 138 | postCellularOnState = TOWER_REPORT_STATE; 139 | state = CELLULAR_ON_STATE; 140 | stateTime = millis(); 141 | break; 142 | 143 | case 'T': 144 | case 't': 145 | Serial.printlnf("starting tests..."); 146 | postCellularOnState = MODEM_REPORT_STATE; 147 | state = CELLULAR_ON_STATE; 148 | stateTime = millis(); 149 | break; 150 | 151 | case '\r': 152 | case '\n': 153 | // Repeat help 154 | state = COMMAND_STATE; 155 | break; 156 | } 157 | } 158 | break; 159 | 160 | case ENTER_KEEPALIVE_STATE: 161 | case ENTER_APN_STATE: 162 | while(Serial.available()) { 163 | serialData = Serial.read(); 164 | if (serialData == '\r' || serialData == '\n') { 165 | if (inputBufferOffset > 0) { 166 | inputBuffer[inputBufferOffset] = 0; 167 | Serial.println(); 168 | 169 | if (state == ENTER_APN_STATE) { 170 | cellular_credentials_set(inputBuffer, "", "", NULL); 171 | Serial.printlnf("APN set to %s", inputBuffer); 172 | } 173 | else { 174 | keepAlive = atoi(inputBuffer); 175 | if (keepAlive >= 10) { 176 | Serial.printlnf("keepalive will be set to %d", keepAlive); 177 | } 178 | } 179 | state = COMMAND_STATE; 180 | stateTime = millis(); 181 | } 182 | } 183 | else 184 | if (inputBufferOffset < (INPUT_BUF_SIZE - 1)) { 185 | inputBuffer[inputBufferOffset++] = serialData; 186 | } 187 | } 188 | break; 189 | 190 | case CELLULAR_ON_STATE: 191 | Serial.println("turning cellular on..."); 192 | Cellular.on(); 193 | state = CELLULAR_ON_WAIT_STATE; 194 | stateTime = millis(); 195 | break; 196 | 197 | case CELLULAR_ON_WAIT_STATE: 198 | if (millis() - stateTime >= MODEM_ON_WAIT_TIME_MS) { 199 | state = postCellularOnState; 200 | } 201 | break; 202 | 203 | case MODEM_REPORT_STATE: 204 | // Print things like the ICCID 205 | runModemTests(); 206 | 207 | // Turn trace level on now. We leave it off for carrier report as it prints too much junk. 208 | enableTraceLogging(); 209 | 210 | Serial.println("attempting to connect to the cellular network..."); 211 | Cellular.connect(); 212 | 213 | state = CONNECT_WAIT_STATE; 214 | stateTime = millis(); 215 | break; 216 | 217 | case CONNECT_WAIT_STATE: 218 | if (Cellular.ready()) { 219 | unsigned long elapsed = millis() - stateTime; 220 | 221 | Serial.printlnf("connected to the cellular network in %lu milliseconds", elapsed); 222 | state = CONNECT_REPORT_STATE; 223 | stateTime = millis(); 224 | } 225 | else 226 | if (Cellular.listening()) { 227 | Serial.println("entered listening mode (blinking dark blue) - probably no SIM installed"); 228 | state = IDLE_STATE; 229 | } 230 | 231 | break; 232 | 233 | case TOWER_REPORT_STATE: 234 | runTowerTest(); 235 | state = COMMAND_STATE; 236 | break; 237 | 238 | case CONNECT_REPORT_STATE: 239 | { 240 | Serial.println("connected to cellular network!"); 241 | runCellularTests(); 242 | 243 | Serial.println("connecting to cloud"); 244 | Particle.connect(); 245 | } 246 | 247 | state = CLOUD_CONNECT_WAIT_STATE; 248 | break; 249 | 250 | case CLOUD_CONNECT_WAIT_STATE: 251 | if (Particle.connected()) { 252 | Serial.println("connected to the cloud!"); 253 | state = CLOUD_CONNECTED_STATE; 254 | 255 | if (keepAlive >= 10) { 256 | Log.info("keepAlive set to %d", keepAlive); 257 | Particle.keepAlive(keepAlive); 258 | } 259 | 260 | } 261 | break; 262 | 263 | case IDLE_STATE: 264 | case CLOUD_CONNECTED_STATE: 265 | break; 266 | 267 | } 268 | 269 | } 270 | 271 | void buttonHandler() { 272 | buttonClicked = true; 273 | } 274 | 275 | void enableTraceLogging() { 276 | Log.info("enabling trace logging"); 277 | 278 | // Get log manager's instance 279 | auto logManager = LogManager::instance(); 280 | 281 | // Configure and register log handler dynamically 282 | auto handler = new StreamLogHandler(Serial, LOG_LEVEL_TRACE); 283 | logManager->addHandler(handler); 284 | } 285 | 286 | void runModemTests() { 287 | Serial.printlnf("deviceID=%s", System.deviceID().c_str()); 288 | 289 | Serial.printlnf("manufacturer=%s", CellularHelper.getManufacturer().c_str()); 290 | 291 | Serial.printlnf("model=%s", CellularHelper.getModel().c_str()); 292 | 293 | Serial.printlnf("firmware version=%s", CellularHelper.getFirmwareVersion().c_str()); 294 | 295 | Serial.printlnf("ordering code=%s", CellularHelper.getOrderingCode().c_str()); 296 | 297 | Serial.printlnf("IMEI=%s", CellularHelper.getIMEI().c_str()); 298 | 299 | Serial.printlnf("IMSI=%s", CellularHelper.getIMSI().c_str()); 300 | 301 | Serial.printlnf("ICCID=%s", CellularHelper.getICCID().c_str()); 302 | 303 | } 304 | 305 | 306 | // Various tests to find out information about the cellular network we connected to 307 | void runCellularTests() { 308 | 309 | Serial.printlnf("operator name=%s", CellularHelper.getOperatorName().c_str()); 310 | 311 | CellularHelperRSSIQualResponse rssiQual = CellularHelper.getRSSIQual(); 312 | Serial.printlnf("rssi=%d, qual=%d", rssiQual.rssi, rssiQual.qual); 313 | 314 | // First try to get info on neighboring cells. This doesn't work for me using the U260 315 | envResp.clear(); 316 | CellularHelper.getEnvironment(5, envResp); 317 | 318 | if (envResp.resp != RESP_OK) { 319 | // We couldn't get neighboring cells, so try just the receiving cell 320 | CellularHelper.getEnvironment(3, envResp); 321 | } 322 | envResp.logResponse(); 323 | 324 | } 325 | 326 | class OperatorName { 327 | public: 328 | int mcc; 329 | int mnc; 330 | String name; 331 | }; 332 | 333 | class CellularHelperCOPNResponse : public CellularHelperCommonResponse { 334 | public: 335 | CellularHelperCOPNResponse(); 336 | 337 | void requestOperator(CellularHelperEnvironmentCellData *data); 338 | void requestOperator(int mcc, int mnc); 339 | void checkOperator(char *buf); 340 | const char *getOperatorName(int mcc, int mnc) const; 341 | 342 | virtual int parse(int type, const char *buf, int len); 343 | 344 | // Maximum number of operator names that can be looked up 345 | static const size_t MAX_OPERATORS = 16; 346 | 347 | private: 348 | size_t numOperators; 349 | OperatorName operators[MAX_OPERATORS]; 350 | }; 351 | CellularHelperCOPNResponse copnResp; 352 | 353 | 354 | CellularHelperCOPNResponse::CellularHelperCOPNResponse() : numOperators(0) { 355 | 356 | } 357 | 358 | void CellularHelperCOPNResponse::requestOperator(CellularHelperEnvironmentCellData *data) { 359 | if (data && data->isValid(true)) { 360 | requestOperator(data->mcc, data->mnc); 361 | } 362 | } 363 | 364 | void CellularHelperCOPNResponse::requestOperator(int mcc, int mnc) { 365 | for(size_t ii = 0; ii < numOperators; ii++) { 366 | if (operators[ii].mcc == mcc && operators[ii].mnc == mnc) { 367 | // Already requested 368 | return; 369 | } 370 | } 371 | if (numOperators < MAX_OPERATORS) { 372 | // There is room to request another 373 | operators[numOperators].mcc = mcc; 374 | operators[numOperators].mnc = mnc; 375 | numOperators++; 376 | } 377 | } 378 | 379 | const char *CellularHelperCOPNResponse::getOperatorName(int mcc, int mnc) const { 380 | for(size_t ii = 0; ii < numOperators; ii++) { 381 | if (operators[ii].mcc == mcc && operators[ii].mnc == mnc) { 382 | return operators[ii].name.c_str(); 383 | } 384 | } 385 | return "unknown"; 386 | } 387 | 388 | 389 | void CellularHelperCOPNResponse::checkOperator(char *buf) { 390 | if (buf[0] == '"') { 391 | char *numStart = &buf[1]; 392 | char *numEnd = strchr(numStart, '"'); 393 | if (numEnd && ((numEnd - numStart) == 6)) { 394 | char temp[4]; 395 | temp[3] = 0; 396 | strncpy(temp, numStart, 3); 397 | int mcc = atoi(temp); 398 | 399 | strncpy(temp, numStart + 3, 3); 400 | int mnc = atoi(temp); 401 | 402 | *numEnd = 0; 403 | char *nameStart = strchr(numEnd + 1, '"'); 404 | if (nameStart) { 405 | nameStart++; 406 | char *nameEnd = strchr(nameStart, '"'); 407 | if (nameEnd) { 408 | *nameEnd = 0; 409 | 410 | // Log.info("mcc=%d mnc=%d name=%s", mcc, mnc, nameStart); 411 | 412 | for(size_t ii = 0; ii < numOperators; ii++) { 413 | if (operators[ii].mcc == mcc && operators[ii].mnc == mnc) { 414 | operators[ii].name = String(nameStart); 415 | } 416 | } 417 | } 418 | } 419 | } 420 | } 421 | } 422 | 423 | 424 | int CellularHelperCOPNResponse::parse(int type, const char *buf, int len) { 425 | if (enableDebug) { 426 | logCellularDebug(type, buf, len); 427 | } 428 | if (type == TYPE_PLUS) { 429 | // Copy to temporary string to make processing easier 430 | char *copy = (char *) malloc(len + 1); 431 | if (copy) { 432 | strncpy(copy, buf, len); 433 | copy[len] = 0; 434 | 435 | /* 436 | 0000018684 [app] INFO: +COPN: "901012","MCP Maritime Com"\r\n 437 | 0000018694 [app] INFO: cellular response type=TYPE_PLUS len=28 438 | 0000018694 [app] INFO: \r\n 439 | 0000018695 [app] INFO: +COPN: "901021","Seanet"\r\n 440 | 0000018705 [app] INFO: cellular response type=TYPE_ERROR len=39 441 | 0000018705 [app] INFO: \r\n 442 | * 443 | */ 444 | 445 | char *start = strstr(copy, "\n+COPN: "); 446 | if (start) { 447 | start += 8; // length of COPN string 448 | 449 | char *end = strchr(start, '\r'); 450 | if (end) { 451 | *end = 0; 452 | 453 | checkOperator(start); 454 | } 455 | } 456 | 457 | free(copy); 458 | } 459 | } 460 | return WAIT; 461 | } 462 | 463 | void printCellData(CellularHelperEnvironmentCellData *data) { 464 | const char *whichG = data->isUMTS ? "3G" : "2G"; 465 | 466 | // Serial.printlnf("mcc=%d mnc=%d", data->mcc, data->mnc); 467 | 468 | const char *operatorName = copnResp.getOperatorName(data->mcc, data->mnc); 469 | 470 | Serial.printlnf("%s %s %s %d bars", whichG, operatorName, data->getBandString().c_str(), data->getBars()); 471 | } 472 | 473 | 474 | void runTowerTest() { 475 | envResp.clear(); 476 | envResp.enableDebug = true; 477 | 478 | const char *model = CellularHelper.getModel().c_str(); 479 | if (strncmp(model, "SARA-R4", 7) == 0) { 480 | Serial.println("Carrier report not available on LTE (SARA-R4)"); 481 | return; 482 | } 483 | 484 | 485 | Serial.println("looking up operators (this may take up to 3 minutes)..."); 486 | 487 | 488 | // Command may take up to 3 minutes to execute! 489 | envResp.resp = Cellular.command(CellularHelperClass::responseCallback, (void *)&envResp, 360000, "AT+COPS=5\r\n"); 490 | if (envResp.resp == RESP_OK) { 491 | envResp.logResponse(); 492 | 493 | copnResp.requestOperator(&envResp.service); 494 | if (envResp.neighbors) { 495 | for(size_t ii = 0; ii < envResp.numNeighbors; ii++) { 496 | copnResp.requestOperator(&envResp.neighbors[ii]); 497 | } 498 | } 499 | } 500 | 501 | 502 | Serial.printlnf("looking up operator names..."); 503 | 504 | copnResp.enableDebug = false; 505 | copnResp.resp = Cellular.command(CellularHelperClass::responseCallback, (void *)&copnResp, 120000, "AT+COPN\r\n"); 506 | 507 | Serial.printlnf("results..."); 508 | 509 | printCellData(&envResp.service); 510 | if (envResp.neighbors) { 511 | for(size_t ii = 0; ii < envResp.numNeighbors; ii++) { 512 | if (envResp.neighbors[ii].isValid(true /* ignoreCI */)) { 513 | printCellData(&envResp.neighbors[ii]); 514 | } 515 | } 516 | } 517 | } 518 | 519 | 520 | 521 | /******************************************************************************* 522 | * Function Name : tinkerDigitalRead 523 | * Description : Reads the digital value of a given pin 524 | * Input : Pin 525 | * Output : None. 526 | * Return : Value of the pin (0 or 1) in INT type 527 | Returns a negative number on failure 528 | *******************************************************************************/ 529 | int tinkerDigitalRead(String pin) 530 | { 531 | //convert ascii to integer 532 | int pinNumber = pin.charAt(1) - '0'; 533 | //Sanity check to see if the pin numbers are within limits 534 | if (pinNumber < 0 || pinNumber > 7) return -1; 535 | 536 | if(pin.startsWith("D")) 537 | { 538 | pinMode(pinNumber, INPUT_PULLDOWN); 539 | return digitalRead(pinNumber); 540 | } 541 | else if (pin.startsWith("A")) 542 | { 543 | pinMode(pinNumber+10, INPUT_PULLDOWN); 544 | return digitalRead(pinNumber+10); 545 | } 546 | #if Wiring_Cellular 547 | else if (pin.startsWith("B")) 548 | { 549 | if (pinNumber > 5) return -3; 550 | pinMode(pinNumber+24, INPUT_PULLDOWN); 551 | return digitalRead(pinNumber+24); 552 | } 553 | else if (pin.startsWith("C")) 554 | { 555 | if (pinNumber > 5) return -4; 556 | pinMode(pinNumber+30, INPUT_PULLDOWN); 557 | return digitalRead(pinNumber+30); 558 | } 559 | #endif 560 | return -2; 561 | } 562 | 563 | /******************************************************************************* 564 | * Function Name : tinkerDigitalWrite 565 | * Description : Sets the specified pin HIGH or LOW 566 | * Input : Pin and value 567 | * Output : None. 568 | * Return : 1 on success and a negative number on failure 569 | *******************************************************************************/ 570 | int tinkerDigitalWrite(String command) 571 | { 572 | bool value = 0; 573 | //convert ascii to integer 574 | int pinNumber = command.charAt(1) - '0'; 575 | //Sanity check to see if the pin numbers are within limits 576 | if (pinNumber < 0 || pinNumber > 7) return -1; 577 | 578 | if(command.substring(3,7) == "HIGH") value = 1; 579 | else if(command.substring(3,6) == "LOW") value = 0; 580 | else return -2; 581 | 582 | if(command.startsWith("D")) 583 | { 584 | pinMode(pinNumber, OUTPUT); 585 | digitalWrite(pinNumber, value); 586 | return 1; 587 | } 588 | else if(command.startsWith("A")) 589 | { 590 | pinMode(pinNumber+10, OUTPUT); 591 | digitalWrite(pinNumber+10, value); 592 | return 1; 593 | } 594 | #if Wiring_Cellular 595 | else if(command.startsWith("B")) 596 | { 597 | if (pinNumber > 5) return -4; 598 | pinMode(pinNumber+24, OUTPUT); 599 | digitalWrite(pinNumber+24, value); 600 | return 1; 601 | } 602 | else if(command.startsWith("C")) 603 | { 604 | if (pinNumber > 5) return -5; 605 | pinMode(pinNumber+30, OUTPUT); 606 | digitalWrite(pinNumber+30, value); 607 | return 1; 608 | } 609 | #endif 610 | else return -3; 611 | } 612 | 613 | /******************************************************************************* 614 | * Function Name : tinkerAnalogRead 615 | * Description : Reads the analog value of a pin 616 | * Input : Pin 617 | * Output : None. 618 | * Return : Returns the analog value in INT type (0 to 4095) 619 | Returns a negative number on failure 620 | *******************************************************************************/ 621 | int tinkerAnalogRead(String pin) 622 | { 623 | //convert ascii to integer 624 | int pinNumber = pin.charAt(1) - '0'; 625 | //Sanity check to see if the pin numbers are within limits 626 | if (pinNumber < 0 || pinNumber > 7) return -1; 627 | 628 | if(pin.startsWith("D")) 629 | { 630 | return -3; 631 | } 632 | else if (pin.startsWith("A")) 633 | { 634 | return analogRead(pinNumber+10); 635 | } 636 | #if Wiring_Cellular 637 | else if (pin.startsWith("B")) 638 | { 639 | if (pinNumber < 2 || pinNumber > 5) return -3; 640 | return analogRead(pinNumber+24); 641 | } 642 | #endif 643 | return -2; 644 | } 645 | 646 | /******************************************************************************* 647 | * Function Name : tinkerAnalogWrite 648 | * Description : Writes an analog value (PWM) to the specified pin 649 | * Input : Pin and Value (0 to 255) 650 | * Output : None. 651 | * Return : 1 on success and a negative number on failure 652 | *******************************************************************************/ 653 | int tinkerAnalogWrite(String command) 654 | { 655 | String value = command.substring(3); 656 | 657 | if(command.substring(0,2) == "TX") 658 | { 659 | pinMode(TX, OUTPUT); 660 | analogWrite(TX, value.toInt()); 661 | return 1; 662 | } 663 | else if(command.substring(0,2) == "RX") 664 | { 665 | pinMode(RX, OUTPUT); 666 | analogWrite(RX, value.toInt()); 667 | return 1; 668 | } 669 | 670 | //convert ascii to integer 671 | int pinNumber = command.charAt(1) - '0'; 672 | //Sanity check to see if the pin numbers are within limits 673 | 674 | if (pinNumber < 0 || pinNumber > 7) return -1; 675 | 676 | if(command.startsWith("D")) 677 | { 678 | pinMode(pinNumber, OUTPUT); 679 | analogWrite(pinNumber, value.toInt()); 680 | return 1; 681 | } 682 | else if(command.startsWith("A")) 683 | { 684 | pinMode(pinNumber+10, OUTPUT); 685 | analogWrite(pinNumber+10, value.toInt()); 686 | return 1; 687 | } 688 | else if(command.substring(0,2) == "TX") 689 | { 690 | pinMode(TX, OUTPUT); 691 | analogWrite(TX, value.toInt()); 692 | return 1; 693 | } 694 | else if(command.substring(0,2) == "RX") 695 | { 696 | pinMode(RX, OUTPUT); 697 | analogWrite(RX, value.toInt()); 698 | return 1; 699 | } 700 | #if Wiring_Cellular 701 | else if (command.startsWith("B")) 702 | { 703 | if (pinNumber > 3) return -3; 704 | pinMode(pinNumber+24, OUTPUT); 705 | analogWrite(pinNumber+24, value.toInt()); 706 | return 1; 707 | } 708 | else if (command.startsWith("C")) 709 | { 710 | if (pinNumber < 4 || pinNumber > 5) return -4; 711 | pinMode(pinNumber+30, OUTPUT); 712 | analogWrite(pinNumber+30, value.toInt()); 713 | return 1; 714 | } 715 | #endif 716 | else return -2; 717 | } 718 | --------------------------------------------------------------------------------