This class provides SMS, USSD and modem file system support for u-blox modules on the C027 and C030 boards (excepting the C030 N2xx flavour) from mbed 5.5 onwards.

Dependents:   example-ublox-at-cellular-interface-ext example-ublox-cellular-driver-gen HelloMQTT ublox_new_driver_test ... more

UbloxCellularDriverGen.cpp

Committer:
fahim.alavi@u-blox.com
Date:
23 months ago
Revision:
9:a5389248c8d0
Parent:
8:e6f6bf43a5d1

File content as of revision 9:a5389248c8d0:

/* Copyright (c) 2017 ublox Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "UbloxCellularDriverGen.h"
#include "string.h"
#if defined(FEATURE_COMMON_PAL)
#include "mbed_trace.h"
#define TRACE_GROUP "UCID"
#else
#define tr_debug(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_info(format, ...)  debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_warn(format, ...)  debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#define tr_error(format, ...) debug_if(_debug_trace_on, format "\n", ## __VA_ARGS__)
#endif

/**********************************************************************
 * PROTECTED METHODS: Short Message Service
 **********************************************************************/

// URC for Short Message listing.
void UbloxCellularDriverGen::CMGL_URC()
{
    char buf[64];
    int index;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +CMGL: <ix>,...
    *buf = 0;
    if (read_at_to_char(buf, sizeof(buf), '\n') > 0) {
        // Now also read out the text message, so that we don't
        // accidentally trigger URCs or the like on any of
        // its contents
        *_smsBuf = 0;
        read_at_to_char(_smsBuf, sizeof(_smsBuf), '\n');
        // Note: don't put any debug in here, this URC is being
        // called multiple times and debug may cause it to
        // miss characters
        if (sscanf(buf, ": %d,", &index) == 1) {
            _smsCount++;
            if ((_userSmsIndex != NULL) && (_userSmsNum > 0)) {
                *_userSmsIndex = index;
                _userSmsIndex++;
                _userSmsNum--;
            }
        }
    }
}

// URC for new SMS messages.
void UbloxCellularDriverGen::CMTI_URC()
{
    char buf[32];

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    *buf = 0;
    if (read_at_to_char(buf, sizeof (buf), '\n') > 0) {
        // No need to parse, any content is good
        tr_info("New SMS received");
    }
}

/**********************************************************************
 * PROTECTED METHODS: Unstructured Supplementary Service Data
 **********************************************************************/

// URC for call waiting.
void UbloxCellularDriverGen::CCWA_URC()
{
    char buf[32];
    int numChars;
    int a;
    int b = 0;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +CCWA: <status>[, <class>]
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        if (sscanf(buf, ": %d,%d", &a, &b) > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+CCWA", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    if (a > 0) {
                        debug_if(_debug_trace_on, "Calling Waiting is active");
                    } else {
                        debug_if(_debug_trace_on, "Calling Waiting is not active");
                    }
                    if (b > 0) {
                        if (b & 0x01) {
                            debug_if(_debug_trace_on, " for voice\n");
                        }
                        if (b & 0x02) {
                            debug_if(_debug_trace_on, " for data\n");
                        }
                        if (b & 0x04) {
                            debug_if(_debug_trace_on, " for fax\n");
                        }
                        if (b & 0x08) {
                            debug_if(_debug_trace_on, " for SMS\n");
                        }
                        if (b & 0x10) {
                            debug_if(_debug_trace_on, " for data circuit sync\n");
                        }
                        if (b & 0x20) {
                            debug_if(_debug_trace_on, " for data circuit async\n");
                        }
                        if (b & 0x40) {
                            debug_if(_debug_trace_on, " for dedicated packet access\n");
                        }
                        if (b & 0x80) {
                            debug_if(_debug_trace_on, " for dedicated PAD access\n");
                        }
                    }
                }
            }
        }
    }
}

// URC for call forwarding.
void UbloxCellularDriverGen::CCFC_URC()
{
    char buf[32];
    int numChars;
    char num[32];
    int a, b;
    int numValues;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +CCFC: <status>[, <class>]
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        memset (num, 0, sizeof (num));
        numValues = sscanf(buf, ": %d,%d,\"%32[^\"][\"]", &a, &b, num);
        if (numValues > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+CCFC", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    if (a > 0) {
                        debug_if(_debug_trace_on, "Calling Forwarding is active ");
                    } else {
                        debug_if(_debug_trace_on, "Calling Forwarding is not active ");
                    }
                    if (numValues > 1) {
                        if (b > 0) {
                            if (b & 0x01) {
                                debug_if(_debug_trace_on, " for voice");
                            }
                            if (b & 0x02) {
                                debug_if(_debug_trace_on, " for data");
                            }
                            if (b & 0x04) {
                                debug_if(_debug_trace_on, " for fax");
                            }
                            if (b & 0x08) {
                                debug_if(_debug_trace_on, " for SMS");
                            }
                            if (b & 0x10) {
                                debug_if(_debug_trace_on, " for data circuit sync");
                            }
                            if (b & 0x20) {
                                debug_if(_debug_trace_on, " for data circuit async");
                            }
                            if (b & 0x40) {
                                debug_if(_debug_trace_on, " for dedicated packet access");
                            }
                            if (b & 0x80) {
                                debug_if(_debug_trace_on, " for dedicated PAD access");
                            }
                        }
                    }
                    if (numValues > 2) {
                        debug_if(_debug_trace_on, " for %s\n", num);
                    } else {
                        debug_if(_debug_trace_on, "\n");
                    }
                }
            }
        }
    }
}


// URC for calling line ID restriction.
void UbloxCellularDriverGen::CLIR_URC()
{
    char buf[32];
    int numChars;
    int a, b;
    int numValues;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +CLIR: <n>[, <m>]
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        numValues = sscanf(buf, ": %d,%d", &a, &b);
        if (numValues > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+CLIR", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    switch (a) {
                        case 0:
                            debug_if(_debug_trace_on, "Calling Line ID restriction is as subscribed\n");
                            break;
                        case 1:
                            debug_if(_debug_trace_on, "Calling Line ID invocation ");
                            break;
                        case 2:
                            debug_if(_debug_trace_on, "Calling Line ID suppression ");
                            break;
                    }
                    if (numValues > 2) {
                        switch (b) {
                            case 0:
                                debug_if(_debug_trace_on, " is not provisioned\n");
                                break;
                            case 1:
                                debug_if(_debug_trace_on, " is provisioned permanently\n");
                                break;
                            case 2:
                                debug_if(_debug_trace_on, " is unknown\n");
                                break;
                            case 3:
                                debug_if(_debug_trace_on, " is in temporary mode, presentation restricted\n");
                                break;
                            case 4:
                                debug_if(_debug_trace_on, " is in temporary mode, presentation allowed\n");
                                break;
                        }
                    }
                }
            }
        }
    }
}

// URC for calling line ID presentation.
void UbloxCellularDriverGen::CLIP_URC()
{
    char buf[32];
    int numChars;
    int a, b;
    int numValues;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +CLIP: <n>[, <m>]
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        numValues = sscanf(buf, ": %d,%d", &a, &b);
        if (numValues > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+CLIP", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    switch (a) {
                        case 0:
                            debug_if(_debug_trace_on, "Calling Line ID disable ");
                            break;
                        case 1:
                            debug_if(_debug_trace_on, "Calling Line ID enable ");
                            break;
                    }
                    if (numValues > 1) {
                        switch (b) {
                            case 0:
                                debug_if(_debug_trace_on, " is not provisioned\n");
                                break;
                            case 1:
                                debug_if(_debug_trace_on, " is provisioned\n");
                                break;
                            case 2:
                                debug_if(_debug_trace_on, " is unknown\n");
                                break;
                        }
                    }
                }
            }
        }
    }
}

// URC for connected line ID presentation.
void UbloxCellularDriverGen::COLP_URC()
{
    char buf[32];
    int numChars;
    int a, b;
    int numValues;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +COLP: <n>[, <m>]
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        numValues = sscanf(buf, ": %d,%d", &a, &b);
        if (numValues > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+COLP", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    switch (a) {
                        case 0:
                            debug_if(_debug_trace_on, "Connected Line ID disable ");
                            break;
                        case 1:
                            debug_if(_debug_trace_on, "Connected Line ID enable ");
                            break;
                    }
                    if (numValues > 1) {
                        switch (b) {
                            case 0:
                                debug_if(_debug_trace_on, " is not provisioned\n");
                                break;
                            case 1:
                                debug_if(_debug_trace_on, " is provisioned\n");
                                break;
                            case 2:
                                debug_if(_debug_trace_on, " is unknown\n");
                                break;
                        }
                    }
                }
            }
        }
    }
}

// URC for connected line ID restriction.
void UbloxCellularDriverGen::COLR_URC()
{
    char buf[32];
    int numChars;
    int a;

    // Note: not calling _at->recv() from here as we're
    // already in an _at->recv()
    // +COLR: <status>
    numChars = read_at_to_char(buf, sizeof (buf), '\n');
    if (numChars > 0) {
        if (sscanf(buf, ": %d", &a) > 0) {
            if (_ssUrcBuf == NULL) {
                _ssUrcBuf = (char *) malloc(numChars + 5 + 1);
                if (_ssUrcBuf != NULL) {
                    memcpy (_ssUrcBuf, "+COLR", 5);
                    memcpy (_ssUrcBuf + 5, buf, numChars);
                    *(_ssUrcBuf + numChars + 5) = 0;
                    switch (a) {
                        case 0:
                            debug_if(_debug_trace_on, "Connected Line ID restriction is not provisioned\n");
                            break;
                        case 1:
                            debug_if(_debug_trace_on, "Connected Line ID restriction is provisioned\n");
                            break;
                        case 2:
                            debug_if(_debug_trace_on, "Connected Line ID restriction is unknown\n");
                            break;
                    }
                }
            }
        }
    }
}

/**********************************************************************
 * PUBLIC METHODS: Generic
 **********************************************************************/

// Constructor.
UbloxCellularDriverGen::UbloxCellularDriverGen(PinName tx, PinName rx,
                                               int baud, bool debug_on)
{
    _userSmsIndex = NULL;
    _userSmsNum = 0;
    _smsCount = 0;
    _ssUrcBuf = NULL;

    // Initialise the base class, which starts the AT parser
    baseClassInit(tx, rx, baud, debug_on);

    // URCs related to SMS
    _at->oob("+CMGL", callback(this, &UbloxCellularDriverGen::CMGL_URC));
    // Include the colon with this one as otherwise it could be found
    // by +CMT, should it ever occur
    _at->oob("+CMTI:", callback(this, &UbloxCellularDriverGen::CMTI_URC));

    // URCs related to supplementary services
    _at->oob("+CCWA", callback(this, &UbloxCellularDriverGen::CCWA_URC));
    _at->oob("+CCFC", callback(this, &UbloxCellularDriverGen::CCFC_URC));
    _at->oob("+CLIR", callback(this, &UbloxCellularDriverGen::CLIR_URC));
    _at->oob("+CLIP", callback(this, &UbloxCellularDriverGen::CLIP_URC));
    _at->oob("+COLP", callback(this, &UbloxCellularDriverGen::COLP_URC));
    _at->oob("+COLR", callback(this, &UbloxCellularDriverGen::COLR_URC));
}

// Destructor.
UbloxCellularDriverGen::~UbloxCellularDriverGen()
{
}

/**********************************************************************
 * PUBLIC METHODS: Short Message Service
 **********************************************************************/

// Count the number of messages on the module.
int UbloxCellularDriverGen::smsList(const char* stat, int* index, int num)
{
    int numMessages = -1;
    LOCK();

    _userSmsIndex = index;
    _userSmsNum = num;
    _smsCount = 0;
    // There is a callback to capture the result
    // +CMGL: <ix>,...
    _at->debug_on(false); // No time for AT interface debug
                          // as the list comes out in one long
                          // stream and we can lose characters if we
                          // pause to do printfs
    if (_at->send("AT+CMGL=\"%s\"", stat) && _at->recv("OK\n")) {
        numMessages = _smsCount;
    }
    _at->debug_on(_debug_trace_on);

    // Set this back to null so that the URC won't trample
    _userSmsIndex = NULL;

    UNLOCK();
    return numMessages;
}

