Skip to content

Instantly share code, notes, and snippets.

@jayeheffernan
Created May 2, 2019 09:33
Show Gist options
  • Save jayeheffernan/4fdc5a172ca9a9d3e0dbb57dd1554e9d to your computer and use it in GitHub Desktop.
Save jayeheffernan/4fdc5a172ca9a9d3e0dbb57dd1554e9d to your computer and use it in GitHub Desktop.

AT

This library provides a class to help manage sending AT commands and receiving responses. It is independent of the any particular method of writing or reading data to the AT command partner device.

The class is configured with an onWrite() function, which it uses to send data to its AT partner, and implements a .feed() method, which should be passed incoming data from its partner as it becomes availabe. The .receive() method takes a special onData() callback which can be used to implement simple or complicated logic for checking and parsing a variable number of responses from the AT partner as part of a single operation. The .expect() method provides a convenient way to generate said callbacks to implement common use cases, such as matching a fixed sequence of strings, or a regexp. The .register() method can be used to intercept and handle "unsolicited" data from the partner (URCs).

Class Usage

Constructor: AT(onWrite[, dfltTo])

This class is instantiated to manage all incoming and outgoing data for an individual AT command partner, e.g. a modem chip connected to the Imp over UART could be managed by one instance. The constructor takes a single parameter: a callback for writing data. This is how the AT lib is configured to write data to its partner.

Parameters

Parameter Type Required Description
onWrite Function Yes Callback for writing data to AT command partner
dfltTo Float No Default timeout for .receive() operations for the instance. Defaults to AT.DFLT_TIMEOUT.

Callback: onWrite(data)

The callback passed in to the constructor should write data to the AT command partner, e.g. for an AT device connected over UART the callback might be uart.write. Outgoing data from the AT instance is passed to this callback; incoming data from the AT partner is passed in to the AT instance via the .feed() method (see below).

Parameters
Parameter Type Required Description
data String Yes Outgoing data to write to the AT command partner

Example

uart <- hardware.uartBCAW;

// Set up the UART
uart.settxfifosize(1024);
uart.setrxfifosize(1024);

// Configure AT lib to write to the UART, with a default timeout for receive
// operations of 90 seconds
at <- AT(uart.write.bindenv(uart), 90);

Instance Methods

feed(data)

This is the method by which the AT class reads/is notified of data from its partner AT device. Lines (or whatever other token type you choose to use) are passed in to this method as they become available.

Parameters

Parameter Type Required Description
data String Yes Incoming data from the AT command partner

Example

// Continuing from previous example,
// configure AT lib to read from the UART
uart.configure(QM_UART_BAUD_RATE, 8, PARITY_NONE, 1, 0, function() {
    at.feed(uart.readstring());
}.bindenv(this));

cmd(cmd[, t], onData, onDone)

This method allows one to send a command to the AT device, wait for a variable number and format of replies, with a timeout, and execute a standard error-first callback when the operation is complete.

This could be considered the "main" method of the class, but it actually is just a convenient wrapper around .send() and .receive(), so refer to those methods for documentation of the parameters.

Example

function waitForOk(data) {
  if (data == "OK") {
    return null;
  } else return CB_REPEAT;
}

// Execute a power down response, wait for an OK response, timeout after 10
// seconds
at.cmd("AT+PWDWN\r", 10, waitForOk, function(err, data) {
  if (err) {
    server.error(err);
  }
  // Carry on, the operation was successful
})

send(cmd)

Used to send data to the AT device. This is a wrapper around the onWrite() callback configured in the constructor, but will fail if the AT class is busy.

Parameters

Parameter Type Required Description
cmd String Yes Data to send/write to the AT partner device

Example

// Send a power down command
// Don't worry about waiting for any response
at.send("AT+PWDWN\r");

receive([t, ] onData[, onDone])

Set a temporary data handler function, wait for a variable number of replies from the AT partner, and execute a standard error-first callback when the operation is finished.

Parameters

Parameter Type Required Description
t Float No Timeout (in seconds) on waiting for the entire operation to complete
onData Function No Data-handling callback function to be used for the duration of this operation. See below.
onDone Function No Error-first callback to execute when the operation is complete. See below.

