diff --git a/.gitignore b/.gitignore index 9d2f753..721a025 100644 --- a/.gitignore +++ b/.gitignore @@ -14,16 +14,24 @@ .cproject .project .settings - -# Markers -.development - -# Extras -extras/docs/* - .pioenvs .piolibdeps .clang_complete .gcc-flags.json platformio.ini lib/readme.txt +include/readme.txt +.vscode/* +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json + +# Markers +.development + +# Extras +extras/docs/* +extras/Module Datasheets/* +extras/Older Command Manuals/* +extras/Older Command Manuals/* +extras/At Command Manuals - Unsupported/* diff --git a/.travis.yml b/.travis.yml index a3dc361..9d1fafb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,23 +18,30 @@ env: - PLATFORMIO_CI_SRC=tools/FactoryReset # Arduino test - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM800' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM900' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_A6' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M590' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_ESP8266' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_UBLOX' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_BG96' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_ESP8266' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M95' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M590' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_MC60' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM800' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM808' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM7000' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_UBLOX' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_XBEE' --project-option='framework=arduino' --board=uno --board=leonardo --board=yun --board=megaatmega2560 --board=genuino101 --board=mkr1000USB --board=zero --board=teensy31 --board=bluepill_f103c8 --board=uno_pic32 --board=esp01 --board=nodemcuv2 --board=esp32dev --board=mayfly" # Energia test - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM800' --project-option='framework=energia' --board=lplm4f120h5qr" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM900' --project-option='framework=energia' --board=lplm4f120h5qr" - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_A6' --project-option='framework=energia' --board=lplm4f120h5qr" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M590' --project-option='framework=energia' --board=lplm4f120h5qr" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_ESP8266' --project-option='framework=energia' --board=lplm4f120h5qr" - - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_UBLOX' --project-option='framework=energia' --board=lplm4f120h5qr" - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_BG96' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_ESP8266' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M95' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_M590' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_MC60' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM800' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM808' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_SIM7000' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_UBLOX' --project-option='framework=energia' --board=lplm4f120h5qr" + - PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_XBEE' --project-option='framework=energia' --board=lplm4f120h5qr" # Disabled due to a bug in Energia readBytes implementation #- PLATFORMIO_CI_SRC=tools/test_build PLATFORMIO_CI_ARGS="--project-option='build_flags=-D TINY_GSM_MODEM_XBEE' --project-option='framework=energia' --board=lplm4f120h5qr" @@ -58,5 +65,7 @@ install: - platformio lib -g install 89 415 1202 1286 - platformio lib -g update - platformio lib update + - platformio lib list + - platformio lib -g list script: make travis-build diff --git a/README.md b/README.md index eb7a825..22adad6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ You can also join our chat: [![Gitter](https://img.shields.io/gitter/room/vshymanskyy/TinyGSM.svg)](https://gitter.im/tinygsm) ### Arduino Client interface support -This library is easy to integrate with lots of sketches, which use Ethernet or WiFi. +This library is easy to integrate with lots of sketches which use Ethernet or WiFi. **PubSubClient ([MQTT](http://mqtt.org/))**, **[Blynk](http://blynk.cc)**, **HTTP Client** and **File Download** examples are provided. ![examples](/extras/examples.png) @@ -29,38 +29,40 @@ The complete WebClient example for Arduino Uno (via Software Serial) takes littl Sketch uses 15022 bytes (46%) of program storage space. Maximum is 32256 bytes. Global variables use 574 bytes (28%) of dynamic memory, leaving 1474 bytes for local variables. Maximum is 2048 bytes. ``` -Arduino GSM library uses 15868 bytes (49%) of Flash and 1113 bytes (54%) of RAM in a similar scenario. -TinyGSM also pulls data gently from the modem (whenever possible), so it can operate on very little RAM. +Arduino GSM library uses 15868 bytes (49%) of Flash and 1113 bytes (54%) of RAM in a similar scenario. +TinyGSM also pulls data gently from the modem (whenever possible), so it can operate on very little RAM. **Now, you have more space for your experiments.** ## Features -Feature \ Modem | SIM8xx | u-Blox | A6/A7/A20 | M590 | ESP8266 | XBee ---- | --- | --- | --- | --- | --- | --- +Feature \ Modem | SIM8xx | u-Blox | A6/A7/A20 | Neoway M590| ESP8266 |Digi XBee|Quectel BG96|Quectel M95|Quectel MC60(E)| SIM7000 | +--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | **Data connections** -TCP (HTTP, MQTT, Blynk, ...) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ -UDP | ◌ | ◌ | | | | ◌ -SSL/TLS (HTTPS) | ✔¹ | ✔ | 🅧 | 🅧 | ✔¹ | ✔¹ +TCP (HTTP, MQTT, Blynk, ...) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | +UDP | ◌ | ◌ | | | | ◌ | ◌ | | | ◌ | +SSL/TLS (HTTPS) | ✔¹ | ✔ | x | x | ✔ | ✔ | ◌ | | | ◌ | **USSD** -Sending USSD requests | ✔ | | ✔ | ✔ | 🅧 | -Decoding 7,8,16-bit response | ✔ | | ✔ | ✔ | 🅧 | +Sending USSD requests | ✔ | | ✔ | ✔ | x | | | | | ✔ | +Decoding 7,8,16-bit response | ✔ | | ✔ | ✔ | x | | | | | ✔ | **SMS** -Sending | ✔ | ✔ | ✔ | ✔ | 🅧 | ✔ -Sending Unicode | ✔ | | ◌ | 🅧 | 🅧 | -Reading | | | | | 🅧 | -Incoming message event | | | | ? | 🅧 | +Sending | ✔ | ✔ | ✔ | ✔ | x | ✔ | ✔ | ✔ | ✔ | ✔ | +Sending Unicode | ✔ | | ◌ | x | x | | ✔ | ✔ | ✔ | ✔ | +Reading | | | | | x | | | | | | +Incoming message event | | | | ? | x | | | | | | **Calls** -Dial, hangup | ✔ | | ✔ | 🅧 | 🅧 | 🅧 -Receiving calls | ✔ | | ✔ | 🅧 | 🅧 | 🅧 -Incoming event (RING) | ◌ | | ◌ | 🅧 | 🅧 | 🅧 -DTMF sending | ✔ | | ✔ | 🅧 | 🅧 | 🅧 -DTMF decoding | ◌ | | 🅧 | 🅧 | 🅧 | 🅧 +Dial, hangup | ✔ | | ✔ | x | x | x | | | | | +Receiving calls | ✔ | | ✔ | x | x | x | | | | | +Incoming event (RING) | ◌ | | ◌ | x | x | x | | | | | +DTMF sending | ✔ | | ✔ | x | x | x | | | | | +DTMF decoding | ◌ | | x | x | x | x | | | | | **Location** -GSM location service | ✔ | ✔ | 🅧 | 🅧 | 🅧 | ✔ -GPS/GNSS | ✔¹ | 🅧 | ◌¹ | 🅧 | 🅧 | 🅧 +GSM location service | ✔ | ✔ | x | x | x | x | | x | ✔ | ✔ | +GPS/GNSS | ✔¹ | x | ◌¹ | x | x | x | | x | | ✔ | +**Credits** +Primary Author/Contributor |[vshymanskyy](https://github.com/vshymanskyy)|[vshymanskyy](https://github.com/vshymanskyy)|[vshymanskyy](https://github.com/vshymanskyy)|[vshymanskyy](https://github.com/vshymanskyy)|[vshymanskyy](https://github.com/vshymanskyy)|[SRGDamia1](https://github.com/SRGDamia1/)|[vshymanskyy](https://github.com/vshymanskyy) |[replicadeltd](https://github.com/replicadeltd)|[V1pr](https://github.com/V1pr)|[captFuture](https://github.com/captFuture/)| -✔ - implemented  ◌ - planned  🅧 - not available on this modem -¹ - only some device models or firmware revisions have this feature (SIM8xx R14.18, A7, etc.) +✔ - implemented  ◌ - planned  x - not available on this modem +¹ - only some device models or firmware revisions have this feature (SIM8xx R14.18, A7, etc.) ## Supported modems @@ -70,8 +72,11 @@ GPS/GNSS | ✔¹ | 🅧 | ◌¹ | 🅧 | - ESP8266 (AT commands interface, similar to GSM modems) - Digi XBee WiFi and Cellular (using XBee command mode) - Neoway M590 -- u-blox Cellular Modems (LEON-G100, LISA-U2xx, SARA-G3xx, SARA-U2xx, TOBY-L2xx, LARA-R2xx, MPCI-L2xx) +- u-blox Cellular Modems (many modules including LEON-G100, LISA-U2xx, SARA-G3xx, SARA-U2xx, TOBY-L2xx, LARA-R2xx, MPCI-L2xx, SARA-R4xx, SARA-N4xx, _but NOT SARA-N2xx_) - Quectel BG96 ***(alpha)*** +- Quectel M95 ***(alpha)*** +- Quectel MC60 ***(alpha)*** +- SIMCom SIM7000 series ***(alpha)*** ### Supported boards/modules - Arduino MKR GSM 1400 @@ -84,9 +89,9 @@ GPS/GNSS | ✔¹ | 🅧 | ◌¹ | 🅧 | - ... other modules, based on supported modems. Some boards require [**special configuration**](https://github.com/vshymanskyy/TinyGSM/wiki/Board-configuration). More modems may be supported later: -- [ ] Quectel M10, M35, M95, UG95, EC21 - [ ] Sequans Monarch LTE Cat M1/NB1 -- [ ] SIMCom SIM5320, SIM5360, SIM5216, SIM7xxx +- [ ] Quectel M10, UG95 +- [ ] SIMCom SIM5320, SIM5360, SIM5216 - [ ] Telit GL865 - [ ] ZTE MG2639 - [ ] Hi-Link HLK-RM04 @@ -98,9 +103,9 @@ Watch this repo for new updates! And of course, contributions are welcome ;) [![Donate BountySource](https://img.shields.io/badge/Donate-BountySource-149E5E.svg)](https://salt.bountysource.com/checkout/amount?team=tinygsm-dev) [![Donate Bitcoin](https://img.shields.io/badge/Donate-Bitcoin-orange.svg)](http://tny.im/aen) -If you have found TinyGSM to be useful in your work, research or company, please consider making a donation to the project commensurate with your resources. Any amount helps! +If you have found TinyGSM to be useful in your work, research or company, please consider making a donation to the project commensurate with your resources. Any amount helps! **All donations will be used strictly to fund the development of TinyGSM:** -- Covering cellular network expences +- Covering cellular network expenses - Buying new hardware and modems for integration - Bounty Budget (to reward other developers for their contributions) - Implementing new features @@ -111,10 +116,10 @@ If you have found TinyGSM to be useful in your work, research or company, please 1. Using your phone: - Disable PIN code on the SIM card - Check your balance - - Check that APN,User,Pass are correct and you have internet + - Check that APN, User, Pass are correct and you have internet 2. Ensure the SIM card is correctly inserted into the module 3. Ensure that GSM antenna is firmly attached - 4. Check if serial connection is working (Hardware Serial is recommended) + 4. Check if serial connection is working (Hardware Serial is recommended) Send an ```AT``` command using [this sketch](tools/AT_Debug/AT_Debug.ino) If you have any issues: @@ -127,20 +132,20 @@ If you have any issues: ## How does it work? -Many GSM modems, WiFi and radio modules can be controlled by sending AT commands over Serial. +Many GSM modems, WiFi and radio modules can be controlled by sending AT commands over Serial. TinyGSM knows which commands to send, and how to handle AT responses, and wraps that into standard Arduino Client interface. ## API Reference -For GPRS data streams, this library provides the standard [Arduino Client](https://www.arduino.cc/en/Reference/ClientConstructor) interface. +For GPRS data streams, this library provides the standard [Arduino Client](https://www.arduino.cc/en/Reference/ClientConstructor) interface. For additional functions, please refer to [this example sketch](examples/AllFunctions/AllFunctions.ino) ## Troubleshooting ### Diagnostics sketch -Use this sketch to diagnose your SIM card and GPRS connection: - File -> Examples -> TynyGSM -> tools -> [Diagnostics](https://github.com/vshymanskyy/TinyGSM/blob/master/tools/Diagnostics/Diagnostics.ino) +Use this sketch to diagnose your SIM card and GPRS connection: + File -> Examples -> TinyGSM -> tools -> [Diagnostics](https://github.com/vshymanskyy/TinyGSM/blob/master/tools/Diagnostics/Diagnostics.ino) ### Ensure stable data & power connection @@ -153,10 +158,10 @@ So this actually solves stability problems in **many** cases: ### SoftwareSerial problems -When using ```SoftwareSerial``` (on Uno, Nano, etc), the speed **115200** may not work. -Try selecting **57600**, **38400**, or even lower - the one that works best for you. -In some cases **9600** is unstable, but using **38400** helps, etc. -Be sure to set correct TX/RX pins in the sketch. Please note that not every Arduino pin can serve as TX or RX pin. +When using ```SoftwareSerial``` (on Uno, Nano, etc), the speed **115200** may not work. +Try selecting **57600**, **38400**, or even lower - the one that works best for you. +In some cases **9600** is unstable, but using **38400** helps, etc. +Be sure to set correct TX/RX pins in the sketch. Please note that not every Arduino pin can serve as TX or RX pin. **Read more about SoftSerial options and configuration [here](https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html) and [here](https://www.arduino.cc/en/Reference/SoftwareSerial).** ### ESP32 HardwareSerial @@ -171,15 +176,15 @@ Please [refer to this comment](https://github.com/vshymanskyy/TinyGSM/issues/102 ### Broken initial configuration -Sometimes (especially if you played with AT comands), your module configuration may become invalid. +Sometimes (especially if you played with AT commands), your module configuration may become invalid. This may result in problems such as: * Can't connect to the GPRS network * Can't connect to the server - * Sent/recieved data contains invalid bytes + * Sent/received data contains invalid bytes * etc. -To return module to **Factory Defaults**, use this sketch: +To return module to **Factory Defaults**, use this sketch: File -> Examples -> TinyGSM -> tools -> [FactoryReset](https://github.com/vshymanskyy/TinyGSM/blob/master/tools/FactoryReset/FactoryReset.ino) ### Goouuu Tech IOT-GA6 vs AI-Thinker A6 confusion diff --git a/examples/AllFunctions/AllFunctions.ino b/examples/AllFunctions/AllFunctions.ino index 573e279..7a417d1 100644 --- a/examples/AllFunctions/AllFunctions.ino +++ b/examples/AllFunctions/AllFunctions.ino @@ -1,7 +1,7 @@ /************************************************************** * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * * NOTE: * Some of the functions may be unavailable for your modem. @@ -10,14 +10,20 @@ **************************************************************/ // Select your modem: -#define TINY_GSM_MODEM_SIM800 +//#define TINY_GSM_MODEM_SIM800 // #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +#define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E +// #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE // Set serial for debug console (to the Serial Monitor, speed 115200) #define SerialMon Serial @@ -34,6 +40,22 @@ //#define DUMP_AT_COMMANDS #define TINY_GSM_DEBUG SerialMon +#define GSM_AUTOBAUD_MIN 9600 +#define GSM_AUTOBAUD_MAX 38400 + +/* + * Test enabled + */ +#define TINY_GSM_USE_GPRS true +#define TINY_GSM_USE_CALL true +#define TINY_GSM_USE_SMS true +#define TINY_GSM_USE_USSD true +// powerdown modem after tests +#define TINY_GSM_POWERDOWN false + +// set GSM PIN, if any +#define GSM_PIN "" + // Set phone numbers, if you want to test SMS and Calls //#define SMS_TARGET "+380xxxxxxxxx" //#define CALL_TARGET "+380xxxxxxxxx" @@ -64,7 +86,7 @@ void setup() { delay(3000); // Set GSM module baud rate - TinyGsmAutoBaud(SerialAT); + TinyGsmAutoBaud(SerialAT,GSM_AUTOBAUD_MIN,GSM_AUTOBAUD_MAX); } void loop() { @@ -73,6 +95,10 @@ void loop() { // To skip it, call init() instead of restart() DBG("Initializing modem..."); if (!modem.restart()) { + DBG("Failed to restart modem, delaying 10s and retrying"); + delay(3000); + // restart autobaud in case GSM just rebooted + TinyGsmAutoBaud(SerialAT,GSM_AUTOBAUD_MIN,GSM_AUTOBAUD_MAX); delay(10000); return; } @@ -80,8 +106,10 @@ void loop() { String modemInfo = modem.getModemInfo(); DBG("Modem:", modemInfo); - // Unlock your SIM card with a PIN - //modem.simUnlock("1234"); + // Unlock your SIM card with a PIN if needed + if ( GSM_PIN && modem.getSimStatus() != 3 ) { + modem.simUnlock(GSM_PIN); + } DBG("Waiting for network..."); if (!modem.waitForNetwork()) { @@ -93,6 +121,7 @@ void loop() { DBG("Network connected"); } +#if TINY_GSM_USE_GPRS DBG("Connecting to", apn); if (!modem.gprsConnect(apn, user, pass)) { delay(10000); @@ -110,7 +139,7 @@ void loop() { String cop = modem.getOperator(); DBG("Operator:", cop); - + IPAddress local = modem.localIP(); DBG("Local IP:", local); @@ -130,25 +159,26 @@ void loop() { DBG("GSM location:", gsmLoc); // This is only supported on SIMxxx series - String gsmTime = modem.getGSMDateTime(DATE_TIME); - DBG("GSM Time:", gsmTime); - String gsmDate = modem.getGSMDateTime(DATE_DATE); - DBG("GSM Date:", gsmDate); + // String gsmTime = modem.getGSMDateTime(DATE_TIME); + // DBG("GSM Time:", gsmTime); + // String gsmDate = modem.getGSMDateTime(DATE_DATE); + // DBG("GSM Date:", gsmDate); String ussd_balance = modem.sendUSSD("*111#"); DBG("Balance (USSD):", ussd_balance); String ussd_phone_num = modem.sendUSSD("*161#"); DBG("Phone number (USSD):", ussd_phone_num); +#endif -#if defined(TINY_GSM_MODEM_SIM808) +#if defined(TINY_GSM_MODEM_HAS_GPS) modem.enableGPS(); String gps_raw = modem.getGPSraw(); modem.disableGPS(); DBG("GPS raw data:", gps_raw); #endif -#if defined(SMS_TARGET) +#if TINY_GSM_USE_SMS && defined(SMS_TARGET) res = modem.sendSMS(SMS_TARGET, String("Hello from ") + imei); DBG("SMS:", res ? "OK" : "fail"); @@ -157,7 +187,7 @@ void loop() { DBG("UTF16 SMS:", res ? "OK" : "fail"); #endif -#if defined(CALL_TARGET) +#if TINY_GSM_USE_CALL && defined(CALL_TARGET) DBG("Calling:", CALL_TARGET); // This is NOT supported on M590 @@ -182,21 +212,24 @@ void loop() { } #endif +#if TINY_GSM_USE_GPRS modem.gprsDisconnect(); if (!modem.isGprsConnected()) { DBG("GPRS disconnected"); } else { DBG("GPRS disconnect: Failed."); } +#endif +#if TINY_GSM_POWERDOWN // Try to power-off (modem may decide to restart automatically) // To turn off modem completely, please use Reset/Enable pins modem.poweroff(); DBG("Poweroff."); +#endif // Do nothing forevermore while (true) { modem.maintain(); } } - diff --git a/examples/BlynkClient/BlynkClient.ino b/examples/BlynkClient/BlynkClient.ino index d3d7ce3..95f8163 100644 --- a/examples/BlynkClient/BlynkClient.ino +++ b/examples/BlynkClient/BlynkClient.ino @@ -4,7 +4,7 @@ * https://github.com/blynkkk/blynk-library/releases/latest * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * ************************************************************** * @@ -30,13 +30,18 @@ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -95,4 +100,3 @@ void loop() { Blynk.run(); } - diff --git a/examples/FileDownload/FileDownload.ino b/examples/FileDownload/FileDownload.ino index 19c73a6..e42629a 100644 --- a/examples/FileDownload/FileDownload.ino +++ b/examples/FileDownload/FileDownload.ino @@ -5,7 +5,7 @@ * or from http://librarymanager/all#CRC32+checksum * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * * ATTENTION! Downloading big files requires of knowledge of * the TinyGSM internals and some modem specifics, @@ -15,13 +15,18 @@ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -204,4 +209,3 @@ void loop() { delay(1000); } } - diff --git a/examples/HttpClient/HttpClient.ino b/examples/HttpClient/HttpClient.ino index 95b3453..f699167 100644 --- a/examples/HttpClient/HttpClient.ino +++ b/examples/HttpClient/HttpClient.ino @@ -8,25 +8,43 @@ * or from http://librarymanager/all#ArduinoHttpClient * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * * For more HTTP API examples, see ArduinoHttpClient library * + * NOTE: This example does NOT work with the XBee because the + * HttpClient library does not empty to serial buffer fast enough + * and the buffer overflow causes the HttpClient library to stall. **************************************************************/ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 -// Increase RX buffer if needed -//#define TINY_GSM_RX_BUFFER 512 +// Increase RX buffer to capture the entire response +// Chips without internal buffering (A6/A7, ESP8266, M590) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#define TINY_GSM_RX_BUFFER 650 + +// See the debugging, if wanted +//#define TINY_GSM_DEBUG Serial +//#define LOGGING + +// Add a reception delay, if needed +//#define TINY_GSM_YIELD() { delay(1); } #include #include @@ -48,8 +66,10 @@ // Your GPRS credentials // Leave empty, if missing user or pass const char apn[] = "YourAPN"; -const char user[] = ""; -const char pass[] = ""; +const char gprsUser[] = ""; +const char gprsPass[] = ""; +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "SSIDpw"; // Server details const char server[] = "vsh.pp.ua"; @@ -71,6 +91,7 @@ void setup() { // Set console baud rate SerialMon.begin(115200); delay(10); + SerialMon.println(F("Wait...")); // Set GSM module baud rate SerialAT.begin(115200); @@ -90,6 +111,17 @@ void setup() { } void loop() { + + if (modem.hasWifi()) { + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); + } + SerialMon.print(F("Waiting for network...")); if (!modem.waitForNetwork()) { SerialMon.println(" fail"); @@ -98,14 +130,16 @@ void loop() { } SerialMon.println(" OK"); - SerialMon.print(F("Connecting to ")); - SerialMon.print(apn); - if (!modem.gprsConnect(apn, user, pass)) { - SerialMon.println(" fail"); - delay(10000); - return; + if (modem.hasGPRS()) { + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); } - SerialMon.println(" OK"); SerialMon.print(F("Performing HTTP GET request... ")); int err = http.get(resource); @@ -116,16 +150,18 @@ void loop() { } int status = http.responseStatusCode(); + SerialMon.print(F("Response status code: ")); SerialMon.println(status); if (!status) { delay(10000); return; } + SerialMon.println(F("Response Headers:")); while (http.headerAvailable()) { String headerName = http.readHeaderName(); String headerValue = http.readHeaderValue(); - //SerialMon.println(headerName + " : " + headerValue); + SerialMon.println(" " + headerName + " : " + headerValue); } int length = http.contentLength(); @@ -157,4 +193,3 @@ void loop() { delay(1000); } } - diff --git a/examples/HttpsClient/HttpsClient.ino b/examples/HttpsClient/HttpsClient.ino index bd892d7..40287a1 100644 --- a/examples/HttpsClient/HttpsClient.ino +++ b/examples/HttpsClient/HttpsClient.ino @@ -8,9 +8,9 @@ * or from http://librarymanager/all#ArduinoHttpClient * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * - * SSL/TLS is currently supported only with: SIM8xx, uBlox + * SSL/TLS is currently supported only with: SIM8xx, uBlox, ESP8266 * * For more HTTP API examples, see ArduinoHttpClient library * @@ -19,10 +19,22 @@ // Select your modem: #define TINY_GSM_MODEM_SIM800 // #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_ESP8266 -// Increase RX buffer if needed -//#define TINY_GSM_RX_BUFFER 512 +// Increase RX buffer to capture the entire response +// Chips without internal buffering (ESP8266) +// need enough space in the buffer for the entire response +// else data will be lost (and the http library will fail). +#define TINY_GSM_RX_BUFFER 650 + +// See the debugging, if wanted +//#define TINY_GSM_DEBUG Serial +//#define LOGGING + +// Add a reception delay, if needed +//#define TINY_GSM_YIELD() { delay(1); } #include #include @@ -44,8 +56,10 @@ // Your GPRS credentials // Leave empty, if missing user or pass const char apn[] = "YourAPN"; -const char user[] = ""; -const char pass[] = ""; +const char gprsUser[] = ""; +const char gprsPass[] = ""; +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "SSIDpw"; // Server details const char server[] = "vsh.pp.ua"; @@ -67,6 +81,7 @@ void setup() { // Set console baud rate SerialMon.begin(115200); delay(10); + SerialMon.println(F("Wait...")); // Set GSM module baud rate SerialAT.begin(115200); @@ -91,6 +106,17 @@ void setup() { } void loop() { + + if (modem.hasWifi()) { + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); + } + SerialMon.print(F("Waiting for network...")); if (!modem.waitForNetwork()) { SerialMon.println(" fail"); @@ -99,14 +125,16 @@ void loop() { } SerialMon.println(" OK"); - SerialMon.print(F("Connecting to ")); - SerialMon.print(apn); - if (!modem.gprsConnect(apn, user, pass)) { - SerialMon.println(" fail"); - delay(10000); - return; + if (modem.hasGPRS()) { + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); } - SerialMon.println(" OK"); SerialMon.print(F("Performing HTTPS GET request... ")); http.connectionKeepAlive(); // Currently, this is needed for HTTPS @@ -118,16 +146,18 @@ void loop() { } int status = http.responseStatusCode(); + SerialMon.print(F("Response status code: ")); SerialMon.println(status); if (!status) { delay(10000); return; } + SerialMon.println(F("Response Headers:")); while (http.headerAvailable()) { String headerName = http.readHeaderName(); String headerValue = http.readHeaderValue(); - //SerialMon.println(headerName + " : " + headerValue); + SerialMon.println(" " + headerName + " : " + headerValue); } int length = http.contentLength(); @@ -159,4 +189,3 @@ void loop() { delay(1000); } } - diff --git a/examples/MqttClient/MqttClient.ino b/examples/MqttClient/MqttClient.ino index da5933a..b7bbd8d 100644 --- a/examples/MqttClient/MqttClient.ino +++ b/examples/MqttClient/MqttClient.ino @@ -5,7 +5,7 @@ * or from http://librarymanager/all#PubSubClient * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * * For more MQTT examples, see PubSubClient library * @@ -29,13 +29,18 @@ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -171,4 +176,3 @@ void mqttCallback(char* topic, byte* payload, unsigned int len) { mqtt.publish(topicLedStatus, ledStatus ? "1" : "0"); } } - diff --git a/examples/WebClient/WebClient.ino b/examples/WebClient/WebClient.ino index e1127fd..e773b81 100644 --- a/examples/WebClient/WebClient.ino +++ b/examples/WebClient/WebClient.ino @@ -4,24 +4,37 @@ * It can be used to perform HTTP/RESTful API calls. * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 +// #define TINY_GSM_MODEM_SIM7000 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 +// #define TINY_GSM_MODEM_XBEE // Increase RX buffer if needed //#define TINY_GSM_RX_BUFFER 512 +// See the debugging, if wanted +//#define TINY_GSM_DEBUG Serial +//#define LOGGING + +// Add a reception delay, if needed +//#define TINY_GSM_YIELD() { delay(1); } + #include // Uncomment this if you want to see all AT commands @@ -44,8 +57,10 @@ // Your GPRS credentials // Leave empty, if missing user or pass const char apn[] = "YourAPN"; -const char user[] = ""; -const char pass[] = ""; +const char gprsUser[] = ""; +const char gprsPass[] = ""; +const char wifiSSID[] = "YourSSID"; +const char wifiPass[] = "SSIDpw"; // Server details const char server[] = "vsh.pp.ua"; @@ -71,9 +86,13 @@ void setup() { // Set console baud rate SerialMon.begin(115200); delay(10); + SerialMon.println(F("Wait...")); + + pinMode(23, OUTPUT); + digitalWrite(23, LOW); // Set GSM module baud rate - SerialAT.begin(115200); + SerialAT.begin(9600); delay(3000); // Restart takes quite some time @@ -90,6 +109,26 @@ void setup() { } void loop() { + + if (modem.hasWifi()) { + SerialMon.print(F("Setting SSID/password...")); + if (!modem.networkConnect(wifiSSID, wifiPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); + } + else if (modem.getModemName().indexOf("XBee") >= 0) { + SerialMon.print(F("Setting APN")); + if (!modem.gprsConnect(apn)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); + } + SerialMon.print(F("Waiting for network...")); if (!modem.waitForNetwork()) { SerialMon.println(" fail"); @@ -98,14 +137,16 @@ void loop() { } SerialMon.println(" OK"); - SerialMon.print(F("Connecting to ")); - SerialMon.print(apn); - if (!modem.gprsConnect(apn, user, pass)) { - SerialMon.println(" fail"); - delay(10000); - return; + if (modem.hasGPRS()) { + SerialMon.print(F("Connecting to ")); + SerialMon.print(apn); + if (!modem.gprsConnect(apn, gprsUser, gprsPass)) { + SerialMon.println(" fail"); + delay(10000); + return; + } + SerialMon.println(" OK"); } - SerialMon.println(" OK"); SerialMon.print(F("Connecting to ")); SerialMon.print(server); @@ -137,12 +178,17 @@ void loop() { client.stop(); SerialMon.println(F("Server disconnected")); - modem.gprsDisconnect(); - SerialMon.println(F("GPRS disconnected")); + if (modem.hasWifi()) { + modem.networkDisconnect(); + SerialMon.println(F("WiFi disconnected")); + } + else { + modem.gprsDisconnect(); + SerialMon.println(F("GPRS disconnected")); + } // Do nothing forevermore while (true) { delay(1000); } } - diff --git a/examples/more/Hologram_Dash/Hologram_Dash.ino b/examples/more/Hologram_Dash/Hologram_Dash.ino index 532fee4..2f195da 100644 --- a/examples/more/Hologram_Dash/Hologram_Dash.ino +++ b/examples/more/Hologram_Dash/Hologram_Dash.ino @@ -4,7 +4,7 @@ * It can be used to perform HTTP/RESTful API calls. * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ diff --git a/examples/more/Industruino/Industruino.ino b/examples/more/Industruino/Industruino.ino index 5e30f86..3757f0c 100644 --- a/examples/more/Industruino/Industruino.ino +++ b/examples/more/Industruino/Industruino.ino @@ -8,7 +8,7 @@ * or from http://librarymanager/all#ArduinoHttpClient * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * * For more HTTP API examples, see ArduinoHttpClient library * diff --git a/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino b/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino index a25babd..e2a679d 100644 --- a/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino +++ b/examples/more/SIM800_SslSetCert/SIM800_SslSetCert.ino @@ -3,7 +3,7 @@ * This sketch uploads SSL certificates to the SIM8xx * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ diff --git a/extras/doc/A6 A7 A6C A20 - AT instructions v1.03.pdf b/extras/AT Command Manuals/A6 A7 A6C A20 - AT instructions v1.03.pdf similarity index 100% rename from extras/doc/A6 A7 A6C A20 - AT instructions v1.03.pdf rename to extras/AT Command Manuals/A6 A7 A6C A20 - AT instructions v1.03.pdf diff --git a/extras/AT Command Manuals/Digi XBee 3G Global Users Guide - 90001541.pdf b/extras/AT Command Manuals/Digi XBee 3G Global Users Guide - 90001541.pdf new file mode 100644 index 0000000..3964de1 Binary files /dev/null and b/extras/AT Command Manuals/Digi XBee 3G Global Users Guide - 90001541.pdf differ diff --git a/extras/AT Command Manuals/Digi XBee LTEC1 Users Guide - 90001525.pdf b/extras/AT Command Manuals/Digi XBee LTEC1 Users Guide - 90001525.pdf new file mode 100644 index 0000000..46f2868 Binary files /dev/null and b/extras/AT Command Manuals/Digi XBee LTEC1 Users Guide - 90001525.pdf differ diff --git a/extras/doc/Digi XBee S6B Users Guide - 90002180.pdf b/extras/AT Command Manuals/Digi XBee S6B Users Guide - 90002180.pdf similarity index 100% rename from extras/doc/Digi XBee S6B Users Guide - 90002180.pdf rename to extras/AT Command Manuals/Digi XBee S6B Users Guide - 90002180.pdf diff --git a/extras/doc/Digi XBee LTEC1 Users Guide - 90001525.pdf b/extras/AT Command Manuals/Digi XBee3 LTEC1 Users Guide - 90002253.pdf similarity index 74% rename from extras/doc/Digi XBee LTEC1 Users Guide - 90001525.pdf rename to extras/AT Command Manuals/Digi XBee3 LTEC1 Users Guide - 90002253.pdf index 7c63957..2b6f995 100644 Binary files a/extras/doc/Digi XBee LTEC1 Users Guide - 90001525.pdf and b/extras/AT Command Manuals/Digi XBee3 LTEC1 Users Guide - 90002253.pdf differ diff --git a/extras/AT Command Manuals/Digi XBee3 LTECM Users Guide - 90002258.pdf b/extras/AT Command Manuals/Digi XBee3 LTECM Users Guide - 90002258.pdf new file mode 100644 index 0000000..8ffb6cc Binary files /dev/null and b/extras/AT Command Manuals/Digi XBee3 LTECM Users Guide - 90002258.pdf differ diff --git a/extras/AT Command Manuals/ESP8266 - AT Command Examples 1.3.pdf b/extras/AT Command Manuals/ESP8266 - AT Command Examples 1.3.pdf new file mode 100644 index 0000000..9d0087b Binary files /dev/null and b/extras/AT Command Manuals/ESP8266 - AT Command Examples 1.3.pdf differ diff --git a/extras/AT Command Manuals/ESP8266 - AT Instruction Set v3.0.0.pdf b/extras/AT Command Manuals/ESP8266 - AT Instruction Set v3.0.0.pdf new file mode 100644 index 0000000..1515206 Binary files /dev/null and b/extras/AT Command Manuals/ESP8266 - AT Instruction Set v3.0.0.pdf differ diff --git a/extras/doc/Neoway M590 - AT Command Sets v3.0.pdf b/extras/AT Command Manuals/Neoway M590 - AT Command Sets v3.0.pdf similarity index 100% rename from extras/doc/Neoway M590 - AT Command Sets v3.0.pdf rename to extras/AT Command Manuals/Neoway M590 - AT Command Sets v3.0.pdf diff --git a/extras/doc/Quectel_BG96_AT_Commands_Manual_V2_0.pdf b/extras/AT Command Manuals/Quectel_BG96_AT_Commands_Manual_V2_0.pdf similarity index 100% rename from extras/doc/Quectel_BG96_AT_Commands_Manual_V2_0.pdf rename to extras/AT Command Manuals/Quectel_BG96_AT_Commands_Manual_V2_0.pdf diff --git a/extras/doc/Quectel_BG96_SSL_AT_Commands_Manual_V1.0.pdf b/extras/AT Command Manuals/Quectel_BG96_SSL_AT_Commands_Manual_V1.0.pdf similarity index 100% rename from extras/doc/Quectel_BG96_SSL_AT_Commands_Manual_V1.0.pdf rename to extras/AT Command Manuals/Quectel_BG96_SSL_AT_Commands_Manual_V1.0.pdf diff --git a/extras/doc/Quectel_BG96_TCPIP_AT_Commands_Manual_V1.0.pdf b/extras/AT Command Manuals/Quectel_BG96_TCPIP_AT_Commands_Manual_V1.0.pdf similarity index 100% rename from extras/doc/Quectel_BG96_TCPIP_AT_Commands_Manual_V1.0.pdf rename to extras/AT Command Manuals/Quectel_BG96_TCPIP_AT_Commands_Manual_V1.0.pdf diff --git a/extras/AT Command Manuals/Quectel_GSM_SSL_TCP_Application_Note.pdf b/extras/AT Command Manuals/Quectel_GSM_SSL_TCP_Application_Note.pdf new file mode 100644 index 0000000..7f9934e Binary files /dev/null and b/extras/AT Command Manuals/Quectel_GSM_SSL_TCP_Application_Note.pdf differ diff --git a/extras/AT Command Manuals/Quectel_GSM_TCPIP_Recommended_Process_V1.2.pdf b/extras/AT Command Manuals/Quectel_GSM_TCPIP_Recommended_Process_V1.2.pdf new file mode 100644 index 0000000..ef45e09 Binary files /dev/null and b/extras/AT Command Manuals/Quectel_GSM_TCPIP_Recommended_Process_V1.2.pdf differ diff --git a/extras/AT Command Manuals/Quectel_M95_at_commands_manual_v1.3.pdf b/extras/AT Command Manuals/Quectel_M95_at_commands_manual_v1.3.pdf new file mode 100644 index 0000000..201fd3e Binary files /dev/null and b/extras/AT Command Manuals/Quectel_M95_at_commands_manual_v1.3.pdf differ diff --git a/extras/AT Command Manuals/Quectel_MC60_AT_Commands_Manual_V1.1.pdf b/extras/AT Command Manuals/Quectel_MC60_AT_Commands_Manual_V1.1.pdf new file mode 100644 index 0000000..392783d Binary files /dev/null and b/extras/AT Command Manuals/Quectel_MC60_AT_Commands_Manual_V1.1.pdf differ diff --git a/extras/AT Command Manuals/SIM7000 Series AT Command Manual V1.03.pdf b/extras/AT Command Manuals/SIM7000 Series AT Command Manual V1.03.pdf new file mode 100644 index 0000000..8ab0e78 Binary files /dev/null and b/extras/AT Command Manuals/SIM7000 Series AT Command Manual V1.03.pdf differ diff --git a/extras/AT Command Manuals/SIM7000 Series TCPIP Application Note V1.01.pdf b/extras/AT Command Manuals/SIM7000 Series TCPIP Application Note V1.01.pdf new file mode 100644 index 0000000..0c4120c Binary files /dev/null and b/extras/AT Command Manuals/SIM7000 Series TCPIP Application Note V1.01.pdf differ diff --git a/extras/doc/SIM800 AT Commands v1.10.pdf b/extras/AT Command Manuals/SIM800 Series AT Command Manual V1.10.pdf similarity index 100% rename from extras/doc/SIM800 AT Commands v1.10.pdf rename to extras/AT Command Manuals/SIM800 Series AT Command Manual V1.10.pdf diff --git a/extras/doc/SIM800 Series_SSL_Application Note_V1.01.pdf b/extras/AT Command Manuals/SIM800 Series SSL Application Note_V1.01.pdf similarity index 100% rename from extras/doc/SIM800 Series_SSL_Application Note_V1.01.pdf rename to extras/AT Command Manuals/SIM800 Series SSL Application Note_V1.01.pdf diff --git a/extras/AT Command Manuals/SIM800 Series_TCPIP_Application Note_V1.02.pdf b/extras/AT Command Manuals/SIM800 Series_TCPIP_Application Note_V1.02.pdf new file mode 100644 index 0000000..88e4588 Binary files /dev/null and b/extras/AT Command Manuals/SIM800 Series_TCPIP_Application Note_V1.02.pdf differ diff --git a/extras/doc/SIM900 AT Commands v1.11.pdf b/extras/AT Command Manuals/SIM900 AT Commands v1.11.pdf similarity index 100% rename from extras/doc/SIM900 AT Commands v1.11.pdf rename to extras/AT Command Manuals/SIM900 AT Commands v1.11.pdf diff --git a/extras/doc/u-blox-CEL_ATCommands_(UBX-13002752).pdf b/extras/AT Command Manuals/u-blox Cellular AT Commands R60 (UBX-13002752).pdf similarity index 54% rename from extras/doc/u-blox-CEL_ATCommands_(UBX-13002752).pdf rename to extras/AT Command Manuals/u-blox Cellular AT Commands R60 (UBX-13002752).pdf index b8b568b..883720b 100644 Binary files a/extras/doc/u-blox-CEL_ATCommands_(UBX-13002752).pdf and b/extras/AT Command Manuals/u-blox Cellular AT Commands R60 (UBX-13002752).pdf differ diff --git a/extras/AT Command Manuals/u-blox SARA-R4 SARA-N4 AT Commands r12 (UBX-17003787).pdf b/extras/AT Command Manuals/u-blox SARA-R4 SARA-N4 AT Commands r12 (UBX-17003787).pdf new file mode 100644 index 0000000..4e992ba Binary files /dev/null and b/extras/AT Command Manuals/u-blox SARA-R4 SARA-N4 AT Commands r12 (UBX-17003787).pdf differ diff --git a/extras/doc/ESP8266 - AT Instruction Set v2.1.0.pdf b/extras/doc/ESP8266 - AT Instruction Set v2.1.0.pdf deleted file mode 100644 index 3a17df6..0000000 Binary files a/extras/doc/ESP8266 - AT Instruction Set v2.1.0.pdf and /dev/null differ diff --git a/extras/doc/Quectel_M35_at_command.pdf b/extras/doc/Quectel_M35_at_command.pdf deleted file mode 100644 index 8557235..0000000 Binary files a/extras/doc/Quectel_M35_at_command.pdf and /dev/null differ diff --git a/extras/doc/Telit_LE866_AT_Commands_Reference_Guide_r5.pdf b/extras/doc/Telit_LE866_AT_Commands_Reference_Guide_r5.pdf deleted file mode 100644 index 45fffc6..0000000 Binary files a/extras/doc/Telit_LE866_AT_Commands_Reference_Guide_r5.pdf and /dev/null differ diff --git a/extras/doc/UBlox SARA-R4 AT Commands Manual (UBX-17003787).pdf b/extras/doc/UBlox SARA-R4 AT Commands Manual (UBX-17003787).pdf deleted file mode 100644 index a0a6d62..0000000 Binary files a/extras/doc/UBlox SARA-R4 AT Commands Manual (UBX-17003787).pdf and /dev/null differ diff --git a/extras/doc/WNC M14A2A AT&T Commands Guide v1.7.pdf b/extras/doc/WNC M14A2A AT&T Commands Guide v1.7.pdf deleted file mode 100644 index 0c61e45..0000000 Binary files a/extras/doc/WNC M14A2A AT&T Commands Guide v1.7.pdf and /dev/null differ diff --git a/library.json b/library.json index 29ac518..a74627c 100644 --- a/library.json +++ b/library.json @@ -1,14 +1,16 @@ { "name": "TinyGSM", - "version": "0.3.5", - "description": "A small Arduino library for GPRS modules, that just works. Includes examples for Blynk, MQTT, File Download, and Web Client. Supports GSM modules with AT command interface: SIM800, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900, SIM900A, SIM900D, SIM908, SIM968", - "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968", + "version": "0.6.1", + "description": "A small Arduino library for GPRS modules, that just works. Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces.", + "keywords": "GSM, AT commands, AT, SIM800, SIM900, A6, A7, M590, ESP8266, SIM7000, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968, M95, MC60, MC60E, BG96, ublox, Quectel, SIMCOM, AI Thinker, LTE, LTE-M", "authors": - { + [ + { "name": "Volodymyr Shymanskyy", "url": "https://github.com/vshymanskyy", - "maintainer": true - }, + "maintainer": true + } + ], "repository": { "type": "git", diff --git a/library.properties b/library.properties index 7762874..146f48b 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=TinyGSM -version=0.3.5 +version=0.6.1 author=Volodymyr Shymanskyy maintainer=Volodymyr Shymanskyy sentence=A small Arduino library for GPRS modules, that just works. -paragraph=Includes examples for Blynk, MQTT, File Download, and Web Client. Supports GSM modules with AT command interface: SIM800, SIM900, A6, A7, M590, ESP8266, SIM800A, SIM800C, SIM800L, SIM800H, SIM808, SIM868, SIM900A, SIM900D, SIM908, SIM968 +paragraph=Includes examples for Blynk, MQTT, File Download, and Web Client. Supports many GSM, LTE, and WiFi modules with AT command interfaces. category=Communication url=https://github.com/vshymanskyy/TinyGSM architectures=* diff --git a/src/TinyGsmClient.h b/src/TinyGsmClient.h index 051dd10..2e4218e 100644 --- a/src/TinyGsmClient.h +++ b/src/TinyGsmClient.h @@ -9,16 +9,9 @@ #ifndef TinyGsmClient_h #define TinyGsmClient_h -#if defined(TINY_GSM_MODEM_SIM800) || defined(TINY_GSM_MODEM_SIM868) || defined(TINY_GSM_MODEM_UBLOX) || defined(TINY_GSM_MODEM_ESP8266) - #define TINY_GSM_MODEM_HAS_SSL -#endif - -#if defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868) || defined(TINY_GSM_MODEM_A7) - #define TINY_GSM_MODEM_HAS_GPS -#endif - -#if defined(TINY_GSM_MODEM_SIM800) || defined(TINY_GSM_MODEM_SIM900) +#if defined(TINY_GSM_MODEM_SIM800) #define TINY_GSM_MODEM_HAS_GPRS + #define TINY_GSM_MODEM_HAS_SSL #include typedef TinyGsmSim800 TinyGsm; typedef TinyGsmSim800::GsmClient TinyGsmClient; @@ -26,18 +19,41 @@ #elif defined(TINY_GSM_MODEM_SIM808) || defined(TINY_GSM_MODEM_SIM868) #define TINY_GSM_MODEM_HAS_GPRS + #define TINY_GSM_MODEM_HAS_SSL + #define TINY_GSM_MODEM_HAS_GPS #include typedef TinyGsmSim808 TinyGsm; typedef TinyGsmSim808::GsmClient TinyGsmClient; typedef TinyGsmSim808::GsmClientSecure TinyGsmClientSecure; +#elif defined(TINY_GSM_MODEM_SIM900) + #define TINY_GSM_MODEM_HAS_GPRS + #include + typedef TinyGsmSim800 TinyGsm; + typedef TinyGsmSim800::GsmClient TinyGsmClient; + +#elif defined(TINY_GSM_MODEM_SIM7000) + #define TINY_GSM_MODEM_HAS_GPRS + #define TINY_GSM_MODEM_HAS_GPS + #include + typedef TinyGsmSim7000 TinyGsm; + typedef TinyGsmSim7000::GsmClient TinyGsmClient; + typedef TinyGsmSim7000::GsmClientSecure TinyGsmClientSecure; + #elif defined(TINY_GSM_MODEM_UBLOX) #define TINY_GSM_MODEM_HAS_GPRS + #define TINY_GSM_MODEM_HAS_SSL #include typedef TinyGsmUBLOX TinyGsm; typedef TinyGsmUBLOX::GsmClient TinyGsmClient; typedef TinyGsmUBLOX::GsmClientSecure TinyGsmClientSecure; +#elif defined(TINY_GSM_MODEM_M95) + #define TINY_GSM_MODEM_HAS_GPRS + #include + typedef TinyGsmM95 TinyGsm; + typedef TinyGsmM95::GsmClient TinyGsmClient; + #elif defined(TINY_GSM_MODEM_BG96) #define TINY_GSM_MODEM_HAS_GPRS #include @@ -56,8 +72,16 @@ typedef TinyGsmM590 TinyGsm; typedef TinyGsmM590::GsmClient TinyGsmClient; +#elif defined(TINY_GSM_MODEM_MC60) || defined(TINY_GSM_MODEM_MC60E) + #include + #define TINY_GSM_MODEM_HAS_GPRS + #define TINY_GSM_MODEM_HAS_GPS + typedef TinyGsmMC60 TinyGsm; + typedef TinyGsmMC60::GsmClient TinyGsmClient; + #elif defined(TINY_GSM_MODEM_ESP8266) #define TINY_GSM_MODEM_HAS_WIFI + #define TINY_GSM_MODEM_HAS_SSL #include typedef TinyGsmESP8266 TinyGsm; typedef TinyGsmESP8266::GsmClient TinyGsmClient; @@ -66,6 +90,7 @@ #elif defined(TINY_GSM_MODEM_XBEE) #define TINY_GSM_MODEM_HAS_GPRS #define TINY_GSM_MODEM_HAS_WIFI + #define TINY_GSM_MODEM_HAS_SSL #include typedef TinyGsmXBee TinyGsm; typedef TinyGsmXBee::GsmClient TinyGsmClient; diff --git a/src/TinyGsmClientA6.h b/src/TinyGsmClientA6.h index 2769395..8fa02bb 100644 --- a/src/TinyGsmClientA6.h +++ b/src/TinyGsmClientA6.h @@ -8,13 +8,10 @@ #ifndef TinyGsmClientA6_h #define TinyGsmClientA6_h +//#pragma message("TinyGSM: TinyGsmClientA6") //#define TINY_GSM_DEBUG Serial -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 256 -#endif - #define TINY_GSM_MUX_COUNT 8 #include @@ -38,22 +35,10 @@ enum RegStatus { REG_UNKNOWN = 4, }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmA6 Class -//============================================================================// -//============================================================================// -class TinyGsmA6 +class TinyGsmA6 : public TinyGsmModem { -//============================================================================// -//============================================================================// -// The Internal A6 Client Class -//============================================================================// -//============================================================================// - - public: class GsmClient : public Client @@ -127,7 +112,7 @@ public: virtual int available() { TINY_GSM_YIELD(); - if (!rx.size() && sock_connected) { + if (!rx.size()) { at->maintain(); } return rx.size(); @@ -136,7 +121,8 @@ public: virtual int read(uint8_t *buf, size_t size) { TINY_GSM_YIELD(); size_t cnt = 0; - while (cnt < size) { + uint32_t _startMillis = millis(); + while (cnt < size && millis() - _startMillis < _timeout) { size_t chunk = TinyGsmMin(size-cnt, rx.size()); if (chunk > 0) { rx.get(buf, chunk); @@ -147,7 +133,6 @@ public: // TODO: Read directly into user buffer? if (!rx.size() && sock_connected) { at->maintain(); - //break; } } return cnt; @@ -185,28 +170,11 @@ private: RxFifo rx; }; -//============================================================================// -//============================================================================// -// The A6 does not have a secure client! -//============================================================================// -//============================================================================// - - - -//============================================================================// -//============================================================================// -// The A6 Modem Functions -//============================================================================// -//============================================================================// public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmA6(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmA6(Stream& stream) -#endif - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -214,11 +182,9 @@ public: /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -226,16 +192,24 @@ public: if (waitResponse() != 1) { return false; } - sendAT(GF("+CMEE=0")); + sendAT(GF("+CMEE=0")); // Turn off verbose errors waitResponse(); - - sendAT(GF("+CMER=3,0,0,2")); + sendAT(GF("+CMER=3,0,0,2")); // Set unsolicited result code output destination waitResponse(); - + DBG(GF("### Modem:"), getModemName()); getSimStatus(); return true; } + String getModemName() { + #if defined(TINY_GSM_MODEM_A6) + return "AI-Thinker A6"; + #elif defined(TINY_GSM_MODEM_A7) + return "AI-Thinker A7"; + #endif + return "AI-Thinker A6"; + } + void setBaud(unsigned long baud) { sendAT(GF("+IPR="), baud); } @@ -243,10 +217,7 @@ public: bool testAT(unsigned long timeout = 10000L) { for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -279,6 +250,14 @@ public: return false; } + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + /* * Power functions */ @@ -380,7 +359,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CSQ")); if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { return 99; @@ -395,20 +374,6 @@ public: return (s == REG_OK_HOME || s == REG_OK_ROAMING); } - bool waitForNetwork(unsigned long timeout = 60000L) { - for (unsigned long start = millis(); millis() - start < timeout; ) { - if (isNetworkConnected()) { - return true; - } - delay(250); - } - return false; - } - - /* - * WiFi functions - */ - /* * GPRS functions */ @@ -467,6 +432,10 @@ public: return (res == 1); } + /* + * IP Address functions + */ + String getLocalIP() { sendAT(GF("+CIFSR")); String res; @@ -479,10 +448,6 @@ public: return res; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } - /* * Phone Call functions */ @@ -621,7 +586,7 @@ public: uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE; - int getBattPercent() { + int8_t getBattPercent() { sendAT(GF("+CBC?")); if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; @@ -632,6 +597,10 @@ public: return res; } + /* + * Client related functions + */ + protected: bool modemConnect(const char* host, uint16_t port, uint8_t* mux) { @@ -654,7 +623,7 @@ protected: return (1 == rsp); } - int modemSend(const void* buff, size_t len, uint8_t mux) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+CIPSEND="), mux, ',', len); if (waitResponse(2000L, GF(GSM_NL ">")) != 1) { return 0; @@ -676,31 +645,9 @@ protected: public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(char c) { - const unsigned long timeout = 1000L; - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { diff --git a/src/TinyGsmClientBG96.h b/src/TinyGsmClientBG96.h index 5616dae..d75ba25 100644 --- a/src/TinyGsmClientBG96.h +++ b/src/TinyGsmClientBG96.h @@ -8,14 +8,11 @@ #ifndef TinyGsmClientBG96_h #define TinyGsmClientBG96_h +//#pragma message("TinyGSM: TinyGsmClientBG96") //#define TINY_GSM_DEBUG Serial //#define TINY_GSM_USE_HEX -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 64 -#endif - #define TINY_GSM_MUX_COUNT 12 #include @@ -39,22 +36,10 @@ enum RegStatus { REG_UNKNOWN = 4, }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmBG96 Class -//============================================================================// -//============================================================================// -class TinyGsmBG96 +class TinyGsmBG96 : public TinyGsmModem { -//============================================================================// -//============================================================================// -// The Internal BG96 Client Class -//============================================================================// -//============================================================================// - - public: class GsmClient : public Client @@ -104,10 +89,21 @@ public: virtual void stop() { TINY_GSM_YIELD(); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. + rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } at->sendAT(GF("+QICLOSE="), mux); sock_connected = false; at->waitResponse(); - rx.clear(); } virtual size_t write(const uint8_t *buf, size_t size) { @@ -190,46 +186,31 @@ private: RxFifo rx; }; -//============================================================================// -//============================================================================// -// The BG96 Secure Client -//============================================================================// -//============================================================================// +// class GsmClientSecure : public GsmClient +// { +// public: +// GsmClientSecure() {} +// +// GsmClientSecure(TinyGsmBG96& modem, uint8_t mux = 1) +// : GsmClient(modem, mux) +// {} +// +// public: +// virtual int connect(const char *host, uint16_t port) { +// stop(); +// TINY_GSM_YIELD(); +// rx.clear(); +// sock_connected = at->modemConnect(host, port, mux, true); +// return sock_connected; +// } +// }; -class GsmClientSecure : public GsmClient -{ -public: - GsmClientSecure() {} - - GsmClientSecure(TinyGsmBG96& modem, uint8_t mux = 1) - : GsmClient(modem, mux) - {} - -public: - virtual int connect(const char *host, uint16_t port) { - stop(); - TINY_GSM_YIELD(); - rx.clear(); - sock_connected = at->modemConnect(host, port, mux, true); - return sock_connected; - } -}; - -//============================================================================// -//============================================================================// -// The BG96 Modem Functions -//============================================================================// -//============================================================================// public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmBG96(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmBG96(Stream& stream) -#endif - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -237,11 +218,9 @@ public: /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -249,10 +228,15 @@ public: if (waitResponse() != 1) { return false; } + DBG(GF("### Modem:"), getModemName()); getSimStatus(); return true; } + String getModemName() { + return "Quectel BG96"; + } + void setBaud(unsigned long baud) { sendAT(GF("+IPR="), baud); } @@ -260,10 +244,7 @@ public: bool testAT(unsigned long timeout = 10000L) { for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -307,6 +288,14 @@ public: return false; // TODO: For now } + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + /* * Power functions */ @@ -324,8 +313,9 @@ public: } bool poweroff() { - sendAT(GF("+QPOWD")); - return waitResponse(GF("POWERED DOWN")) == 1; // TODO + sendAT(GF("+QPOWD=1")); + waitResponse(300); // returns OK first + return waitResponse(300, GF("POWERED DOWN")) == 1; } bool radioOff() { @@ -413,7 +403,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CSQ")); if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { return 99; @@ -428,42 +418,30 @@ public: return (s == REG_OK_HOME || s == REG_OK_ROAMING); } - bool waitForNetwork(unsigned long timeout = 60000L) { - for (unsigned long start = millis(); millis() - start < timeout; ) { - if (isNetworkConnected()) { - return true; - } - delay(250); - } - return false; - } - - /* - * WiFi functions - */ - /* * GPRS functions */ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { gprsDisconnect(); + //Configure the TCPIP Context sendAT(GF("+QICSGP=1,1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); if (waitResponse() != 1) { return false; } + //Activate GPRS/CSD Context sendAT(GF("+QIACT=1")); if (waitResponse(150000L) != 1) { return false; } + //Attach to Packet Domain service - is this necessary? sendAT(GF("+CGATT=1")); if (waitResponse(60000L) != 1) { return false; } - return true; } @@ -485,15 +463,16 @@ public: if (res != 1) return false; - return localIP() != 0; + return localIP() != IPAddress(0,0,0,0); } + /* + * IP Address functions + */ + String getLocalIP() { - sendAT(GF("+CGPADDR=1")); - if (waitResponse(10000L, GF(GSM_NL "+CGPADDR:")) != 1) { - return ""; - } - streamSkipUntil(','); + sendAT(GF("+QILOCIP")); + stream.readStringUntil('\n'); String res = stream.readStringUntil('\n'); if (waitResponse() != 1) { return ""; @@ -501,10 +480,6 @@ public: return res; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } - /* * Phone Call functions */ @@ -560,6 +535,8 @@ public: bool sendSMS_UTF16(const String& number, const void* text, size_t len) { sendAT(GF("+CMGF=1")); waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); sendAT(GF("+CSMP=17,167,0,8")); waitResponse(); @@ -592,14 +569,42 @@ public: /* * Battery functions */ - uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_IMPLEMENTED; - int getBattPercent() TINY_GSM_ATTR_NOT_IMPLEMENTED; + // Use: float vBatt = modem.getBattVoltage() / 1000.0; + uint16_t getBattVoltage() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip + streamSkipUntil(','); // Skip + + uint16_t res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + int8_t getBattPercent() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return false; + } + stream.readStringUntil(','); + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + /* + * Client related functions + */ protected: bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { int rsp; + // (1-16), (0-11),"TCP/UDP/TCP LISTENER/UDP SERVICE", + // "/",,,(0-2 0=buffer) sendAT(GF("+QIOPEN=1,"), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port, GF(",0,0")); rsp = waitResponse(); @@ -616,7 +621,7 @@ protected: return (0 == rsp); } - int modemSend(const void* buff, size_t len, uint8_t mux) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+QISEND="), mux, ',', len); if (waitResponse(GF(">")) != 1) { return 0; @@ -685,31 +690,9 @@ protected: public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(char c) { - const unsigned long timeout = 1000L; - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { diff --git a/src/TinyGsmClientESP8266.h b/src/TinyGsmClientESP8266.h index 51c18ec..f6c7419 100644 --- a/src/TinyGsmClientESP8266.h +++ b/src/TinyGsmClientESP8266.h @@ -8,13 +8,10 @@ #ifndef TinyGsmClientESP8266_h #define TinyGsmClientESP8266_h +//#pragma message("TinyGSM: TinyGsmClientESP8266") //#define TINY_GSM_DEBUG Serial -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 512 -#endif - #define TINY_GSM_MUX_COUNT 5 #include @@ -38,22 +35,10 @@ enum RegStatus { }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmESP8266 Class -//============================================================================// -//============================================================================// - -class TinyGsmESP8266 +class TinyGsmESP8266 : public TinyGsmModem { - //============================================================================// - //============================================================================// - // The ESP8266 Internal Client Class - //============================================================================// - //============================================================================// - public: class GsmClient : public Client @@ -124,7 +109,7 @@ public: virtual int available() { TINY_GSM_YIELD(); - if (!rx.size() && sock_connected) { + if (!rx.size()) { at->maintain(); } return rx.size(); @@ -133,7 +118,8 @@ public: virtual int read(uint8_t *buf, size_t size) { TINY_GSM_YIELD(); size_t cnt = 0; - while (cnt < size) { + uint32_t _startMillis = millis(); + while (cnt < size && millis() - _startMillis < _timeout) { size_t chunk = TinyGsmMin(size-cnt, rx.size()); if (chunk > 0) { rx.get(buf, chunk); @@ -144,7 +130,6 @@ public: // TODO: Read directly into user buffer? if (!rx.size() && sock_connected) { at->maintain(); - //break; } } return cnt; @@ -182,12 +167,6 @@ private: RxFifo rx; }; -//============================================================================// -//============================================================================// -// The Secure ESP8266 Client Class -//============================================================================// -//============================================================================// - class GsmClientSecure : public GsmClient { @@ -209,20 +188,10 @@ public: }; -//============================================================================// -//============================================================================// -// The ESP8266 Modem Functions -//============================================================================// -//============================================================================// - public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmESP8266(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmESP8266(Stream& stream) -#endif - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -230,11 +199,9 @@ public: /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -250,20 +217,22 @@ public: if (waitResponse() != 1) { return false; } + DBG(GF("### Modem:"), getModemName()); return true; } + String getModemName() { + return "ESP8266"; + } + void setBaud(unsigned long baud) { - sendAT(GF("+IPR="), baud); + sendAT(GF("+UART_CUR="), baud, "8,1,0,0"); } bool testAT(unsigned long timeout = 10000L) { for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -294,6 +263,14 @@ public: return true; } + bool hasWifi() { + return true; + } + + bool hasGPRS() { + return false; + } + /* * Power functions */ @@ -313,7 +290,10 @@ public: return init(); } - bool poweroff() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool poweroff() { + sendAT(GF("+GSLP=0")); // Power down indefinitely - until manually reset! + return waitResponse() == 1; + } bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED; @@ -335,7 +315,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CWJAP_CUR?")); int res1 = waitResponse(GF("No AP"), GF("+CWJAP_CUR:")); if (res1 != 2) { @@ -390,6 +370,10 @@ public: return retVal; } + /* + * IP Address functions + */ + String getLocalIP() { sendAT(GF("+CIPSTA_CUR??")); int res1 = waitResponse(GF("ERROR"), GF("+CWJAP_CUR:")); @@ -401,32 +385,10 @@ public: return res2; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } - /* - * GPRS functions + * Client related functions */ - /* - * Messaging functions - */ - - /* - * Location functions - */ - - String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE; - - /* - * Battery functions - */ - - uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE; - - int getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE; - protected: bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { @@ -444,7 +406,7 @@ protected: return (1 == rsp); } - int modemSend(const void* buff, size_t len, uint8_t mux) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+CIPSEND="), mux, ',', len); if (waitResponse(GF(">")) != 1) { return 0; @@ -464,31 +426,9 @@ protected: public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(char c) { - const unsigned long timeout = 1000L; - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { diff --git a/src/TinyGsmClientM590.h b/src/TinyGsmClientM590.h index cd04c71..1f5ea00 100644 --- a/src/TinyGsmClientM590.h +++ b/src/TinyGsmClientM590.h @@ -8,13 +8,10 @@ #ifndef TinyGsmClientM590_h #define TinyGsmClientM590_h +//#pragma message("TinyGSM: TinyGsmClientM590") //#define TINY_GSM_DEBUG Serial -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 256 -#endif - #define TINY_GSM_MUX_COUNT 2 #include @@ -39,22 +36,9 @@ enum RegStatus { }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmM590 Class -//============================================================================// -//============================================================================// - -class TinyGsmM590 +class TinyGsmM590 : public TinyGsmModem { -//============================================================================// -//============================================================================// -// The M590 Internal Client Class -//============================================================================// -//============================================================================// - - public: class GsmClient : public Client @@ -134,7 +118,8 @@ public: virtual int read(uint8_t *buf, size_t size) { TINY_GSM_YIELD(); size_t cnt = 0; - while (cnt < size) { + uint32_t _startMillis = millis(); + while (cnt < size && millis() - _startMillis < _timeout) { size_t chunk = TinyGsmMin(size-cnt, rx.size()); if (chunk > 0) { rx.get(buf, chunk); @@ -143,9 +128,8 @@ public: continue; } // TODO: Read directly into user buffer? - if (!rx.size() && sock_connected) { + if (!rx.size()) { at->maintain(); - //break; } } return cnt; @@ -183,28 +167,11 @@ private: RxFifo rx; }; -//============================================================================// -//============================================================================// -// The M590 Has no Secure client! -//============================================================================// -//============================================================================// - - - -//============================================================================// -//============================================================================// -// The M590 Modem Functions -//============================================================================// -//============================================================================// public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmM590(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmM590(Stream& stream) -#endif - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -212,11 +179,9 @@ public: /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -228,11 +193,15 @@ public: sendAT(GF("+CMEE=2")); waitResponse(); #endif - + DBG(GF("### Modem:"), getModemName()); getSimStatus(); return true; } + String getModemName() { + return "Neoway M590"; + } + void setBaud(unsigned long baud) { sendAT(GF("+IPR="), baud); } @@ -240,10 +209,7 @@ public: bool testAT(unsigned long timeout = 10000L) { for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -284,6 +250,14 @@ public: return false; } + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + /* * Power functions */ @@ -389,7 +363,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CSQ")); if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { return 99; @@ -404,20 +378,6 @@ public: return (s == REG_OK_HOME || s == REG_OK_ROAMING); } - bool waitForNetwork(unsigned long timeout = 60000L) { - for (unsigned long start = millis(); millis() - start < timeout; ) { - if (isNetworkConnected()) { - return true; - } - delay(250); - } - return false; - } - - /* - * WiFi functions - */ - /* * GPRS functions */ @@ -474,6 +434,10 @@ public: return res == 1; } + /* + * IP Address functions + */ + String getLocalIP() { sendAT(GF("+XIIC?")); if (waitResponse(GF(GSM_NL "+XIIC:")) != 1) { @@ -486,10 +450,6 @@ public: return res; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } - /* * Phone Call functions */ @@ -563,7 +523,11 @@ public: uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE; - int getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE; + int8_t getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE; + + /* + * Client related functions + */ protected: @@ -587,7 +551,7 @@ protected: return false; } - int modemSend(const void* buff, size_t len, uint8_t mux) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+TCPSEND="), mux, ',', len); if (waitResponse(GF(">")) != 1) { return 0; @@ -622,31 +586,9 @@ protected: public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(char c) { - const unsigned long timeout = 1000L; - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { diff --git a/src/TinyGsmClientM95.h b/src/TinyGsmClientM95.h new file mode 100644 index 0000000..a3818ce --- /dev/null +++ b/src/TinyGsmClientM95.h @@ -0,0 +1,848 @@ +/** + * @file TinyGsmClientM95.h + * @author Volodymyr Shymanskyy, Pacman Pereira, and Replicade Ltd. + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy, (c)2017 Replicade Ltd. + * @date Nov 2016 + */ + +#ifndef TinyGsmClientM95_h +#define TinyGsmClientM95_h +//#pragma message("TinyGSM: TinyGsmClientM95") + +//#define TINY_GSM_DEBUG Serial +//#define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 6 + +#include + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; + +enum SimStatus { + SIM_ERROR = 0, + SIM_READY = 1, + SIM_LOCKED = 2, +}; + +enum RegStatus { + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + + +class TinyGsmM95 : public TinyGsmModem +{ + +public: + +class GsmClient : public Client +{ + friend class TinyGsmM95; + typedef TinyGsmFifo RxFifo; + +public: + GsmClient() {} + + GsmClient(TinyGsmM95& modem, uint8_t mux = 1) { + init(&modem, mux); + } + + bool init(TinyGsmM95* modem, uint8_t mux = 1) { + this->at = modem; + this->mux = mux; + sock_available = 0; + sock_connected = false; + got_data = false; + + at->sockets[mux] = this; + + return true; + } + +public: + virtual int connect(const char *host, uint16_t port) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux); + return sock_connected; + } + + virtual int connect(IPAddress ip, uint16_t port) { + String host; host.reserve(16); + host += ip[0]; + host += "."; + host += ip[1]; + host += "."; + host += ip[2]; + host += "."; + host += ip[3]; + return connect(host.c_str(), port); + } + + virtual void stop() { + TINY_GSM_YIELD(); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. + rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } + at->sendAT(GF("+QICLOSE="), mux); + sock_connected = false; + at->waitResponse(60000L, GF("CLOSED"), GF("CLOSE OK"), GF("ERROR")); + } + + virtual size_t write(const uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + return at->modemSend(buf, size, mux); + } + + virtual size_t write(uint8_t c) { + return write(&c, 1); + } + + virtual size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + + virtual int available() { + TINY_GSM_YIELD(); + if (!rx.size()) { + at->maintain(); + } + return rx.size() + sock_available; + } + + virtual int read(uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + size_t cnt = 0; + while (cnt < size) { + size_t chunk = TinyGsmMin(size-cnt, rx.size()); + if (chunk > 0) { + rx.get(buf, chunk); + buf += chunk; + cnt += chunk; + continue; + } + // TODO: Read directly into user buffer? + at->maintain(); + if (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + } else { + break; + } + } + return cnt; + } + + virtual int read() { + uint8_t c; + if (read(&c, 1) == 1) { + return c; + } + return -1; + } + + virtual int peek() { return -1; } //TODO + virtual void flush() { at->stream.flush(); } + + virtual uint8_t connected() { + if (available()) { + return true; + } + return sock_connected; + } + virtual operator bool() { return connected(); } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + +private: + TinyGsmM95* at; + uint8_t mux; + uint16_t sock_available; + bool sock_connected; + bool got_data; + RxFifo rx; +}; + + +// class GsmClientSecure : public GsmClient +// { +// public: +// GsmClientSecure() {} +// +// GsmClientSecure(TinyGsmm95& modem, uint8_t mux = 1) +// : GsmClient(modem, mux) +// {} +// +// public: +// virtual int connect(const char *host, uint16_t port) { +// stop(); +// TINY_GSM_YIELD(); +// rx.clear(); +// sock_connected = at->modemConnect(host, port, mux, true); +// return sock_connected; +// } +// }; + + +public: + + TinyGsmM95(Stream& stream) + : TinyGsmModem(stream), stream(stream) + { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + if (!testAT()) { + return false; + } + sendAT(GF("&FZE0")); // Factory + Reset + Echo Off + if (waitResponse() != 1) { + return false; + } +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); + waitResponse(); +#endif + DBG(GF("### Modem:"), getModemName()); + getSimStatus(); + return true; + } + + String getModemName() { + return "Quectel M95"; + } + + void setBaud(unsigned long baud) { + sendAT(GF("+IPR="), baud); + } + + bool testAT(unsigned long timeout = 10000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("")); + if (waitResponse(200) == 1) return true; + delay(100); + } + return false; + } + + void maintain() { + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClient* sock = sockets[mux]; + if (sock && sock->got_data) { + sock->got_data = false; + sock->sock_available = modemGetAvailable(mux); + } + } + while (stream.available()) { + waitResponse(10, NULL, NULL); + } + } + + bool factoryDefault() { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("+IPR=0")); // Auto-baud + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + String getModemInfo() { + sendAT(GF("I")); + String res; + if (waitResponse(1000L, res) != 1) { + return ""; + } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, " "); + res.trim(); + return res; + } + + bool hasSSL() { + return false; // TODO: For now + } + + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + + /* + * Power functions + */ + + bool restart() { + if (!testAT()) { + return false; + } + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L, GF("NORMAL POWER DOWN"), GF("OK"), GF("FAIL")) == 3) { + return false; + } + sendAT(GF("+CFUN=1")); + if (waitResponse(10000L, GF("Call Ready"), GF("OK"), GF("FAIL")) == 3) { + return false; + } + return init(); + } + + bool poweroff() { + sendAT(GF("+QPOWD=1")); + return waitResponse(300, GF("NORMAL POWER DOWN")) == 1; + } + + bool radioOff() { + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L) != 1) { + return false; + } + delay(3000); + return true; + } + + bool sleepEnable(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * SIM card functions + */ + + bool simUnlock(const char *pin) { + sendAT(GF("+CPIN=\""), pin, GF("\"")); + return waitResponse() == 1; + } + + String getSimCCID() { + sendAT(GF("+ICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + String getIMEI() { + sendAT(GF("+GSN")); + if (waitResponse(GF(GSM_NL)) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + SimStatus getSimStatus(unsigned long timeout = 10000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("+CPIN?")); + if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) { + delay(1000); + continue; + } + int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK")); + waitResponse(); + switch (status) { + case 2: + case 3: return SIM_LOCKED; + case 1: return SIM_READY; + default: return SIM_ERROR; + } + } + return SIM_ERROR; + } + + RegStatus getRegistrationStatus() { + sendAT(GF("+CREG?")); + if (waitResponse(GF(GSM_NL "+CREG:")) != 1) { + return REG_UNKNOWN; + } + streamSkipUntil(','); // Skip format (0) + int status = stream.readStringUntil('\n').toInt(); + waitResponse(); + return (RegStatus)status; + } + + String getOperator() { + sendAT(GF("+COPS?")); + if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { + return ""; + } + streamSkipUntil('"'); // Skip mode and format + String res = stream.readStringUntil('"'); + waitResponse(); + return res; + } + + /* + * Generic network functions + */ + + int16_t getSignalQuality() { + sendAT(GF("+CSQ")); + if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { + return 99; + } + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + bool isNetworkConnected() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + void setHostFormat( bool useDottedQuad ) { + if ( useDottedQuad ) { + sendAT(GF("+QIDNSIP=0")); + } else { + sendAT(GF("+QIDNSIP=1")); + } + waitResponse(); + } + + /* + * GPRS functions + */ + bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { + gprsDisconnect(); + + // select foreground context 0 = VIRTUAL_UART_1 + sendAT(GF("+QIFGCNT=0")); + if (waitResponse() != 1) { + return false; + } + + //Select GPRS (=1) as the Bearer + sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse() != 1) { + return false; + } + + //Start TCPIP Task and Set APN, User Name and Password + sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\"" ); + if (waitResponse() != 1) { + return false; + } + + //Activate GPRS/CSD Context + sendAT(GF("+QIACT")); + if (waitResponse(10000) != 1) { + return false; + } + + //Enable multiple TCP/IP connections + sendAT(GF("+QIMUX=1")); + if (waitResponse() != 1) { + return false; + } + + //Request an IP header for received data ("IPD(data length):") + sendAT(GF("+QIHEAD=1")); + if (waitResponse() != 1) { + return false; + } + + //Set Method to Handle Received TCP/IP Data - Retrieve Data by Command + sendAT(GF("+QINDI=1")); + if (waitResponse() != 1) { + return false; + } + + return true; + } + + bool gprsDisconnect() { + sendAT(GF("+QIDEACT")); + return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; + } + + bool isGprsConnected() { + sendAT(GF("+CGATT?")); + if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) { + return false; + } + int res = stream.readStringUntil('\n').toInt(); + waitResponse(); + if (res != 1) + return false; + + return localIP() != IPAddress(0,0,0,0); + } + + /* + * IP Address functions + */ + + String getLocalIP() { + sendAT(GF("+QILOCIP")); + stream.readStringUntil('\n'); + String res = stream.readStringUntil('\n'); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + + String sendUSSD(const String& code) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CUSD=1,\""), code, GF("\"")); + if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) { + return ""; + } + stream.readStringUntil('"'); + String hex = stream.readStringUntil('"'); + stream.readStringUntil(','); + int dcs = stream.readStringUntil('\n').toInt(); + + if (waitResponse() != 1) { + return ""; + } + + if (dcs == 15) { + return TinyGsmDecodeHex8bit(hex); + } else if (dcs == 72) { + return TinyGsmDecodeHex16bit(hex); + } else { + return hex; + } + } + + bool sendSMS(const String& number, const String& text) { + sendAT(GF("+CMGF=1")); + waitResponse(); + //Set GSM 7 bit default alphabet (3GPP TS 23.038) + sendAT(GF("+CSCS=\"GSM\"")); + waitResponse(); + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + stream.print(text); + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + bool sendSMS_UTF16(const String& number, const void* text, size_t len) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CSMP=17,167,0,8")); + waitResponse(); + + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + + uint16_t* t = (uint16_t*)text; + for (size_t i=0; i> 8; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + c = t[i] & 0xFF; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + } + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + /** Delete all SMS */ + bool deleteAllSMS() { + sendAT(GF("+QMGDA=6")); + if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1) ) { + return true; + } + return false; + } + + /* + * Phone Call functions + */ + + bool setGsmBusy(bool busy = true) TINY_GSM_ATTR_NOT_AVAILABLE; + bool callAnswer() TINY_GSM_ATTR_NOT_AVAILABLE; + bool callNumber(const String& number) TINY_GSM_ATTR_NOT_AVAILABLE; + bool callHangup() TINY_GSM_ATTR_NOT_AVAILABLE; + + /* + * Location functions + */ + + String getGsmLocation() TINY_GSM_ATTR_NOT_AVAILABLE; + + /* + * Battery functions + */ + + // Use: float vBatt = modem.getBattVoltage() / 1000.0; + uint16_t getBattVoltage() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip + streamSkipUntil(','); // Skip + + uint16_t res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + int8_t getBattPercent() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return false; + } + stream.readStringUntil(','); + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + /* + * Client related functions + */ + +protected: + + bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { + sendAT(GF("+QIOPEN="), mux, GF("\"TCP"), GF("\",\""), host, GF("\","), port); + int rsp = waitResponse(75000L, + GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+QISEND="), mux, ',', len); + if (waitResponse(GF(">")) != 1) { + return 0; + } + stream.write((uint8_t*)buff, len); + stream.flush(); + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { + return 0; + } + + bool allAcknowledged = false; + // bool failed = false; + while ( !allAcknowledged ) { + sendAT( GF("+QISACK")); + if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { + return -1; + } else { + streamSkipUntil(','); /** Skip total */ + streamSkipUntil(','); /** Skip acknowledged data size */ + if ( stream.readStringUntil('\n').toInt() == 0 ) { + allAcknowledged = true; + } + } + } + waitResponse(5000L); + + // streamSkipUntil(','); // Skip mux + // return stream.readStringUntil('\n').toInt(); + return 1; + } + + size_t modemRead(size_t size, uint8_t mux) { + sendAT(GF("+QIRD="), mux, ',', size); + if (waitResponse(GF("+QIRD:")) != 1) { + return 0; + } + size_t len = stream.readStringUntil('\n').toInt(); + + for (size_t i=0; irx.put(c); + } + waitResponse(); + DBG("### READ:", mux, ",", len); + return len; + } + + size_t modemGetAvailable(uint8_t mux) { + sendAT(GF("+QIRD="), mux, GF(",0")); + size_t result = 0; + if (waitResponse(GF("+QIRD:")) == 1) { + streamSkipUntil(','); // Skip total received + streamSkipUntil(','); // Skip have read + result = stream.readStringUntil('\n').toInt(); + DBG("### STILL:", mux, "has", result); + waitResponse(); + } + if (!result) { + sockets[mux]->sock_connected = modemGetConnected(mux); + } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+QISTATE=1,"), mux); + //+QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + + if (waitResponse(GF("+QISTATE:"))) + return false; + + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int res = stream.readStringUntil(',').toInt(); // socket state + + waitResponse(); + + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; + } + +public: + + /* + Utilities + */ + + template + void sendAT(Args... cmd) { + streamWrite("AT", cmd..., GSM_NL); + stream.flush(); + TINY_GSM_YIELD(); + //DBG("### AT:", cmd...); + } + + // TODO: Optimize this! + uint8_t waitResponse(uint32_t timeout, String& data, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + int index = 0; + unsigned long startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + int a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += (char)a; + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIRD:"))) { + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + int mux = stream.readStringUntil('\n').toInt(); + DBG("### Got Data:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int nl = data.lastIndexOf(GSM_NL, data.length()-8); + int coma = data.indexOf(',', nl+2); + int mux = data.substring(nl+2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout); +finish: + if (!index) { + data.trim(); + if (data.length()) { + DBG("### Unhandled:", data); + } + data = ""; + } + //DBG('<', index, '>'); + return index; + } + + uint8_t waitResponse(uint32_t timeout, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + String data; + return waitResponse(timeout, data, r1, r2, r3, r4, r5); + } + + uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + +public: + Stream& stream; + +protected: + GsmClient* sockets[TINY_GSM_MUX_COUNT]; +}; + +#endif diff --git a/src/TinyGsmClientMC60.h b/src/TinyGsmClientMC60.h new file mode 100644 index 0000000..b3239b3 --- /dev/null +++ b/src/TinyGsmClientMC60.h @@ -0,0 +1,880 @@ +/** + * @file TinyGsmClientMC60.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + * + * @MC60 support added by Tamas Dajka 2017.10.15 - with fixes by Sara Damiano + * + */ + +#ifndef TinyGsmClientMC60_h +#define TinyGsmClientMC60_h +//#pragma message("TinyGSM: TinyGsmClientMC60") + +//#define TINY_GSM_DEBUG Serial +//#define TINY_GSM_USE_HEX + +#define TINY_GSM_MUX_COUNT 6 + +#include + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; + +enum SimStatus { + SIM_ERROR = 0, + SIM_READY = 1, + SIM_LOCKED = 2, + SIM_ANTITHEFT_LOCKED = 3, +}; + +enum RegStatus { + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + + +class TinyGsmMC60 : public TinyGsmModem +{ + +public: + +class GsmClient : public Client +{ + friend class TinyGsmMC60; + typedef TinyGsmFifo RxFifo; + +public: + GsmClient() {} + + GsmClient(TinyGsmMC60& modem, uint8_t mux = 1) { + init(&modem, mux); + } + + bool init(TinyGsmMC60* modem, uint8_t mux = 1) { + this->at = modem; + this->mux = mux; + sock_available = 0; + sock_connected = false; + got_data = false; + + at->sockets[mux] = this; + + return true; + } + +public: + virtual int connect(const char *host, uint16_t port) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux); + return sock_connected; + } + + virtual int connect(IPAddress ip, uint16_t port) { + String host; host.reserve(16); + host += ip[0]; + host += "."; + host += ip[1]; + host += "."; + host += ip[2]; + host += "."; + host += ip[3]; + return connect(host.c_str(), port); + } + + virtual void stop() { + TINY_GSM_YIELD(); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. + rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } + at->sendAT(GF("+QICLOSE="), mux); + sock_connected = false; + at->waitResponse(60000L, GF("CLOSED"), GF("CLOSE OK"), GF("ERROR")); + } + + virtual size_t write(const uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + return at->modemSend(buf, size, mux); + } + + virtual size_t write(uint8_t c) { + return write(&c, 1); + } + + virtual size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + + virtual int available() { + TINY_GSM_YIELD(); + if (!rx.size()) { + at->maintain(); + } + return rx.size() + sock_available; + } + + virtual int read(uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + size_t cnt = 0; + while (cnt < size) { + size_t chunk = TinyGsmMin(size-cnt, rx.size()); + if (chunk > 0) { + rx.get(buf, chunk); + buf += chunk; + cnt += chunk; + continue; + } + // TODO: Read directly into user buffer? + at->maintain(); + if (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + } else { + break; + } + } + return cnt; + } + + virtual int read() { + uint8_t c; + if (read(&c, 1) == 1) { + return c; + } + return -1; + } + + virtual int peek() { return -1; } //TODO + virtual void flush() { at->stream.flush(); } + + virtual uint8_t connected() { + if (available()) { + return true; + } + return sock_connected; + } + virtual operator bool() { return connected(); } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + +private: + TinyGsmMC60* at; + uint8_t mux; + uint16_t sock_available; + bool sock_connected; + bool got_data; + RxFifo rx; +}; + + +// class GsmClientSecure : public GsmClient +// { +// public: +// GsmClientSecure() {} +// +// GsmClientSecure(TinyGsmMC60& modem, uint8_t mux = 1) +// : GsmClient(modem, mux) +// {} +// +// public: +// virtual int connect(const char *host, uint16_t port) { +// stop(); +// TINY_GSM_YIELD(); +// rx.clear(); +// sock_connected = at->modemConnect(host, port, mux, true); +// return sock_connected; +// } +// }; + + +public: + + TinyGsmMC60(Stream& stream) + : TinyGsmModem(stream), stream(stream) + { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + if (!testAT()) { + return false; + } + sendAT(GF("&FZ")); // Factory + Reset + waitResponse(); + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { + return false; + } + DBG(GF("### Modem:"), getModemName()); + getSimStatus(); + return true; + } + + String getModemName() { + #if defined(TINY_GSM_MODEM_MC60) + return "Quectel MC60"; + #elif defined(TINY_GSM_MODEM_MC60E) + return "Quectel MC60E"; + #endif + return "Quectel MC60"; + } + + void setBaud(unsigned long baud) { + sendAT(GF("+IPR="), baud); + } + + bool testAT(unsigned long timeout = 10000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("")); + if (waitResponse(200) == 1) return true; + delay(100); + } + return false; + } + + void maintain() { + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClient* sock = sockets[mux]; + if (sock && sock->got_data) { + sock->got_data = false; + sock->sock_available = modemGetAvailable(mux); + } + } + while (stream.available()) { + waitResponse(10, NULL, NULL); + } + } + + bool factoryDefault() { + sendAT(GF("&FZE0&W")); // Factory + Reset + Echo Off + Write + waitResponse(); + sendAT(GF("+IPR=0")); // Auto-baud + waitResponse(); + sendAT(GF("&W")); // Write configuration + return waitResponse() == 1; + } + + String getModemInfo() { + sendAT(GF("I")); + String res; + if (waitResponse(1000L, res) != 1) { + return ""; + } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, " "); + res.trim(); + return res; + } + + /* + * under development + */ + // bool hasSSL() { + // sendAT(GF("+QIPSSL=?")); + // if (waitResponse(GF(GSM_NL "+CIPSSL:")) != 1) { + // return false; + // } + // return waitResponse() == 1; + // } + + bool hasSSL() { + return false; // TODO: For now + } + + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + + /* + * Power functions + */ + + bool restart() { + if (!testAT()) { + return false; + } + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L) != 1) { + return false; + } + sendAT(GF("+CFUN=1,1")); + if (waitResponse(10000L) != 1) { + return false; + } + delay(3000); + return init(); + } + + bool poweroff() { + sendAT(GF("+QPOWD=1")); + return waitResponse(GF("NORMAL POWER DOWN")) == 1; + } + + bool radioOff() { + if (!testAT()) { + return false; + } + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L) != 1) { + return false; + } + delay(3000); + return true; + } + + bool sleepEnable(bool enable = true) TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * SIM card functions + */ + + bool simUnlock(const char *pin) { + sendAT(GF("+CPIN=\""), pin, GF("\"")); + return waitResponse() == 1; + } + + String getSimCCID() { + sendAT(GF("+ICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + String getIMEI() { + sendAT(GF("+GSN")); + if (waitResponse(GF(GSM_NL)) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + SimStatus getSimStatus(unsigned long timeout = 10000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("+CPIN?")); + if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) { + delay(1000); + continue; + } + int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), GF("NOT INSERTED"), GF("PH_SIM PIN"), GF("PH_SIM PUK")); + waitResponse(); + switch (status) { + case 2: + case 3: return SIM_LOCKED; + case 5: + case 6: return SIM_ANTITHEFT_LOCKED; + case 1: return SIM_READY; + default: return SIM_ERROR; + } + } + return SIM_ERROR; + } + + RegStatus getRegistrationStatus() { + sendAT(GF("+CREG?")); + if (waitResponse(GF(GSM_NL "+CREG:")) != 1) { + return REG_UNKNOWN; + } + streamSkipUntil(','); // Skip format (0) + int status = stream.readStringUntil('\n').toInt(); + waitResponse(); + return (RegStatus)status; + } + + String getOperator() { + sendAT(GF("+COPS?")); + if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { + return ""; + } + streamSkipUntil('"'); // Skip mode and format + String res = stream.readStringUntil('"'); + waitResponse(); + return res; + } + + /* + * Generic network functions + */ + + int16_t getSignalQuality() { + sendAT(GF("+CSQ")); + if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { + return 99; + } + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + bool isNetworkConnected() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + /* + * GPRS functions + */ + bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { + gprsDisconnect(); + + // select foreground context 0 = VIRTUAL_UART_1 + sendAT(GF("+QIFGCNT=0")); + if (waitResponse() != 1) { + return false; + } + + //Select GPRS (=1) as the Bearer + sendAT(GF("+QICSGP=1,\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse() != 1) { + return false; + } + + //Define PDP context - is this necessary? + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Activate PDP context - is this necessary? + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + //Start TCPIP Task and Set APN, User Name and Password + sendAT("+QIREGAPP=\"", apn, "\",\"", user, "\",\"", pwd, "\"" ); + if (waitResponse() != 1) { + return false; + } + + //Activate GPRS/CSD Context + sendAT(GF("+QIACT")); + if (waitResponse(60000L) != 1) { + return false; + } + + //Enable multiple TCP/IP connections + sendAT(GF("+QIMUX=1")); + if (waitResponse() != 1) { + return false; + } + + //Request an IP header for received data ("IPD(data length):") + sendAT(GF("+QIHEAD=1")); + if (waitResponse() != 1) { + return false; + } + + //Set Method to Handle Received TCP/IP Data - Retrieve Data by Command + sendAT(GF("+QINDI=1")); + if (waitResponse() != 1) { + return false; + } + + // Check that we have a local IP address + if (localIP() != 0) { + return true; + } + + return false; + } + + bool gprsDisconnect() { + sendAT(GF("+QIDEACT")); + return waitResponse(60000L, GF("DEACT OK"), GF("ERROR")) == 1; + } + + bool isGprsConnected() { + sendAT(GF("+CGATT?")); + if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) { + return false; + } + int res = stream.readStringUntil('\n').toInt(); + waitResponse(); + if (res != 1) + return false; + + return localIP() != IPAddress(0,0,0,0); + } + + /* + * IP Address functions + */ + + String getLocalIP() { + sendAT(GF("+QILOCIP")); + stream.readStringUntil('\n'); + String res = stream.readStringUntil('\n'); + res.trim(); + return res; + } + + /* + * Messaging functions + */ + + String sendUSSD(const String& code) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CUSD=1,\""), code, GF("\"")); + if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) { + return ""; + } + stream.readStringUntil('"'); + String hex = stream.readStringUntil('"'); + stream.readStringUntil(','); + int dcs = stream.readStringUntil('\n').toInt(); + + if (waitResponse() != 1) { + return ""; + } + + if (dcs == 15) { + return TinyGsmDecodeHex8bit(hex); + } else if (dcs == 72) { + return TinyGsmDecodeHex16bit(hex); + } else { + return hex; + } + } + + bool sendSMS(const String& number, const String& text) { + sendAT(GF("+CMGF=1")); + waitResponse(); + //Set GSM 7 bit default alphabet (3GPP TS 23.038) + sendAT(GF("+CSCS=\"GSM\"")); + waitResponse(); + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + stream.print(text); + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + bool sendSMS_UTF16(const String& number, const void* text, size_t len) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CSMP=17,167,0,8")); + waitResponse(); + + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + + uint16_t* t = (uint16_t*)text; + for (size_t i=0; i> 8; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + c = t[i] & 0xFF; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + } + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + /** Delete all SMS */ + bool deleteAllSMS() { + sendAT(GF("+QMGDA=6")); + if (waitResponse(waitResponse(60000L, GF("OK"), GF("ERROR")) == 1) ) { + return true; + } + return false; + } + + /* + * Location functions + */ + + String getGsmLocation() { + sendAT(GF("+CIPGSMLOC=1,1")); + if (waitResponse(10000L, GF(GSM_NL "+CIPGSMLOC:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + /* + * Battery functions + */ + + // Use: float vBatt = modem.getBattVoltage() / 1000.0; + uint16_t getBattVoltage() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip + streamSkipUntil(','); // Skip + + uint16_t res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + int8_t getBattPercent() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return false; + } + stream.readStringUntil(','); + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + /* + * Client related functions + */ + +protected: + + bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { + sendAT(GF("+QIOPEN="), mux, GF("\"TCP"), GF("\",\""), host, GF("\","), port); + int rsp = waitResponse(75000L, + GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL)); + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+QISEND="), mux, ',', len); + if (waitResponse(GF(">")) != 1) { + return 0; + } + stream.write((uint8_t*)buff, len); + stream.flush(); + if (waitResponse(GF(GSM_NL "SEND OK")) != 1) { + return 0; + } + + bool allAcknowledged = false; + // bool failed = false; + while ( !allAcknowledged ) { + sendAT( GF("+QISACK")); + if (waitResponse(5000L, GF(GSM_NL "+QISACK:")) != 1) { + return -1; + } else { + streamSkipUntil(','); /** Skip total */ + streamSkipUntil(','); /** Skip acknowledged data size */ + if ( stream.readStringUntil('\n').toInt() == 0 ) { + allAcknowledged = true; + } + } + } + waitResponse(5000L); + + // streamSkipUntil(','); // Skip mux + // return stream.readStringUntil('\n').toInt(); + return 1; + } + + size_t modemRead(size_t size, uint8_t mux) { + sendAT(GF("+QIRD="), mux, ',', size); + if (waitResponse(GF("+QIRD:")) != 1) { + return 0; + } + size_t len = stream.readStringUntil('\n').toInt(); + + for (size_t i=0; irx.put(c); + } + waitResponse(); + DBG("### READ:", mux, ",", len); + return len; + } + + size_t modemGetAvailable(uint8_t mux) { + sendAT(GF("+QIRD="), mux, GF(",0")); + size_t result = 0; + if (waitResponse(GF("+QIRD:")) == 1) { + streamSkipUntil(','); // Skip total received + streamSkipUntil(','); // Skip have read + result = stream.readStringUntil('\n').toInt(); + DBG("### STILL:", mux, "has", result); + waitResponse(); + } + if (!result) { + sockets[mux]->sock_connected = modemGetConnected(mux); + } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+QISTATE=1,"), mux); + //+QISTATE: 0,"TCP","151.139.237.11",80,5087,4,1,0,0,"uart1" + + if (waitResponse(GF("+QISTATE:"))) + return false; + + streamSkipUntil(','); // Skip mux + streamSkipUntil(','); // Skip socket type + streamSkipUntil(','); // Skip remote ip + streamSkipUntil(','); // Skip remote port + streamSkipUntil(','); // Skip local port + int res = stream.readStringUntil(',').toInt(); // socket state + + waitResponse(); + + // 0 Initial, 1 Opening, 2 Connected, 3 Listening, 4 Closing + return 2 == res; + } + +public: + + /* + Utilities + */ + + template + void sendAT(Args... cmd) { + streamWrite("AT", cmd..., GSM_NL); + stream.flush(); + TINY_GSM_YIELD(); + //DBG("### AT:", cmd...); + } + + // TODO: Optimize this! + uint8_t waitResponse(uint32_t timeout, String& data, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL, GsmConstStr r6=NULL) + { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + String r6s(r6); r6s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s, ",", r6s);*/ + data.reserve(64); + int index = 0; + unsigned long startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + int a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += (char)a; + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (r6 && data.endsWith(r6)) { + index = 6; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+QIRD:"))) { + streamSkipUntil(','); // Skip the context + streamSkipUntil(','); // Skip the role + int mux = stream.readStringUntil('\n').toInt(); + DBG("### Got Data:", mux); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int nl = data.lastIndexOf(GSM_NL, data.length()-8); + int coma = data.indexOf(',', nl+2); + int mux = data.substring(nl+2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout); +finish: + if (!index) { + data.trim(); + if (data.length()) { + DBG("### Unhandled:", data); + } + data = ""; + } + //DBG('<', index, '>'); + return index; + } + + uint8_t waitResponse(uint32_t timeout, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL, GsmConstStr r6=NULL) + { + String data; + return waitResponse(timeout, data, r1, r2, r3, r4, r5, r6); + } + + uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL, GsmConstStr r6=NULL) + { + return waitResponse(1000, r1, r2, r3, r4, r5, r6); + } + +public: + Stream& stream; + +protected: + GsmClient* sockets[TINY_GSM_MUX_COUNT]; +}; + +#endif diff --git a/src/TinyGsmClientSIM7000.h b/src/TinyGsmClientSIM7000.h new file mode 100644 index 0000000..ef64d5d --- /dev/null +++ b/src/TinyGsmClientSIM7000.h @@ -0,0 +1,1099 @@ +/** + * @file TinyGsmClientSIM7000.h + * @author Volodymyr Shymanskyy + * @license LGPL-3.0 + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @date Nov 2016 + */ + +#ifndef TinyGsmClientSIM7000_h +#define TinyGsmClientSIM7000_h + +#define TINY_GSM_DEBUG Serial +//#define TINY_GSM_USE_HEX + +#if !defined(TINY_GSM_RX_BUFFER) + #define TINY_GSM_RX_BUFFER 64 +#endif + +#define TINY_GSM_MUX_COUNT 8 + +#include + +#define GSM_NL "\r\n" +static const char GSM_OK[] TINY_GSM_PROGMEM = "OK" GSM_NL; +static const char GSM_ERROR[] TINY_GSM_PROGMEM = "ERROR" GSM_NL; + +enum SimStatus { + SIM_ERROR = 0, + SIM_READY = 1, + SIM_LOCKED = 2, +}; + +enum RegStatus { + REG_UNREGISTERED = 0, + REG_SEARCHING = 2, + REG_DENIED = 3, + REG_OK_HOME = 1, + REG_OK_ROAMING = 5, + REG_UNKNOWN = 4, +}; + +enum TinyGSMDateTimeFormat { + DATE_FULL = 0, + DATE_TIME = 1, + DATE_DATE = 2 +}; + +class TinyGsmSim7000 : public TinyGsmModem +{ + +public: + +class GsmClient : public Client +{ + friend class TinyGsmSim7000; + typedef TinyGsmFifo RxFifo; + +public: + GsmClient() {} + + GsmClient(TinyGsmSim7000& modem, uint8_t mux = 1) { + init(&modem, mux); + } + + bool init(TinyGsmSim7000* modem, uint8_t mux = 1) { + this->at = modem; + this->mux = mux; + sock_available = 0; + prev_check = 0; + sock_connected = false; + got_data = false; + + at->sockets[mux] = this; + + return true; + } + +public: + virtual int connect(const char *host, uint16_t port) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux); + return sock_connected; + } + + virtual int connect(IPAddress ip, uint16_t port) { + String host; host.reserve(16); + host += ip[0]; + host += "."; + host += ip[1]; + host += "."; + host += ip[2]; + host += "."; + host += ip[3]; + return connect(host.c_str(), port); + } + + virtual void stop() { + TINY_GSM_YIELD(); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. + rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } + at->sendAT(GF("+CIPCLOSE="), mux); + sock_connected = false; + at->waitResponse(); + } + + virtual size_t write(const uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + return at->modemSend(buf, size, mux); + } + + virtual size_t write(uint8_t c) { + return write(&c, 1); + } + + virtual size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + + virtual int available() { + TINY_GSM_YIELD(); + if (!rx.size()) { + // TODO: Is this needed for SIM7000? + // Workaround: sometimes SIM7000 forgets to notify about data arrival. + // TODO: Currently we ping the module periodically, + // but maybe there's a better indicator that we need to poll + if (millis() - prev_check > 250) { + got_data = true; + prev_check = millis(); + } + at->maintain(); + } + return rx.size() + sock_available; + } + + virtual int read(uint8_t *buf, size_t size) { + TINY_GSM_YIELD(); + at->maintain(); + size_t cnt = 0; + while (cnt < size) { + size_t chunk = TinyGsmMin(size-cnt, rx.size()); + if (chunk > 0) { + rx.get(buf, chunk); + buf += chunk; + cnt += chunk; + continue; + } + // TODO: Is this needed for SIM7000? + // Workaround: sometimes SIM7000 forgets to notify about data arrival. + // TODO: Currently we ping the module periodically, + // but maybe there's a better indicator that we need to poll + if (millis() - prev_check > 250) { + got_data = true; + prev_check = millis(); + } + at->maintain(); + // TODO: Read directly into user buffer? + if (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + } else { + break; + } + } + return cnt; + } + + virtual int read() { + uint8_t c; + if (read(&c, 1) == 1) { + return c; + } + return -1; + } + + virtual int peek() { return -1; } //TODO + virtual void flush() { at->stream.flush(); } + + virtual uint8_t connected() { + if (available()) { + return true; + } + return sock_connected; + } + virtual operator bool() { return connected(); } + + /* + * Extended API + */ + + String remoteIP() TINY_GSM_ATTR_NOT_IMPLEMENTED; + +private: + TinyGsmSim7000* at; + uint8_t mux; + uint16_t sock_available; + uint32_t prev_check; + bool sock_connected; + bool got_data; + RxFifo rx; +}; + + +class GsmClientSecure : public GsmClient +{ +public: + GsmClientSecure() {} + + GsmClientSecure(TinyGsmSim7000& modem, uint8_t mux = 1) + : GsmClient(modem, mux) + {} + +public: + virtual int connect(const char *host, uint16_t port) { + stop(); + TINY_GSM_YIELD(); + rx.clear(); + sock_connected = at->modemConnect(host, port, mux, true); + return sock_connected; + } +}; + + +public: + + TinyGsmSim7000(Stream& stream) + : TinyGsmModem(stream), stream(stream) + { + memset(sockets, 0, sizeof(sockets)); + } + + /* + * Basic functions + */ + + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + if (!testAT()) { + return false; + } + sendAT(GF("E0")); // Echo Off + if (waitResponse() != 1) { + return false; + } + DBG(GF("### Modem:"), getModemName()); + getSimStatus(); + return true; + } + + String getModemName() { + return "SIMCom SIM7000"; + } + + void setBaud(unsigned long baud) { + sendAT(GF("+IPR="), baud); + } + + bool testAT(unsigned long timeout = 10000L) { + //streamWrite(GF("AAAAA" GSM_NL)); // TODO: extra A's to help detect the baud rate + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("")); + if (waitResponse(200) == 1) return true; + delay(100); + } + return false; + } + + void maintain() { + for (int mux = 0; mux < TINY_GSM_MUX_COUNT; mux++) { + GsmClient* sock = sockets[mux]; + if (sock && sock->got_data) { + sock->got_data = false; + sock->sock_available = modemGetAvailable(mux); + } + } + while (stream.available()) { + waitResponse(10, NULL, NULL); + } + } + + bool factoryDefault() { // these commands aren't supported + return false; + } + + String getModemInfo() { + sendAT(GF("I")); + String res; + if (waitResponse(1000L, res) != 1) { + return ""; + } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, " "); + res.trim(); + return res; + } + + bool hasSSL() { + return false; // TODO: Module supports SSL, but not yet implemented + } + + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + + /* + * Power functions + */ + + bool restart() { + if (!testAT()) { + return false; + } + //Enable Local Time Stamp for getting network time + // TODO: Find a better place for this + sendAT(GF("+CLTS=1")); + if (waitResponse(10000L) != 1) { + return false; + } + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L) != 1) { + return false; + } + sendAT(GF("+CFUN=1,1")); + if (waitResponse(10000L) != 1) { + return false; + } + delay(3000); //TODO: Test this delay + return init(); + } + + bool poweroff() { + sendAT(GF("+CPOWD=1")); + return waitResponse(GF("NORMAL POWER DOWN")) == 1; + } + + bool radioOff() { + sendAT(GF("+CFUN=0")); + if (waitResponse(10000L) != 1) { + return false; + } + delay(3000); + return true; + } + + /* + During sleep, the SIM7000 module has its serial communication disabled. + In order to reestablish communication pull the DRT-pin of the SIM7000 module + LOW for at least 50ms. Then use this function to disable sleep mode. + The DTR-pin can then be released again. + */ + bool sleepEnable(bool enable = true) { + sendAT(GF("+CSCLK="), enable); + return waitResponse() == 1; + } + + /* + * SIM card functions + */ + + bool simUnlock(const char *pin) { + sendAT(GF("+CPIN=\""), pin, GF("\"")); + return waitResponse() == 1; + } + + String getSimCCID() { + sendAT(GF("+ICCID")); + if (waitResponse(GF(GSM_NL "+ICCID:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + String getIMEI() { + sendAT(GF("+GSN")); + if (waitResponse(GF(GSM_NL)) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + SimStatus getSimStatus(unsigned long timeout = 10000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + sendAT(GF("+CPIN?")); + if (waitResponse(GF(GSM_NL "+CPIN:")) != 1) { + delay(1000); + continue; + } + int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK")); + waitResponse(); + switch (status) { + case 2: + case 3: return SIM_LOCKED; + case 1: return SIM_READY; + default: return SIM_ERROR; + } + } + return SIM_ERROR; + } + + RegStatus getRegistrationStatus() { + sendAT(GF("+CGREG?")); + if (waitResponse(GF(GSM_NL "+CGREG:")) != 1) { + return REG_UNKNOWN; + } + streamSkipUntil(','); // Skip format (0) + int status = stream.readStringUntil('\n').toInt(); + waitResponse(); + return (RegStatus)status; + } + + String getOperator() { + sendAT(GF("+COPS?")); + if (waitResponse(GF(GSM_NL "+COPS:")) != 1) { + return ""; + } + streamSkipUntil('"'); // Skip mode and format + String res = stream.readStringUntil('"'); + waitResponse(); + return res; + } + + /* + * Generic network functions + */ + + int16_t getSignalQuality() { + sendAT(GF("+CSQ")); + if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { + return 99; + } + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + bool isNetworkConnected() { + RegStatus s = getRegistrationStatus(); + return (s == REG_OK_HOME || s == REG_OK_ROAMING); + } + + String getNetworkModes() { + sendAT(GF("+CNMP=?")); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + String setNetworkMode(uint8_t mode) { + sendAT(GF("+CNMP="), mode); + if (waitResponse(GF(GSM_NL "+CNMP:")) != 1) { + return "OK"; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + String getPreferredModes() { + sendAT(GF("+CMNB=?")); + if (waitResponse(GF(GSM_NL "+CMNB:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + String setPreferredMode(uint8_t mode) { + sendAT(GF("+CMNB="), mode); + if (waitResponse(GF(GSM_NL "+CMNB:")) != 1) { + return "OK"; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + return res; + } + + + /* + * GPRS functions + */ + bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { + gprsDisconnect(); + + // Set the Bearer for the IP + sendAT(GF("+SAPBR=3,1,\"Contype\",\"GPRS\"")); // Set the connection type to GPRS + waitResponse(); + + sendAT(GF("+SAPBR=3,1,\"APN\",\""), apn, '"'); // Set the APN + waitResponse(); + + if (user && strlen(user) > 0) { + sendAT(GF("+SAPBR=3,1,\"USER\",\""), user, '"'); // Set the user name + waitResponse(); + } + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+SAPBR=3,1,\"PWD\",\""), pwd, '"'); // Set the password + waitResponse(); + } + + // Define the PDP context + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); + waitResponse(); + + // Activate the PDP context + sendAT(GF("+CGACT=1,1")); + waitResponse(60000L); + + // Open the definied GPRS bearer context + sendAT(GF("+SAPBR=1,1")); + waitResponse(85000L); + // Query the GPRS bearer context status + sendAT(GF("+SAPBR=2,1")); + if (waitResponse(30000L) != 1) + return false; + + // Attach to GPRS + sendAT(GF("+CGATT=1")); + if (waitResponse(60000L) != 1) + return false; + + // TODO: wait AT+CGATT? + + // Set to multi-IP + sendAT(GF("+CIPMUX=1")); + if (waitResponse() != 1) { + return false; + } + + // Put in "quick send" mode (thus no extra "Send OK") + sendAT(GF("+CIPQSEND=1")); + if (waitResponse() != 1) { + return false; + } + + // Set to get data manually + sendAT(GF("+CIPRXGET=1")); + if (waitResponse() != 1) { + return false; + } + + // Start Task and Set APN, USER NAME, PASSWORD + sendAT(GF("+CSTT=\""), apn, GF("\",\""), user, GF("\",\""), pwd, GF("\"")); + if (waitResponse(60000L) != 1) { + return false; + } + + // Bring Up Wireless Connection with GPRS or CSD + sendAT(GF("+CIICR")); + if (waitResponse(60000L) != 1) { + return false; + } + + // Get Local IP Address, only assigned after connection + sendAT(GF("+CIFSR;E0")); + if (waitResponse(10000L) != 1) { + return false; + } + + return true; + } + + bool gprsDisconnect() { + // Shut the TCP/IP connection + sendAT(GF("+CIPSHUT")); + if (waitResponse(60000L) != 1) + return false; + + sendAT(GF("+CGATT=0")); // Deactivate the bearer context + if (waitResponse(60000L) != 1) + return false; + + return true; + } + + bool isGprsConnected() { + sendAT(GF("+CGATT?")); + if (waitResponse(GF(GSM_NL "+CGATT:")) != 1) { + return false; + } + int res = stream.readStringUntil('\n').toInt(); + waitResponse(); + if (res != 1) + return false; + + sendAT(GF("+CIFSR;E0")); // Another option is to use AT+CGPADDR=1 + if (waitResponse() != 1) + return false; + + return true; + } + + /* + * IP Address functions + */ + + String getLocalIP() { + sendAT(GF("+CIFSR;E0")); + String res; + if (waitResponse(10000L, res) != 1) { + return ""; + } + res.replace(GSM_NL "OK" GSM_NL, ""); + res.replace(GSM_NL, ""); + res.trim(); + return res; + } + + + /* + * Phone Call functions + */ + + bool setGsmBusy() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool callAnswer() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool callNumber() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool callHangup() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool dtmfSend() TINY_GSM_ATTR_NOT_IMPLEMENTED; + + /* + * Messaging functions + */ + + String sendUSSD(const String& code) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CUSD=1,\""), code, GF("\"")); + if (waitResponse() != 1) { + return ""; + } + if (waitResponse(10000L, GF(GSM_NL "+CUSD:")) != 1) { + return ""; + } + stream.readStringUntil('"'); + String hex = stream.readStringUntil('"'); + stream.readStringUntil(','); + int dcs = stream.readStringUntil('\n').toInt(); + + if (dcs == 15) { + return TinyGsmDecodeHex8bit(hex); + } else if (dcs == 72) { + return TinyGsmDecodeHex16bit(hex); + } else { + return hex; + } + } + + bool sendSMS(const String& number, const String& text) { + + sendAT(GF("+AT+CSCA?")); + waitResponse(); + sendAT(GF("+CMGF=1")); + waitResponse(); + //Set GSM 7 bit default alphabet (3GPP TS 23.038) + sendAT(GF("+CSCS=\"GSM\"")); + waitResponse(); + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + stream.print(text); + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + bool sendSMS_UTF16(const String& number, const void* text, size_t len) { + sendAT(GF("+CMGF=1")); + waitResponse(); + sendAT(GF("+CSCS=\"HEX\"")); + waitResponse(); + sendAT(GF("+CSMP=17,167,0,8")); + waitResponse(); + + sendAT(GF("+CMGS=\""), number, GF("\"")); + if (waitResponse(GF(">")) != 1) { + return false; + } + + uint16_t* t = (uint16_t*)text; + for (size_t i=0; i> 8; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + c = t[i] & 0xFF; + if (c < 0x10) { stream.print('0'); } + stream.print(c, HEX); + } + stream.write((char)0x1A); + stream.flush(); + return waitResponse(60000L) == 1; + } + + + /* + * Location functions + */ + + String getGsmLocation() { + sendAT(GF("+CIPGSMLOC=1,1")); + if (waitResponse(10000L, GF(GSM_NL "+CIPGSMLOC:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + + /* + * GPS location functions + */ + + // enable GPS + bool enableGPS() { + sendAT(GF("+CGNSPWR=1")); + if (waitResponse() != 1) { + return false; + } + return true; + } + + bool disableGPS() { + sendAT(GF("+CGNSPWR=0")); + if (waitResponse() != 1) { + return false; + } + return true; + } + + // get the RAW GPS output + String getGPSraw() { + sendAT(GF("+CGNSINF")); + if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) { + return ""; + } + String res = stream.readStringUntil('\n'); + waitResponse(); + res.trim(); + return res; + } + + // get GPS informations + bool getGPS(float *lat, float *lon, float *speed=0, int *alt=0, int *vsat=0, int *usat=0) { + //String buffer = ""; + bool fix = false; + + sendAT(GF("+CGNSINF")); + if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) { + return false; + } + + stream.readStringUntil(','); // mode + if ( stream.readStringUntil(',').toInt() == 1 ) fix = true; + stream.readStringUntil(','); //utctime + *lat = stream.readStringUntil(',').toFloat(); //lat + *lon = stream.readStringUntil(',').toFloat(); //lon + if (alt != NULL) *alt = stream.readStringUntil(',').toFloat(); //lon + if (speed != NULL) *speed = stream.readStringUntil(',').toFloat(); //speed + stream.readStringUntil(','); + stream.readStringUntil(','); + stream.readStringUntil(','); + stream.readStringUntil(','); + stream.readStringUntil(','); + stream.readStringUntil(','); + stream.readStringUntil(','); + if (vsat != NULL) *vsat = stream.readStringUntil(',').toInt(); //viewed satelites + if (usat != NULL) *usat = stream.readStringUntil(',').toInt(); //used satelites + stream.readStringUntil('\n'); + + waitResponse(); + + return fix; + } + + /* + * Time functions + */ + String getGSMDateTime(TinyGSMDateTimeFormat format) { + sendAT(GF("+CCLK?")); + if (waitResponse(2000L, GF(GSM_NL "+CCLK: \"")) != 1) { + return ""; + } + + String res; + + switch(format) { + case DATE_FULL: + res = stream.readStringUntil('"'); + break; + case DATE_TIME: + streamSkipUntil(','); + res = stream.readStringUntil('"'); + break; + case DATE_DATE: + res = stream.readStringUntil(','); + break; + } + return res; + } + + // get GPS time + bool getGPSTime(int *year, int *month, int *day, int *hour, int *minute, int *second) { + bool fix = false; + char chr_buffer[12]; + sendAT(GF("+CGNSINF")); + if (waitResponse(GF(GSM_NL "+CGNSINF:")) != 1) { + return false; + } + + for (int i = 0; i < 3; i++) { + String buffer = stream.readStringUntil(','); + buffer.toCharArray(chr_buffer, sizeof(chr_buffer)); + switch (i) { + case 0: + //mode + break; + case 1: + //fixstatus + if ( buffer.toInt() == 1 ) { + fix = buffer.toInt(); + } + break; + case 2: + *year = buffer.substring(0,4).toInt(); + *month = buffer.substring(4,6).toInt(); + *day = buffer.substring(6,8).toInt(); + *hour = buffer.substring(8,10).toInt(); + *minute = buffer.substring(10,12).toInt(); + *second = buffer.substring(12,14).toInt(); + break; + + default: + // if nothing else matches, do the default + // default is optional + break; + } + } + String res = stream.readStringUntil('\n'); + waitResponse(); + + if (fix) { + return true; + } else { + return false; + } + } + + /* + * Battery functions + */ + // Use: float vBatt = modem.getBattVoltage() / 1000.0; + uint16_t getBattVoltage() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip + streamSkipUntil(','); // Skip + + uint16_t res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + int8_t getBattPercent() { + sendAT(GF("+CBC")); + if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { + return false; + } + stream.readStringUntil(','); + int res = stream.readStringUntil(',').toInt(); + waitResponse(); + return res; + } + + /* + * Client related functions + */ + +protected: + + bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { + int rsp; + sendAT(GF("+CIPSTART="), mux, ',', GF("\"TCP"), GF("\",\""), host, GF("\","), port); + rsp = waitResponse(75000L, + GF("CONNECT OK" GSM_NL), + GF("CONNECT FAIL" GSM_NL), + GF("ALREADY CONNECT" GSM_NL), + GF("ERROR" GSM_NL), + GF("CLOSE OK" GSM_NL) // Happens when HTTPS handshake fails + ); + return (1 == rsp); + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { + sendAT(GF("+CIPSEND="), mux, ',', len); + if (waitResponse(GF(">")) != 1) { + return 0; + } + stream.write((uint8_t*)buff, len); + stream.flush(); + if (waitResponse(GF(GSM_NL "DATA ACCEPT:")) != 1) { + return 0; + } + streamSkipUntil(','); // Skip mux + return stream.readStringUntil('\n').toInt(); + } + + size_t modemRead(size_t size, uint8_t mux) { +#ifdef TINY_GSM_USE_HEX + sendAT(GF("+CIPRXGET=3,"), mux, ',', size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { + return 0; + } +#else + sendAT(GF("+CIPRXGET=2,"), mux, ',', size); + if (waitResponse(GF("+CIPRXGET:")) != 1) { + return 0; + } +#endif + streamSkipUntil(','); // Skip mode 2/3 + streamSkipUntil(','); // Skip mux + size_t len = stream.readStringUntil(',').toInt(); + sockets[mux]->sock_available = stream.readStringUntil('\n').toInt(); + + for (size_t i=0; irx.put(c); + } + waitResponse(); + return len; + } + + size_t modemGetAvailable(uint8_t mux) { + sendAT(GF("+CIPRXGET=4,"), mux); + size_t result = 0; + if (waitResponse(GF("+CIPRXGET:")) == 1) { + streamSkipUntil(','); // Skip mode 4 + streamSkipUntil(','); // Skip mux + result = stream.readStringUntil('\n').toInt(); + waitResponse(); + } + if (!result) { + sockets[mux]->sock_connected = modemGetConnected(mux); + } + return result; + } + + bool modemGetConnected(uint8_t mux) { + sendAT(GF("+CIPSTATUS="), mux); + int res = waitResponse(GF(",\"CONNECTED\""), GF(",\"CLOSED\""), GF(",\"CLOSING\""), GF(",\"INITIAL\"")); + waitResponse(); + return 1 == res; + } + +public: + + /* + Utilities + */ + + template + void sendAT(Args... cmd) { + streamWrite("AT", cmd..., GSM_NL); + stream.flush(); + TINY_GSM_YIELD(); + //DBG("### AT:", cmd...); + } + + // TODO: Optimize this! + uint8_t waitResponse(uint32_t timeout, String& data, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + /*String r1s(r1); r1s.trim(); + String r2s(r2); r2s.trim(); + String r3s(r3); r3s.trim(); + String r4s(r4); r4s.trim(); + String r5s(r5); r5s.trim(); + DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ + data.reserve(64); + int index = 0; + unsigned long startMillis = millis(); + do { + TINY_GSM_YIELD(); + while (stream.available() > 0) { + int a = stream.read(); + if (a <= 0) continue; // Skip 0x00 bytes, just in case + data += (char)a; + if (r1 && data.endsWith(r1)) { + index = 1; + goto finish; + } else if (r2 && data.endsWith(r2)) { + index = 2; + goto finish; + } else if (r3 && data.endsWith(r3)) { + index = 3; + goto finish; + } else if (r4 && data.endsWith(r4)) { + index = 4; + goto finish; + } else if (r5 && data.endsWith(r5)) { + index = 5; + goto finish; + } else if (data.endsWith(GF(GSM_NL "+CIPRXGET:"))) { + String mode = stream.readStringUntil(','); + if (mode.toInt() == 1) { + int mux = stream.readStringUntil('\n').toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->got_data = true; + } + data = ""; + } else { + data += mode; + } + } else if (data.endsWith(GF("CLOSED" GSM_NL))) { + int nl = data.lastIndexOf(GSM_NL, data.length()-8); + int coma = data.indexOf(',', nl+2); + int mux = data.substring(nl+2, coma).toInt(); + if (mux >= 0 && mux < TINY_GSM_MUX_COUNT && sockets[mux]) { + sockets[mux]->sock_connected = false; + } + data = ""; + DBG("### Closed: ", mux); + } + } + } while (millis() - startMillis < timeout); +finish: + if (!index) { + data.trim(); + if (data.length()) { + DBG("### Unhandled:", data); + } + data = ""; + } + //DBG('<', index, '>'); + return index; + } + + uint8_t waitResponse(uint32_t timeout, + GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + String data; + return waitResponse(timeout, data, r1, r2, r3, r4, r5); + } + + uint8_t waitResponse(GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), + GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) + { + return waitResponse(1000, r1, r2, r3, r4, r5); + } + +public: + Stream& stream; + +protected: + GsmClient* sockets[TINY_GSM_MUX_COUNT]; +}; + +#endif diff --git a/src/TinyGsmClientSIM800.h b/src/TinyGsmClientSIM800.h index 5660c7c..1e19928 100644 --- a/src/TinyGsmClientSIM800.h +++ b/src/TinyGsmClientSIM800.h @@ -8,14 +8,11 @@ #ifndef TinyGsmClientSIM800_h #define TinyGsmClientSIM800_h +//#pragma message("TinyGSM: TinyGsmClientSIM800") //#define TINY_GSM_DEBUG Serial //#define TINY_GSM_USE_HEX -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 64 -#endif - #define TINY_GSM_MUX_COUNT 5 #include @@ -45,7 +42,7 @@ enum TinyGSMDateTimeFormat { DATE_DATE = 2 }; -class TinyGsmSim800 +class TinyGsmSim800 : public TinyGsmModem { public: @@ -98,10 +95,21 @@ public: virtual void stop() { TINY_GSM_YIELD(); - at->sendAT(GF("+CIPCLOSE="), mux); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. + rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } + at->sendAT(GF("+CIPCLOSE="), mux, GF(",1")); // Quick close sock_connected = false; at->waitResponse(); - rx.clear(); } virtual size_t write(const uint8_t *buf, size_t size) { @@ -121,11 +129,11 @@ public: virtual int available() { TINY_GSM_YIELD(); - if (!rx.size() && sock_connected) { + if (!rx.size()) { // Workaround: sometimes SIM800 forgets to notify about data arrival. // TODO: Currently we ping the module periodically, // but maybe there's a better indicator that we need to poll - if (millis() - prev_check > 500) { + if (millis() - prev_check > 250) { got_data = true; prev_check = millis(); } @@ -138,7 +146,7 @@ public: TINY_GSM_YIELD(); at->maintain(); size_t cnt = 0; - while (cnt < size && sock_connected) { + while (cnt < size) { size_t chunk = TinyGsmMin(size-cnt, rx.size()); if (chunk > 0) { rx.get(buf, chunk); @@ -146,10 +154,17 @@ public: cnt += chunk; continue; } - // TODO: Read directly into user buffer? + // Workaround: sometimes SIM800 forgets to notify about data arrival. + // TODO: Currently we ping the module periodically, + // but maybe there's a better indicator that we need to poll + if (millis() - prev_check > 250) { + got_data = true; + prev_check = millis(); + } at->maintain(); + // TODO: Read directly into user buffer? if (sock_available > 0) { - at->modemRead(rx.free(), mux); + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); } else { break; } @@ -192,6 +207,7 @@ private: RxFifo rx; }; + class GsmClientSecure : public GsmClient { public: @@ -211,10 +227,11 @@ public: } }; + public: TinyGsmSim800(Stream& stream) - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); } @@ -222,11 +239,9 @@ public: /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -236,10 +251,24 @@ public: if (waitResponse() != 1) { return false; } + DBG(GF("### Modem:"), getModemName()); getSimStatus(); return true; } + String getModemName() { + #if defined(TINY_GSM_MODEM_SIM800) + return "SIMCom SIM800"; + #elif defined(TINY_GSM_MODEM_SIM808) + return "SIMCom SIM808"; + #elif defined(TINY_GSM_MODEM_SIM868) + return "SIMCom SIM868"; + #elif defined(TINY_GSM_MODEM_SIM900) + return "SIMCom SIM900"; + #endif + return "SIMCom SIM800"; + } + void setBaud(unsigned long baud) { sendAT(GF("+IPR="), baud); } @@ -248,10 +277,7 @@ public: //streamWrite(GF("AAAAA" GSM_NL)); // TODO: extra A's to help detect the baud rate for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -309,6 +335,14 @@ public: #endif } + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + /* * Power functions */ @@ -402,10 +436,10 @@ public: int status = waitResponse(GF("READY"), GF("SIM PIN"), GF("SIM PUK"), GF("NOT INSERTED")); waitResponse(); switch (status) { - case 2: - case 3: return SIM_LOCKED; - case 1: return SIM_READY; - default: return SIM_ERROR; + case 2: + case 3: return SIM_LOCKED; + case 1: return SIM_READY; + default: return SIM_ERROR; } } return SIM_ERROR; @@ -437,7 +471,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CSQ")); if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { return 99; @@ -452,16 +486,6 @@ public: return (s == REG_OK_HOME || s == REG_OK_ROAMING); } - bool waitForNetwork(unsigned long timeout = 60000L) { - for (unsigned long start = millis(); millis() - start < timeout; ) { - if (isNetworkConnected()) { - return true; - } - delay(250); - } - return false; - } - /* * GPRS functions */ @@ -582,6 +606,10 @@ public: return true; } + /* + * IP Address functions + */ + String getLocalIP() { sendAT(GF("+CIFSR;E0")); String res; @@ -594,9 +622,6 @@ public: return res; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } /* * Phone Call functions @@ -780,7 +805,7 @@ public: return res; } - int getBattPercent() { + int8_t getBattPercent() { sendAT(GF("+CBC")); if (waitResponse(GF(GSM_NL "+CBC:")) != 1) { return false; @@ -791,6 +816,10 @@ public: return res; } + /* + * Client related functions + */ + protected: bool modemConnect(const char* host, uint16_t port, uint8_t mux, bool ssl = false) { @@ -813,7 +842,7 @@ protected: return (1 == rsp); } - int modemSend(const void* buff, size_t len, uint8_t mux) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+CIPSEND="), mux, ',', len); if (waitResponse(GF(">")) != 1) { return 0; @@ -885,30 +914,9 @@ protected: public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(const char c, const unsigned long timeout = 3000L) { - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { @@ -984,6 +992,7 @@ finish: } data = ""; } + //DBG('<', index, '>'); return index; } diff --git a/src/TinyGsmClientSIM808.h b/src/TinyGsmClientSIM808.h index b49ab85..35754a0 100644 --- a/src/TinyGsmClientSIM808.h +++ b/src/TinyGsmClientSIM808.h @@ -8,16 +8,11 @@ #ifndef TinyGsmClientSIM808_h #define TinyGsmClientSIM808_h +//#pragma message("TinyGSM: TinyGsmClientSIM808") #include -//============================================================================// -//============================================================================// -// Declaration and Definitio of the TinyGsmSim808 Class -//============================================================================// -//============================================================================// - class TinyGsmSim808: public TinyGsmSim800 { @@ -33,7 +28,7 @@ public: // enable GPS bool enableGPS() { - uint16_t state; + // uint16_t state; sendAT(GF("+CGNSPWR=1")); if (waitResponse() != 1) { @@ -44,7 +39,7 @@ public: } bool disableGPS() { - uint16_t state; + // uint16_t state; sendAT(GF("+CGNSPWR=0")); if (waitResponse() != 1) { @@ -71,7 +66,7 @@ public: // works only with ans SIM808 V2 bool getGPS(float *lat, float *lon, float *speed=0, int *alt=0, int *vsat=0, int *usat=0) { //String buffer = ""; - char chr_buffer[12]; + // char chr_buffer[12]; bool fix = false; sendAT(GF("+CGNSINF")); diff --git a/src/TinyGsmClientUBLOX.h b/src/TinyGsmClientUBLOX.h index 929d1c3..d5e4912 100644 --- a/src/TinyGsmClientUBLOX.h +++ b/src/TinyGsmClientUBLOX.h @@ -8,14 +8,11 @@ #ifndef TinyGsmClientUBLOX_h #define TinyGsmClientUBLOX_h +//#pragma message("TinyGSM: TinyGsmClientUBLOX") //#define TINY_GSM_DEBUG Serial -#if !defined(TINY_GSM_RX_BUFFER) - #define TINY_GSM_RX_BUFFER 64 -#endif - -#define TINY_GSM_MUX_COUNT 5 +#define TINY_GSM_MUX_COUNT 7 #include @@ -39,22 +36,10 @@ enum RegStatus { REG_UNKNOWN = 4, }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmUBLOX Class -//============================================================================// -//============================================================================// -class TinyGsmUBLOX +class TinyGsmUBLOX : public TinyGsmModem { -//============================================================================// -//============================================================================// -// The UBLOX Internal Client Class -//============================================================================// -//============================================================================// - - public: class GsmClient : public Client @@ -65,14 +50,15 @@ class GsmClient : public Client public: GsmClient() {} - GsmClient(TinyGsmUBLOX& modem, uint8_t mux = 1) { + GsmClient(TinyGsmUBLOX& modem, uint8_t mux = 0) { init(&modem, mux); } - bool init(TinyGsmUBLOX* modem, uint8_t mux = 1) { + bool init(TinyGsmUBLOX* modem, uint8_t mux = 0) { this->at = modem; this->mux = mux; sock_available = 0; + prev_check = 0; sock_connected = false; got_data = false; return true; @@ -80,11 +66,24 @@ public: public: virtual int connect(const char *host, uint16_t port) { - stop(); + if (sock_connected) { + stop(); + // If we're creating a new connection on the same client, we need to wait + // until the async close has finished on Cat-M modems. + // After close has completed, the +UUSOCL should appear. + if (at->isCatM) { + DBG("Waiting for +UUSOCL URC on", mux); + for (unsigned long start = millis(); millis() - start < 120000L; ) { + at->maintain(); + if (!sock_connected) break; + } + } + } TINY_GSM_YIELD(); rx.clear(); sock_connected = at->modemConnect(host, port, &mux); at->sockets[mux] = this; + at->maintain(); return sock_connected; } @@ -102,10 +101,22 @@ public: virtual void stop() { TINY_GSM_YIELD(); - at->sendAT(GF("+USOCL="), mux); - sock_connected = false; - at->waitResponse(); + // Read and dump anything remaining in the modem's internal buffer. + // The socket will appear open in response to connected() even after it + // closes until all data is read from the buffer. + // Doing it this way allows the external mcu to find and get all of the data + // that it wants from the socket even if it was closed externally. rx.clear(); + at->maintain(); + while (sock_available > 0) { + sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); + rx.clear(); + at->maintain(); + } + at->modemDisconnect(mux); + // We don't actually know if the CatM modem has finished closing because + // we're using an "asynchronous" close + if (!at->isCatM) sock_connected = false; } virtual size_t write(const uint8_t *buf, size_t size) { @@ -125,7 +136,14 @@ public: virtual int available() { TINY_GSM_YIELD(); - if (!rx.size() && sock_connected) { + if (!rx.size()) { + // Workaround: sometimes SARA R410 forgets to notify about data arrival. + // TODO: Currently we ping the module periodically, + // but maybe there's a better indicator that we need to poll + if (millis() - prev_check > 250) { + got_data = true; + prev_check = millis(); + } at->maintain(); } return rx.size() + sock_available; @@ -143,8 +161,15 @@ public: cnt += chunk; continue; } - // TODO: Read directly into user buffer? + // Workaround: sometimes SARA R410 forgets to notify about data arrival. + // TODO: Currently we ping the module periodically, + // but maybe there's a better indicator that we need to poll + if (millis() - prev_check > 250) { + got_data = true; + prev_check = millis(); + } at->maintain(); + // TODO: Read directly into user buffer? if (sock_available > 0) { sock_available -= at->modemRead(TinyGsmMin((uint16_t)rx.free(), sock_available), mux); } else { @@ -183,17 +208,12 @@ private: TinyGsmUBLOX* at; uint8_t mux; uint16_t sock_available; + uint32_t prev_check; bool sock_connected; bool got_data; RxFifo rx; }; -//============================================================================// -//============================================================================// -// The Secure UBLOX Client Class -//============================================================================// -//============================================================================// - class GsmClientSecure : public GsmClient { @@ -211,36 +231,27 @@ public: rx.clear(); sock_connected = at->modemConnect(host, port, &mux, true); at->sockets[mux] = this; + at->maintain(); return sock_connected; } }; -//============================================================================// -//============================================================================// -// The UBLOX Modem Functions -//============================================================================// -//============================================================================// public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmUBLOX(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmUBLOX(Stream& stream) -#endif - : stream(stream) + : TinyGsmModem(stream), stream(stream) { memset(sockets, 0, sizeof(sockets)); + isCatM = false; // For SARA R4 and N4 series } /* * Basic functions */ - bool begin(const char* pin = NULL) { - return init(pin); - } bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); if (!testAT()) { return false; } @@ -248,11 +259,52 @@ public: if (waitResponse() != 1) { return false; } + +#ifdef TINY_GSM_DEBUG + sendAT(GF("+CMEE=2")); // turn on verbose error codes +#else + sendAT(GF("+CMEE=0")); // turn off error codes +#endif + waitResponse(); + + String name = getModemName(); + DBG(GF("### Modem:"), name); + if (name.startsWith("u-blox SARA-R4") or name.startsWith("u-blox SARA-N4")) { + isCatM = true; + } + else if (name.startsWith("u-blox SARA-N2")) { + DBG(GF("### SARA N2 NB-IoT modems not supported!"), name); + } int ret = getSimStatus(); + // if the sim isn't ready and a pin has been provided, try to unlock the sim if (ret != SIM_READY && pin != NULL && strlen(pin) > 0) { simUnlock(pin); + return (getSimStatus() == SIM_READY); } - return (getSimStatus() == SIM_READY); + // if the sim is ready, or it's locked but no pin has been provided, return true + else { + return (ret == SIM_READY || ret == SIM_LOCKED); + } + } + + String getModemName() { + sendAT(GF("+CGMI")); + String res1; + if (waitResponse(1000L, res1) != 1) { + return "u-blox Cellular Modem"; + } + res1.replace(GSM_NL "OK" GSM_NL, ""); + res1.trim(); + + sendAT(GF("+GMM")); + String res2; + if (waitResponse(1000L, res2) != 1) { + return "u-blox Cellular Modem"; + } + res2.replace(GSM_NL "OK" GSM_NL, ""); + res2.trim(); + + return res1 + String(' ') + res2; } void setBaud(unsigned long baud) { @@ -262,10 +314,7 @@ public: bool testAT(unsigned long timeout = 10000L) { for (unsigned long start = millis(); millis() - start < timeout; ) { sendAT(GF("")); - if (waitResponse(200) == 1) { - delay(100); - return true; - } + if (waitResponse(200) == 1) return true; delay(100); } return false; @@ -280,15 +329,21 @@ public: } } while (stream.available()) { - waitResponse(10, NULL, NULL); + waitResponse(15, NULL, NULL); } } bool factoryDefault() { - sendAT(GF("+UFACTORY=0,1")); // Factory + Reset + Echo Off - waitResponse(); - sendAT(GF("+CFUN=16")); // Auto-baud - return waitResponse() == 1; + if (!isCatM) { + sendAT(GF("+UFACTORY=0,1")); // No factory restore, erase NVM + waitResponse(); + sendAT(GF("+CFUN=16")); // Reset + return waitResponse() == 1; + } + else { + sendAT(GF("&F")); // Resets the current profile, other NVM not affected + return waitResponse() == 1; + } } String getModemInfo() { @@ -307,6 +362,14 @@ public: return true; } + bool hasWifi() { + return false; + } + + bool hasGPRS() { + return true; + } + /* * Power functions */ @@ -315,15 +378,25 @@ public: if (!testAT()) { return false; } - sendAT(GF("+CFUN=16")); + if (!isCatM) + { + sendAT(GF("+CFUN=16")); + } + else + { + sendAT(GF("+CFUN=15")); + } if (waitResponse(10000L) != 1) { return false; } - delay(3000); + delay(3000); // TODO: Verify delay timing here return init(); } - bool poweroff() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool poweroff() { + sendAT(GF("+CPWROFF")); + return waitResponse(40000L) == 1; + } bool radioOff() { sendAT(GF("+CFUN=0")); @@ -387,9 +460,17 @@ public: } RegStatus getRegistrationStatus() { - sendAT(GF("+CGREG?")); - if (waitResponse(GF(GSM_NL "+CGREG:")) != 1) { - return REG_UNKNOWN; + if (isCatM) { // Check EPS registration for LTE modules + sendAT(GF("+CEREG?")); + if (waitResponse(GF(GSM_NL "+CEREG:")) != 1) { + return REG_UNKNOWN; + } + } + else { + sendAT(GF("+CGREG?")); // Check GPRS registration for others + if (waitResponse(GF(GSM_NL "+CGREG:")) != 1) { + return REG_UNKNOWN; + } } streamSkipUntil(','); // Skip format (0) int status = stream.readStringUntil('\n').toInt(); @@ -412,7 +493,7 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { sendAT(GF("+CSQ")); if (waitResponse(GF(GSM_NL "+CSQ:")) != 1) { return 99; @@ -424,70 +505,118 @@ public: bool isNetworkConnected() { RegStatus s = getRegistrationStatus(); - return (s == REG_OK_HOME || s == REG_OK_ROAMING); + if (s == REG_OK_HOME || s == REG_OK_ROAMING) + return true; + else if (s == REG_UNKNOWN) // for some reason, it can hang at unknown.. + return isGprsConnected(); + else return false; } - bool waitForNetwork(unsigned long timeout = 60000L) { - for (unsigned long start = millis(); millis() - start < timeout; ) { - if (isNetworkConnected()) { - return true; - } - delay(250); + bool setURAT( uint8_t urat ) { + // AT+URAT=[,[,<2ndPreferredAct>]] + + sendAT(GF("+COPS=2")); // Deregister from network + if (waitResponse() != 1) { + return false; } - return false; + sendAT(GF("+URAT="), urat); // Radio Access Technology (RAT) selection + if (waitResponse() != 1) { + return false; + } + sendAT(GF("+COPS=0")); // Auto-register to the network + if (waitResponse() != 1) { + return false; + } + return restart(); } - /* - * WiFi functions - */ - /* * GPRS functions */ bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { gprsDisconnect(); - sendAT(GF("+CGATT=1")); - if (waitResponse(60000L) != 1) { + sendAT(GF("+CGATT=1")); // attach to GPRS + if (waitResponse(360000L) != 1) { return false; } - sendAT(GF("+UPSD=0,1,\""), apn, '"'); - waitResponse(); + // Using CGDCONT sets up an "external" PCP context, i.e. a data connection + // using the external IP stack (e.g. Windows dial up) and PPP link over the + // serial interface. This is the only command set supported by the LTE-M + // and LTE NB-IoT modules (SARA-R4xx, SARA-N4xx) - if (user && strlen(user) > 0) { - sendAT(GF("+UPSD=0,2,\""), user, '"'); + // Setting up the PSD profile/PDP context with the UPSD commands sets up an + // "internal" PDP context, i.e. a data connection using the internal IP + // stack and related AT commands for sockets. This is what we're using for + // all of the other modules. + + if (isCatM) { + if (user && strlen(user) > 0) { + sendAT(GF("+CGAUTH=1,0,\""), user, GF("\",\""), pwd, '"'); // Set the authentication + waitResponse(); + } + + sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"'); // Define PDP context 1 waitResponse(); + + sendAT(GF("+CGACT=1,1")); // activate PDP profile/context 1 + if (waitResponse(150000L) != 1) { + return false; + } + + return true; } - if (pwd && strlen(pwd) > 0) { - sendAT(GF("+UPSD=0,3,\""), pwd, '"'); + + else { + sendAT(GF("+UPSD=0,1,\""), apn, '"'); // Set APN for PSD profile 0 waitResponse(); - } - sendAT(GF("+UPSD=0,7,\"0.0.0.0\"")); // Dynamic IP - waitResponse(); + if (user && strlen(user) > 0) { + sendAT(GF("+UPSD=0,2,\""), user, '"'); // Set user for PSD profile 0 + waitResponse(); + } + if (pwd && strlen(pwd) > 0) { + sendAT(GF("+UPSD=0,3,\""), pwd, '"'); // Set password for PSD profile 0 + waitResponse(); + } - sendAT(GF("+UPSDA=0,3")); - if (waitResponse(60000L) != 1) { - return false; - } + sendAT(GF("+UPSD=0,7,\"0.0.0.0\"")); // Dynamic IP on PSD profile 0 + waitResponse(); - // Open a GPRS context - sendAT(GF("+UPSND=0,8")); - if (waitResponse(GF(",8,1")) != 1) { - return false; + sendAT(GF("+UPSDA=0,3")); // Activate the PDP context associated with profile 0 + if (waitResponse(360000L) != 1) { + return false; + } + + sendAT(GF("+UPSND=0,8")); // Activate PSD profile 0 + if (waitResponse(GF(",8,1")) != 1) { + return false; + } + waitResponse(); + + return true; } - waitResponse(); - return true; } bool gprsDisconnect() { - sendAT(GF("+UPSDA=0,4")); - if (waitResponse(60000L) != 1) - return false; - sendAT(GF("+CGATT=0")); - if (waitResponse(60000L) != 1) + // LTE-M and NB-IoT modules do not support UPSx commands + if (isCatM) { + sendAT(GF("+CGACT=1,0")); // Deactivate PDP context 1 + if (waitResponse(40000L) != 1) { + return false; + } + } + + else { + sendAT(GF("+UPSDA=0,4")); // Deactivate the PDP context associated with profile 0 + if (waitResponse(360000L) != 1) + return false; + } + + sendAT(GF("+CGATT=0")); // detach from GPRS + if (waitResponse(360000L) != 1) return false; return true; @@ -503,25 +632,40 @@ public: if (res != 1) return false; - return localIP() != 0; + return localIP() != IPAddress(0,0,0,0); } + /* + * IP Address functions + */ + String getLocalIP() { - sendAT(GF("+UPSND=0,0")); - if (waitResponse(GF(GSM_NL "+UPSND:")) != 1) { - return ""; + // LTE-M and NB-IoT modules do not support UPSx commands + if (isCatM) { + sendAT(GF("+CGPADDR")); + if (waitResponse(GF(GSM_NL "+CGPADDR:")) != 1) { + return ""; + } + streamSkipUntil(','); // Skip context id + String res = stream.readStringUntil('\r'); + if (waitResponse() != 1) { + return ""; + } + return res; } - streamSkipUntil(','); // Skip PSD profile - streamSkipUntil('\"'); // Skip request type - String res = stream.readStringUntil('\"'); - if (waitResponse() != 1) { - return ""; + else { + sendAT(GF("+UPSND=0,0")); + if (waitResponse(GF(GSM_NL "+UPSND:")) != 1) { + return ""; + } + streamSkipUntil(','); // Skip PSD profile + streamSkipUntil('\"'); // Skip request type + String res = stream.readStringUntil('\"'); + if (waitResponse() != 1) { + return ""; + } + return res; } - return res; - } - - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); } /* @@ -580,7 +724,7 @@ public: */ uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE; - int getBattPercent() { + int8_t getBattPercent() { sendAT(GF("+CIND?")); if (waitResponse(GF(GSM_NL "+CIND:")) != 1) { return 0; @@ -591,11 +735,15 @@ public: return res; } + /* + * Client related functions + */ + protected: bool modemConnect(const char* host, uint16_t port, uint8_t* mux, bool ssl = false) { - sendAT(GF("+USOCR=6")); - if (waitResponse(GF(GSM_NL "+USOCR:")) != 1) { + sendAT(GF("+USOCR=6")); // create a socket + if (waitResponse(GF(GSM_NL "+USOCR:")) != 1) { // reply is +USOCR: ## of socket created return false; } *mux = stream.readStringUntil('\n').toInt(); @@ -614,12 +762,28 @@ protected: //sendAT(GF("+USOSO="), *mux, GF(",6,2,30000")); //waitResponse(); + // connect on the allocated socket + // TODO: Use faster "asynchronous" connection? + // We would have to wait for the +UUSOCO URC to verify connection sendAT(GF("+USOCO="), *mux, ",\"", host, "\",", port); - int rsp = waitResponse(75000L); + int rsp = waitResponse(120000L); return (1 == rsp); } - int modemSend(const void* buff, size_t len, uint8_t mux) { + bool modemDisconnect(uint8_t mux) { + TINY_GSM_YIELD(); + if (isCatM) { // These modems allow a faster "asynchronous" close + sendAT(GF("+USOCL="), mux, GF(",1")); + int rsp = waitResponse(120000L); + return (1 == rsp); // but it still can take up to 120s to get a response + } + else { // no async close + sendAT(GF("+USOCL="), mux); + return (1 == waitResponse()); + } + } + + int16_t modemSend(const void* buff, size_t len, uint8_t mux) { sendAT(GF("+USOWR="), mux, ',', len); if (waitResponse(GF("@")) != 1) { return 0; @@ -634,6 +798,7 @@ protected: streamSkipUntil(','); // Skip mux int sent = stream.readStringUntil('\n').toInt(); waitResponse(); + maintain(); // look for a very quick response return sent; } @@ -659,12 +824,15 @@ protected: size_t modemGetAvailable(uint8_t mux) { sendAT(GF("+USORD="), mux, ",0"); size_t result = 0; - if (waitResponse(GF(GSM_NL "+USORD:")) == 1) { + uint8_t res = waitResponse(GF(GSM_NL "+USORD:")); + // Will give error "operation not allowed" when attempting to read a socket + // that you have already told to close + if (res == 1) { streamSkipUntil(','); // Skip mux result = stream.readStringUntil('\n').toInt(); waitResponse(); } - if (!result) { + if (!result && res != 2 && res != 3) { // Don't check modemGetConnected after an error sockets[mux]->sock_connected = modemGetConnected(mux); } return result; @@ -672,43 +840,34 @@ protected: bool modemGetConnected(uint8_t mux) { sendAT(GF("+USOCTL="), mux, ",10"); - if (waitResponse(GF(GSM_NL "+USOCTL:")) != 1) + uint8_t res = waitResponse(GF(GSM_NL "+USOCTL:")); + if (res != 1) return false; streamSkipUntil(','); // Skip mux streamSkipUntil(','); // Skip type int result = stream.readStringUntil('\n').toInt(); + // 0: the socket is in INACTIVE status (it corresponds to CLOSED status + // defined in RFC793 "TCP Protocol Specification" [112]) + // 1: the socket is in LISTEN status + // 2: the socket is in SYN_SENT status + // 3: the socket is in SYN_RCVD status + // 4: the socket is in ESTABILISHED status + // 5: the socket is in FIN_WAIT_1 status + // 6: the socket is in FIN_WAIT_2 status + // 7: the sokcet is in CLOSE_WAIT status + // 8: the socket is in CLOSING status + // 9: the socket is in LAST_ACK status + // 10: the socket is in TIME_WAIT status waitResponse(); - return result != 0; + return (result != 0); } public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } - - bool streamSkipUntil(char c) { - const unsigned long timeout = 1000L; - unsigned long startMillis = millis(); - while (millis() - startMillis < timeout) { - while (millis() - startMillis < timeout && !stream.available()) { - TINY_GSM_YIELD(); - } - if (stream.read() == c) - return true; - } - return false; - } + /* + Utilities + */ template void sendAT(Args... cmd) { @@ -802,6 +961,7 @@ public: protected: GsmClient* sockets[TINY_GSM_MUX_COUNT]; + bool isCatM; }; #endif diff --git a/src/TinyGsmClientXBee.h b/src/TinyGsmClientXBee.h index ca9ada9..865139c 100644 --- a/src/TinyGsmClientXBee.h +++ b/src/TinyGsmClientXBee.h @@ -2,17 +2,21 @@ * @file TinyGsmClientXBee.h * @author Volodymyr Shymanskyy * @license LGPL-3.0 - * @copyright Copyright (c) 2016 Volodymyr Shymanskyy + * @copyright Copyright (c) 2016 Volodymyr Shymanskyy, XBee module by Sara Damiano * @date Nov 2016 */ #ifndef TinyGsmClientXBee_h #define TinyGsmClientXBee_h +//#pragma message("TinyGSM: TinyGsmClientXBee") //#define TINY_GSM_DEBUG Serial - -#define TINY_GSM_MUX_COUNT 1 // Multi-plexing isn't supported using command mode +// XBee's do not support multi-plexing in transparent/command mode +// The much more complicated API mode is needed for multi-plexing +#define TINY_GSM_MUX_COUNT 1 +// XBee's have a default guard time of 1 second (1000ms, 10 extra for safety here) +#define TINY_GSM_XBEE_GUARD_TIME 1010 #include @@ -36,35 +40,24 @@ enum RegStatus { // These are responses to the HS command to get "hardware series" enum XBeeType { + XBEE_UNKNOWN = 0, XBEE_S6B_WIFI = 0x601, // Digi XBee® Wi-Fi XBEE_LTE1_VZN = 0xB01, // Digi XBee® Cellular LTE Cat 1 XBEE_3G = 0xB02, // Digi XBee® Cellular 3G - XBEE3_LTE1_ATT = 1, // Digi XBee3™ Cellular LTE CAT 1 -- HS unknown to SRGD - XBEE3_LTEM_ATT = 2, // Digi XBee3™ Cellular LTE-M -- HS unknown to SRGD - XBEE3_LTENB = 3, // Digi XBee3™ Cellular NB-IoT -- HS unknown to SRGD + XBEE3_LTE1_ATT = 0xB06, // Digi XBee3™ Cellular LTE CAT 1 + XBEE3_LTEM_ATT = 0xB08, // Digi XBee3™ Cellular LTE-M }; -//============================================================================// -//============================================================================// -// Declaration of the TinyGsmXBee Class -//============================================================================// -//============================================================================// -class TinyGsmXBee +class TinyGsmXBee : public TinyGsmModem { -//============================================================================// -//============================================================================// -// The XBee Internal Client Class -//============================================================================// -//============================================================================// - - public: class GsmClient : public Client { friend class TinyGsmXBee; + // typedef TinyGsmFifo RxFifo; public: GsmClient() {} @@ -84,47 +77,56 @@ public: } public: + // NOTE: The XBee saves all paramter information in flash. When you turn it + // on it immediately prepares to re-connect to whatever was last connected to. + // All the modemConnect() function does is tell it the paramters to put into + // flash. The connection itself is not opened until you attempt to send data. + // Because all settings are saved to flash, it is possible (or likely) that + // you could send out data even if you haven't "made" any connection. virtual int connect(const char *host, uint16_t port) { - at->streamClear(); // Empty anything remaining in the buffer; - bool sock_connected = false; + at->streamClear(); // Empty anything in the buffer before starting if (at->commandMode()) { // Don't try if we didn't successfully get into command mode sock_connected = at->modemConnect(host, port, mux, false); at->writeChanges(); at->exitCommand(); } - at->streamClear(); // Empty anything remaining in the buffer; return sock_connected; } virtual int connect(IPAddress ip, uint16_t port) { - at->streamClear(); // Empty anything remaining in the buffer; - bool sock_connected = false; + at->streamClear(); // Empty anything in the buffer before starting if (at->commandMode()) { // Don't try if we didn't successfully get into command mode sock_connected = at->modemConnect(ip, port, mux, false); at->writeChanges(); at->exitCommand(); } - at->streamClear(); // Empty anything remaining in the buffer; return sock_connected; } - // This is a hack to shut the socket by setting the timeout to zero and - // then sending an empty line to the server. virtual void stop() { - at->streamClear(); // Empty anything remaining in the buffer; + at->streamClear(); // Empty anything in the buffer at->commandMode(); - at->sendAT(GF("TM0")); // Set socket timeout to 0; - at->waitResponse(); + // For WiFi models, there's no direct way to close the socket. This is a + // hack to shut the socket by setting the timeout to zero. + if (at->beeType == XBEE_S6B_WIFI) { + at->sendAT(GF("TM0")); // Set socket timeout (using Digi default of 10 seconds) + at->waitResponse(5000); // This response can be slow + at->writeChanges(); + } + // For cellular models, per documentation: If you change the TM (socket + // timeout) value while in Transparent Mode, the current connection is + // immediately closed. + at->sendAT(GF("TM64")); // Set socket timeout (using Digi default of 10 seconds) + at->waitResponse(5000); // This response can be slow at->writeChanges(); at->exitCommand(); - at->modemSend("", 1, mux); - at->commandMode(); - at->sendAT(GF("TM64")); // Set socket timeout back to 10 seconds; - at->waitResponse(); - at->writeChanges(); - at->exitCommand(); - at->streamClear(); // Empty anything remaining in the buffer; + at->streamClear(); // Empty anything remaining in the buffer sock_connected = false; + // Note: because settings are saved in flash, the XBEE will attempt to + // reconnect to the previous socket if it receives any outgoing data. + // Setting sock_connected to false after the stop ensures that connected() + // will return false after a stop has been ordered. This makes it play + // much more nicely with libraries like PubSubClient. } virtual size_t write(const uint8_t *buf, size_t size) { @@ -144,16 +146,47 @@ public: virtual int available() { TINY_GSM_YIELD(); return at->stream.available(); + /* + if (!rx.size() || at->stream.available()) { + at->maintain(); + } + return at->stream.available() + rx.size(); + */ } virtual int read(uint8_t *buf, size_t size) { TINY_GSM_YIELD(); - return at->stream.readBytes((char*)buf, size); + return at->stream.readBytes((char *)buf, size); + /* + size_t cnt = 0; + uint32_t _startMillis = millis(); + while (cnt < size && millis() - _startMillis < _timeout) { + size_t chunk = TinyGsmMin(size-cnt, rx.size()); + if (chunk > 0) { + rx.get(buf, chunk); + buf += chunk; + cnt += chunk; + continue; + } + // TODO: Read directly into user buffer? + if (!rx.size() || at->stream.available()) { + at->maintain(); + } + } + return cnt; + */ } virtual int read() { TINY_GSM_YIELD(); return at->stream.read(); + /* + uint8_t c; + if (read(&c, 1) == 1) { + return c; + } + return -1; + */ } virtual int peek() { return at->stream.peek(); } @@ -163,6 +196,12 @@ public: if (available()) { return true; } + // Double check that we don't know it's closed + // NOTE: modemGetConnected() is likely to return a "false" true because + // it will return unknown until after data is sent over the connection. + // If the socket is definitely closed, modemGetConnected() will set + // sock_connected to false; + at->modemGetConnected(); return sock_connected; } virtual operator bool() { return connected(); } @@ -177,76 +216,77 @@ private: TinyGsmXBee* at; uint8_t mux; bool sock_connected; + // RxFifo rx; }; -//============================================================================// -//============================================================================// -// The Secure XBee Client Class -//============================================================================// -//============================================================================// - class GsmClientSecure : public GsmClient { public: GsmClientSecure() {} - GsmClientSecure(TinyGsmXBee& modem, uint8_t mux = 1) + GsmClientSecure(TinyGsmXBee& modem, uint8_t mux = 0) : GsmClient(modem, mux) {} public: virtual int connect(const char *host, uint16_t port) { - at->streamClear(); // Empty anything remaining in the buffer; - bool sock_connected = false; + at->streamClear(); // Empty anything in the buffer before starting if (at->commandMode()) { // Don't try if we didn't successfully get into command mode sock_connected = at->modemConnect(host, port, mux, true); at->writeChanges(); at->exitCommand(); } - at->streamClear(); // Empty anything remaining in the buffer; return sock_connected; } virtual int connect(IPAddress ip, uint16_t port) { - at->streamClear(); // Empty anything remaining in the buffer; - bool sock_connected = false; + at->streamClear(); // Empty anything in the buffer before starting if (at->commandMode()) { // Don't try if we didn't successfully get into command mode - sock_connected = at->modemConnect(ip, port, mux, true); + sock_connected = at->modemConnect(ip, port, mux, false); at->writeChanges(); at->exitCommand(); } - at->streamClear(); // Empty anything remaining in the buffer; return sock_connected; } }; -//============================================================================// -//============================================================================// -// The XBee Modem Functions -//============================================================================// -//============================================================================// - public: -#ifdef GSM_DEFAULT_STREAM - TinyGsmXBee(Stream& stream = GSM_DEFAULT_STREAM) -#else TinyGsmXBee(Stream& stream) -#endif - : stream(stream) - {} + : TinyGsmModem(stream), stream(stream) + { + beeType = XBEE_UNKNOWN; // Start not knowing what kind of bee it is + guardTime = TINY_GSM_XBEE_GUARD_TIME; // Start with the default guard time of 1 second + resetPin = -1; + savedIP = IPAddress(0,0,0,0); + savedHost = ""; + memset(sockets, 0, sizeof(sockets)); + } + + TinyGsmXBee(Stream& stream, int8_t resetPin) + : TinyGsmModem(stream), stream(stream) + { + beeType = XBEE_UNKNOWN; // Start not knowing what kind of bee it is + guardTime = TINY_GSM_XBEE_GUARD_TIME; // Start with the default guard time of 1 second + this->resetPin = resetPin; + savedIP = IPAddress(0,0,0,0); + savedHost = ""; + memset(sockets, 0, sizeof(sockets)); + } /* * Basic functions */ - bool begin() { - return init(); - } - bool init() { - guardTime = 1100; // Start with a default guard time of 1 second + bool init(const char* pin = NULL) { + DBG(GF("### TinyGSM Version:"), TINYGSM_VERSION); + + if (resetPin >= 0) { + pinMode(resetPin, OUTPUT); + digitalWrite(resetPin, HIGH); + } if (!commandMode(10)) return false; // Try up to 10 times for the init @@ -257,19 +297,18 @@ public: sendAT(GF("GT64")); // shorten the guard time to 100ms ret_val &= waitResponse(); ret_val &= writeChanges(); - if (ret_val) guardTime = 125; + if (ret_val) guardTime = 110; - sendAT(GF("HS")); // Get the "Hardware Series"; - String res = readResponse(); - char buf[4] = {0,}; // Set up buffer for response - res.toCharArray(buf, 4); - int intRes = strtol(buf, 0, 16); - beeType = (XBeeType)intRes; + getSeries(); // Get the "Hardware Series"; exitCommand(); return ret_val; } + String getModemName() { + return getBeeName(); + } + void setBaud(unsigned long baud) { if (!commandMode()) return; switch(baud) @@ -301,16 +340,24 @@ public: { sendAT(); if (waitResponse(200) == 1) { + exitCommand(); return true; } - exitCommand(); } delay(100); } return false; } - void maintain() {} + void maintain() { + // this only happens OUTSIDE command mode, so if we're getting characters + // they should be data received from the TCP connection + // TINY_GSM_YIELD(); + // while (stream.available()) { + // char c = stream.read(); + // if (c > 0) sockets[0]->rx.put(c); + // } + } bool factoryDefault() { if (!commandMode()) return false; // Return immediately @@ -318,6 +365,9 @@ public: bool ret_val = waitResponse() == 1; ret_val &= writeChanges(); exitCommand(); + // Make sure the guard time for the modem object is set back to default + // otherwise communication would fail after the reset + guardTime = 1010; return ret_val; } @@ -326,7 +376,7 @@ public: if (!commandMode()) return modemInf; // Try up to 10 times for the init sendAT(GF("HS")); // Get the "Hardware Series" - modemInf += readResponse(); + modemInf += readResponseString(); exitCommand(); return modemInf; @@ -337,6 +387,16 @@ public: else return true; } + bool hasWifi() { + if (beeType == XBEE_S6B_WIFI) return true; + else return false; + } + + bool hasGPRS() { + if (beeType == XBEE_S6B_WIFI) return false; + else return true; + } + XBeeType getBeeType() { return beeType; } @@ -348,7 +408,7 @@ public: case XBEE_3G: return "Digi XBee® Cellular 3G"; case XBEE3_LTE1_ATT: return "Digi XBee3™ Cellular LTE CAT 1"; case XBEE3_LTEM_ATT: return "Digi XBee3™ Cellular LTE-M"; - case XBEE3_LTENB: return "Digi XBee3™ Cellular NB-IoT"; + default: return "Digi XBee®"; } } @@ -356,52 +416,77 @@ public: * Power functions */ + // The XBee's have a bad habit of getting into an unresponsive funk + // This uses the board's hardware reset pin to force it to reset + void pinReset() { + if (resetPin >= 0) { + DBG("### Forcing a modem reset!\r\n"); + digitalWrite(resetPin, LOW); + delay(1); + digitalWrite(resetPin, HIGH); + } + } + bool restart() { if (!commandMode()) return false; // Return immediately - sendAT(GF("AM1")); // Digi suggests putting into airplane mode before restarting - // This allows the sockets and connections to close cleanly - writeChanges(); - if (waitResponse() != 1) goto fail; - sendAT(GF("FR")); - if (waitResponse() != 1) goto fail; + if (beeType == XBEE_UNKNOWN) getSeries(); // how we restart depends on this - delay (2000); // Actually resets about 2 seconds later + if (beeType != XBEE_S6B_WIFI) { + sendAT(GF("AM1")); // Digi suggests putting cellular modules into airplane mode before restarting + // This allows the sockets and connections to close cleanly + if (waitResponse() != 1) return exitAndFail(); + if (!writeChanges()) return exitAndFail(); + } + + sendAT(GF("FR")); + if (waitResponse() != 1) return exitAndFail(); + + if (beeType == XBEE_S6B_WIFI) delay(2000); // Wifi module actually resets about 2 seconds later + else delay(100); // cellular modules wait 100ms before reset happes // Wait until reboot complete and responds to command mode call again for (unsigned long start = millis(); millis() - start < 60000L; ) { - if (commandMode(1)) { - sendAT(GF("AM0")); // Turn off airplane mode - writeChanges(); - exitCommand(); - delay(250); // wait a litle before trying again - } + if (commandMode(1)) break; + delay(250); // wait a litle before trying again } - return true; + if (beeType != XBEE_S6B_WIFI) { + sendAT(GF("AM0")); // Turn off airplane mode + if (waitResponse() != 1) return exitAndFail(); + if (!writeChanges()) return exitAndFail(); + } - fail: - exitCommand(); - return false; + exitCommand(); + + return init(); } void setupPinSleep(bool maintainAssociation = false) { if (!commandMode()) return; // Return immediately + + if (beeType == XBEE_UNKNOWN) getSeries(); // Command depends on series + sendAT(GF("SM"),1); // Pin sleep waitResponse(); + if (beeType == XBEE_S6B_WIFI && !maintainAssociation) { sendAT(GF("SO"),200); // For lowest power, dissassociated deep sleep waitResponse(); } + else if (!maintainAssociation){ - sendAT(GF("SO"),1); // For lowest power, dissassociated deep sleep + sendAT(GF("SO"),1); // For supported cellular modules, maintain association // Not supported by all modules, will return "ERROR" waitResponse(); } + writeChanges(); exitCommand(); } - bool poweroff() TINY_GSM_ATTR_NOT_IMPLEMENTED; + bool poweroff() { // Not supported + return false; + } bool radioOff() TINY_GSM_ATTR_NOT_IMPLEMENTED; @@ -418,7 +503,7 @@ public: String getSimCCID() { if (!commandMode()) return ""; // Return immediately sendAT(GF("S#")); - String res = readResponse(); + String res = readResponseString(); exitCommand(); return res; } @@ -426,7 +511,7 @@ public: String getIMEI() { if (!commandMode()) return ""; // Return immediately sendAT(GF("IM")); - String res = readResponse(); + String res = readResponseString(); exitCommand(); return res; } @@ -438,70 +523,79 @@ public: RegStatus getRegistrationStatus() { if (!commandMode()) return REG_UNKNOWN; // Return immediately + if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know the bee type to interpret response + sendAT(GF("AI")); - String res = readResponse(); - char buf[3] = {0,}; // Set up buffer for response - res.toCharArray(buf, 3); - int intRes = strtol(buf, 0, 16); + int16_t intRes = readResponseInt(); RegStatus stat = REG_UNKNOWN; switch (beeType){ case XBEE_S6B_WIFI: { - if(intRes == 0x00) // 0x00 Successfully joined an access point, established IP addresses and IP listening sockets - stat = REG_OK; - else if(intRes == 0x01) // 0x01 Wi-Fi transceiver initialization in progress. - stat = REG_SEARCHING; - else if(intRes == 0x02) // 0x02 Wi-Fi transceiver initialized, but not yet scanning for access point. - stat = REG_SEARCHING; - else if(intRes == 0x13) { // 0x13 Disconnecting from access point. - restart(); // Restart the device; the S6B tends to get stuck "disconnecting" - stat = REG_UNREGISTERED; + switch (intRes) { + case 0x00: // 0x00 Successfully joined an access point, established IP addresses and IP listening sockets + stat = REG_OK; + break; + case 0x01: // 0x01 Wi-Fi transceiver initialization in progress. + case 0x02: // 0x02 Wi-Fi transceiver initialized, but not yet scanning for access point. + case 0x40: // 0x40 Waiting for WPA or WPA2 Authentication. + case 0x41: // 0x41 Device joined a network and is waiting for IP configuration to complete + case 0x42: // 0x42 Device is joined, IP is configured, and listening sockets are being set up. + case 0xFF: // 0xFF Device is currently scanning for the configured SSID. + stat = REG_SEARCHING; + break; + case 0x13: // 0x13 Disconnecting from access point. + restart(); // Restart the device; the S6B tends to get stuck "disconnecting" + stat = REG_UNREGISTERED; + break; + case 0x23: // 0x23 SSID not configured. + stat = REG_UNREGISTERED; + break; + case 0x24: // 0x24 Encryption key invalid (either NULL or invalid length for WEP). + case 0x27: // 0x27 SSID was found, but join failed. + stat = REG_DENIED; + break; + default: + stat = REG_UNKNOWN; + break; } - else if(intRes == 0x23) // 0x23 SSID not configured. - stat = REG_UNREGISTERED; - else if(intRes == 0x24) // 0x24 Encryption key invalid (either NULL or invalid length for WEP). - stat = REG_DENIED; - else if(intRes == 0x27) // 0x27 SSID was found, but join failed. - stat = REG_DENIED; - else if(intRes == 0x40) // 0x40 Waiting for WPA or WPA2 Authentication. - stat = REG_SEARCHING; - else if(intRes == 0x41) // 0x41 Device joined a network and is waiting for IP configuration to complete - stat = REG_SEARCHING; - else if(intRes == 0x42) // 0x42 Device is joined, IP is configured, and listening sockets are being set up. - stat = REG_SEARCHING; - else if(intRes == 0xFF) // 0xFF Device is currently scanning for the configured SSID. - stat = REG_SEARCHING; - else stat = REG_UNKNOWN; break; } - default: { - if(intRes == 0x00) // 0x00 Connected to the Internet. - stat = REG_OK; - else if(intRes == 0x22) // 0x22 Registering to cellular network. - stat = REG_SEARCHING; - else if(intRes == 0x23) // 0x23 Connecting to the Internet. - stat = REG_SEARCHING; - else if(intRes == 0x24) // 0x24 The cellular component is missing, corrupt, or otherwise in error. - stat = REG_UNKNOWN; - else if(intRes == 0x25) // 0x25 Cellular network registration denied. - stat = REG_DENIED; - else if(intRes == 0x2A) { // 0x2A Airplane mode. - sendAT(GF("AM0")); // Turn off airplane mode - waitResponse(); - writeChanges(); - stat = REG_UNKNOWN; - } - else if(intRes == 0x2F) { // 0x2F Bypass mode active. - sendAT(GF("AP0")); // Set back to transparent mode - waitResponse(); - writeChanges(); - stat = REG_UNKNOWN; - } - else if(intRes == 0xFF) // 0xFF Device is currently scanning for the configured SSID. - stat = REG_SEARCHING; - else stat = REG_UNKNOWN; + default: { // Cellular XBee's + switch (intRes) { + case 0x00: // 0x00 Connected to the Internet. + stat = REG_OK; + break; + case 0x22: // 0x22 Registering to cellular network. + case 0x23: // 0x23 Connecting to the Internet. + case 0xFF: // 0xFF Initializing. + stat = REG_SEARCHING; break; + case 0x24: // 0x24 The cellular component is missing, corrupt, or otherwise in error. + case 0x2B: // 0x2B USB Direct active. + case 0x2C: // 0x2C Cellular component is in PSM (power save mode). + stat = REG_UNKNOWN; + break; + case 0x25: // 0x25 Cellular network registration denied. + stat = REG_DENIED; + break; + case 0x2A: // 0x2A Airplane mode. + sendAT(GF("AM0")); // Turn off airplane mode + waitResponse(); + writeChanges(); + stat = REG_UNKNOWN; + break; + case 0x2F: // 0x2F Bypass mode active. + sendAT(GF("AP0")); // Set back to transparent mode + waitResponse(); + writeChanges(); + stat = REG_UNKNOWN; + break; + default: + stat = REG_UNKNOWN; + break; } + break; + } } exitCommand(); @@ -511,7 +605,7 @@ public: String getOperator() { if (!commandMode()) return ""; // Return immediately sendAT(GF("MN")); - String res = readResponse(); + String res = readResponseString(); exitCommand(); return res; } @@ -520,15 +614,14 @@ public: * Generic network functions */ - int getSignalQuality() { + int16_t getSignalQuality() { if (!commandMode()) return 0; // Return immediately + if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know what type of bee so we know how to ask if (beeType == XBEE_S6B_WIFI) sendAT(GF("LM")); // ask for the "link margin" - the dB above sensitivity else sendAT(GF("DB")); // ask for the cell strength in dBm - String res = readResponse(); // it works better if we read in as a string + int16_t intRes = readResponseInt(); exitCommand(); - char buf[3] = {0,}; // Set up buffer for response - res.toCharArray(buf, 3); - int intRes = strtol(buf, 0, 16); + if (beeType == XBEE3_LTEM_ATT && intRes == 105) intRes = 0; // tends to reply with "69" when signal is unknown if (beeType == XBEE_S6B_WIFI) return -93 + intRes; // the maximum sensitivity is -93dBm else return -1*intRes; // need to convert to negative number } @@ -543,7 +636,7 @@ public: if (isNetworkConnected()) { return true; } - // delay(250); // Enough delay going in and out of command mode + delay(250); // per Neil H. - more stable with delay } return false; } @@ -554,50 +647,54 @@ public: bool networkConnect(const char* ssid, const char* pwd) { if (!commandMode()) return false; // return immediately + //nh For no pwd don't set setscurity or pwd + if (NULL == ssid ) return exitAndFail(); - sendAT(GF("EE"), 2); // Set security to WPA2 - if (waitResponse() != 1) goto fail; + if (NULL != pwd) + { + sendAT(GF("EE"), 2); // Set security to WPA2 + if (waitResponse() != 1) return exitAndFail(); + sendAT(GF("PK"), pwd); + } else { + sendAT(GF("EE"), 0); // Set No security + } + if (waitResponse() != 1) return exitAndFail(); sendAT(GF("ID"), ssid); - if (waitResponse() != 1) goto fail; + if (waitResponse() != 1) return exitAndFail(); - sendAT(GF("PK"), pwd); - if (waitResponse() != 1) goto fail; - - writeChanges(); + if (!writeChanges()) return exitAndFail(); exitCommand(); return true; - -fail: - exitCommand(); - return false; } bool networkDisconnect() { if (!commandMode()) return false; // return immediately sendAT(GF("NR0")); // Do a network reset in order to disconnect - int res = (1 == waitResponse(5000)); + // NOTE: On wifi modules, using a network reset will not + // allow the same ssid to re-join without rebooting the module. + int8_t res = (1 == waitResponse(5000)); writeChanges(); exitCommand(); return res; } + /* + * IP Address functions + */ + String getLocalIP() { if (!commandMode()) return ""; // Return immediately sendAT(GF("MY")); String IPaddr; IPaddr.reserve(16); // wait for the response - this response can be very slow - IPaddr = readResponse(30000); + IPaddr = readResponseString(30000); exitCommand(); IPaddr.trim(); return IPaddr; } - IPAddress localIP() { - return TinyGsmIpFromString(getLocalIP()); - } - /* * GPRS functions */ @@ -613,7 +710,7 @@ fail: bool gprsDisconnect() { if (!commandMode()) return false; // return immediately sendAT(GF("AM1")); // Cheating and disconnecting by turning on airplane mode - int res = (1 == waitResponse(5000)); + int8_t res = (1 == waitResponse(5000)); writeChanges(); sendAT(GF("AM0")); // Airplane mode off waitResponse(5000); @@ -636,21 +733,17 @@ fail: if (!commandMode()) return false; // Return immediately sendAT(GF("IP"), 2); // Put in text messaging mode - if (waitResponse() !=1) goto fail; + if (waitResponse() !=1) return exitAndFail(); sendAT(GF("PH"), number); // Set the phone number - if (waitResponse() !=1) goto fail; + if (waitResponse() !=1) return exitAndFail(); sendAT(GF("TDD")); // Set the text delimiter to the standard 0x0D (carriage return) - if (waitResponse() !=1) goto fail; - if (!writeChanges()) goto fail; + if (waitResponse() !=1) return exitAndFail(); + if (!writeChanges()) return exitAndFail(); exitCommand(); streamWrite(text); stream.write((char)0x0D); // close off with the carriage return return true; - - fail: - exitCommand(); - return false; } /* @@ -665,34 +758,58 @@ fail: uint16_t getBattVoltage() TINY_GSM_ATTR_NOT_AVAILABLE; - int getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE; + int8_t getBattPercent() TINY_GSM_ATTR_NOT_AVAILABLE; + + /* + * Client related functions + */ protected: - bool modemConnect(const char* host, uint16_t port, uint8_t mux = 0, bool ssl = false) { + IPAddress getHostIP(const char* host) { String strIP; strIP.reserve(16); unsigned long startMillis = millis(); bool gotIP = false; // XBee's require a numeric IP address for connection, but do provide the // functionality to look up the IP address from a fully qualified domain name - while (!gotIP && millis() - startMillis < 45000L) // the lookup can take a while + while (millis() - startMillis < 45000L) // the lookup can take a while { sendAT(GF("LA"), host); - while (stream.available() < 4) {}; // wait for any response + while (stream.available() < 4 && (millis() - startMillis < 45000L)) {}; // wait for any response strIP = stream.readStringUntil('\r'); // read result strIP.trim(); - DBG("<<< ", strIP); - if (!strIP.endsWith(GF("ERROR"))) gotIP = true; - delay(100); // short wait before trying again + if (!strIP.endsWith(GF("ERROR"))) { + gotIP = true; + break; + } + delay(2500); // wait a bit before trying again } if (gotIP) { // No reason to continue if we don't know the IP address - IPAddress ip = TinyGsmIpFromString(strIP); - return modemConnect(ip, port, mux, ssl); + return TinyGsmIpFromString(strIP); + } + else return IPAddress(0,0,0,0); + } + + bool modemConnect(const char* host, uint16_t port, uint8_t mux = 0, bool ssl = false) { + // If requested host is the same as the previous one and we already + // have a valid IP address, we don't have to do anything. + if (this->savedHost == String(host) && savedIP != IPAddress(0,0,0,0)) { + return true; + } + + // Otherwise, set the new host and mark the IP as invalid + this->savedHost = String(host); + savedIP = getHostIP(host); // This will return 0.0.0.0 if lookup fails + + // If we now have a valid IP address, use it to connect + if (savedIP != IPAddress(0,0,0,0)) { // Only re-set connection information if we have an IP address + return modemConnect(savedIP, port, mux, ssl); } else return false; } bool modemConnect(IPAddress ip, uint16_t port, uint8_t mux = 0, bool ssl = false) { + savedIP = ip; // Set the newly requested IP address bool success = true; String host; host.reserve(16); host += ip[0]; @@ -716,77 +833,72 @@ protected: return success; } - int modemSend(const void* buff, size_t len, uint8_t mux = 0) { + int16_t modemSend(const void* buff, size_t len, uint8_t mux = 0) { stream.write((uint8_t*)buff, len); stream.flush(); return len; } - bool modemGetConnected(uint8_t mux = 0) { - if (!commandMode()) return false; - sendAT(GF("AI")); - int res = waitResponse(GF("0")); - exitCommand(); - return 1 == res; + // NOTE: The CI command returns the status of the TCP connection as open only + // after data has been sent on the socket. If it returns 0xFF the socket may + // really be open, but no data has yet been sent. We return this unknown value + // as true so there's a possibility it's wrong. + bool modemGetConnected() { + + if (!commandMode()) return false; // Return immediately + + // If the IP address is 0, it's not valid so we can't be connected + if (savedIP == IPAddress(0,0,0,0)) return false; + + // Verify that we're connected to the *right* IP address + // We might be connected - but to the wrong thing + // NOTE: In transparent mode, there is only one connection possible - no multiplex + String strIP; strIP.reserve(16); + sendAT(GF("DL")); + strIP = stream.readStringUntil('\r'); // read result + if (TinyGsmIpFromString(strIP) != savedIP) return exitAndFail(); + + if (beeType == XBEE_UNKNOWN) getSeries(); // Need to know the bee type to interpret response + + switch (beeType){ // The wifi be can only say if it's connected to the netowrk + case XBEE_S6B_WIFI: { + RegStatus s = getRegistrationStatus(); + if (s != REG_OK) { + sockets[0]->sock_connected = false; + } + return (s == REG_OK); // if it's connected, we hope the sockets are too + } + default: { // Cellular XBee's + sendAT(GF("CI")); + int16_t intRes = readResponseInt(); + exitCommand(); + switch(intRes) { + case 0x00: // 0x00 = The socket is definitely open + case 0xFF: // 0xFF = No known status - this is always returned prior to sending data + return true; + case 0x02: // 0x02 = Invalid parameters (bad IP/host) + case 0x12: // 0x12 = DNS query lookup failure + case 0x25: // 0x25 = Unknown server - DNS lookup failed (0x22 for UDP socket!) + savedIP = IPAddress(0,0,0,0); // force a lookup next time! + default: // If it's anything else (inc 0x02, 0x12, and 0x25)... + sockets[0]->sock_connected = false; // ...it's definitely NOT connected + return false; + } + } + } } public: - /* Utilities */ - - template - void streamWrite(T last) { - stream.print(last); - } - - template - void streamWrite(T head, Args... tail) { - stream.print(head); - streamWrite(tail...); - } + /* + Utilities + */ void streamClear(void) { - TINY_GSM_YIELD(); - while (stream.available()) { stream.read(); } - } - - bool commandMode(int retries = 2) { - int triesMade = 0; - bool success = false; - streamClear(); // Empty everything in the buffer before starting - while (!success and triesMade < retries) { - // Cannot send anything for 1 "guard time" before entering command mode - // Default guard time is 1s, but the init fxn decreases it to 250 ms - delay(guardTime); - streamWrite(GF("+++")); // enter command mode - DBG("+++"); - success = (1 == waitResponse(guardTime*2)); - triesMade ++; + while (stream.available()) { + stream.read(); + TINY_GSM_YIELD(); } - return success; - } - - bool writeChanges(void) { - sendAT(GF("WR")); // Write changes to flash - if (1 != waitResponse()) return false; - sendAT(GF("AC")); // Apply changes - if (1 != waitResponse()) return false; - return true; - } - - void exitCommand(void) { - sendAT(GF("CN")); // Exit command mode - waitResponse(); - } - - String readResponse(uint32_t timeout = 1000) { - TINY_GSM_YIELD(); - unsigned long startMillis = millis(); - while (!stream.available() && millis() - startMillis < timeout) {}; - String res = stream.readStringUntil('\r'); // lines end with carriage returns - res.trim(); - DBG("<<< ", res); - return res; } template @@ -798,6 +910,9 @@ public: } // TODO: Optimize this! + // NOTE: This function is used while INSIDE command mode, so we're only + // waiting for requested responses. The XBee has no unsoliliced responses + // (URC's) when in command mode. uint8_t waitResponse(uint32_t timeout, String& data, GsmConstStr r1=GFP(GSM_OK), GsmConstStr r2=GFP(GSM_ERROR), GsmConstStr r3=NULL, GsmConstStr r4=NULL, GsmConstStr r5=NULL) @@ -809,7 +924,7 @@ public: String r5s(r5); r5s.trim(); DBG("### ..:", r1s, ",", r2s, ",", r3s, ",", r4s, ",", r5s);*/ data.reserve(16); // Should never be getting much here for the XBee - int index = 0; + int8_t index = 0; unsigned long startMillis = millis(); do { TINY_GSM_YIELD(); @@ -843,16 +958,16 @@ finish: if (data.length()) { DBG("### Unhandled:", data, "\r\n"); } else { - DBG("### NO RESPONSE!\r\n"); + DBG("### NO RESPONSE FROM MODEM!\r\n"); } } else { data.trim(); data.replace(GSM_NL GSM_NL, GSM_NL); data.replace(GSM_NL, "\r\n "); if (data.length()) { - // DBG("<<< ", data); } } + //DBG('<', index, '>'); return index; } @@ -870,12 +985,82 @@ finish: return waitResponse(1000, r1, r2, r3, r4, r5); } + bool commandMode(uint8_t retries = 3) { + uint8_t triesMade = 0; + uint8_t triesUntilReset = 2; // only reset after 2 failures + bool success = false; + streamClear(); // Empty everything in the buffer before starting + while (!success and triesMade < retries) { + // Cannot send anything for 1 "guard time" before entering command mode + // Default guard time is 1s, but the init fxn decreases it to 250 ms + delay(guardTime); + streamWrite(GF("+++")); // enter command mode + int res = waitResponse(guardTime*2); + success = (1 == res); + if (0 == res) { + triesUntilReset--; + if (triesUntilReset == 0) { + triesUntilReset = 2; + pinReset(); // if it's unresponsive, reset + delay(250); // a short delay to allow it to come back up TODO-optimize this + } + } + triesMade ++; + } + return success; + } + + bool writeChanges(void) { + sendAT(GF("WR")); // Write changes to flash + if (1 != waitResponse()) return false; + sendAT(GF("AC")); // Apply changes + if (1 != waitResponse()) return false; + return true; + } + + void exitCommand(void) { + sendAT(GF("CN")); // Exit command mode + waitResponse(); + } + + bool exitAndFail(void) { + exitCommand(); // Exit command mode + return false; + } + + void getSeries(void) { + sendAT(GF("HS")); // Get the "Hardware Series"; + int16_t intRes = readResponseInt(); + beeType = (XBeeType)intRes; + DBG(GF("### Modem: "), getModemName()); + } + + String readResponseString(uint32_t timeout = 1000) { + TINY_GSM_YIELD(); + unsigned long startMillis = millis(); + while (!stream.available() && millis() - startMillis < timeout) {}; + String res = stream.readStringUntil('\r'); // lines end with carriage returns + res.trim(); + return res; + } + + int16_t readResponseInt(uint32_t timeout = 1000) { + String res = readResponseString(timeout); // it just works better reading a string first + char buf[5] = {0,}; + res.toCharArray(buf, 5); + int16_t intRes = strtol(buf, 0, 16); + return intRes; + } + public: Stream& stream; protected: - int guardTime; + int16_t guardTime; + int8_t resetPin; XBeeType beeType; + IPAddress savedIP; + String savedHost; GsmClient* sockets[TINY_GSM_MUX_COUNT]; }; diff --git a/src/TinyGsmCommon.h b/src/TinyGsmCommon.h index b803651..a8c7be4 100644 --- a/src/TinyGsmCommon.h +++ b/src/TinyGsmCommon.h @@ -9,6 +9,9 @@ #ifndef TinyGsmCommon_h #define TinyGsmCommon_h +// The current library version number +#define TINYGSM_VERSION "0.6.1" + #if defined(SPARK) || defined(PARTICLE) #include "Particle.h" #elif defined(ARDUINO) @@ -31,6 +34,10 @@ #define TINY_GSM_YIELD() { delay(0); } #endif +#if !defined(TINY_GSM_RX_BUFFER) + #define TINY_GSM_RX_BUFFER 256 +#endif + #define TINY_GSM_ATTR_NOT_AVAILABLE __attribute__((error("Not available on this modem type"))) #define TINY_GSM_ATTR_NOT_IMPLEMENTED __attribute__((error("Not implemented"))) @@ -69,6 +76,7 @@ namespace { } } #else + #define DBG_PLAIN(...) #define DBG(...) #endif @@ -194,4 +202,137 @@ String TinyGsmDecodeHex16bit(String &instr) { return result; } + + + +class TinyGsmModem +{ + +public: + + TinyGsmModem(Stream& stream) + : stream(stream) + {} + + /* + * Basic functions + */ + + // Prepare the modem for further functionality + virtual bool init(const char* pin = NULL) = 0; + // Begin is redundant with init + virtual bool begin(const char* pin = NULL) { + return init(pin); + } + // Returns a string with the chip name + virtual String getModemName() = 0; + // Sets the serial communication baud rate + virtual void setBaud(unsigned long baud) = 0; + // Checks that the modem is responding to standard AT commands + virtual bool testAT(unsigned long timeout = 10000L) = 0; + // Holds open communication with the modem waiting for data to come in + virtual void maintain() = 0; + // Resets all modem chip settings to factor defaults + virtual bool factoryDefault() = 0; + // Returns the response to a get info request. The format varies by modem. + virtual String getModemInfo() = 0; + // Answers whether types of communication are available on this modem + virtual bool hasSSL() = 0; + virtual bool hasWifi() = 0; + virtual bool hasGPRS() = 0; + + /* + * Power functions + */ + + virtual bool restart() = 0; + virtual bool poweroff() = 0; + + /* + * SIM card functions - only apply to cellular modems + */ + + virtual bool simUnlock(const char *pin) { return false; } + virtual String getSimCCID() { return ""; } + virtual String getIMEI() { return ""; } + virtual String getOperator() { return ""; } + + /* + * Generic network functions + */ + + virtual int16_t getSignalQuality() = 0; + // NOTE: this returns whether the modem is registered on the cellular or WiFi + // network NOT whether GPRS or other internet connections are available + virtual bool isNetworkConnected() = 0; + virtual bool waitForNetwork(unsigned long timeout = 60000L) { + for (unsigned long start = millis(); millis() - start < timeout; ) { + if (isNetworkConnected()) { + return true; + } + delay(250); + } + return false; + } + + /* + * WiFi functions - only apply to WiFi modems + */ + + virtual bool networkConnect(const char* ssid, const char* pwd) { return false; } + virtual bool networkDisconnect() { return false; } + + /* + * GPRS functions - only apply to cellular modems + */ + + virtual bool gprsConnect(const char* apn, const char* user = NULL, const char* pwd = NULL) { + return false; + } + virtual bool gprsDisconnect() { return false; } + virtual bool isGprsConnected() { return false; } + + /* + * IP Address functions + */ + + virtual String getLocalIP() = 0; + virtual IPAddress localIP() { + return TinyGsmIpFromString(getLocalIP()); + } + + /* + Utilities + */ + + template + void streamWrite(T last) { + stream.print(last); + } + + template + void streamWrite(T head, Args... tail) { + stream.print(head); + streamWrite(tail...); + } + + bool streamSkipUntil(const char c, const unsigned long timeout = 1000L) { + unsigned long startMillis = millis(); + while (millis() - startMillis < timeout) { + while (millis() - startMillis < timeout && !stream.available()) { + TINY_GSM_YIELD(); + } + if (stream.read() == c) + return true; + } + return false; + } + +public: + Stream& stream; +}; + + + + #endif diff --git a/tools/AT_Debug/AT_Debug.ino b/tools/AT_Debug/AT_Debug.ino index e8e985d..d455422 100644 --- a/tools/AT_Debug/AT_Debug.ino +++ b/tools/AT_Debug/AT_Debug.ino @@ -4,19 +4,23 @@ * and allows direct AT commands access * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -79,4 +83,3 @@ void loop() { delay(0); } } - diff --git a/tools/AT_Spy/AT_Spy.ino b/tools/AT_Spy/AT_Spy.ino index a427918..c7c0759 100644 --- a/tools/AT_Spy/AT_Spy.ino +++ b/tools/AT_Spy/AT_Spy.ino @@ -4,7 +4,7 @@ * between an Arduino and the modem. * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ diff --git a/tools/Diagnostics/Diagnostics.ino b/tools/Diagnostics/Diagnostics.ino index a57f2b8..8283c59 100644 --- a/tools/Diagnostics/Diagnostics.ino +++ b/tools/Diagnostics/Diagnostics.ino @@ -5,19 +5,23 @@ * or from http://librarymanager/all#StreamDebugger * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -183,4 +187,3 @@ void loop() { delay(1000); } } - diff --git a/tools/FactoryReset/FactoryReset.ino b/tools/FactoryReset/FactoryReset.ino index 1570cfc..615d981 100644 --- a/tools/FactoryReset/FactoryReset.ino +++ b/tools/FactoryReset/FactoryReset.ino @@ -5,19 +5,23 @@ * or from http://librarymanager/all#StreamDebugger * * TinyGSM Getting Started guide: - * http://tiny.cc/tiny-gsm-readme + * https://tiny.cc/tinygsm-readme * **************************************************************/ // Select your modem: #define TINY_GSM_MODEM_SIM800 -// #define TINY_GSM_MODEM_SIM808 // #define TINY_GSM_MODEM_SIM900 +// #define TINY_GSM_MODEM_SIM808 +// #define TINY_GSM_MODEM_SIM868 // #define TINY_GSM_MODEM_UBLOX +// #define TINY_GSM_MODEM_M95 // #define TINY_GSM_MODEM_BG96 // #define TINY_GSM_MODEM_A6 // #define TINY_GSM_MODEM_A7 // #define TINY_GSM_MODEM_M590 +// #define TINY_GSM_MODEM_MC60 +// #define TINY_GSM_MODEM_MC60E // #define TINY_GSM_MODEM_ESP8266 // #define TINY_GSM_MODEM_XBEE @@ -67,4 +71,3 @@ void setup() { void loop() { } - diff --git a/tools/test_build/test_build.ino b/tools/test_build/test_build.ino index 245e38a..296f623 100644 --- a/tools/test_build/test_build.ino +++ b/tools/test_build/test_build.ino @@ -3,6 +3,7 @@ * DO NOT USE THIS - this is just a compilation test! * **************************************************************/ +// #define TINY_GSM_MODEM_SIM800 #include @@ -77,4 +78,3 @@ void loop() { modem.networkDisconnect(); #endif } -