// Send an SMS message.
bool UbloxCellularDriverGen::smsSend(const char* num, const char* buf)
{
    bool success = false;
    char typeOfAddress = TYPE_OF_ADDRESS_NATIONAL;
    LOCK();

    if ((strlen (num) > 0) && (*(num) == '+')) {
        typeOfAddress = TYPE_OF_ADDRESS_INTERNATIONAL;
    }
    if (_at->send("AT+CMGS=\"%s\",%d", num, typeOfAddress) && _at->recv(">")) {
        if ((_at->write(buf, (int) strlen(buf)) >= (int) strlen(buf)) &&
            (_at->putc(0x1A) == 0) &&  // CTRL-Z
            _at->recv("OK")) {
            success = true;
        }
    }

    UNLOCK();
    return success;
}

bool UbloxCellularDriverGen::smsDelete(int index)
{
    bool success;
    LOCK();

    success = _at->send("AT+CMGD=%d", index) && _at->recv("OK");

    UNLOCK();
    return success;
}

bool UbloxCellularDriverGen::smsRead(int index, char* num, char* buf, int len)
{
    bool success = false;
    char * endOfString;
    int smsReadLength = 0;
    LOCK();

    if (len > 0) {
        //+CMGR: "REC READ", "+393488535999",,"07/04/05,18:02:28+08",145,4,0,0,"+393492000466",145,93
        // The text of the message.
        // OK
        memset (_smsBuf, 0, sizeof (SMS_BUFFER_SIZE)); // Ensure terminator
        if (_at->send("AT+CMGR=%d", index) &&
            _at->recv("+CMGR: \"%*[^\"]\",\"%15[^\"]\"%*[^\n]\n", num) &&
            _at->recv("%" stringify(SMS_BUFFER_SIZE) "[^\n]\n", _smsBuf) &&
            _at->recv("OK")) {
            endOfString = strchr(_smsBuf, 0);
            if (endOfString != NULL) {
                smsReadLength = endOfString - _smsBuf;
                if (smsReadLength + 1 > len) { // +1 for terminator
                    smsReadLength = len - 1;
                }
                memcpy(buf, _smsBuf, smsReadLength);
                *(buf + smsReadLength) = 0; // Add terminator
                success = true;
            }
        }
    }

    UNLOCK();
    return success;
}

/**********************************************************************
 * PUBLIC  METHODS: Unstructured Supplementary Service Data
 **********************************************************************/

// Perform a USSD command.
bool UbloxCellularDriverGen::ussdCommand(const char* cmd, char* buf, int len)
{
    bool success = false;
    char * tmpBuf;
    int atTimeout;
    int x;
    Timer timer;
    LOCK();
    atTimeout = _at_timeout; // Has to be inside LOCK()s

    if (len > 0) {
        *buf = 0;
        if (len > USSD_STRING_LENGTH + 1) {
            len = USSD_STRING_LENGTH + 1;
        }

        tmpBuf = (char *) malloc(USSD_STRING_LENGTH + 1);

        if (tmpBuf != NULL) {
            memset (tmpBuf, 0, USSD_STRING_LENGTH + 1);
            // +CUSD: \"%*d, \"%128[^\"]\",%*d"
            if (_at->send("AT+CUSD=1,\"%s\"", cmd)) {
                // Wait for either +CUSD to come back or
                // one of the other SS related URCs to trigger
                if (_ssUrcBuf != NULL) {
                    free(_ssUrcBuf);
                    _ssUrcBuf = NULL;
                }
                timer.start();
                _at->set_timeout(1000);
                while (!success && (timer.read_ms() < 30000)) {
                    if (_at->recv("+CUSD: %*d,\"")) {
                        // Note: don't wait for "OK" here as the +CUSD response may come
                        // before or after the OK
                        // Also, the return string may include newlines so can't just use
                        // recv() to capture it as recv() will stop capturing at a newline.
                        if (read_at_to_char(tmpBuf, USSD_STRING_LENGTH, '\"') > 0) {
                            success = true;
                            memcpy (buf, tmpBuf, len);
                            *(buf + len - 1) = 0;
                        }
                    } else {
                        // Some of the return values do not appear as +CUSD but
                        // instead as the relevant URC for call waiting, call forwarding,
                        // etc.  Test those here.
                        if (_ssUrcBuf != NULL) {
                            success = true;
                            x = strlen (_ssUrcBuf);
                            if (x > len - 1 ) {
                                x = len - 1;
                            }
                            memcpy (buf, _ssUrcBuf, x);
                            *(buf + x) = 0;
                            free (_ssUrcBuf);
                            _ssUrcBuf = NULL;
                        }
                    }
                }
                at_set_timeout(atTimeout);
                timer.stop();
            }
        }
    }

    UNLOCK();
    return success;
}

/**********************************************************************
 * PUBLIC: Module File System
 **********************************************************************/

// Delete a file from the module's file system.
bool UbloxCellularDriverGen::delFile(const char* filename)
{
    bool success;
    LOCK();

    success = _at->send("AT+UDELFILE=\"%s\"", filename) && _at->recv("OK");

    UNLOCK();
    return success;
}

// Write a buffer of data to a file in the module's file system.
int UbloxCellularDriverGen::writeFile(const char* filename, const char* buf, int len)
{
    int bytesWritten = -1;
    LOCK();

    if (_at->send("AT+UDWNFILE=\"%s\",%d", filename, len) && _at->recv(">")) {
        if ((_at->write(buf, len) >= len) && _at->recv("OK")) {
            bytesWritten = len;
        }
    }

    UNLOCK();
    return bytesWritten;
}

// Read a file from the module's file system
// Note: this is implemented with block reads since UARTSerial
// does not currently allow flow control and there is a danger
// of character loss with large whole-file reads
int UbloxCellularDriverGen::readFile(const char* filename, char* buf, int len)
{
    int countBytes = -1;  // Counter for file reading (default value)
    int bytesToRead = fileSize(filename);  // Retrieve the size of the file
    int offset = 0;
    int blockSize = FILE_BUFFER_SIZE;
    char respFilename[48 + 1];
    int sz, sz_read;
    bool success = true;
    int ch = 0;
    int timeLimit;
    Timer timer;

    debug_if(_debug_trace_on, "readFile: filename is %s; size is %d\n", filename, bytesToRead);

    memset(respFilename, 0, sizeof (respFilename));  // Ensure terminator
    if (bytesToRead > 0)
    {
        if (bytesToRead > len) {
            bytesToRead = len;
        }

        while (success && (bytesToRead > 0)) {

            if (bytesToRead < blockSize) {
                blockSize = bytesToRead;
            }
            LOCK();

            if (blockSize > 0) {
                if (_at->send("AT+URDBLOCK=\"%s\",%d,%d\r\n", filename, offset, blockSize) &&
                    _at->recv("+URDBLOCK:%48[^,],%d,\"", respFilename, &sz)) {
                    // Note: not checking respFilename as some modules (e.g. Sara-G350 00S-00)
                    // doesn't always include it.
                    // Would use _at->read() here, but if it runs ahead of the
                    // serial stream it returns -1 instead of the number of characters
                    // read so far, which is not very helpful so instead use _at->getc() and
                    // a time limit. The time limit is four times the amount of time it
                    // should take to read the block at the working baud rate with a minimum
                    // of 100 ms (for short files)
                    timer.reset();
                    timer.start();

                    // Add startup delay
                    timeLimit = 200+ FILE_BUFFER_SIZE * 4 / ((MBED_CONF_UBLOX_CELL_BAUD_RATE / 8) / 1000);

                    sz_read = 0;

                    while ((sz_read < blockSize) && (timer.read_ms() < timeLimit)) {
                        ch = _at->getc();
                        if (ch >= 0) {
                            *buf = ch;
                            buf++;
                            sz_read++;
                        }
                    }
                    timer.stop();

                    if (sz_read == blockSize) {
                        bytesToRead -= sz_read;
                        offset += sz_read;
                        _at->recv("OK");
                    } else {
                    debug_if(_debug_trace_on, "blockSize %d but only received %d bytes, configured retry\n", blockSize, sz_read);
                        //success = false;
                    }
               } else {
                   success = false;
               }
            }

            UNLOCK();
        }

        if (success) {
            countBytes = offset;
        }
    }

    return countBytes;
}

// Return the size of a file.
int UbloxCellularDriverGen::fileSize(const char* filename)
{
    int returnValue = -1;
    int fileSize;
    LOCK();

    if (_at->send("AT+ULSTFILE=2,\"%s\"", filename) &&
        _at->recv("+ULSTFILE: %d\n", &fileSize) &&
        _at->recv("OK")) {
        returnValue = fileSize;
    }

    UNLOCK();
    return returnValue;
}

// End of file