If you've ever used one of the many modem-handling libraries that exist, you're familiar with the frustration that is waiting for a response from a long-running command. Between sending your command and receiving a response (or worse -- that command timing out), your program is halted, and your microcontroller is wasiting valuable cycles. This library aims to fix that problem by allowing you to queue commands that will be asynchronously sent to your device without blocking your microcontroller loop.
The setup procedure for an LTE modem involves sending a series of commands and
waiting for their responses. Using most libraries, this would mean that your
microcontroller would be halted waiting to compare the returned responses with
what it was expecting. Using AsyncModem
, setup
will complete immediately
and the LTE modem will be initialized automatically behind the scenes:
#include <AsyncModem.h>
AsyncModem::SIM7000 lte = AsyncModem::SIM7000();
void setup() {
lte.begin(&Serial1); // If your modem is on a different serial port, use that instead
lte.enableGPRS("hologram");
}
void loop() {
lte.loop();
}
Executing the below will queue relevant commands for dispatching the SMS message:
lte.sendSMS("+15555555555", "My Message");
If you would like to display a message when the message is sent (or fails to send), you can pass callacks to do so:
lte.sendSMS(
"+15555555555",
"My Message",
[](MatchState ms) {
Serial.println("Sent :-)");
},
[](ManagedSerialDevice::Command* cmd) {
Serial.println("Failed to send :-(");
},
);
Say that you want to turn on GPS; on the SIM7000, that is controlled by sending the
command AT+CGNSPWR=1
; you can send that command by running:
lte.execute("AT+CGNSWPR=1")
But what if you wanted to make sure it was successful? Looking at the docs,
this can return either "OK" or "ERROR". To help out with that, execute
accepts
a few more optional parameters:
char*
): The command to send; above: AT+CGNSPWR=1
.char*
; default: ""
): What you hope to see -- in this case: OK
.std::function<void(MatchState)>
; default: NULL
): A function to execute
once the output matches your expectation regex. Note that the passed-in MatchState
instance can be used for extracting data from capture groups that you might have
defined in your expectation regex.std::function<void(ManagedSerialDevice::Command*)
; default: NULL
): A function to execute
if the output doesn't match your expectation regex in time (see "Timeout"). A
pointer to the failed command is given to you so you can easily handle retry logic.uint16_t
; default: 2500
): Allow up to this many milliseconds
to pass before giving up and calling the defined failure function.uint32_t
; default: 0
): Do not execute this command until this many
milliseconds has elapsed after queueing the command.For example; to first turn on GPS and then, if it's successful, fetch the latest GPS coordinates, you can write this call:
float latitude = 0;
float longitude = 0;
lte.execute(
"AT+CGNSWPR=1",
"OK",
[<e,&latitude,&longitude](MatchState ms) {
lte.AsyncExecute(
"AT+CGNSINF",
"%+CGNSINF:[%d]+,[%d]+,[%d]+,([%d.%+%-]+),([%d.%+%-]+),.*",
[](MatchState ms) {
char latitudeBuffer[12];
char longitudeBuffer[12];
ms.GetCapture(latitudeBuffer, 0);
ms.GetCapture(longitudeBuffer, 0);
latitude = atof(latitudeBuffer);
longitude = atof(latitudeBuffer);
Serial.print("Latitude: ");
Serial.println(latitude);
Serial.print("Longitude: ");
Serial.println(longitude);
},
[](Command*) {
"Did not receive the expected response from the LTE modem."
}
)
},
[](Command*) {
Serial.println("Failed to turn on GPS.")
}
)
For more options regarding how to execute and chain together commands, see the documentation for Arduino Async Duplex.