Callback: onData(data)

This callback should implement the main logic of parsing or otherwise checking responses that come from the AT command partner. It will be called once when data sent from the partner becomes available. Depending on the data and the implementation of the function, this function will indicate any of:

  1. The operation completed successfully
  2. The operation completed with an error
  3. The operation is still in progress, the same onData() callback should be kept to handle the next available data.
  4. The operation is still in progress, but a new onData() callback should be used to handle the next available data.

The callback will have its environment bound to the AT instance, so if will have the AT methods available in its scope (if it was not already bound).

If this callback is not provided (i.e. it's value is null), the default behaviour is to accept any single string as a response, then immediately complete the .receive() operation by passing the received string to onData

Parameters
Parameter Type Required Description
data String Yes Data from the AT command partner, as passed into .feed()
Return Value

The way the receive operation procedes is controlled by this callback, according to the following rules:

  1. If the function returns another function, the operation is considered still in progress, and the newly provided function replaces the existing onData() callback.
  2. If the function returns the constant AT.CB_REPEAT, the operation is considered still in progress, and the existing onData() callback is retained.
  3. If the function returns any other value, the operation is considered successfully completed. The return value is then passed as the second argument to the onDone() callback.
  4. If the function raises an exception, the operation is considered complete by failure. The exception is passed as the first argument to the onDone() callback.
  5. If the timeout time is reached before the callback has indicated that the operation is complete, the operation is considered complete by failure, and the error message AT_ERR_TIMEOUT is passed as the first argument to the onDone() callback.

Callback: onDone(err, data)

A standard asynchronous/Node.js-style error-first callback. This is called once when the operation is complete.

Parameters
Parameter Type Required Description
err String Yes Any error that occurred during the receive operation
data Any Yes The result of the operation, if any. This is the value returned by the last invocation of the onData() callback

Example

// Imagine that a power down has been triggered on the AT partner, e.g. by pulling
// a power pin high and then low.  We now wish to wait for the partner to send
// "POWERING DOWN", and then "POWER DOWN" to indicate that it has finished
// powering down.  We also wish to time how long the partner takes to power down.

// Record the start time
local start = hardware.millis();

// Start receiving responses
at.receive(function(data) {
  // Fail on "ERROR", this will be caught and passed to `onDone()`
  if (data == "ERROR") throw "failed to power down";
  // Wait for the expected response
  if (data != "POWERING DOWN") return CB_REPEAT;

  // We got the "POWERING DOWN" response, now wait for "POWER DOWN"
  return function(data) {
    // Fail on error, this will be caught and passed to `onDone()`
    if (data == "ERROR") throw "failed while powering down";
    // Wait for the expected response
    else if (data != "POWER DOWN") return CB_REPEAT;

    // We've now received both responses.  The value we return now will be the
    // result of the operation, passed on to our `onDone()` callback below

    // Calculate the duration of the operation
    return hardware.millis() - start;
  }
}, function(err, duration) {
  if (err) server.error("power down failed: " + err);
  else server.log(format("powered down after %d milliseconds", duration));
});

stop(err, data)

Manually stop an in-progress .receive() operation. The currently set onData() callback will be removed, and the onDone() callback (if set) will be called. This allows a way to end a .receive() operation that is alternative to the using the return value of onData, which obviously can only have an effect each time there is data to process. For example, you might wish to stop .receiving() after a set period of time (but without triggering a timeout error), or to stop .receiving() in response to some other stimuli, e.g. a power status pin going low indicating that the partner has unexpectedly powered down.

.stop() will fail if the AT instance is not actually in the middle of an operation. Should you need to check, you can check for this with .busy().

Parameters

Parameter Type Required Description
err Any (usually a String or Null) No Error to pass to onDone()
data Any No Data to pass to onDone()

Example

Suppose an AT command will give you multiple lines of data, which you wish to record. You don't know what they might look like, or how many there might be, but you expect them all to arrive within 5 seconds. You could collect all responses for 5 seconds, and then manually end the .receive(), like so:

local received = [];

// Execute the send, and initiate the receive operation
at.cmd("AT+SENSORVALUES?\r", function(data) {
  received.push(data);
  return CB_REPEAT;
}, function(err, values) {
  if (err) {
    // Handle the error
    // ....
  }

  server.log(format("got %d sensor value", values.len()));
  // Do something with the values
  // ...
});

// After 5 seconds, end the receive operation, passing the array of received
responses to the onDone callback
imp.wakeup(5, function() {
  at.stop(null, received);
});

Bad Example

A non-recommended use-case is to attempt to abort any currently running operation, without knowing what the operation actually is. For example, if some part of your code decides it has a really important AT command to execute, so it does the following:

// Stop whatever command is running now, if there is one
if (at.busy()) at.stop();

// Now the instance isn't busy, so we can run a comand, right?  Probably wrong.
Don't do this.
at.cmd(/* ... */);

This is bad, because the AT partner might still be about to send us a response for a previously sent AT command, for which the .receive() operation had not finished. If this response comes during our next command instead, we won't be expecting it and that could problems.


busy()

Check if the AT instance is "busy", i.e. in the middle of a .receive() operation. The instance is unavailable for other .send(), or .receive() commands while it is busy. The instance cannot be .stop()ed unless it is already busy.

Return Value

A boolean.

Example

// Define a function that will check the clock on our AT partner every 60 seconds
// or so, as long as it's not busy executing more important commands for some
// other part of our application
local checkClockIfIdle;
checkClockIfIdle = function() {
  imp.wakeup(60, function() {
    if (at.busy()) return checkClockIfIdle();
    at.cmd("AT+CLOCK\r", expect(regexp(@"^\d+$")), function(err, clock) {
      if (err) server.error("failed to check clock: " + err);
      else server.log(format("Imp time: %d, AT partner clock time: %s", time(), clock));
      checkClockIfIdle();
    });
  });
}

// Start checking
checkClockIfIdle();

register(expected[, dedupe], onRegData)

Register a callback to run each time a piece of incoming data matches an expected specification, e.g. when a line starts with a certain prefix string. Registered functions are checked against incoming data in order from last registered to first registered.

This can be used for when the AT partner (e.g. a modem) sends unsolicited results asynchronously at times we can't predict (e.g. notifications of UDP that has been sent to our modem from another source).

Parameters

Parameter Type Required Description
expected MatchSpecification Yes A value used to test if incoming data is a match. See the documentation for .match() for more details on this type.
dedupe Boolean No Whether existing registrations should be deduplcated. If true, all existing registrations with the same expected MatchSpecification will be removed.
onRegData Function Yes Callback to run on matching data.
Callback: onRegData(data)

Called each time a match is seen.

Parameters
Parameter Type Required Description
data string Yes The matching data
Return Value

Can return false to indicate that the data was not actually relevant (despite matching). This will cause the AT instance to continue checking for other matching registrations, or fall through to passing to the data to an in-progress .receive() operation.

Example
function readUdpData(notification) {
  // Read and process UDP data from the AT modem
  // ...
}

// Regexp match for unsolicited UDP notifications, which indicate that there is
// UDP data available to be read from our AT partner
local udpNotificationRe = regexp("^\+UDP: .*$");

// Register our read callback to run whenever we are notified that there is data
// available
at.register(re, readUdpData);

deregister(expected[, all])

Deregister a registration registered by .register().

Parameters

Parameter Type Required Description
expected MatchSpecification Yes As originally passed to .register()
all Boolean No Whether to remove all matching registrations. Default is false, which removes only the most recent matching registration.

Example

// Continuing on from `.register()` example...

// We are no longer expecting any incoming UDP data, so deregister the callback
// Note that we must remember the `expected` MatchSpecification that was used
// to register the callback, to use as an ID for deregistering
at.deregister(udpNotificationRe);

resetTimeout(t)

Reset the timeout on a .receive() operation. This could be used in an onData() callback to extend the timeout during a .receive() operation.

Parameters

Parameter Type Required Description
t Float No Time (in seconds, from now) until timeout. Defaults to the original timeout time set in .receive(), but reset to be from the current time.

Example

function waitForOk(data) {
  if (data == "OK") {
    return null;
  } else {
    // Reset the timeout any time we receive any data
    resetTimeout()
    return CB_REPEAT;
  };
}

// Execute a command.  Wait for an OK response, and timeout if 10 seconds
passes without *any* responses
at.cmd("AT+PWDWN\r", 10, waitForOk, onDone);

seq(cmds, cb)

Chains together multiple .send() and .receive() operations in a sequence. An iterable sequence of asynchronous commands is executed until completion (the last command completes successfully, or there is an error), then the result is passed to the callback cb().

Parameters

Parameter Type Required Description
iter Iterable (see .toGen()) Yes Asynchronous command specification (see below, and see examples)
cb Function Yes Callback to execute when sequence is complete. If successful, the callback will be passed the data returned by the last command in the sequence (as its second argument).

cmds is converted to a generator in order to iterate the commands asynchronously, according to the following rules:

Type Behaviour
Generator Passes through untouched
Function Called multiple times to generate each new value, until it returns null, at which point the generator is exhausted
Other (Array, Class, Table) Iterated according to foreach (value in cmds) { ... }

To read more about generators, see the Squirrel 3.0 Reference Manual or the Electric Imp docs.

At each step, the value yielded by the iterable may be of one of 3 cases:

Type Meaning
Null The sequence is complete
Function This is assumed to be an asynchronous function taking one function, a binary error/data callback function. The function is passed a callback, and the sequence continues when the function calls the callback.
Other In this case it is assumed that a .receive() operation has been initiated, but was not been given an onDone callback. .seq() will set its own callback in order to wait for the operation to complete before continuing.

Example

local numberRe = regexp("^\d+$");

function checkTime(cb=null) {
  // Execute clock command, wait for "OK" and then time
  return at.cmd("AT+CLOCK?\r", at.expect("OK", numberRe), function(err, time) {
    if (err) return cb(err, null);
    server.log("external clock time: " + time);
  });
}

function checkTemp(cb=null) {
  // Execute check temperature command, wait for "OK" and then temperature
  return at.cmd("AT+TEMP?\r", at.expect("OK", numberRe), function(err, temp) {
    if (err) return cb(err, null);
    server.log("temperature reading: " + temp);
  });
}

// Define callback to run when a sequence is complete
// `data` will be the result obtained by executing the sequence
function onDone(err, data) {
  if (err) server.error("Error in sequence: " + err);
  else server.log("Sequence completed with result: " + data);
}

// Check time
checkTime(onDone);

// Check time, then temperature
at.seq([
  @(cb) checkTime(cb),
  @(cb) checkTemp(cb),
], onDone);

// Equivalently...
at.seq([
  checkTime,
  checkTemp,
], onDone);

// Check time and temperature twice, doing something else in between
at.seq([
  checkTime,
  checkTemp,
  function(cb) {
    try {
      // Do something...
      cb(null, data);
    }
    catch (err) {
      cb(err, null);
    }
  },
  checkTime,
  checkTemp,
], onDone);

// Check time and temperature twice, waiting 10 seconds in between
at.seq([
  checkTime,
  checkTemp,
  at.wait(10),
  checkTime,
  checkTemp,
], onDone);

// Equivalently...
// In this example we pass in a generator, by defining a generator function and
// calling it to get an instance of the generator.  There is little advantage to
// using a generator over an array for this example, save for the fact that the
// function returned by `at.wait(10)` does not need to be loaded into memory until
// (if at all) it is needed.
at.seq(function() {
  // no callback passed in, it will be set by `.seq()` with `.onDone()` instead (since a non-null and non-function value is returned).
  yield checkTime();
  yield checkTemp();
  // yielding an async function still works too
  yield at.wait(10);
  yield checkTime();
  yield checkTemp();
}(), onDone);

// Check time and temperature every 10 seconds forever (or until an error occurs)
// Here the generator shows its strength: we can use it to represent an
// infinite sequence, without loading an infinite number of functions (in an
// array) into memory
at.seq(function() {
  while (true) {
    yield checkTime();
    yield checkTemp();
    yield at.wait(10);
  }
}(), onDone);

// Equivallently, using a function instead of a generator
local cmds = [checkTime, checkTemp, at.wait(10)];
local i = 0;
// The provided function is called at the end of each command to get the next
// command to run
at.seq(function() {
  // Get the next command
  local cmd = cmds[i];
  // Increment the index, wrapping around the end of the array
  i = (i + 1) % cmds.len();
  return cmd;
}, onDone);

// Check time continuously forever (or until an error occurs), as fast as possible.
// The `checkTime()` function can be used directly.  It is called to inititate
// each command in the sequence (in this case, always "check the time").  Since it
// returns a non-null, non-function value, `.seq()` will wait until the command it
// initiated completes before the calling the function again to initiate the next
// command.
at.seq(checkTime, onDone);

expect(expected[, n, ex])

A factory method for generating onData() callbacks to handle common use cases. Calling this method, with one or more arguments for configuration, will return a callback that can be used as an onData() callback. The callback will expect one or more matching responses in a given order, either exclusively (by default) or with potentially other responses in between. Completes the .receive() operation when matches have been seen for all expected responses in the correct order, or raises an exception.

Parameters

Parameter Type Required Description
expected MatchSpecification or [MatchSpecification] Yes Specification(s) of expected response(s). Note that an array value will be assumed to be an array of MatchSpecifications each representing a single expected response, not an array-type MatchSpecification intended to match a single response with multiple potential values. If you wish to match a single response with an array-type MatchSpecification, simply wrap it in another array to indicate that you are expecting a sequence of length 1 - and the response should be matched against the inner array MatchSpecification. See the nested array examples below.
flags Integer No Effects the behaviour of the resulting callback function in various ways, for example allowing the expected responses to come out of order. See the examples below for some examples of various flag combinations and their behaviour. See the description of each flag later in this document for more information.
n Integer No Index into expected array of response that should be passed on as data to the onDone() callback. E.g. if a .receive() operation should expect 3 responses, and only the second response contains meaninful data, then use n=1. Defaults to the last item, n=expect.len()-1
Example
// Expect an "OK" response
at.cmd("AT+PWDWN\r", 10, at.expect("OK"), onDone);

// Expect an "OK" response, then a "POWER DOWN" response
at.cmd("AT+PWDWN\r", 10, at.expect(["OK", "POWER DOWN"]), onDone);

// Expect an "OK" response, then either "POWER DOWN" or "POWER DOWN"
at.cmd("AT+PWDWN\r", 10, at.expect(["OK", ["POWER DOWN", "POWERED DOWN"]]), onDone);

// Expect either "POWER DOWN" or "POWER DOWN"
// NB: without the second layer of array, this would expect "POWER DOWN", *and
// then* "POWERED DOWN"
at.cmd("AT+PWDWN\r", 10, at.expect([["POWER DOWN", "POWERED DOWN"]]), onDone);

// Expect a "POWER DOWN" response eventually
at.cmd("AT+PWDWN\r", 10, at.expect("POWER DOWN", AT.IGNORE_NON_MATCHING), onDone);

// Expect an OK response, followed by some data, followed by another OK
// The data response (index 1) should be passed to `onDone()`
at.cmd("AT+VERSION\r", 10, at.expect(["OK", regexp("^data: .*$"), "OK"], AT.NO_FLAGS, 1), onDone);

// Expect an "OK" and a "POWER DOWN", in either order
at.cmd("AT+PWDWN\r", 10, at.expect(["OK", "POWER DOWN"], AT.UNORDERED), onDone);

// Expect an "OK" response, and a "POWER DOWN" response, in either order, and
// possibly with other non-matching responses in-between
at.cmd("AT+PWDWN\r", 10, at.expect(["OK", "POWER DOWN"], AT.UNORDERED | AT.IGNORE_NON_MATCHING), onDone);

// Expect at least one "OK", and at least one "POWER DOWN", and no other
// responses.  Calls `onDone()` as soon as both responses have been seen once or
// more.
at.cmd("AT+PWDWN\r", 10, at.expect(["OK", "POWER DOWN"], AT.UNORDERED | AT.ALLOW_REPEATS), onDone);

// Expect some numbers, followed by an "OK"
// Collect all these responses into an array to pass to `onDone()`
at.cmd("AT+SENSORDATA?\r", 10, at.expect([regexp(@"^\d+$"), "OK"], AT.ALLOW_REPEATS | AT.COLLECT_ALL), onDone);

Class Methods

expectMatch(expected, str)

Utility method. Throws a nice error message if string str does not match MatchSpecification expected, according to the behaviour of .match(). Can be used inside of onData() callbacks to matke assertions that throw meaningful error messages without extra boilerplate.

Example

at.cmd("AT+CLOCK\r", function(data) {
  // Make sure that the format of the data matches what we expect
  // We can reference `at.expectMatch()` as `expectMatch()` because this
  // `onData()` callback will bound to the AT instance by `.cmd()`
  expectMatch(regexp("^\d+$"), data);
  // Convert data to integer before passing to `onDone`
  return data.tointeger();
}, function(err, clockTime) {
  // ... handle the data
});

match(spec, str)

Checks if a string matches a "thing". That "thing" is defined as a "MatchSpecification", documented below.

Parameters

Parameter Type Required Description
spec MatchSpecification Yes Specification of match. See below.
str String Yes String to match against.

MatchSpecification

A match specification works in different ways depending on its type.

Type Behaviour
Boolean Always matches (if true) or doesn't match (if false), ignoring the contents of the string
String Matches if the strings are identical
Regexp Matches if the regexp matches the string according to the regexp.match() method
Function Is called, with the string as its only argument. The return value (a boolean) indicates whether there was a match
[MatchSpecification] Matches if the string matches any of the the given array of MatchSpecifications
Class, Instance, or Table Must implement a .match() method, which is called with the string as its only argument, similar to using a function type MatchSpecification. You can pass in an instance of an object that implements custom matching logic (e.g. an instance of a custom Regexp class, or a custom parsing class), as long as you have implemented this .match() method.

Example

// Always true
at.match(true, s);

// Always false
at.match(false, s);

// Matches "OK" exactly
at.match("OK", s);

// Matches if string contains "OK"
at.match(regexp(@"^.*OK.*$"), s);

// Matches "OK" case-insentively
at.match(function (str) {
  return str.toupper() == "OK";
}, s);

// Equivalently...
at.match(@(str) str.toupper() == "OK", s);

// Implement a custom Regexp class with a better `._tostring()` method for easier debugging
class RE {
    _re = null;
    _str = null;

    constructor(str) {
        _str = str;
        _re = ::regexp(str);
    }

    // Debuggable _tostring() method
    function _tostring() {
        return "/" + _str + "/";
    }

    // Defer to the interal regexp object for any other properties or methods
    function _get(idx) {
        local v = _re[idx]
        return typeof v == "function" ? v.bindenv(_re) : v;
    }

}

// Matches if string contains "OK"
at.match(RE(@"^.*OK.*$"), s);

// Passes if string contains "OK", otherwise throws an error.  The error
// message will contain the MatchSpecification, which is now more readable with
// our custom RE class than it would have been for a raw regexp object
at.expectMatch(RE(@"^.*OK.*$"), s);

wait(t, cb)

A convenient wrapper around imp.wakeup() to wait for a period of time, and then execute a callback. The only difference is that the the callback is a standard error-first, binary callback (although both arguments will always be null).

Return Value

Returns the timer value, as returned by imp.wakeup().

Example

at.wait(10, function(err, data) {
  assert(err == null);
  assert(data == null);
  // Pretty boring...
})

See examples for .seq() for examples of .wait() used in context.

Instance Properties

acc

Short for "accumulator", this property is meant to be used, if necessary, by onData callbacks to maintain state throughout the course of a .receive() operation. It is reset to null whenever a .receive() operation completes. An onData callback can check if acc == null to check that this is the first time it is executing during this operation, and it may then initialise state into acc, which can be modified in other calls to the onData callback.

Note that acc cannot be accessed within onDone, since it will have already been cleared. To use the value of acc in onDone, the .receive() operation should be ended by returning acc from onData, or by passing acc to .stop();

Example

function countResponsesUntilPowerDown(data) {
  // Initialise state (count)
  // We can reference `acc` directly, since we get executed within the scope of
  // the AT instance
  if (acc == null) acc = 0;
  // When we get "POWER DOWN", return the count (to be passed to the `onDone`
  // callback)
  if (data == "POWER DOWN") return acc;
  else {
    // We have received an extra response while waiting for power down, count it
    acc++;
    return CB_REPEAT;
  }
}

at.cmd("AT+PWDWN\r", countResponsesUntilPowerDown, function(err, count) {
  if (err) {
    // Handle the error...
  }
  server.log(format("Got %d extra responses before power down.", count));
})

Class Properties

DFLT_TIMEOUT

The default timeout for .receive() operations, if one is not explicitly configured for the instance by passing it to the constructor. The value is 60 seconds.

CB_REPEAT

A constant for returning from onData() callbacks, to indicate that the same callback should be used again to handle the next available piece of data.

Errors

ERR_TIMEOUT

An error message string, passed to onDone() callbacks when a .receive() operation times out.


ERR_BUSY

An error message string, thrown when attempting to .send() to the AT partner while already busy with a .receive() operation.

.expect() Flags

NO_FLAGS

Alias for 0. Using this constant can be more clear than using 0 when you need to provide a value for flags, but you don't actually care to set any, for example when using the n argument to .expect(). See example below.

Example
// Returns an onData callback that expects to see a number, then "OK"
expect([regexp(@"^\d+$"), "OK"]);

// Returns an onData callback that expects to see a number, then "OK"
// Will pass on the number to the onDone callback
expect([regexp(@"^\d+$"), "OK"], AT.NO_FLAGS, 0);
// (equivalently)
expect([regexp(@"^\d+$"), "OK"], 0, 0);
// (equivalently)
expect([regexp(@"^\d+$"), "OK"], null, 0);

UNORDERED

Flag for controlling behaviour of onData() callbacks generated by .expect(). Indicates that expected values should be allowed to arrive in any order.

Example
// Returns an onData callback that expects to see A, then B, then C
expect(["A", "B", "C"]);

// Returns an onData callback that expects to see exactly A, B, and C in any
// order
expect(["A", "B", "C"], AT.UNORDERED);

IGNORE_NON_MATCHING

Flag for controlling behaviour of onData() callbacks generated by .expect(). Indicates that unexpected values should be ignored, rather than treated as unexpected errors.

Example
// onData callback that expects to see 1 "OK" only
expect("OK");

// onData callback that expects to see 1 "OK", but may will silently ignore any
// other responses that come before then
expect("OK", AT.IGNORE_NON_MATCHING);

ALLOW_REPEATS

Flag for controlling behaviour of onData() callbacks generated by .expect(). Relevant only when AT.IGNORE_NON_MATCHING is not set. When AT.UNORDERED is also set, this flag will cause the callback to allow seeing multiple responses matching each element of expected. When matching in an ordered fashion, this will flag will allow repeated responses matching the same (current) expected element before moving onto the next one.

Example
// Returns an onData callback that expects to see A, then B, then C
expect(["A", "B", "C"]);

// Returns an onData callback that expects to see 1 or more A`s, then one or
// more B's, then one or more C's
expect(["A", "B", "C"], AT.ALLOW_REPEATS);

// Returns an onData callback that expects to see exactly 1 A, 1 B, and 1 C, in
// any order
expect(["A", "B", "C"], AT.UNORDERED);

// Returns an onData callback that expects to see at least 1 A, B, and C, in
// any order.  It will complete a corresponding receive operation has soon as it
// has seen at least 1 of each.
expect(["A", "B", "C"], AT.UNORDERED | AT.ALLOW_REPEATS);

COLLECT_ALL

Flag for controlling behaviour of onData() callbacks generated by .expect(). Indicates that all matching data strings should be collected into an array to be passed to the onDone() callback at the end. The default (with AT.COLLECT_ALL not set) is to only pass on the last piece of data seen, or, if the n argument is passed to .expect(), to pass on the (n-1)-th element only.

Example
// A regexp to match an integer
local nRe = regexp("^\d+$");

// Generates an onData callback that expects to see 3 numbers
// Only the last will be passed on to onDone
expect(array(3, nRe));

// Now only the 1st will be passed on to onDone
expect(array(3, nRe), 0);
// (equivalently)
expect(array(3, nRe), AT.NO_FLAGS, 0);

// Now an array containing all 3 numbers in the order they arrived will be
// passed on to onDone
expect(array(3, nRe), AT.COLLECT_ALL);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment