Diff
checker
テキスト
テキスト
画像
ドキュメント
Excel
フォルダ
Legal
Enterprise
デスクトップ
料金
ログイン
Diffchecker デスクトップのダウンロード
テキスト比較
2 つのテキスト ファイルの違いを見つける
ツール
履歴
ライブエディター
空白の変更を非表示
未変更行を折りたたむ
折り返しなし
レイアウト
分割
統合
比較精度
スマート
単語
文字
テキストスタイル
外観を変更
シンタックスハイライト
構文を選択
無視
テキスト変換
最初の差分へ移動
入力を編集
Diffchecker Desktop
Diffcheckerを実行する最も安全な方法。Diffchecker Desktopアプリを入手:あなたの差分はコンピューターから出ることはありません!
Desktopを入手
Untitled diff
作成日
7 年前
差分は期限切れになりません
クリア
エクスポート
共有
説明
86 削除
行
合計
削除
文字
合計
削除
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
692 行
すべてコピー
110 追加
行
合計
追加
文字
合計
追加
この機能を引き続き使用するには、アップグレードしてください
Diff
checker
Pro
価格を見る
690 行
すべてコピー
/*
/*
******************************************************************************
******************************************************************************
* Copyright (c) 2015 Particle Industries, Inc. All rights reserved.
* Copyright (c) 2015 Particle Industries, Inc. All rights reserved.
*
*
* This library is free software; you can redistribute it and/or
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later version.
* version 3 of the License, or (at your option) any later version.
*
*
* This library is distributed in the hope that it will be useful,
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* Lesser General Public License for more details.
*
*
* You should have received a copy of the GNU Lesser General Public
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
******************************************************************************
******************************************************************************
*/
*/
#ifndef HAL_CELLULAR_EXCLUDE
#ifndef HAL_CELLULAR_EXCLUDE
/* Includes -----------------------------------------------------------------*/
/* Includes -----------------------------------------------------------------*/
#include <stdio.h>
#include <stdio.h>
#include <stdint.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdarg.h>
#include <string.h>
#include <string.h>
#include "mdm_hal.h"
#include "mdm_hal.h"
#include "timer_hal.h"
#include "timer_hal.h"
#include "delay_hal.h"
#include "delay_hal.h"
#include "pinmap_hal.h"
#include "pinmap_hal.h"
#include "pinmap_impl.h"
#include "pinmap_impl.h"
#include "gpio_hal.h"
#include "gpio_hal.h"
#include "mdmapn_hal.h"
#include "mdmapn_hal.h"
#include "stm32f2xx.h"
#include "stm32f2xx.h"
コピー
コピー済み
コピー
コピー済み
#include "dns_client.h"
#include "service_debug.h"
#include "service_debug.h"
コピー
コピー済み
コピー
コピー済み
#include "bytes2hexbuf.h"
#include "hex_to_bytes.h"
#include "concurrent_hal.h"
#include "concurrent_hal.h"
#include <mutex>
#include <mutex>
#include "net_hal.h"
#include "net_hal.h"
#include <limits>
#include <limits>
std::recursive_mutex mdm_mutex;
std::recursive_mutex mdm_mutex;
/* Private typedef ----------------------------------------------------------*/
/* Private typedef ----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private define -----------------------------------------------------------*/
/* Private macro ------------------------------------------------------------*/
/* Private macro ------------------------------------------------------------*/
#define PROFILE "0" //!< this is the psd profile used
#define PROFILE "0" //!< this is the psd profile used
#define MAX_SIZE 1024 //!< max expected messages (used with RX)
#define MAX_SIZE 1024 //!< max expected messages (used with RX)
#define USO_MAX_WRITE 1024 //!< maximum number of bytes to write to socket (used with TX)
#define USO_MAX_WRITE 1024 //!< maximum number of bytes to write to socket (used with TX)
コピー
コピー済み
コピー
コピー済み
// ID of the PDP context used to configure the default EPS bearer when registering in an LTE network
// Note: There are no PDP contexts in LTE, SARA-R4 uses this naming for the sake of simplicity
#define PDP_CONTEXT 1
// Enable hex mode for socket commands. SARA-R410M-01B has a bug which causes truncation of
// data read from a socket if the data contains a null byte
// #define SOCKET_HEX_MODE
// Timeout for socket write operations
#define SOCKET_WRITE_TIMEOUT 30000
// Timeout for +COPS command
#define COPS_TIMEOUT (3 * 60 * 1000)
// num sockets
// num sockets
#define NUMSOCKETS ((int)(sizeof(_sockets)/sizeof(*_sockets)))
#define NUMSOCKETS ((int)(sizeof(_sockets)/sizeof(*_sockets)))
//! test if it is a socket is ok to use
//! test if it is a socket is ok to use
#define ISSOCKET(s) (((s) >= 0) && ((s) < NUMSOCKETS) && (_sockets[s].handle != MDM_SOCKET_ERROR))
#define ISSOCKET(s) (((s) >= 0) && ((s) < NUMSOCKETS) && (_sockets[s].handle != MDM_SOCKET_ERROR))
//! check for timeout
//! check for timeout
#define TIMEOUT(t, ms) ((ms != TIMEOUT_BLOCKING) && ((HAL_Timer_Get_Milli_Seconds() - t) > ms))
#define TIMEOUT(t, ms) ((ms != TIMEOUT_BLOCKING) && ((HAL_Timer_Get_Milli_Seconds() - t) > ms))
//! registration ok check helper
//! registration ok check helper
#define REG_OK(r) ((r == REG_HOME) || (r == REG_ROAMING))
#define REG_OK(r) ((r == REG_HOME) || (r == REG_ROAMING))
//! registration done check helper (no need to poll further)
//! registration done check helper (no need to poll further)
#define REG_DONE(r) ((r == REG_HOME) || (r == REG_ROAMING) || (r == REG_DENIED))
#define REG_DONE(r) ((r == REG_HOME) || (r == REG_ROAMING) || (r == REG_DENIED))
//! helper to make sure that lock unlock pair is always balanced
//! helper to make sure that lock unlock pair is always balanced
#define LOCK() std::lock_guard<std::recursive_mutex> __mdm_guard(mdm_mutex);
#define LOCK() std::lock_guard<std::recursive_mutex> __mdm_guard(mdm_mutex);
//! helper to make sure that lock unlock pair is always balanced
//! helper to make sure that lock unlock pair is always balanced
#define UNLOCK()
#define UNLOCK()
static volatile uint32_t gprs_timeout_start;
static volatile uint32_t gprs_timeout_start;
static volatile uint32_t gprs_timeout_duration;
static volatile uint32_t gprs_timeout_duration;
inline void ARM_GPRS_TIMEOUT(uint32_t dur) {
inline void ARM_GPRS_TIMEOUT(uint32_t dur) {
gprs_timeout_start = HAL_Timer_Get_Milli_Seconds();
gprs_timeout_start = HAL_Timer_Get_Milli_Seconds();
gprs_timeout_duration = dur;
gprs_timeout_duration = dur;
DEBUG("GPRS WD Set %d",(dur));
DEBUG("GPRS WD Set %d",(dur));
}
}
inline bool IS_GPRS_TIMEOUT() {
inline bool IS_GPRS_TIMEOUT() {
return gprs_timeout_duration && ((HAL_Timer_Get_Milli_Seconds()-gprs_timeout_start)>gprs_timeout_duration);
return gprs_timeout_duration && ((HAL_Timer_Get_Milli_Seconds()-gprs_timeout_start)>gprs_timeout_duration);
}
}
inline void CLR_GPRS_TIMEOUT() {
inline void CLR_GPRS_TIMEOUT() {
gprs_timeout_duration = 0;
gprs_timeout_duration = 0;
DEBUG("GPRS WD Cleared, was %d", gprs_timeout_duration);
DEBUG("GPRS WD Cleared, was %d", gprs_timeout_duration);
}
}
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
#if 0 // colored terminal output using ANSI escape sequences
#if 0 // colored terminal output using ANSI escape sequences
#define COL(c) "\033[" c
#define COL(c) "\033[" c
#else
#else
#define COL(c) ""
#define COL(c) ""
#endif
#endif
#define DEF COL("39m")
#define DEF COL("39m")
#define BLA COL("30m")
#define BLA COL("30m")
#define RED COL("31m")
#define RED COL("31m")
#define GRE COL("32m")
#define GRE COL("32m")
#define YEL COL("33m")
#define YEL COL("33m")
#define BLU COL("34m")
#define BLU COL("34m")
#define MAG COL("35m")
#define MAG COL("35m")
#define CYA COL("36m")
#define CYA COL("36m")
#define WHY COL("37m")
#define WHY COL("37m")
void dumpAtCmd(const char* buf, int len)
void dumpAtCmd(const char* buf, int len)
{
{
DEBUG_D(" %3d \"", len);
DEBUG_D(" %3d \"", len);
while (len --) {
while (len --) {
char ch = *buf++;
char ch = *buf++;
if ((ch > 0x1F) && (ch < 0x7F)) { // is printable
if ((ch > 0x1F) && (ch < 0x7F)) { // is printable
if (ch == '%') DEBUG_D("%%");
if (ch == '%') DEBUG_D("%%");
else if (ch == '"') DEBUG_D("\\\"");
else if (ch == '"') DEBUG_D("\\\"");
else if (ch == '\\') DEBUG_D("\\\\");
else if (ch == '\\') DEBUG_D("\\\\");
else DEBUG_D("%c", ch);
else DEBUG_D("%c", ch);
} else {
} else {
if (ch == '\a') DEBUG_D("\\a"); // BEL (0x07)
if (ch == '\a') DEBUG_D("\\a"); // BEL (0x07)
else if (ch == '\b') DEBUG_D("\\b"); // Backspace (0x08)
else if (ch == '\b') DEBUG_D("\\b"); // Backspace (0x08)
else if (ch == '\t') DEBUG_D("\\t"); // Horizontal Tab (0x09)
else if (ch == '\t') DEBUG_D("\\t"); // Horizontal Tab (0x09)
else if (ch == '\n') DEBUG_D("\\n"); // Linefeed (0x0A)
else if (ch == '\n') DEBUG_D("\\n"); // Linefeed (0x0A)
else if (ch == '\v') DEBUG_D("\\v"); // Vertical Tab (0x0B)
else if (ch == '\v') DEBUG_D("\\v"); // Vertical Tab (0x0B)
else if (ch == '\f') DEBUG_D("\\f"); // Formfeed (0x0C)
else if (ch == '\f') DEBUG_D("\\f"); // Formfeed (0x0C)
else if (ch == '\r') DEBUG_D("\\r"); // Carriage Return (0x0D)
else if (ch == '\r') DEBUG_D("\\r"); // Carriage Return (0x0D)
else DEBUG_D("\\x%02x", (unsigned char)ch);
else DEBUG_D("\\x%02x", (unsigned char)ch);
}
}
}
}
DEBUG_D("\"\r\n");
DEBUG_D("\"\r\n");
}
}
void MDMParser::_debugPrint(int level, const char* color, const char* format, ...)
void MDMParser::_debugPrint(int level, const char* color, const char* format, ...)
{
{
if (_debugLevel >= level)
if (_debugLevel >= level)
{
{
コピー
コピー済み
コピー
コピー済み
va_list args;
va_start (args, format);
if (color) DEBUG_D(color);
if (color) DEBUG_D(color);
コピー
コピー済み
コピー
コピー済み
DEBUG_D(
format, args);
va_list args;
va_start(args, format);
log_printf_v(LOG_LEVEL_TRACE, LOG_THIS_CATEGORY(), nullptr,
format, args);
va_end(args);
if (color) DEBUG_D(DEF);
if (color) DEBUG_D(DEF);
コピー
コピー済み
コピー
コピー済み
va_end (args);
DEBUG_D("\r\n");
DEBUG_D("\r\n");
}
}
}
}
コピー
コピー済み
コピー
コピー済み
// Warning: Do not use these for anything other than constant char messages,
// they will yield incorrect values for integers. Use DEBUG_D() instead.
#define MDM_ERROR(
_fmt,
...) do {_debugPrint(0, RED,
_fmt, ##
__VA_ARGS__);}while(0)
#define MDM_ERROR(
...) do {_debugPrint(0, RED,
__VA_ARGS__);}while(0)
#define MDM_INFO(
_fmt,
...) do {_debugPrint(1, GRE,
_fmt, ##
__VA_ARGS__);}while(0)
#define MDM_INFO(
...) do {_debugPrint(1, GRE,
__VA_ARGS__);}while(0)
#define MDM_TRACE(
_fmt,
...) do {_debugPrint(2, DEF,
_fmt, ##
__VA_ARGS__);}while(0)
#define MDM_TRACE(
...) do {_debugPrint(2, DEF,
__VA_ARGS__);}while(0)
#define MDM_TEST(
_fmt,
...) do {_debugPrint(3, CYA,
_fmt, ##
__VA_ARGS__);}while(0)
#define MDM_TEST(
...) do {_debugPrint(3, CYA,
__VA_ARGS__);}while(0)
#else
#else
#define MDM_ERROR(...) // no tracing
#define MDM_ERROR(...) // no tracing
#define MDM_TEST(...) // no tracing
#define MDM_TEST(...) // no tracing
#define MDM_INFO(...) // no tracing
#define MDM_INFO(...) // no tracing
#define MDM_TRACE(...) // no tracing
#define MDM_TRACE(...) // no tracing
#endif
#endif
/* Private variables --------------------------------------------------------*/
/* Private variables --------------------------------------------------------*/
MDMParser* MDMParser::inst;
MDMParser* MDMParser::inst;
/* Extern variables ---------------------------------------------------------*/
/* Extern variables ---------------------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
/* Private function prototypes ----------------------------------------------*/
MDMParser::MDMParser(void)
MDMParser::MDMParser(void)
{
{
inst = this;
inst = this;
memset(&_dev, 0, sizeof(_dev));
memset(&_dev, 0, sizeof(_dev));
memset(&_net, 0, sizeof(_net));
memset(&_net, 0, sizeof(_net));
_net.lac = 0xFFFF;
_net.lac = 0xFFFF;
_net.ci = 0xFFFFFFFF;
_net.ci = 0xFFFFFFFF;
_ip = NOIP;
_ip = NOIP;
_init = false;
_init = false;
_pwr = false;
_pwr = false;
_activated = false;
_activated = false;
_attached = false;
_attached = false;
_attached_urc = false; // updated by GPRS detached/attached URC,
_attached_urc = false; // updated by GPRS detached/attached URC,
// used to notify system of prolonged GPRS detach.
// used to notify system of prolonged GPRS detach.
_power_mode = 1; // default power mode is AT+UPSV=1
_power_mode = 1; // default power mode is AT+UPSV=1
_cancel_all_operations = false;
_cancel_all_operations = false;
sms_cb = NULL;
sms_cb = NULL;
memset(_sockets, 0, sizeof(_sockets));
memset(_sockets, 0, sizeof(_sockets));
for (int socket = 0; socket < NUMSOCKETS; socket ++)
for (int socket = 0; socket < NUMSOCKETS; socket ++)
_sockets[socket].handle = MDM_SOCKET_ERROR;
_sockets[socket].handle = MDM_SOCKET_ERROR;
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
_debugLevel = 3;
_debugLevel = 3;
_debugTime = HAL_Timer_Get_Milli_Seconds();
_debugTime = HAL_Timer_Get_Milli_Seconds();
#endif
#endif
}
}
void MDMParser::setPowerMode(int mode) {
void MDMParser::setPowerMode(int mode) {
_power_mode = mode;
_power_mode = mode;
}
}
void MDMParser::cancel(void) {
void MDMParser::cancel(void) {
MDM_INFO("\r\n[ Modem::cancel ] = = = = = = = = = = = = = = =");
MDM_INFO("\r\n[ Modem::cancel ] = = = = = = = = = = = = = = =");
_cancel_all_operations = true;
_cancel_all_operations = true;
}
}
void MDMParser::resume(void) {
void MDMParser::resume(void) {
MDM_INFO("\r\n[ Modem::resume ] = = = = = = = = = = = = = = =");
MDM_INFO("\r\n[ Modem::resume ] = = = = = = = = = = = = = = =");
_cancel_all_operations = false;
_cancel_all_operations = false;
}
}
void MDMParser::setSMSreceivedHandler(_CELLULAR_SMS_CB cb, void* data) {
void MDMParser::setSMSreceivedHandler(_CELLULAR_SMS_CB cb, void* data) {
sms_cb = cb;
sms_cb = cb;
sms_data = data;
sms_data = data;
}
}
void MDMParser::SMSreceived(int index) {
void MDMParser::SMSreceived(int index) {
sms_cb(sms_data, index); // call the SMS callback with the index of the new SMS
sms_cb(sms_data, index); // call the SMS callback with the index of the new SMS
}
}
int MDMParser::send(const char* buf, int len)
int MDMParser::send(const char* buf, int len)
{
{
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
if (_debugLevel >= 3) {
if (_debugLevel >= 3) {
DEBUG_D("%10.3f AT send ", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001);
DEBUG_D("%10.3f AT send ", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001);
dumpAtCmd(buf,len);
dumpAtCmd(buf,len);
}
}
#endif
#endif
return _send(buf, len);
return _send(buf, len);
}
}
int MDMParser::sendFormated(const char* format, ...) {
int MDMParser::sendFormated(const char* format, ...) {
va_list args;
va_list args;
va_start(args, format);
va_start(args, format);
const int ret = sendFormattedWithArgs(format, args);
const int ret = sendFormattedWithArgs(format, args);
va_end(args);
va_end(args);
return ret;
return ret;
}
}
int MDMParser::sendFormattedWithArgs(const char* format, va_list args) {
int MDMParser::sendFormattedWithArgs(const char* format, va_list args) {
if (_cancel_all_operations) {
if (_cancel_all_operations) {
return 0;
return 0;
}
}
va_list argsCopy;
va_list argsCopy;
va_copy(argsCopy, args);
va_copy(argsCopy, args);
char buf[128];
char buf[128];
int n = vsnprintf(buf, sizeof(buf), format, args);
int n = vsnprintf(buf, sizeof(buf), format, args);
if (n >= 0) {
if (n >= 0) {
if ((size_t)n < sizeof(buf)) {
if ((size_t)n < sizeof(buf)) {
n = send(buf, n);
n = send(buf, n);
} else {
} else {
char buf[n + 1]; // Use larger buffer
char buf[n + 1]; // Use larger buffer
n = vsnprintf(buf, sizeof(buf), format, argsCopy);
n = vsnprintf(buf, sizeof(buf), format, argsCopy);
if (n >= 0) {
if (n >= 0) {
n = send(buf, n);
n = send(buf, n);
}
}
}
}
}
}
va_end(argsCopy);
va_end(argsCopy);
return n;
return n;
}
}
int MDMParser::waitFinalResp(_CALLBACKPTR cb /* = NULL*/,
int MDMParser::waitFinalResp(_CALLBACKPTR cb /* = NULL*/,
void* param /* = NULL*/,
void* param /* = NULL*/,
system_tick_t timeout_ms /*= 5000*/)
system_tick_t timeout_ms /*= 5000*/)
{
{
if (_cancel_all_operations) return WAIT;
if (_cancel_all_operations) return WAIT;
// If we went from a GPRS attached state to detached via URC,
// If we went from a GPRS attached state to detached via URC,
// a WDT was set and now expired. Notify system of disconnect.
// a WDT was set and now expired. Notify system of disconnect.
if (IS_GPRS_TIMEOUT()) {
if (IS_GPRS_TIMEOUT()) {
_ip = NOIP;
_ip = NOIP;
_attached = false;
_attached = false;
CLR_GPRS_TIMEOUT();
CLR_GPRS_TIMEOUT();
// HAL_NET_notify_dhcp(false);
// HAL_NET_notify_dhcp(false);
HAL_NET_notify_disconnected();
HAL_NET_notify_disconnected();
}
}
char buf[MAX_SIZE + 64 /* add some more space for framing */];
char buf[MAX_SIZE + 64 /* add some more space for framing */];
system_tick_t start = HAL_Timer_Get_Milli_Seconds();
system_tick_t start = HAL_Timer_Get_Milli_Seconds();
do {
do {
int ret = getLine(buf, sizeof(buf));
int ret = getLine(buf, sizeof(buf));
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
if ((_debugLevel >= 3) && (ret != WAIT) && (ret != NOT_FOUND))
if ((_debugLevel >= 3) && (ret != WAIT) && (ret != NOT_FOUND))
{
{
int len = LENGTH(ret);
int len = LENGTH(ret);
int type = TYPE(ret);
int type = TYPE(ret);
const char* s = (type == TYPE_UNKNOWN)? YEL "UNK" DEF :
const char* s = (type == TYPE_UNKNOWN)? YEL "UNK" DEF :
(type == TYPE_TEXT) ? MAG "TXT" DEF :
(type == TYPE_TEXT) ? MAG "TXT" DEF :
(type == TYPE_OK ) ? GRE "OK " DEF :
(type == TYPE_OK ) ? GRE "OK " DEF :
(type == TYPE_ERROR) ? RED "ERR" DEF :
(type == TYPE_ERROR) ? RED "ERR" DEF :
(type == TYPE_ABORTED) ? RED "ABT" DEF :
(type == TYPE_ABORTED) ? RED "ABT" DEF :
(type == TYPE_PLUS) ? CYA " + " DEF :
(type == TYPE_PLUS) ? CYA " + " DEF :
(type == TYPE_PROMPT) ? BLU " > " DEF :
(type == TYPE_PROMPT) ? BLU " > " DEF :
"..." ;
"..." ;
DEBUG_D("%10.3f AT read %s", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001, s);
DEBUG_D("%10.3f AT read %s", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001, s);
dumpAtCmd(buf, len);
dumpAtCmd(buf, len);
(void)s;
(void)s;
}
}
#endif
#endif
if ((ret != WAIT) && (ret != NOT_FOUND))
if ((ret != WAIT) && (ret != NOT_FOUND))
{
{
int type = TYPE(ret);
int type = TYPE(ret);
// handle unsolicited commands here
// handle unsolicited commands here
if (type == TYPE_PLUS) {
if (type == TYPE_PLUS) {
const char* cmd = buf+3;
const char* cmd = buf+3;
int a, b, c, d, r;
int a, b, c, d, r;
char s[32];
char s[32];
// SMS Command ---------------------------------
// SMS Command ---------------------------------
// +CNMI: <mem>,<index>
// +CNMI: <mem>,<index>
if (sscanf(cmd, "CMTI: \"%*[^\"]\",%d", &a) == 1) {
if (sscanf(cmd, "CMTI: \"%*[^\"]\",%d", &a) == 1) {
DEBUG_D("New SMS at index %d\r\n", a);
DEBUG_D("New SMS at index %d\r\n", a);
if (sms_cb) SMSreceived(a);
if (sms_cb) SMSreceived(a);
}
}
else if ((sscanf(cmd, "CIEV: 9,%d", &a) == 1)) {
else if ((sscanf(cmd, "CIEV: 9,%d", &a) == 1)) {
DEBUG_D("CIEV matched: 9,%d\r\n", a);
DEBUG_D("CIEV matched: 9,%d\r\n", a);
// Wait until the system is attached before attempting to act on GPRS detach
// Wait until the system is attached before attempting to act on GPRS detach
if (_attached) {
if (_attached) {
_attached_urc = (a==2)?1:0;
_attached_urc = (a==2)?1:0;
if (!_attached_urc) ARM_GPRS_TIMEOUT(15*1000); // If detached, set WDT
if (!_attached_urc) ARM_GPRS_TIMEOUT(15*1000); // If detached, set WDT
else CLR_GPRS_TIMEOUT(); // else if re-attached clear WDT.
else CLR_GPRS_TIMEOUT(); // else if re-attached clear WDT.
}
}
// Socket Specific Command ---------------------------------
// Socket Specific Command ---------------------------------
// +USORD: <socket>,<length>
// +USORD: <socket>,<length>
} else if ((sscanf(cmd, "USORD: %d,%d", &a, &b) == 2)) {
} else if ((sscanf(cmd, "USORD: %d,%d", &a, &b) == 2)) {
int socket = _findSocket(a);
int socket = _findSocket(a);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
if (socket != MDM_SOCKET_ERROR)
if (socket != MDM_SOCKET_ERROR)
_sockets[socket].pending = b;
_sockets[socket].pending = b;
// +UUSORD: <socket>,<length>
// +UUSORD: <socket>,<length>
} else if ((sscanf(cmd, "UUSORD: %d,%d", &a, &b) == 2)) {
} else if ((sscanf(cmd, "UUSORD: %d,%d", &a, &b) == 2)) {
int socket = _findSocket(a);
int socket = _findSocket(a);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
if (socket != MDM_SOCKET_ERROR)
if (socket != MDM_SOCKET_ERROR)
_sockets[socket].pending = b;
_sockets[socket].pending = b;
// +USORF: <socket>,<length>
// +USORF: <socket>,<length>
} else if ((sscanf(cmd, "USORF: %d,%d", &a, &b) == 2)) {
} else if ((sscanf(cmd, "USORF: %d,%d", &a, &b) == 2)) {
int socket = _findSocket(a);
int socket = _findSocket(a);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
if (socket != MDM_SOCKET_ERROR)
if (socket != MDM_SOCKET_ERROR)
_sockets[socket].pending = b;
_sockets[socket].pending = b;
// +UUSORF: <socket>,<length>
// +UUSORF: <socket>,<length>
} else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2)) {
} else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2)) {
int socket = _findSocket(a);
int socket = _findSocket(a);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b);
if (socket != MDM_SOCKET_ERROR)
if (socket != MDM_SOCKET_ERROR)
_sockets[socket].pending = b;
_sockets[socket].pending = b;
// +UUSOCL: <socket>
// +UUSOCL: <socket>
} else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1)) {
} else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1)) {
int socket = _findSocket(a);
int socket = _findSocket(a);
DEBUG_D("Socket %d: handle %d closed by remote host\r\n", socket, a);
DEBUG_D("Socket %d: handle %d closed by remote host\r\n", socket, a);
if (socket != MDM_SOCKET_ERROR) {
if (socket != MDM_SOCKET_ERROR) {
_socketFree(socket);
_socketFree(socket);
}
}
}
}
// GSM/UMTS Specific -------------------------------------------
// GSM/UMTS Specific -------------------------------------------
// +UUPSDD: <profile_id>
// +UUPSDD: <profile_id>
if (sscanf(cmd, "UUPSDD: %s", s) == 1) {
if (sscanf(cmd, "UUPSDD: %s", s) == 1) {
DEBUG_D("UUPSDD: %s matched\r\n", PROFILE);
DEBUG_D("UUPSDD: %s matched\r\n", PROFILE);
if ( !strcmp(s, PROFILE) ) {
if ( !strcmp(s, PROFILE) ) {
_ip = NOIP;
_ip = NOIP;
_attached = false;
_attached = false;
DEBUG("PDP context deactivated remotely!\r\n");
DEBUG("PDP context deactivated remotely!\r\n");
// PDP context was remotely deactivated via URC,
// PDP context was remotely deactivated via URC,
// Notify system of disconnect.
// Notify system of disconnect.
HAL_NET_notify_dhcp(false);
HAL_NET_notify_dhcp(false);
}
}
} else {
} else {
// +CREG|CGREG: <n>,<stat>[,<lac>,<ci>[,AcT[,<rac>]]] // reply to AT+CREG|AT+CGREG
// +CREG|CGREG: <n>,<stat>[,<lac>,<ci>[,AcT[,<rac>]]] // reply to AT+CREG|AT+CGREG
// +CREG|CGREG: <stat>[,<lac>,<ci>[,AcT[,<rac>]]] // URC
// +CREG|CGREG: <stat>[,<lac>,<ci>[,AcT[,<rac>]]] // URC
b = (int)0xFFFF; c = (int)0xFFFFFFFF; d = -1;
b = (int)0xFFFF; c = (int)0xFFFFFFFF; d = -1;
r = sscanf(cmd, "%s %*d,%d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d);
r = sscanf(cmd, "%s %*d,%d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d);
if (r <= 1)
if (r <= 1)
r = sscanf(cmd, "%s %d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d);
r = sscanf(cmd, "%s %d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d);
if (r >= 2) {
if (r >= 2) {
Reg *reg = !strcmp(s, "CREG:") ? &_net.csd :
Reg *reg = !strcmp(s, "CREG:") ? &_net.csd :
コピー
コピー済み
コピー
コピー済み
!strcmp(s, "CGREG:") ? &_net.psd :
NULL;
!strcmp(s, "CGREG:") ? &_net.psd :
!strcmp(s, "CEREG:") ? &_net.eps :
NULL;
if (reg) {
if (reg) {
// network status
// network status
if (a == 0) *reg = REG_NONE; // 0: not registered, home network
if (a == 0) *reg = REG_NONE; // 0: not registered, home network
else if (a == 1) *reg = REG_HOME; // 1: registered, home network
else if (a == 1) *reg = REG_HOME; // 1: registered, home network
else if (a == 2) *reg = REG_NONE; // 2: not registered, but MT is currently searching a new operator to register to
else if (a == 2) *reg = REG_NONE; // 2: not registered, but MT is currently searching a new operator to register to
else if (a == 3) *reg = REG_DENIED; // 3: registration denied
else if (a == 3) *reg = REG_DENIED; // 3: registration denied
else if (a == 4) *reg = REG_UNKNOWN; // 4: unknown
else if (a == 4) *reg = REG_UNKNOWN; // 4: unknown
else if (a == 5) *reg = REG_ROAMING; // 5: registered, roaming
else if (a == 5) *reg = REG_ROAMING; // 5: registered, roaming
if ((r >= 3) && (b != (int)0xFFFF)) _net.lac = b; // location area code
if ((r >= 3) && (b != (int)0xFFFF)) _net.lac = b; // location area code
if ((r >= 4) && (c != (int)0xFFFFFFFF)) _net.ci = c; // cell ID
if ((r >= 4) && (c != (int)0xFFFFFFFF)) _net.ci = c; // cell ID
// access technology
// access technology
if (r >= 5) {
if (r >= 5) {
if (d == 0) _net.act = ACT_GSM; // 0: GSM
if (d == 0) _net.act = ACT_GSM; // 0: GSM
else if (d == 1) _net.act = ACT_GSM; // 1: GSM COMPACT
else if (d == 1) _net.act = ACT_GSM; // 1: GSM COMPACT
else if (d == 2) _net.act = ACT_UTRAN; // 2: UTRAN
else if (d == 2) _net.act = ACT_UTRAN; // 2: UTRAN
else if (d == 3) _net.act = ACT_EDGE; // 3: GSM with EDGE availability
else if (d == 3) _net.act = ACT_EDGE; // 3: GSM with EDGE availability
else if (d == 4) _net.act = ACT_UTRAN; // 4: UTRAN with HSDPA availability
else if (d == 4) _net.act = ACT_UTRAN; // 4: UTRAN with HSDPA availability
else if (d == 5) _net.act = ACT_UTRAN; // 5: UTRAN with HSUPA availability
else if (d == 5) _net.act = ACT_UTRAN; // 5: UTRAN with HSUPA availability
else if (d == 6) _net.act = ACT_UTRAN; // 6: UTRAN with HSDPA and HSUPA availability
else if (d == 6) _net.act = ACT_UTRAN; // 6: UTRAN with HSDPA and HSUPA availability
コピー
コピー済み
コピー
コピー済み
else if (d == 7) _net.act = ACT_LTE; // 7: LTE
else if (d == 8) _net.act = ACT_LTE_CAT_M1; // 8: LTE Cat M1
else if (d == 9) _net.act = ACT_LTE_CAT_NB1; // 9: LTE Cat NB1
}
}
}
}
}
}
}
}
} // end ==TYPE_PLUS
} // end ==TYPE_PLUS
if (cb) {
if (cb) {
int len = LENGTH(ret);
int len = LENGTH(ret);
int ret = cb(type, buf, len, param);
int ret = cb(type, buf, len, param);
if (WAIT != ret)
if (WAIT != ret)
return ret;
return ret;
}
}
if (type == TYPE_OK)
if (type == TYPE_OK)
return RESP_OK;
return RESP_OK;
if (type == TYPE_ERROR)
if (type == TYPE_ERROR)
return RESP_ERROR;
return RESP_ERROR;
if (type == TYPE_PROMPT)
if (type == TYPE_PROMPT)
return RESP_PROMPT;
return RESP_PROMPT;
if (type == TYPE_ABORTED)
if (type == TYPE_ABORTED)
return RESP_ABORTED; // This means the current command was ABORTED, so retry your command if critical.
return RESP_ABORTED; // This means the current command was ABORTED, so retry your command if critical.
}
}
// relax a bit
// relax a bit
HAL_Delay_Milliseconds(10);
HAL_Delay_Milliseconds(10);
}
}
while (!TIMEOUT(start, timeout_ms) && !_cancel_all_operations);
while (!TIMEOUT(start, timeout_ms) && !_cancel_all_operations);
return WAIT;
return WAIT;
}
}
int MDMParser::sendCommandWithArgs(const char* fmt, va_list args, _CALLBACKPTR cb, void* param, system_tick_t timeout)
int MDMParser::sendCommandWithArgs(const char* fmt, va_list args, _CALLBACKPTR cb, void* param, system_tick_t timeout)
{
{
LOCK();
LOCK();
sendFormattedWithArgs(fmt, args);
sendFormattedWithArgs(fmt, args);
const int ret = waitFinalResp(cb, param, timeout);
const int ret = waitFinalResp(cb, param, timeout);
UNLOCK();
UNLOCK();
return ret;
return ret;
}
}
void MDMParser::lock()
void MDMParser::lock()
{
{
mdm_mutex.lock();
mdm_mutex.lock();
}
}
void MDMParser::unlock()
void MDMParser::unlock()
{
{
mdm_mutex.unlock();
mdm_mutex.unlock();
}
}
int MDMParser::_cbString(int type, const char* buf, int len, char* str)
int MDMParser::_cbString(int type, const char* buf, int len, char* str)
{
{
if (str && (type == TYPE_UNKNOWN)) {
if (str && (type == TYPE_UNKNOWN)) {
if (sscanf(buf, "\r\n%s\r\n", str) == 1)
if (sscanf(buf, "\r\n%s\r\n", str) == 1)
/*nothing*/;
/*nothing*/;
}
}
return WAIT;
return WAIT;
}
}
int MDMParser::_cbInt(int type, const char* buf, int len, int* val)
int MDMParser::_cbInt(int type, const char* buf, int len, int* val)
{
{
if (val && (type == TYPE_UNKNOWN)) {
if (val && (type == TYPE_UNKNOWN)) {
if (sscanf(buf, "\r\n%d\r\n", val) == 1)
if (sscanf(buf, "\r\n%d\r\n", val) == 1)
/*nothing*/;
/*nothing*/;
}
}
return WAIT;
return WAIT;
}
}
// ----------------------------------------------------------------
// ----------------------------------------------------------------
bool MDMParser::connect(
bool MDMParser::connect(
コピー
コピー済み
コピー
コピー済み
const char* simpin,
const char* apn, const char* username,
const char* apn, const char* username,
const char* password, Auth auth)
const char* password, Auth auth)
{
{
コピー
コピー済み
コピー
コピー済み
bool ok =
powerOn(simpin
);
bool ok =
registerNet(apn
);
if (!ok)
/*
return false;
ok = init();
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
コピー
コピー済み
コピー
コピー済み
if (_debugLevel >= 1)
dump
Dev
Status(&_
dev);
if (_debugLevel >= 1)
{
dump
Net
Status(&_
net);
}
#endif
#endif
コピー
コピー済み
コピー
コピー済み
if (!ok)
*/
if (!ok)
{
return false;
return false;
コピー
コピー済み
コピー
コピー済み
ok =
registerNet();
}
ok =
pdp(apn);
/*
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
コピー
コピー済み
コピー
コピー済み
if (_debugLevel >= 1)
dumpNetStatus(&_net);
if (_debugLevel >= 1)
{
dumpNetStatus(&_net);
}
#endif
#endif
コピー
コピー済み
コピー
コピー済み
if (!ok)
*/
if (!ok)
{
return false;
return false;
コピー
コピー済み
コピー
コピー済み
MDM_IP ip = join(apn,
username,
password,
auth);
}
const
MDM_IP ip = join(apn,
username,
password,
auth);
/*
#ifdef MDM_DEBUG
#ifdef MDM_DEBUG
コピー
コピー済み
コピー
コピー済み
if (_debugLevel >= 1)
dumpIp(ip);
if (_debugLevel >= 1)
{
dumpIp(ip);
}
#endif
#endif
コピー
コピー済み
コピー
コピー済み
if (ip == NOIP)
*/
if (ip == NOIP)
{
return false;
}
HAL_NET_notify_connected();
HAL_NET_notify_dhcp(true);
return true;
}
bool MDMParser::disconnect() {
if (!deactivate()) {
return false;
}
if (!detach()) {
return false;
return false;
コピー
コピー済み
コピー
コピー済み
}
HAL_NET_notify_disconnected();
return true;
return true;
}
}
void MDMParser::reset(void)
void MDMParser::reset(void)
{
{
MDM_INFO("[ Modem reset ]");
MDM_INFO("[ Modem reset ]");
コピー
コピー済み
コピー
コピー済み
unsigned delay = 100;
if (_dev.dev == DEV_UNKNOWN || _dev.dev == DEV_SARA_R410) {
delay = 10000; // SARA-R4: 10s
}
HAL_GPIO_Write(RESET_UC, 0);
HAL_GPIO_Write(RESET_UC, 0);
コピー
コピー済み
コピー
コピー済み
HAL_Delay_Milliseconds(
100
);
HAL_Delay_Milliseconds(
delay
);
HAL_GPIO_Write(RESET_UC, 1);
HAL_GPIO_Write(RESET_UC, 1);
}
}
bool MDMParser::_powerOn(void)
bool MDMParser::_powerOn(void)
{
{
LOCK();
LOCK();
/* Initialize I/O */
/* Initialize I/O */
STM32_Pin_Info* PIN_MAP_PARSER = HAL_Pin_Map();
STM32_Pin_Info* PIN_MAP_PARSER = HAL_Pin_Map();
// This pin tends to stay low when floating on the output of the buffer (PWR_UB)
// This pin tends to stay low when floating on the output of the buffer (PWR_UB)
// It shouldn't hurt if it goes low temporarily on STM32 boot, but strange behavior
// It shouldn't hurt if it goes low temporarily on STM32 boot, but strange behavior
// was noticed when it was left to do whatever it wanted. By adding a 100k pull up
// was noticed when it was left to do whatever it wanted. By adding a 100k pull up
// resistor all flakey behavior has ceased (i.e., the modem had previously stopped
// resistor all flakey behavior has ceased (i.e., the modem had previously stopped
// responding to AT commands). This is how we set it HIGH before enabling the OUTPUT.
// responding to AT commands). This is how we set it HIGH before enabling the OUTPUT.
PIN_MAP_PARSER[PWR_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[PWR_UC].gpio_pin;
PIN_MAP_PARSER[PWR_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[PWR_UC].gpio_pin;
HAL_Pin_Mode(PWR_UC, OUTPUT);
HAL_Pin_Mode(PWR_UC, OUTPUT);
// This pin tends to stay high when floating on the output of the buffer (RESET_UB),
// This pin tends to stay high when floating on the output of the buffer (RESET_UB),
// but we need to ensure it gets set high before being set to an OUTPUT.
// but we need to ensure it gets set high before being set to an OUTPUT.
// If this pin goes LOW, the modem will be reset and all configuration will be lost.
// If this pin goes LOW, the modem will be reset and all configuration will be lost.
PIN_MAP_PARSER[RESET_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[RESET_UC].gpio_pin;
PIN_MAP_PARSER[RESET_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[RESET_UC].gpio_pin;
HAL_Pin_Mode(RESET_UC, OUTPUT);
HAL_Pin_Mode(RESET_UC, OUTPUT);
コピー
コピー済み
コピー
コピー済み
#if USE_USART3_HARDWARE_FLOW_CONTROL_RTS_CTS
_dev.dev = DEV_UNKNOWN;
_dev.lpm = LPM_ENABLED;
_dev.lpm = LPM_ENABLED;
コピー
コピー済み
コピー
コピー済み
#else
HAL_Pin_Mode(RTS_UC, OUTPUT);
HAL_GPIO_Write(RTS_UC, 0); // VERY IMPORTANT FOR CORRECT OPERATION W/O HW FLOW CONTROL!!
#endif
HAL_Pin_Mode(LVLOE_UC, OUTPUT);
HAL_Pin_Mode(LVLOE_UC, OUTPUT);
HAL_GPIO_Write(LVLOE_UC, 0);
HAL_GPIO_Write(LVLOE_UC, 0);
if (!_init) {
if (!_init) {
MDM_INFO("[ ElectronSerialPipe::begin ] = = = = = = = =");
MDM_INFO("[ ElectronSerialPipe::begin ] = = = = = = = =");
コピー
コピー済み
コピー
コピー済み
/
* Instantiate
the U
S
ART
3
hardware
*/
/
/ Here we initialize
the U
ART
with
hardware
flow control enabled, even though some of
electronMDM.begin(115200
);
// the modems don't support it (SARA-R4 at the time of writing). It is assumed that the
// modem still keeps the CTS pin in a correct state even if doesn't support the CTS/RTS
/*
Initialize only once
*/
// flow control
electronMDM.begin(115200
, true
/*
hwFlowControl
*/
);
_init = true;
_init = true;
}
}
MDM_INFO("\r\n[ Modem::powerOn ] = = = = = = = = = = = = = =");
MDM_INFO("\r\n[ Modem::powerOn ] = = = = = = = = = = = = = =");
bool continue_cancel = false;
bool continue_cancel = false;
bool retried_after_reset = false;
bool retried_after_reset = false;
int i = 10;
int i = 10;
while (i--) {
while (i--) {
// SARA-U2/LISA-U2 50..80us
// SARA-U2/LISA-U2 50..80us
HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(50);
HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(50);
HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(10);
HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(10);
コピー
コピー済み
コピー
コピー済み
// SARA-G35 >5ms, LISA-C2 > 150ms, LEON-G2 >5ms
// SARA-G35 >5ms, LISA-C2 > 150ms, LEON-G2 >5ms
, SARA-R4 >= 150ms
HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(150);
HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(150);
HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(100);
HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(100);
// purge any messages
// purge any messages
purge();
purge();
// Save desire to cancel, but since we are already here
// Save desire to cancel, but since we are already here
// trying to power up the modem when we received a cancel
// trying to power up the modem when we received a cancel
// resume AT parser to ensure it's ready to receive
// resume AT parser to ensure it's ready to receive
// power down commands.
// power down commands.
if (_cancel_all_operations) {
if (_cancel_all_operations) {
continue_cancel = true;
continue_cancel = true;
resume(); // make sure we can talk to the modem
resume(); // make sure we can talk to the modem
}
}
// check interface
// check interface
sendFormated("AT\r\n");
sendFormated("AT\r\n");
int r = waitFinalResp(NULL,NULL,1000);
int r = waitFinalResp(NULL,NULL,1000);
if(RESP_OK == r) {
if(RESP_OK == r) {
_pwr = true;
_pwr = true;
break;
break;
}
}
else if (i==0 && !retried_after_reset) {
else if (i==0 && !retried_after_reset) {
retried_after_reset = true; // only perform reset & retry sequence once
retried_after_reset = true; // only perform reset & retry sequence once
i = 10;
i = 10;
reset();
reset();
}
}
}
}
if (i < 0) {
if (i < 0) {
MDM_ERROR("[ No Reply from Modem ]\r\n");
MDM_ERROR("[ No Reply from Modem ]\r\n");
コピー
コピー済み
コピー
コピー済み
} else {
// Determine type of the modem
sendFormated("AT+CGMM\r\n");
waitFinalResp(_cbCGMM, &_dev);
if (_dev.dev == DEV_SARA_R410) {
// SARA-R410 doesn't support hardware flow control, reinitialize the UART
electronMDM.begin(115200, false /* hwFlowControl */);
// Power saving modes defined by the +UPSV command are not supported
_dev.lpm = LPM_DISABLED;
}
}
}
if (continue_cancel) {
if (continue_cancel) {
cancel();
cancel();
goto failure;
goto failure;
}
}
// Flush any on-boot URCs that can cause syncing issues later
// Flush any on-boot URCs that can cause syncing issues later
waitFinalResp(NULL,NULL,200);
waitFinalResp(NULL,NULL,200);
// echo off
// echo off
sendFormated("AT E0\r\n");
sendFormated("AT E0\r\n");
if(RESP_OK != waitFinalResp())
if(RESP_OK != waitFinalResp())
goto failure;
goto failure;
// enable verbose error messages
// enable verbose error messages
sendFormated("AT+CMEE=2\r\n");
sendFormated("AT+CMEE=2\r\n");
if(RESP_OK != waitFinalResp())
if(RESP_OK != waitFinalResp())
goto failure;
goto failure;
// Configures sending of URCs from MT to DTE for indications
// Configures sending of URCs from MT to DTE for indications
sendFormated("AT+CMER=1,0,0,2,1\r\n");
sendFormated("AT+CMER=1,0,0,2,1\r\n");
if(RESP_OK != waitFinalResp())
if(RESP_OK != waitFinalResp())
goto failure;
goto failure;
// set baud rate
// set baud rate
sendFormated("AT+IPR=115200\r\n");
sendFormated("AT+IPR=115200\r\n");
if (RESP_OK != waitFinalResp())
if (RESP_OK != waitFinalResp())
goto failure;
goto failure;
// wait some time until baudrate is applied
// wait some time until baudrate is applied
HAL_Delay_Milliseconds(100); // SARA-G > 40ms
HAL_Delay_Milliseconds(100); // SARA-G > 40ms
UNLOCK();
UNLOCK();
return true;
return true;
failure:
failure:
UNLOCK();
UNLOCK();
return false;
return false;
}
}
bool MDMParser::powerOn(const char* simpin)
bool MDMParser::powerOn(const char* simpin)
{
{
LOCK();
LOCK();
memset(&_dev, 0, sizeof(_dev));
memset(&_dev, 0, sizeof(_dev));
bool retried_after_reset = false;
bool retried_after_reset = false;
/* Power on the modem and perform basic initialization */
/* Power on the modem and perform basic initialization */
if (!_powerOn())
if (!_powerOn())
goto failure;
goto failure;
/* The ATI command is undocumented, and in practice the response
/* The ATI command is undocumented, and in practice the response
* time varies greatly. On inital power-on of the module, ATI
* time varies greatly. On inital power-on of the module, ATI
* will respond with "OK" before a device type number, which
* will respond with "OK" before a device type number, which
* requires wasting time in a for() loop to solve.
* requires wasting time in a for() loop to solve.
* Instead, use AT+CGMM and _dev.model for future use of module identification.
* Instead, use AT+CGMM and _dev.model for future use of module identification.
*
*
* identify the module
* identify the module
* sendFormated("ATI\r\n");
* sendFormated("ATI\r\n");
* if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev))
* if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev))
* goto failure;
* goto failure;
* if (_dev.dev == DEV_UNKNOWN)
* if (_dev.dev == DEV_UNKNOWN)
* goto failure;
* goto failure;
*/
*/
// check the sim card
// check the sim card
for (int i = 0; (i < 5) && (_dev.sim != SIM_READY) && !_cancel_all_operations; i++) {
for (int i = 0; (i < 5) && (_dev.sim != SIM_READY) && !_cancel_all_operations; i++) {
コピー
コピー済み
コピー
コピー済み
sendFormated("AT+
CPIN?\r\n");
sendFormated("AT+
int ret = waitFinalResp(_cbCPIN, &_dev.sim);
// having an error here is ok (sim may still be initializing)
if ((RESP_OK != ret) && (RESP_ERROR != ret)) {
goto failure;
}
else if (i==4 && (RESP_OK != ret) && !retried_after_reset) {
retried_after_reset = true; // only perform reset & retry sequence once
i = 0;
if(!powerOff())
reset();
/* Power on the modem and perform basic initialization again */
if (!_powerOn())
goto failure;
}
// Enter PIN if needed
if (_dev.sim == SIM_PIN) {
if (!simpin) {
MDM_ERROR("SIM PIN not available\r\n");
goto failure;
}
sendFormated("AT+CPIN=%s\r\n", simpin);
if (RESP_OK != waitFinalResp(_cbCPIN, &_dev.sim))
goto failure;
} else if (_dev.sim != SIM_READY) {
// wait for up to one second while looking for slow "+CPIN: READY" URCs
waitFinalResp(_cbCPIN, &_dev.sim, 1000);
}
}
if (_dev.sim != SIM_READY) {
if (_dev.sim == SIM_MISSING) {
MDM_ERROR("SIM not inserted\r\n");
}
goto failure;
}
UNLOCK();
return true;
failure:
if (_cancel_all_operations) {
// fake out the has_credentials() function so we don't end up in listening mode
_dev.sim = SIM_READY;
// return true to prevent from entering Listening Mode
// UNLOCK();
// return true;
}
UNLOCK();
return false;
}
bool MDMParser::init(DevStatus* status)
{
LOCK();
MDM_INFO("\r\n[ Modem::init ] = = = = = = = = = = = = = = =");
// Returns the product serial number, IMEI (International Mobile Equipment Identity)
sendFormated("AT+CGSN\r\n");
i
保存された差分
原文
ファイルを開く
/* ****************************************************************************** * Copyright (c) 2015 Particle Industries, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see <http://www.gnu.org/licenses/>. ****************************************************************************** */ #ifndef HAL_CELLULAR_EXCLUDE /* Includes -----------------------------------------------------------------*/ #include <stdio.h> #include <stdint.h> #include <stdarg.h> #include <string.h> #include "mdm_hal.h" #include "timer_hal.h" #include "delay_hal.h" #include "pinmap_hal.h" #include "pinmap_impl.h" #include "gpio_hal.h" #include "mdmapn_hal.h" #include "stm32f2xx.h" #include "service_debug.h" #include "concurrent_hal.h" #include <mutex> #include "net_hal.h" #include <limits> std::recursive_mutex mdm_mutex; /* Private typedef ----------------------------------------------------------*/ /* Private define -----------------------------------------------------------*/ /* Private macro ------------------------------------------------------------*/ #define PROFILE "0" //!< this is the psd profile used #define MAX_SIZE 1024 //!< max expected messages (used with RX) #define USO_MAX_WRITE 1024 //!< maximum number of bytes to write to socket (used with TX) // num sockets #define NUMSOCKETS ((int)(sizeof(_sockets)/sizeof(*_sockets))) //! test if it is a socket is ok to use #define ISSOCKET(s) (((s) >= 0) && ((s) < NUMSOCKETS) && (_sockets[s].handle != MDM_SOCKET_ERROR)) //! check for timeout #define TIMEOUT(t, ms) ((ms != TIMEOUT_BLOCKING) && ((HAL_Timer_Get_Milli_Seconds() - t) > ms)) //! registration ok check helper #define REG_OK(r) ((r == REG_HOME) || (r == REG_ROAMING)) //! registration done check helper (no need to poll further) #define REG_DONE(r) ((r == REG_HOME) || (r == REG_ROAMING) || (r == REG_DENIED)) //! helper to make sure that lock unlock pair is always balanced #define LOCK() std::lock_guard<std::recursive_mutex> __mdm_guard(mdm_mutex); //! helper to make sure that lock unlock pair is always balanced #define UNLOCK() static volatile uint32_t gprs_timeout_start; static volatile uint32_t gprs_timeout_duration; inline void ARM_GPRS_TIMEOUT(uint32_t dur) { gprs_timeout_start = HAL_Timer_Get_Milli_Seconds(); gprs_timeout_duration = dur; DEBUG("GPRS WD Set %d",(dur)); } inline bool IS_GPRS_TIMEOUT() { return gprs_timeout_duration && ((HAL_Timer_Get_Milli_Seconds()-gprs_timeout_start)>gprs_timeout_duration); } inline void CLR_GPRS_TIMEOUT() { gprs_timeout_duration = 0; DEBUG("GPRS WD Cleared, was %d", gprs_timeout_duration); } #ifdef MDM_DEBUG #if 0 // colored terminal output using ANSI escape sequences #define COL(c) "\033[" c #else #define COL(c) "" #endif #define DEF COL("39m") #define BLA COL("30m") #define RED COL("31m") #define GRE COL("32m") #define YEL COL("33m") #define BLU COL("34m") #define MAG COL("35m") #define CYA COL("36m") #define WHY COL("37m") void dumpAtCmd(const char* buf, int len) { DEBUG_D(" %3d \"", len); while (len --) { char ch = *buf++; if ((ch > 0x1F) && (ch < 0x7F)) { // is printable if (ch == '%') DEBUG_D("%%"); else if (ch == '"') DEBUG_D("\\\""); else if (ch == '\\') DEBUG_D("\\\\"); else DEBUG_D("%c", ch); } else { if (ch == '\a') DEBUG_D("\\a"); // BEL (0x07) else if (ch == '\b') DEBUG_D("\\b"); // Backspace (0x08) else if (ch == '\t') DEBUG_D("\\t"); // Horizontal Tab (0x09) else if (ch == '\n') DEBUG_D("\\n"); // Linefeed (0x0A) else if (ch == '\v') DEBUG_D("\\v"); // Vertical Tab (0x0B) else if (ch == '\f') DEBUG_D("\\f"); // Formfeed (0x0C) else if (ch == '\r') DEBUG_D("\\r"); // Carriage Return (0x0D) else DEBUG_D("\\x%02x", (unsigned char)ch); } } DEBUG_D("\"\r\n"); } void MDMParser::_debugPrint(int level, const char* color, const char* format, ...) { if (_debugLevel >= level) { va_list args; va_start (args, format); if (color) DEBUG_D(color); DEBUG_D(format, args); if (color) DEBUG_D(DEF); va_end (args); DEBUG_D("\r\n"); } } // Warning: Do not use these for anything other than constant char messages, // they will yield incorrect values for integers. Use DEBUG_D() instead. #define MDM_ERROR(...) do {_debugPrint(0, RED, __VA_ARGS__);}while(0) #define MDM_INFO(...) do {_debugPrint(1, GRE, __VA_ARGS__);}while(0) #define MDM_TRACE(...) do {_debugPrint(2, DEF, __VA_ARGS__);}while(0) #define MDM_TEST(...) do {_debugPrint(3, CYA, __VA_ARGS__);}while(0) #else #define MDM_ERROR(...) // no tracing #define MDM_TEST(...) // no tracing #define MDM_INFO(...) // no tracing #define MDM_TRACE(...) // no tracing #endif /* Private variables --------------------------------------------------------*/ MDMParser* MDMParser::inst; /* Extern variables ---------------------------------------------------------*/ /* Private function prototypes ----------------------------------------------*/ MDMParser::MDMParser(void) { inst = this; memset(&_dev, 0, sizeof(_dev)); memset(&_net, 0, sizeof(_net)); _net.lac = 0xFFFF; _net.ci = 0xFFFFFFFF; _ip = NOIP; _init = false; _pwr = false; _activated = false; _attached = false; _attached_urc = false; // updated by GPRS detached/attached URC, // used to notify system of prolonged GPRS detach. _power_mode = 1; // default power mode is AT+UPSV=1 _cancel_all_operations = false; sms_cb = NULL; memset(_sockets, 0, sizeof(_sockets)); for (int socket = 0; socket < NUMSOCKETS; socket ++) _sockets[socket].handle = MDM_SOCKET_ERROR; #ifdef MDM_DEBUG _debugLevel = 3; _debugTime = HAL_Timer_Get_Milli_Seconds(); #endif } void MDMParser::setPowerMode(int mode) { _power_mode = mode; } void MDMParser::cancel(void) { MDM_INFO("\r\n[ Modem::cancel ] = = = = = = = = = = = = = = ="); _cancel_all_operations = true; } void MDMParser::resume(void) { MDM_INFO("\r\n[ Modem::resume ] = = = = = = = = = = = = = = ="); _cancel_all_operations = false; } void MDMParser::setSMSreceivedHandler(_CELLULAR_SMS_CB cb, void* data) { sms_cb = cb; sms_data = data; } void MDMParser::SMSreceived(int index) { sms_cb(sms_data, index); // call the SMS callback with the index of the new SMS } int MDMParser::send(const char* buf, int len) { #ifdef MDM_DEBUG if (_debugLevel >= 3) { DEBUG_D("%10.3f AT send ", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001); dumpAtCmd(buf,len); } #endif return _send(buf, len); } int MDMParser::sendFormated(const char* format, ...) { va_list args; va_start(args, format); const int ret = sendFormattedWithArgs(format, args); va_end(args); return ret; } int MDMParser::sendFormattedWithArgs(const char* format, va_list args) { if (_cancel_all_operations) { return 0; } va_list argsCopy; va_copy(argsCopy, args); char buf[128]; int n = vsnprintf(buf, sizeof(buf), format, args); if (n >= 0) { if ((size_t)n < sizeof(buf)) { n = send(buf, n); } else { char buf[n + 1]; // Use larger buffer n = vsnprintf(buf, sizeof(buf), format, argsCopy); if (n >= 0) { n = send(buf, n); } } } va_end(argsCopy); return n; } int MDMParser::waitFinalResp(_CALLBACKPTR cb /* = NULL*/, void* param /* = NULL*/, system_tick_t timeout_ms /*= 5000*/) { if (_cancel_all_operations) return WAIT; // If we went from a GPRS attached state to detached via URC, // a WDT was set and now expired. Notify system of disconnect. if (IS_GPRS_TIMEOUT()) { _ip = NOIP; _attached = false; CLR_GPRS_TIMEOUT(); // HAL_NET_notify_dhcp(false); HAL_NET_notify_disconnected(); } char buf[MAX_SIZE + 64 /* add some more space for framing */]; system_tick_t start = HAL_Timer_Get_Milli_Seconds(); do { int ret = getLine(buf, sizeof(buf)); #ifdef MDM_DEBUG if ((_debugLevel >= 3) && (ret != WAIT) && (ret != NOT_FOUND)) { int len = LENGTH(ret); int type = TYPE(ret); const char* s = (type == TYPE_UNKNOWN)? YEL "UNK" DEF : (type == TYPE_TEXT) ? MAG "TXT" DEF : (type == TYPE_OK ) ? GRE "OK " DEF : (type == TYPE_ERROR) ? RED "ERR" DEF : (type == TYPE_ABORTED) ? RED "ABT" DEF : (type == TYPE_PLUS) ? CYA " + " DEF : (type == TYPE_PROMPT) ? BLU " > " DEF : "..." ; DEBUG_D("%10.3f AT read %s", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001, s); dumpAtCmd(buf, len); (void)s; } #endif if ((ret != WAIT) && (ret != NOT_FOUND)) { int type = TYPE(ret); // handle unsolicited commands here if (type == TYPE_PLUS) { const char* cmd = buf+3; int a, b, c, d, r; char s[32]; // SMS Command --------------------------------- // +CNMI: <mem>,<index> if (sscanf(cmd, "CMTI: \"%*[^\"]\",%d", &a) == 1) { DEBUG_D("New SMS at index %d\r\n", a); if (sms_cb) SMSreceived(a); } else if ((sscanf(cmd, "CIEV: 9,%d", &a) == 1)) { DEBUG_D("CIEV matched: 9,%d\r\n", a); // Wait until the system is attached before attempting to act on GPRS detach if (_attached) { _attached_urc = (a==2)?1:0; if (!_attached_urc) ARM_GPRS_TIMEOUT(15*1000); // If detached, set WDT else CLR_GPRS_TIMEOUT(); // else if re-attached clear WDT. } // Socket Specific Command --------------------------------- // +USORD: <socket>,<length> } else if ((sscanf(cmd, "USORD: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSORD: <socket>,<length> } else if ((sscanf(cmd, "UUSORD: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +USORF: <socket>,<length> } else if ((sscanf(cmd, "USORF: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSORF: <socket>,<length> } else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSOCL: <socket> } else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d closed by remote host\r\n", socket, a); if (socket != MDM_SOCKET_ERROR) { _socketFree(socket); } } // GSM/UMTS Specific ------------------------------------------- // +UUPSDD: <profile_id> if (sscanf(cmd, "UUPSDD: %s", s) == 1) { DEBUG_D("UUPSDD: %s matched\r\n", PROFILE); if ( !strcmp(s, PROFILE) ) { _ip = NOIP; _attached = false; DEBUG("PDP context deactivated remotely!\r\n"); // PDP context was remotely deactivated via URC, // Notify system of disconnect. HAL_NET_notify_dhcp(false); } } else { // +CREG|CGREG: <n>,<stat>[,<lac>,<ci>[,AcT[,<rac>]]] // reply to AT+CREG|AT+CGREG // +CREG|CGREG: <stat>[,<lac>,<ci>[,AcT[,<rac>]]] // URC b = (int)0xFFFF; c = (int)0xFFFFFFFF; d = -1; r = sscanf(cmd, "%s %*d,%d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d); if (r <= 1) r = sscanf(cmd, "%s %d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d); if (r >= 2) { Reg *reg = !strcmp(s, "CREG:") ? &_net.csd : !strcmp(s, "CGREG:") ? &_net.psd : NULL; if (reg) { // network status if (a == 0) *reg = REG_NONE; // 0: not registered, home network else if (a == 1) *reg = REG_HOME; // 1: registered, home network else if (a == 2) *reg = REG_NONE; // 2: not registered, but MT is currently searching a new operator to register to else if (a == 3) *reg = REG_DENIED; // 3: registration denied else if (a == 4) *reg = REG_UNKNOWN; // 4: unknown else if (a == 5) *reg = REG_ROAMING; // 5: registered, roaming if ((r >= 3) && (b != (int)0xFFFF)) _net.lac = b; // location area code if ((r >= 4) && (c != (int)0xFFFFFFFF)) _net.ci = c; // cell ID // access technology if (r >= 5) { if (d == 0) _net.act = ACT_GSM; // 0: GSM else if (d == 1) _net.act = ACT_GSM; // 1: GSM COMPACT else if (d == 2) _net.act = ACT_UTRAN; // 2: UTRAN else if (d == 3) _net.act = ACT_EDGE; // 3: GSM with EDGE availability else if (d == 4) _net.act = ACT_UTRAN; // 4: UTRAN with HSDPA availability else if (d == 5) _net.act = ACT_UTRAN; // 5: UTRAN with HSUPA availability else if (d == 6) _net.act = ACT_UTRAN; // 6: UTRAN with HSDPA and HSUPA availability } } } } } // end ==TYPE_PLUS if (cb) { int len = LENGTH(ret); int ret = cb(type, buf, len, param); if (WAIT != ret) return ret; } if (type == TYPE_OK) return RESP_OK; if (type == TYPE_ERROR) return RESP_ERROR; if (type == TYPE_PROMPT) return RESP_PROMPT; if (type == TYPE_ABORTED) return RESP_ABORTED; // This means the current command was ABORTED, so retry your command if critical. } // relax a bit HAL_Delay_Milliseconds(10); } while (!TIMEOUT(start, timeout_ms) && !_cancel_all_operations); return WAIT; } int MDMParser::sendCommandWithArgs(const char* fmt, va_list args, _CALLBACKPTR cb, void* param, system_tick_t timeout) { LOCK(); sendFormattedWithArgs(fmt, args); const int ret = waitFinalResp(cb, param, timeout); UNLOCK(); return ret; } void MDMParser::lock() { mdm_mutex.lock(); } void MDMParser::unlock() { mdm_mutex.unlock(); } int MDMParser::_cbString(int type, const char* buf, int len, char* str) { if (str && (type == TYPE_UNKNOWN)) { if (sscanf(buf, "\r\n%s\r\n", str) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbInt(int type, const char* buf, int len, int* val) { if (val && (type == TYPE_UNKNOWN)) { if (sscanf(buf, "\r\n%d\r\n", val) == 1) /*nothing*/; } return WAIT; } // ---------------------------------------------------------------- bool MDMParser::connect( const char* simpin, const char* apn, const char* username, const char* password, Auth auth) { bool ok = powerOn(simpin); if (!ok) return false; ok = init(); #ifdef MDM_DEBUG if (_debugLevel >= 1) dumpDevStatus(&_dev); #endif if (!ok) return false; ok = registerNet(); #ifdef MDM_DEBUG if (_debugLevel >= 1) dumpNetStatus(&_net); #endif if (!ok) return false; MDM_IP ip = join(apn,username,password,auth); #ifdef MDM_DEBUG if (_debugLevel >= 1) dumpIp(ip); #endif if (ip == NOIP) return false; return true; } void MDMParser::reset(void) { MDM_INFO("[ Modem reset ]"); HAL_GPIO_Write(RESET_UC, 0); HAL_Delay_Milliseconds(100); HAL_GPIO_Write(RESET_UC, 1); } bool MDMParser::_powerOn(void) { LOCK(); /* Initialize I/O */ STM32_Pin_Info* PIN_MAP_PARSER = HAL_Pin_Map(); // This pin tends to stay low when floating on the output of the buffer (PWR_UB) // It shouldn't hurt if it goes low temporarily on STM32 boot, but strange behavior // was noticed when it was left to do whatever it wanted. By adding a 100k pull up // resistor all flakey behavior has ceased (i.e., the modem had previously stopped // responding to AT commands). This is how we set it HIGH before enabling the OUTPUT. PIN_MAP_PARSER[PWR_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[PWR_UC].gpio_pin; HAL_Pin_Mode(PWR_UC, OUTPUT); // This pin tends to stay high when floating on the output of the buffer (RESET_UB), // but we need to ensure it gets set high before being set to an OUTPUT. // If this pin goes LOW, the modem will be reset and all configuration will be lost. PIN_MAP_PARSER[RESET_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[RESET_UC].gpio_pin; HAL_Pin_Mode(RESET_UC, OUTPUT); #if USE_USART3_HARDWARE_FLOW_CONTROL_RTS_CTS _dev.lpm = LPM_ENABLED; #else HAL_Pin_Mode(RTS_UC, OUTPUT); HAL_GPIO_Write(RTS_UC, 0); // VERY IMPORTANT FOR CORRECT OPERATION W/O HW FLOW CONTROL!! #endif HAL_Pin_Mode(LVLOE_UC, OUTPUT); HAL_GPIO_Write(LVLOE_UC, 0); if (!_init) { MDM_INFO("[ ElectronSerialPipe::begin ] = = = = = = = ="); /* Instantiate the USART3 hardware */ electronMDM.begin(115200); /* Initialize only once */ _init = true; } MDM_INFO("\r\n[ Modem::powerOn ] = = = = = = = = = = = = = ="); bool continue_cancel = false; bool retried_after_reset = false; int i = 10; while (i--) { // SARA-U2/LISA-U2 50..80us HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(50); HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(10); // SARA-G35 >5ms, LISA-C2 > 150ms, LEON-G2 >5ms HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(150); HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(100); // purge any messages purge(); // Save desire to cancel, but since we are already here // trying to power up the modem when we received a cancel // resume AT parser to ensure it's ready to receive // power down commands. if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can talk to the modem } // check interface sendFormated("AT\r\n"); int r = waitFinalResp(NULL,NULL,1000); if(RESP_OK == r) { _pwr = true; break; } else if (i==0 && !retried_after_reset) { retried_after_reset = true; // only perform reset & retry sequence once i = 10; reset(); } } if (i < 0) { MDM_ERROR("[ No Reply from Modem ]\r\n"); } if (continue_cancel) { cancel(); goto failure; } // Flush any on-boot URCs that can cause syncing issues later waitFinalResp(NULL,NULL,200); // echo off sendFormated("AT E0\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // enable verbose error messages sendFormated("AT+CMEE=2\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // Configures sending of URCs from MT to DTE for indications sendFormated("AT+CMER=1,0,0,2,1\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // set baud rate sendFormated("AT+IPR=115200\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // wait some time until baudrate is applied HAL_Delay_Milliseconds(100); // SARA-G > 40ms UNLOCK(); return true; failure: UNLOCK(); return false; } bool MDMParser::powerOn(const char* simpin) { LOCK(); memset(&_dev, 0, sizeof(_dev)); bool retried_after_reset = false; /* Power on the modem and perform basic initialization */ if (!_powerOn()) goto failure; /* The ATI command is undocumented, and in practice the response * time varies greatly. On inital power-on of the module, ATI * will respond with "OK" before a device type number, which * requires wasting time in a for() loop to solve. * Instead, use AT+CGMM and _dev.model for future use of module identification. * * identify the module * sendFormated("ATI\r\n"); * if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev)) * goto failure; * if (_dev.dev == DEV_UNKNOWN) * goto failure; */ // check the sim card for (int i = 0; (i < 5) && (_dev.sim != SIM_READY) && !_cancel_all_operations; i++) { sendFormated("AT+CPIN?\r\n"); int ret = waitFinalResp(_cbCPIN, &_dev.sim); // having an error here is ok (sim may still be initializing) if ((RESP_OK != ret) && (RESP_ERROR != ret)) { goto failure; } else if (i==4 && (RESP_OK != ret) && !retried_after_reset) { retried_after_reset = true; // only perform reset & retry sequence once i = 0; if(!powerOff()) reset(); /* Power on the modem and perform basic initialization again */ if (!_powerOn()) goto failure; } // Enter PIN if needed if (_dev.sim == SIM_PIN) { if (!simpin) { MDM_ERROR("SIM PIN not available\r\n"); goto failure; } sendFormated("AT+CPIN=%s\r\n", simpin); if (RESP_OK != waitFinalResp(_cbCPIN, &_dev.sim)) goto failure; } else if (_dev.sim != SIM_READY) { // wait for up to one second while looking for slow "+CPIN: READY" URCs waitFinalResp(_cbCPIN, &_dev.sim, 1000); } } if (_dev.sim != SIM_READY) { if (_dev.sim == SIM_MISSING) { MDM_ERROR("SIM not inserted\r\n"); } goto failure; } UNLOCK(); return true; failure: if (_cancel_all_operations) { // fake out the has_credentials() function so we don't end up in listening mode _dev.sim = SIM_READY; // return true to prevent from entering Listening Mode // UNLOCK(); // return true; } UNLOCK(); return false; } bool MDMParser::init(DevStatus* status) { LOCK(); MDM_INFO("\r\n[ Modem::init ] = = = = = = = = = = = = = = ="); // Returns the product serial number, IMEI (International Mobile Equipment Identity) sendFormated("AT+CGSN\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.imei)) goto failure; if (_dev.sim != SIM_READY) { if (_dev.sim == SIM_MISSING) MDM_ERROR("SIM not inserted\r\n"); goto failure; } // get the manufacturer sendFormated("AT+CGMI\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.manu)) goto failure; // get the model identification sendFormated("AT+CGMM\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.model)) goto failure; // get the version sendFormated("AT+CGMR\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.ver)) goto failure; // Returns the ICCID (Integrated Circuit Card ID) of the SIM-card. // ICCID is a serial number identifying the SIM. sendFormated("AT+CCID\r\n"); if (RESP_OK != waitFinalResp(_cbCCID, _dev.ccid)) goto failure; // enable power saving if (_dev.lpm != LPM_DISABLED) { // enable power saving (requires flow control, cts at least) sendFormated("AT+UPSV=%d\r\n", _power_mode); if (RESP_OK != waitFinalResp()) goto failure; if (_power_mode != 0) { _dev.lpm = LPM_ACTIVE; } } // Setup SMS in text mode sendFormated("AT+CMGF=1\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // setup new message indication sendFormated("AT+CNMI=2,1\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // Request IMSI (International Mobile Subscriber Identification) sendFormated("AT+CIMI\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.imsi)) goto failure; if (status) memcpy(status, &_dev, sizeof(DevStatus)); UNLOCK(); return true; failure: UNLOCK(); return false; } bool MDMParser::powerOff(void) { LOCK(); bool ok = false; bool continue_cancel = false; if (_init && _pwr) { MDM_INFO("\r\n[ Modem::powerOff ] = = = = = = = = = = = = = ="); if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } for (int i=0; i<3; i++) { // try 3 times sendFormated("AT+CPWROFF\r\n"); int ret = waitFinalResp(NULL,NULL,40*1000); if (RESP_OK == ret) { _pwr = false; // todo - add if these are automatically done on power down //_activated = false; //_attached = false; ok = true; break; } else if (RESP_ABORTED == ret) { MDM_INFO("\r\n[ Modem::powerOff ] found ABORTED, retrying..."); } else { MDM_INFO("\r\n[ Modem::powerOff ] timeout, retrying..."); } } } HAL_Pin_Mode(PWR_UC, INPUT); HAL_Pin_Mode(RESET_UC, INPUT); #if USE_USART3_HARDWARE_FLOW_CONTROL_RTS_CTS #else HAL_Pin_Mode(RTS_UC, INPUT); #endif HAL_Pin_Mode(LVLOE_UC, INPUT); if (continue_cancel) cancel(); UNLOCK(); return ok; } int MDMParser::_cbATI(int type, const char* buf, int len, Dev* dev) { if ((type == TYPE_UNKNOWN) && dev) { if (strstr(buf, "SARA-G350")) *dev = DEV_SARA_G350; else if (strstr(buf, "LISA-U200")) *dev = DEV_LISA_U200; else if (strstr(buf, "LISA-C200")) *dev = DEV_LISA_C200; else if (strstr(buf, "SARA-U260")) *dev = DEV_SARA_U260; else if (strstr(buf, "SARA-U270")) *dev = DEV_SARA_U270; else if (strstr(buf, "LEON-G200")) *dev = DEV_LEON_G200; } return WAIT; } int MDMParser::_cbCPIN(int type, const char* buf, int len, Sim* sim) { if (sim) { if (type == TYPE_PLUS){ char s[16]; if (sscanf(buf, "\r\n+CPIN: %[^\r]\r\n", s) >= 1) *sim = (0 == strcmp("READY", s)) ? SIM_READY : SIM_PIN; } else if (type == TYPE_ERROR) { if (strstr(buf, "+CME ERROR: SIM not inserted")) *sim = SIM_MISSING; } } return WAIT; } int MDMParser::_cbCCID(int type, const char* buf, int len, char* ccid) { if ((type == TYPE_PLUS) && ccid) { if (sscanf(buf, "\r\n+CCID: %[^\r]\r\n", ccid) == 1) { //DEBUG_D("Got CCID: %s\r\n", ccid); } } return WAIT; } bool MDMParser::registerNet(NetStatus* status /*= NULL*/, system_tick_t timeout_ms /*= 180000*/) { LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::register ] = = = = = = = = = = = = = ="); // Check to see if we are already connected. If so don't issue these // commands as they will knock us off the cellular network. if (checkNetStatus() == false) { // setup the GPRS network registration URC (Unsolicited Response Code) // 0: (default value and factory-programmed value): network registration URC disabled // 1: network registration URC enabled // 2: network registration and location information URC enabled sendFormated("AT+CGREG=2\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // setup the network registration URC (Unsolicited Response Code) // 0: (default value and factory-programmed value): network registration URC disabled // 1: network registration URC enabled // 2: network registration and location information URC enabled sendFormated("AT+CREG=2\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // Now check every 15 seconds for 5 minutes to see if we're connected to the tower (GSM and GPRS) system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (!checkNetStatus(status) && !TIMEOUT(start, timeout_ms) && !_cancel_all_operations) { system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while ((HAL_Timer_Get_Milli_Seconds() - start < 15000UL) && !_cancel_all_operations); // just wait //HAL_Delay_Milliseconds(15000); } if (_net.csd == REG_DENIED) MDM_ERROR("CSD Registration Denied\r\n"); if (_net.psd == REG_DENIED) MDM_ERROR("PSD Registration Denied\r\n"); // if (_net.csd == REG_DENIED || _net.psd == REG_DENIED) { // sendFormated("AT+CEER\r\n"); // waitFinalResp(); // } } UNLOCK(); return REG_OK(_net.csd) && REG_OK(_net.psd); } failure: UNLOCK(); return false; } bool MDMParser::checkNetStatus(NetStatus* status /*= NULL*/) { bool ok = false; LOCK(); memset(&_net, 0, sizeof(_net)); _net.lac = 0xFFFF; _net.ci = 0xFFFFFFFF; // check registration sendFormated("AT+CREG?\r\n"); waitFinalResp(); // don't fail as service could be not subscribed // check PSD registration sendFormated("AT+CGREG?\r\n"); waitFinalResp(); // don't fail as service could be not subscribed if (REG_OK(_net.csd) || REG_OK(_net.psd)) { sendFormated("AT+COPS?\r\n"); if (RESP_OK != waitFinalResp(_cbCOPS, &_net)) goto failure; // get the MSISDNs related to this subscriber sendFormated("AT+CNUM\r\n"); if (RESP_OK != waitFinalResp(_cbCNUM, _net.num)) goto failure; // get the signal strength indication sendFormated("AT+CSQ\r\n"); if (RESP_OK != waitFinalResp(_cbCSQ, &_net)) goto failure; } if (status) { memcpy(status, &_net, sizeof(NetStatus)); } // don't return true until fully registered ok = REG_OK(_net.csd) && REG_OK(_net.psd); UNLOCK(); return ok; failure: //unlock(); return false; } bool MDMParser::getSignalStrength(NetStatus &status) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::getSignalStrength ] = = = = = = = = = ="); sendFormated("AT+CSQ\r\n"); if (RESP_OK == waitFinalResp(_cbCSQ, &_net)) { ok = true; status = _net; } } UNLOCK(); return ok; } bool MDMParser::getDataUsage(MDM_DataUsage &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::getDataUsage ] = = = = = = = = = ="); sendFormated("AT+UGCNTRD\r\n"); if (RESP_OK == waitFinalResp(_cbUGCNTRD, &_data_usage)) { ok = true; data.cid = _data_usage.cid; data.tx_session = _data_usage.tx_session; data.rx_session = _data_usage.rx_session; data.tx_total = _data_usage.tx_total; data.rx_total = _data_usage.rx_total; } } UNLOCK(); return ok; } void MDMParser::_setBandSelectString(MDM_BandSelect &data, char* bands, int index /*= 0*/) { char band[5]; for (int x=index; x<data.count; x++) { sprintf(band, "%d", data.band[x]); strcat(bands, band); if ((x+1) < data.count) strcat(bands, ","); } } bool MDMParser::setBandSelect(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::setBandSelect ] = = = = = = = = = ="); char bands_to_set[22] = ""; _setBandSelectString(data, bands_to_set, 0); if (strcmp(bands_to_set,"") == 0) goto failure; // create default bands string MDM_BandSelect band_avail; if (!getBandAvailable(band_avail)) goto failure; char band_defaults[22] = ""; if (band_avail.band[0] == BAND_DEFAULT) _setBandSelectString(band_avail, band_defaults, 1); // create selected bands string MDM_BandSelect band_sel; if (!getBandSelect(band_sel)) goto failure; char bands_selected[22] = ""; _setBandSelectString(band_sel, bands_selected, 0); if (strcmp(bands_to_set, "0") == 0) { if (strcmp(bands_selected, band_defaults) == 0) { ok = true; goto success; } } if (strcmp(bands_selected, bands_to_set) != 0) { sendFormated("AT+UBANDSEL=%s\r\n", bands_to_set); if (RESP_OK == waitFinalResp(NULL,NULL,40000)) { ok = true; } } else { ok = true; } } success: UNLOCK(); return ok; failure: UNLOCK(); return false; } bool MDMParser::getBandSelect(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_BandSelect data_sel; MDM_INFO("\r\n[ Modem::getBandSelect ] = = = = = = = = = ="); sendFormated("AT+UBANDSEL?\r\n"); if (RESP_OK == waitFinalResp(_cbBANDSEL, &data_sel)) { ok = true; memcpy(&data, &data_sel, sizeof(MDM_BandSelect)); } } UNLOCK(); return ok; } bool MDMParser::getBandAvailable(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_BandSelect data_avail; MDM_INFO("\r\n[ Modem::getBandAvailable ] = = = = = = = = = ="); sendFormated("AT+UBANDSEL=?\r\n"); if (RESP_OK == waitFinalResp(_cbBANDAVAIL, &data_avail)) { ok = true; memcpy(&data, &data_avail, sizeof(MDM_BandSelect)); } } UNLOCK(); return ok; } int MDMParser::_cbUGCNTRD(int type, const char* buf, int len, MDM_DataUsage* data) { if ((type == TYPE_PLUS) && data) { int a,b,c,d,e; // +UGCNTRD: 31,2704,1819,2724,1839\r\n // +UGCNTRD: <cid>,<tx_sess_bytes>,<rx_sess_bytes>,<tx_total_bytes>,<rx_total_bytes> if (sscanf(buf, "\r\n+UGCNTRD: %d,%d,%d,%d,%d\r\n", &a,&b,&c,&d,&e) == 5) { data->cid = a; data->tx_session = b; data->rx_session = c; data->tx_total = d; data->rx_total = e; } } return WAIT; } int MDMParser::_cbBANDAVAIL(int type, const char* buf, int len, MDM_BandSelect* data) { if ((type == TYPE_PLUS) && data) { int c; int b[5]; // \r\n+UBANDSEL: (0,850,900,1800,1900)\r\n if ((c = sscanf(buf, "\r\n+UBANDSEL: (%d,%d,%d,%d,%d)\r\n", &b[0],&b[1],&b[2],&b[3],&b[4])) > 0) { for (int i=0; i<c; i++) { data->band[i] = (MDM_Band)b[i]; } data->count = c; } } return WAIT; } int MDMParser::_cbBANDSEL(int type, const char* buf, int len, MDM_BandSelect* data) { if ((type == TYPE_PLUS) && data) { int c; int b[4]; // \r\n+UBANDSEL: 850\r\n // \r\n+UBANDSEL: 850,1900\r\n if ((c = sscanf(buf, "\r\n+UBANDSEL: %d,%d,%d,%d\r\n", &b[0],&b[1],&b[2],&b[3])) > 0) { for (int i=0; i<c; i++) { data->band[i] = (MDM_Band)b[i]; } data->count = c; } } return WAIT; } int MDMParser::_cbCOPS(int type, const char* buf, int len, NetStatus* status) { if ((type == TYPE_PLUS) && status){ int act = 99; // +COPS: <mode>[,<format>,<oper>[,<AcT>]] if (sscanf(buf, "\r\n+COPS: %*d,%*d,\"%[^\"]\",%d",status->opr,&act) >= 1) { if (act == 0) status->act = ACT_GSM; // 0: GSM, else if (act == 2) status->act = ACT_UTRAN; // 2: UTRAN } } return WAIT; } int MDMParser::_cbCNUM(int type, const char* buf, int len, char* num) { if ((type == TYPE_PLUS) && num){ int a; if ((sscanf(buf, "\r\n+CNUM: \"My Number\",\"%31[^\"]\",%d", num, &a) == 2) && ((a == 129) || (a == 145))) { } } return WAIT; } int MDMParser::_cbCSQ(int type, const char* buf, int len, NetStatus* status) { if ((type == TYPE_PLUS) && status){ int a,b; char _qual[] = { 49, 43, 37, 25, 19, 13, 7, 0 }; // see 3GPP TS 45.008 [20] subclause 8.2.4 // +CSQ: <rssi>,<qual> if (sscanf(buf, "\r\n+CSQ: %d,%d",&a,&b) == 2) { if (a != 99) status->rssi = -113 + 2*a; // 0: -113 1: -111 ... 30: -53 dBm with 2 dBm steps if ((b != 99) && (b < (int)sizeof(_qual))) status->qual = _qual[b]; // switch (status->act) { case ACT_GSM: case ACT_EDGE: status->rxlev = (a != 99) ? (2 * a) : a; status->rxqual = b; break; case ACT_UTRAN: status->rscp = (a != 99) ? (status->rssi + 116) : 255; status->ecno = (b != 99) ? std::min((7 + (7 - b) * 6), 44) : 255; break; default: // Unknown access tecnhology status->asu = std::numeric_limits<int32_t>::min(); status->aqual = std::numeric_limits<int32_t>::min(); break; } } } return WAIT; } int MDMParser::_cbUACTIND(int type, const char* buf, int len, int* i) { if ((type == TYPE_PLUS) && i){ int a; if (sscanf(buf, "\r\n+UACTIND: %d", &a) == 1) { *i = a; } } return WAIT; } // ---------------------------------------------------------------- // setup the PDP context bool MDMParser::pdp(const char* apn) { bool ok = true; // bool is3G = _dev.dev == DEV_SARA_U260 || _dev.dev == DEV_SARA_U270; LOCK(); if (_init && _pwr) { // todo - refactor // This is setting up an external PDP context, join() creates an internal one // which is ultimately the one that's used by the system. So no need for this. #if 0 MDM_INFO("Modem::pdp\r\n"); DEBUG_D("Define the PDP context 1 with PDP type \"IP\" and APN \"%s\"\r\n", apn); sendFormated("AT+CGDCONT=1,\"IP\",\"%s\"\r\n", apn); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; if (is3G) { MDM_INFO("Define a QoS profile for PDP context 1"); /* with Traffic Class 3 (background), * maximum bit rate 64 kb/s both for UL and for DL, no Delivery Order requirements, * a maximum SDU size of 320 octets, an SDU error ratio of 10-4, a residual bit error * ratio of 10-5, delivery of erroneous SDUs allowed and Traffic Handling Priority 3. */ sendFormated("AT+CGEQREQ=1,3,64,64,,,0,320,\"1E4\",\"1E5\",1,,3\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; } MDM_INFO("Activate PDP context 1..."); sendFormated("AT+CGACT=1,1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 20000)) { sendFormated("AT+CEER\r\n"); waitFinalResp(); MDM_INFO("Test PDP context 1 for non-zero IP address..."); sendFormated("AT+CGPADDR=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) MDM_INFO("Read the PDP contexts’ parameters..."); sendFormated("AT+CGDCONT?\r\n"); // +CGPADDR: 1, "99.88.111.88" if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) if (is3G) { MDM_INFO("Read the negotiated QoS profile for PDP context 1..."); sendFormated("AT+CGEQNEG=1\r\n"); goto failure; } } MDM_INFO("Test PDP context 1 for non-zero IP address..."); sendFormated("AT+CGPADDR=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; MDM_INFO("Read the PDP contexts’ parameters..."); sendFormated("AT+CGDCONT?\r\n"); // +CGPADDR: 1, "99.88.111.88" if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; if (is3G) { MDM_INFO("Read the negotiated QoS profile for PDP context 1..."); sendFormated("AT+CGEQNEG=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; } _activated = true; // PDP #endif UNLOCK(); return ok; } // failure: UNLOCK(); return false; } // ---------------------------------------------------------------- // internet connection MDM_IP MDMParser::join(const char* apn /*= NULL*/, const char* username /*= NULL*/, const char* password /*= NULL*/, Auth auth /*= AUTH_DETECT*/) { LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::join ] = = = = = = = = = = = = = = = ="); _ip = NOIP; int a = 0; bool force = false; // If we are already connected, don't force a reconnect. // perform GPRS attach sendFormated("AT+CGATT=1\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000)) goto failure; // Check the if the PSD profile is activated (a=1) sendFormated("AT+UPSND=" PROFILE ",8\r\n"); if (RESP_OK != waitFinalResp(_cbUPSND, &a)) goto failure; if (a == 1) { _activated = true; // PDP activated if (force) { // deactivate the PSD profile if it is already activated sendFormated("AT+UPSDA=" PROFILE ",4\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,40*1000)) goto failure; a = 0; } } if (a == 0) { bool ok = false; _activated = false; // PDP deactived // try to lookup the apn settings from our local database by mccmnc const char* config = NULL; if (!apn && !username && !password) config = apnconfig(_dev.imsi); // Set up the dynamic IP address assignment. sendFormated("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"\r\n"); if (RESP_OK != waitFinalResp()) goto failure; do { if (config) { apn = _APN_GET(config); username = _APN_GET(config); password = _APN_GET(config); DEBUG_D("Testing APN Settings(\"%s\",\"%s\",\"%s\")\r\n", apn, username, password); } // Set up the APN if (apn && *apn) { sendFormated("AT+UPSD=" PROFILE ",1,\"%s\"\r\n", apn); if (RESP_OK != waitFinalResp()) goto failure; } if (username && *username) { sendFormated("AT+UPSD=" PROFILE ",2,\"%s\"\r\n", username); if (RESP_OK != waitFinalResp()) goto failure; } if (password && *password) { sendFormated("AT+UPSD=" PROFILE ",3,\"%s\"\r\n", password); if (RESP_OK != waitFinalResp()) goto failure; } // try different Authentication Protocols // 0 = none // 1 = PAP (Password Authentication Protocol) // 2 = CHAP (Challenge Handshake Authentication Protocol) for (int i = AUTH_NONE; i <= AUTH_CHAP && !ok; i ++) { if ((auth == AUTH_DETECT) || (auth == i)) { // Set up the Authentication Protocol sendFormated("AT+UPSD=" PROFILE ",6,%d\r\n", i); if (RESP_OK != waitFinalResp()) goto failure; // Activate the PSD profile and make connection sendFormated("AT+UPSDA=" PROFILE ",3\r\n"); if (RESP_OK == waitFinalResp(NULL,NULL,150*1000)) { _activated = true; // PDP activated ok = true; } } } } while (!ok && config && *config); // maybe use next setting ? if (!ok) { MDM_ERROR("Your modem APN/password/username may be wrong\r\n"); goto failure; } } //Get local IP address sendFormated("AT+UPSND=" PROFILE ",0\r\n"); if (RESP_OK != waitFinalResp(_cbUPSND, &_ip)) goto failure; UNLOCK(); _attached = true; // GPRS return _ip; } failure: unlock(); return NOIP; } int MDMParser::_cbUDOPN(int type, const char* buf, int len, char* mccmnc) { if ((type == TYPE_PLUS) && mccmnc) { if (sscanf(buf, "\r\n+UDOPN: 0,\"%[^\"]\"", mccmnc) == 1) ; } return WAIT; } int MDMParser::_cbCMIP(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_UNKNOWN) && ip) { int a,b,c,d; if (sscanf(buf, "\r\n" IPSTR, &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } int MDMParser::_cbUPSND(int type, const char* buf, int len, int* act) { if ((type == TYPE_PLUS) && act) { if (sscanf(buf, "\r\n+UPSND: %*d,%*d,%d", act) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbUPSND(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_PLUS) && ip) { int a,b,c,d; // +UPSND=<profile_id>,<param_tag>[,<dynamic_param_val>] if (sscanf(buf, "\r\n+UPSND: " PROFILE ",0,\"" IPSTR "\"", &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } int MDMParser::_cbUDNSRN(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_PLUS) && ip) { int a,b,c,d; if (sscanf(buf, "\r\n+UDNSRN: \"" IPSTR "\"", &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } bool MDMParser::reconnect(void) { bool ok = false; LOCK(); if (_activated) { MDM_INFO("\r\n[ Modem::reconnect ] = = = = = = = = = = = = = ="); if (!_attached) { /* Activates the PDP context assoc. with this profile */ /* If GPRS is detached, this will force a re-attach */ sendFormated("AT+UPSDA=" PROFILE ",3\r\n"); if (RESP_OK == waitFinalResp(NULL, NULL, 150*1000)) { //Get local IP address sendFormated("AT+UPSND=" PROFILE ",0\r\n"); if (RESP_OK == waitFinalResp(_cbUPSND, &_ip)) { ok = true; _attached = true; } } } } UNLOCK(); return ok; } // TODO - refactor disconnect() and detach() // disconnect() can be called before detach() but not vice versa or // disconnect() will ERROR because its PDP context will already be // deactivated. // _attached and _activated flags are currently associated inversely // to what's happening. When refactoring, consider combining... bool MDMParser::disconnect(void) { bool ok = false; bool continue_cancel = false; LOCK(); if (_attached) { if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } MDM_INFO("\r\n[ Modem::disconnect ] = = = = = = = = = = = = ="); if (_ip != NOIP) { /* Deactivates the PDP context assoc. with this profile * ensuring that no additional data is sent or received * by the device. */ sendFormated("AT+UPSDA=" PROFILE ",4\r\n"); if (RESP_OK == waitFinalResp()) { _ip = NOIP; ok = true; _attached = false; } } } if (continue_cancel) cancel(); UNLOCK(); return ok; } bool MDMParser::detach(void) { bool ok = false; bool continue_cancel = false; LOCK(); if (_activated) { if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } MDM_INFO("\r\n[ Modem::detach ] = = = = = = = = = = = = = = ="); // if (_ip != NOIP) { // if we disconnect() first we won't have an IP /* Detach from the GPRS network and conserve network resources. */ /* Any active PDP context will also be deactivated. */ sendFormated("AT+CGATT=0\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000)) { ok = true; _activated = false; } // } } if (continue_cancel) cancel(); UNLOCK(); return ok; } MDM_IP MDMParser::gethostbyname(const char* host) { MDM_IP ip = NOIP; int a,b,c,d; if (sscanf(host, IPSTR, &a,&b,&c,&d) == 4) ip = IPADR(a,b,c,d); else { LOCK(); sendFormated("AT+UDNSRN=0,\"%s\"\r\n", host); if (RESP_OK != waitFinalResp(_cbUDNSRN, &ip, 30*1000)) ip = NOIP; UNLOCK(); } return ip; } // ---------------------------------------------------------------- // sockets int MDMParser::_cbUSOCR(int type, const char* buf, int len, int* handle) { if ((type == TYPE_PLUS) && handle) { // +USOCR: socket if (sscanf(buf, "\r\n+USOCR: %d", handle) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbUSOCTL(int type, const char* buf, int len, int* handle) { if ((type == TYPE_PLUS) && handle) { // +USOCTL: socket,param_id,param_val if (sscanf(buf, "\r\n+USOCTL: %d,%*d,%*d", handle) == 1) /*nothing*/; } return WAIT; } /* Tries to close any currently unused socket handles */ int MDMParser::_socketCloseUnusedHandles() { bool ok = false; LOCK(); for (int s = 0; s < NUMSOCKETS; s++) { // If this HANDLE is not found to be in use, try to close it if (_findSocket(s) == MDM_SOCKET_ERROR) { if (_socketCloseHandleIfOpen(s)) { ok = true; // If any actually close, return true } } } UNLOCK(); return ok; } /* Tries to close the specified socket handle */ int MDMParser::_socketCloseHandleIfOpen(int socket_handle) { bool ok = false; LOCK(); // Check if socket_handle is open // AT+USOCTL=0,1 // +USOCTL: 0,1,0 int handle = MDM_SOCKET_ERROR; sendFormated("AT+USOCTL=%d,1\r\n", socket_handle); if ((RESP_OK == waitFinalResp(_cbUSOCTL, &handle)) && (handle != MDM_SOCKET_ERROR)) { DEBUG_D("Socket handle %d was open, now closing...\r\n", handle); // Close it if it's open // AT+USOCL=0 // OK sendFormated("AT+USOCL=%d\r\n", handle); if (RESP_OK == waitFinalResp()) { DEBUG_D("Socket handle %d was closed.\r\n", handle); ok = true; } } UNLOCK(); return ok; } int MDMParser::_socketSocket(int socket, IpProtocol ipproto, int port) { int rv = socket; LOCK(); if (ipproto == MDM_IPPROTO_UDP) { // sending port can only be set on 2G/3G modules if (port != -1) { sendFormated("AT+USOCR=17,%d\r\n", port); } } else /*(ipproto == MDM_IPPROTO_TCP)*/ { sendFormated("AT+USOCR=6\r\n"); } int handle = MDM_SOCKET_ERROR; if ((RESP_OK == waitFinalResp(_cbUSOCR, &handle)) && (handle != MDM_SOCKET_ERROR)) { DEBUG_D("Socket %d: handle %d was created\r\n", socket, handle); _sockets[socket].handle = handle; _sockets[socket].timeout_ms = TIMEOUT_BLOCKING; _sockets[socket].connected = (ipproto == MDM_IPPROTO_UDP); _sockets[socket].pending = 0; _sockets[socket].open = true; } else { rv = MDM_SOCKET_ERROR; } UNLOCK(); return rv; } int MDMParser::socketSocket(IpProtocol ipproto, int port) { int socket; static bool checkedOnce = false; LOCK(); if (!_attached) { if (!reconnect()) { socket = MDM_SOCKET_ERROR; } } if (_attached) { if (!checkedOnce) { checkedOnce = true; // prevent re-entry DEBUG_D("On first socketSocket use, free all open sockets\r\n"); // Clean up any open sockets, we may have power cycled the STM32 // while the modem remained connected. for (int s = 0; s < NUMSOCKETS; s++) { _socketCloseHandleIfOpen(s); // re-initialize the socket element _socketFree(s); } } // find an free socket socket = _findSocket(MDM_SOCKET_ERROR); DEBUG_D("socketSocket(%s)\r\n", (ipproto?"UDP":"TCP")); if (socket != MDM_SOCKET_ERROR) { int _socket = _socketSocket(socket, ipproto, port); if (_socket != MDM_SOCKET_ERROR) { socket = _socket; } else { // A socket should be available, but errored on trying to create one if (_socketCloseUnusedHandles()) { // find a new free socket and try again _socket = _findSocket(MDM_SOCKET_ERROR); socket = _socketSocket(_socket, ipproto, port); } else { // We tried to close unused handles, but also failed. socket = MDM_SOCKET_ERROR; } } } } UNLOCK(); return socket; } bool MDMParser::socketConnect(int socket, const char * host, int port) { MDM_IP ip = gethostbyname(host); if (ip == NOIP) return false; DEBUG_D("socketConnect(host: %s)\r\n", host); // connect to socket return socketConnect(socket, ip, port); } bool MDMParser::socketConnect(int socket, const MDM_IP& ip, int port) { bool ok = false; LOCK(); if (ISSOCKET(socket) && (!_sockets[socket].connected)) { DEBUG_D("socketConnect(%d,port:%d)\r\n", socket,port); sendFormated("AT+USOCO=%d,\"" IPSTR "\",%d\r\n", _sockets[socket].handle, IPNUM(ip), port); if (RESP_OK == waitFinalResp()) ok = _sockets[socket].connected = true; } UNLOCK(); return ok; } bool MDMParser::socketIsConnected(int socket) { bool ok = false; LOCK(); ok = ISSOCKET(socket) && _sockets[socket].connected; //DEBUG_D("socketIsConnected(%d) %s\r\n", socket, ok?"yes":"no"); UNLOCK(); return ok; } bool MDMParser::socketSetBlocking(int socket, system_tick_t timeout_ms) { bool ok = false; LOCK(); // DEBUG_D("socketSetBlocking(%d,%d)\r\n", socket,timeout_ms); if (ISSOCKET(socket)) { _sockets[socket].timeout_ms = timeout_ms; ok = true; } UNLOCK(); return ok; } bool MDMParser::socketClose(int socket) { bool ok = false; LOCK(); if (ISSOCKET(socket) && (_sockets[socket].connected || _sockets[socket].open)) { DEBUG_D("socketClose(%d)\r\n", socket); sendFormated("AT+USOCL=%d\r\n", _sockets[socket].handle); if (RESP_ERROR == waitFinalResp()) { sendFormated("AT+CEER\r\n"); // For logging visibility waitFinalResp(); } // Assume RESP_OK in most situations, and assume closed // already if we couldn't close it, even though this can // be false. Recovery added to socketSocket(); _sockets[socket].connected = false; _sockets[socket].open = false; ok = true; } UNLOCK(); return ok; } bool MDMParser::_socketFree(int socket) { bool ok = false; LOCK(); if ((socket >= 0) && (socket < NUMSOCKETS)) { if (_sockets[socket].handle != MDM_SOCKET_ERROR) { DEBUG_D("socketFree(%d)\r\n", socket); _sockets[socket].handle = MDM_SOCKET_ERROR; _sockets[socket].timeout_ms = TIMEOUT_BLOCKING; _sockets[socket].connected = false; _sockets[socket].pending = 0; _sockets[socket].open = false; } ok = true; } UNLOCK(); return ok; // only false if invalid socket } bool MDMParser::socketFree(int socket) { // make sure it is closed socketClose(socket); return _socketFree(socket); } int MDMParser::socketSend(int socket, const char * buf, int len) { //DEBUG_D("socketSend(%d,,%d)\r\n", socket,len); int cnt = len; while (cnt > 0) { int blk = USO_MAX_WRITE; if (cnt < blk) blk = cnt; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { sendFormated("AT+USOWR=%d,%d\r\n",_sockets[socket].handle,blk); if (RESP_PROMPT == waitFinalResp()) { HAL_Delay_Milliseconds(50); send(buf, blk); if (RESP_OK == waitFinalResp()) ok = true; } } UNLOCK(); } if (!ok) return MDM_SOCKET_ERROR; buf += blk; cnt -= blk; } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORD=%d,0\r\n", _sockets[socket].handle); // TCP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); return (len - cnt); } int MDMParser::socketSendTo(int socket, MDM_IP ip, int port, const char * buf, int len) { DEBUG_D("socketSendTo(%d," IPSTR ",%d,,%d)\r\n", socket,IPNUM(ip),port,len); int cnt = len; while (cnt > 0) { int blk = USO_MAX_WRITE; if (cnt < blk) blk = cnt; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { sendFormated("AT+USOST=%d,\"" IPSTR "\",%d,%d\r\n",_sockets[socket].handle,IPNUM(ip),port,blk); if (RESP_PROMPT == waitFinalResp()) { HAL_Delay_Milliseconds(50); send(buf, blk); if (RESP_OK == waitFinalResp()) ok = true; } } UNLOCK(); } if (!ok) return MDM_SOCKET_ERROR; buf += blk; cnt -= blk; } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORF=%d,0\r\n", _sockets[socket].handle); // UDP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); return (len - cnt); } int MDMParser::socketReadable(int socket) { int pending = MDM_SOCKET_ERROR; if (_cancel_all_operations) return MDM_SOCKET_ERROR; LOCK(); if (ISSOCKET(socket) && _sockets[socket].connected) { //DEBUG_D("socketReadable(%d)\r\n", socket); // allow to receive unsolicited commands waitFinalResp(NULL, NULL, 0); if (_sockets[socket].connected) pending = _sockets[socket].pending; } UNLOCK(); return pending; } int MDMParser::_cbUSORD(int type, const char* buf, int len, USORDparam* param) { if ((type == TYPE_PLUS) && param) { int sz, sk; if ((sscanf(buf, "\r\n+USORD: %d,%d,", &sk, &sz) == 2) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { memcpy(param->buf, &buf[len-1-sz], sz); param->len = sz; } else { param->len = 0; } } return WAIT; } int MDMParser::socketRecv(int socket, char* buf, int len) { int cnt = 0; /* DEBUG_D("socketRecv(%d,%d)\r\n", socket, len); #ifdef MDM_DEBUG memset(buf, '\0', len); #endif */ system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (len) { // DEBUG_D("socketRecv: LEN: %d\r\n", len); int blk = MAX_SIZE; // still need space for headers and unsolicited commands if (len < blk) blk = len; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { if (_sockets[socket].connected) { int available = socketReadable(socket); if (available<0) { // DEBUG_D("socketRecv: SOCKET CLOSED or NO AVAIL DATA\r\n"); // Socket may have been closed remotely during read, or no more data to read. // Zero the `len` to break out of the while(len), and set `ok` to true so // we return the `cnt` recv'd up until the socket was closed. len = 0; ok = true; } else { if (blk > available) // only read up to the amount available. When 0, blk = available;// skip reading and check timeout. if (blk > 0) { // DEBUG_D("socketRecv: _cbUSORD\r\n"); sendFormated("AT+USORD=%d,%d\r\n",_sockets[socket].handle, blk); USORDparam param; param.buf = buf; if (RESP_OK == waitFinalResp(_cbUSORD, ¶m)) { blk = param.len; _sockets[socket].pending -= blk; len -= blk; cnt += blk; buf += blk; ok = true; } } else if (!TIMEOUT(start, _sockets[socket].timeout_ms)) { // DEBUG_D("socketRecv: WAIT FOR URCs\r\n"); ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs } else { // DEBUG_D("socketRecv: TIMEOUT\r\n"); len = 0; ok = true; } } } else { // DEBUG_D("socketRecv: SOCKET NOT CONNECTED\r\n"); len = 0; ok = true; } } UNLOCK(); } if (!ok) { // DEBUG_D("socketRecv: ERROR\r\n"); return MDM_SOCKET_ERROR; } } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORD=%d,0\r\n", _sockets[socket].handle); // TCP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); // DEBUG_D("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt); return cnt; } int MDMParser::_cbUSORF(int type, const char* buf, int len, USORFparam* param) { if ((type == TYPE_PLUS) && param) { int sz, sk, p, a,b,c,d; int r = sscanf(buf, "\r\n+USORF: %d,\"" IPSTR "\",%d,%d,", &sk,&a,&b,&c,&d,&p,&sz); if ((r == 7) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { memcpy(param->buf, &buf[len-1-sz], sz); param->ip = IPADR(a,b,c,d); param->port = p; param->len = sz; } else { param->len = 0; } } return WAIT; } int MDMParser::socketRecvFrom(int socket, MDM_IP* ip, int* port, char* buf, int len) { int cnt = 0; // DEBUG_D("socketRecvFrom(%d,,%d)\r\n", socket, len); #ifdef MDM_DEBUG memset(buf, '\0', len); #endif system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (len) { int blk = MAX_SIZE; // still need space for headers and unsolicited commands if (len < blk) blk = len; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { if (blk > 0) { sendFormated("AT+USORF=%d,%d\r\n",_sockets[socket].handle, blk); USORFparam param; param.buf = buf; if (RESP_OK == waitFinalResp(_cbUSORF, ¶m)) { *ip = param.ip; *port = param.port; blk = param.len; _sockets[socket].pending -= blk; len -= blk; cnt += blk; buf += blk; len = 0; // done ok = true; } } else if (!TIMEOUT(start, _sockets[socket].timeout_ms)) { ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs } else { len = 0; // no more data and socket closed or timed-out ok = true; } } UNLOCK(); } if (!ok) { DEBUG_D("socketRecv: ERROR\r\n"); return MDM_SOCKET_ERROR; } } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORF=%d,0\r\n", _sockets[socket].handle); // UDP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); // DEBUG_D("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt); return cnt; } int MDMParser::_findSocket(int handle) { for (int socket = 0; socket < NUMSOCKETS; socket ++) { if (_sockets[socket].handle == handle) return socket; } return MDM_SOCKET_ERROR; } // ---------------------------------------------------------------- int MDMParser::_cbCMGL(int type, const char* buf, int len, CMGLparam* param) { if ((type == TYPE_PLUS) && param && param->num) { // +CMGL: <ix>,... int ix; if (sscanf(buf, "\r\n+CMGL: %d,", &ix) == 1) { *param->ix++ = ix; param->num--; } } return WAIT; } int MDMParser::smsList(const char* stat /*= "ALL"*/, int* ix /*=NULL*/, int num /*= 0*/) { int ret = -1; LOCK(); sendFormated("AT+CMGL=\"%s\"\r\n", stat); CMGLparam param; param.ix = ix; param.num = num; if (RESP_OK == waitFinalResp(_cbCMGL, ¶m)) ret = num - param.num; UNLOCK(); return ret; } bool MDMParser::smsSend(const char* num, const char* buf) { bool ok = false; LOCK(); sendFormated("AT+CMGS=\"%s\"\r\n",num); if (RESP_PROMPT == waitFinalResp(NULL,NULL,150*1000)) { send(buf, strlen(buf)); const char ctrlZ = 0x1A; send(&ctrlZ, sizeof(ctrlZ)); ok = (RESP_OK == waitFinalResp()); } UNLOCK(); return ok; } bool MDMParser::smsDelete(int ix) { bool ok = false; LOCK(); sendFormated("AT+CMGD=%d\r\n",ix); ok = (RESP_OK == waitFinalResp()); UNLOCK(); return ok; } int MDMParser::_cbCMGR(int type, const char* buf, int len, CMGRparam* param) { if (param) { if (type == TYPE_PLUS) { if (sscanf(buf, "\r\n+CMGR: \"%*[^\"]\",\"%[^\"]", param->num) == 1) { } } else if ((type == TYPE_UNKNOWN) && (buf[len-2] == '\r') && (buf[len-1] == '\n')) { memcpy(param->buf, buf, len-2); param->buf[len-2] = '\0'; } } return WAIT; } bool MDMParser::smsRead(int ix, char* num, char* buf, int len) { bool ok = false; LOCK(); CMGRparam param; param.num = num; param.buf = buf; sendFormated("AT+CMGR=%d\r\n",ix); ok = (RESP_OK == waitFinalResp(_cbCMGR, ¶m)); UNLOCK(); return ok; } // ---------------------------------------------------------------- int MDMParser::_cbCUSD(int type, const char* buf, int len, char* resp) { if ((type == TYPE_PLUS) && resp) { // +USD: \"%*[^\"]\",\"%[^\"]\",,\"%*[^\"]\",%d,%d,%d,%d,\"*[^\"]\",%d,%d"..); if (sscanf(buf, "\r\n+CUSD: %*d,\"%[^\"]\",%*d", resp) == 1) { /*nothing*/ } } return WAIT; } bool MDMParser::ussdCommand(const char* cmd, char* buf) { bool ok = false; LOCK(); *buf = '\0'; // 2G/3G devices only sendFormated("AT+CUSD=1,\"%s\"\r\n",cmd); ok = (RESP_OK == waitFinalResp(_cbCUSD, buf)); UNLOCK(); return ok; } // ---------------------------------------------------------------- int MDMParser::_cbUDELFILE(int type, const char* buf, int len, void*) { if ((type == TYPE_ERROR) && strstr(buf, "+CME ERROR: FILE NOT FOUND")) return RESP_OK; // file does not exist, so all ok... return WAIT; } bool MDMParser::delFile(const char* filename) { bool ok = false; LOCK(); sendFormated("AT+UDELFILE=\"%s\"\r\n", filename); ok = (RESP_OK == waitFinalResp(_cbUDELFILE)); UNLOCK(); return ok; } int MDMParser::writeFile(const char* filename, const char* buf, int len) { bool ok = false; LOCK(); sendFormated("AT+UDWNFILE=\"%s\",%d\r\n", filename, len); if (RESP_PROMPT == waitFinalResp()) { send(buf, len); ok = (RESP_OK == waitFinalResp()); } UNLOCK(); return ok ? len : -1; } int MDMParser::readFile(const char* filename, char* buf, int len) { URDFILEparam param; param.filename = filename; param.buf = buf; param.sz = len; param.len = 0; LOCK(); sendFormated("AT+URDFILE=\"%s\"\r\n", filename, len); if (RESP_OK != waitFinalResp(_cbURDFILE, ¶m)) param.len = -1; UNLOCK(); return param.len; } int MDMParser::_cbURDFILE(int type, const char* buf, int len, URDFILEparam* param) { if ((type == TYPE_PLUS) && param && param->filename && param->buf) { char filename[48]; int sz; if ((sscanf(buf, "\r\n+URDFILE: \"%[^\"]\",%d,", filename, &sz) == 2) && (0 == strcmp(param->filename, filename)) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { param->len = (sz < param->sz) ? sz : param->sz; memcpy(param->buf, &buf[len-1-sz], param->len); } } return WAIT; } // ---------------------------------------------------------------- bool MDMParser::setDebug(int level) { #ifdef MDM_DEBUG if ((_debugLevel >= -1) && (level >= -1) && (_debugLevel <= 3) && (level <= 3)) { _debugLevel = level; return true; } #endif return false; } void MDMParser::dumpDevStatus(DevStatus* status) { MDM_INFO("\r\n[ Modem::devStatus ] = = = = = = = = = = = = = ="); const char* txtDev[] = { "Unknown", "SARA-G350", "LISA-U200", "LISA-C200", "SARA-U260", "SARA-U270", "LEON-G200" }; if (status->dev < sizeof(txtDev)/sizeof(*txtDev) && (status->dev != DEV_UNKNOWN)) DEBUG_D(" Device: %s\r\n", txtDev[status->dev]); const char* txtLpm[] = { "Disabled", "Enabled", "Active" }; if (status->lpm < sizeof(txtLpm)/sizeof(*txtLpm)) DEBUG_D(" Power Save: %s\r\n", txtLpm[status->lpm]); const char* txtSim[] = { "Unknown", "Missing", "Pin", "Ready" }; if (status->sim < sizeof(txtSim)/sizeof(*txtSim) && (status->sim != SIM_UNKNOWN)) DEBUG_D(" SIM: %s\r\n", txtSim[status->sim]); if (*status->ccid) DEBUG_D(" CCID: %s\r\n", status->ccid); if (*status->imei) DEBUG_D(" IMEI: %s\r\n", status->imei); if (*status->imsi) DEBUG_D(" IMSI: %s\r\n", status->imsi); if (*status->meid) DEBUG_D(" MEID: %s\r\n", status->meid); // LISA-C if (*status->manu) DEBUG_D(" Manufacturer: %s\r\n", status->manu); if (*status->model) DEBUG_D(" Model: %s\r\n", status->model); if (*status->ver) DEBUG_D(" Version: %s\r\n", status->ver); } void MDMParser::dumpNetStatus(NetStatus *status) { MDM_INFO("\r\n[ Modem::netStatus ] = = = = = = = = = = = = = ="); const char* txtReg[] = { "Unknown", "Denied", "None", "Home", "Roaming" }; if (status->csd < sizeof(txtReg)/sizeof(*txtReg) && (status->csd != REG_UNKNOWN)) DEBUG_D(" CSD Registration: %s\r\n", txtReg[status->csd]); if (status->psd < sizeof(txtReg)/sizeof(*txtReg) && (status->psd != REG_UNKNOWN)) DEBUG_D(" PSD Registration: %s\r\n", txtReg[status->psd]); const char* txtAct[] = { "Unknown", "GSM", "Edge", "3G", "CDMA" }; if (status->act < sizeof(txtAct)/sizeof(*txtAct) && (status->act != ACT_UNKNOWN)) DEBUG_D(" Access Technology: %s\r\n", txtAct[status->act]); if (status->rssi) DEBUG_D(" Signal Strength: %d dBm\r\n", status->rssi); if (status->qual) DEBUG_D(" Signal Quality: %d\r\n", status->qual); if (*status->opr) DEBUG_D(" Operator: %s\r\n", status->opr); if (status->lac != 0xFFFF) DEBUG_D(" Location Area Code: %04X\r\n", status->lac); if (status->ci != 0xFFFFFFFF) DEBUG_D(" Cell ID: %08X\r\n", status->ci); if (*status->num) DEBUG_D(" Phone Number: %s\r\n", status->num); } void MDMParser::dumpIp(MDM_IP ip) { if (ip != NOIP) { DEBUG_D("\r\n[ Modem:IP " IPSTR " ] = = = = = = = = = = = = = =\r\n", IPNUM(ip)); } } // ---------------------------------------------------------------- int MDMParser::_parseMatch(Pipe<char>* pipe, int len, const char* sta, const char* end) { int o = 0; if (sta) { while (*sta) { if (++o > len) return WAIT; char ch = pipe->next(); if (*sta++ != ch) return NOT_FOUND; } } if (!end) return o; // no termination // at least any char if (++o > len) return WAIT; pipe->next(); // check the end int x = 0; while (end[x]) { if (++o > len) return WAIT; char ch = pipe->next(); x = (end[x] == ch) ? x + 1 : (end[0] == ch) ? 1 : 0; } return o; } int MDMParser::_parseFormated(Pipe<char>* pipe, int len, const char* fmt) { int o = 0; int num = 0; if (fmt) { while (*fmt) { if (++o > len) return WAIT; char ch = pipe->next(); if (*fmt == '%') { fmt++; if (*fmt == 'd') { // numeric fmt ++; num = 0; while (ch >= '0' && ch <= '9') { num = num * 10 + (ch - '0'); if (++o > len) return WAIT; ch = pipe->next(); } } else if (*fmt == 'c') { // char buffer (takes last numeric as length) fmt ++; while (num --) { if (++o > len) return WAIT; ch = pipe->next(); } } else if (*fmt == 's') { fmt ++; if (ch != '\"') return NOT_FOUND; do { if (++o > len) return WAIT; ch = pipe->next(); } while (ch != '\"'); if (++o > len) return WAIT; ch = pipe->next(); } } if (*fmt++ != ch) return NOT_FOUND; } } return o; } int MDMParser::_getLine(Pipe<char>* pipe, char* buf, int len) { int unkn = 0; int sz = pipe->size(); int fr = pipe->free(); if (len > sz) len = sz; while (len > 0) { static struct { const char* fmt; int type; } lutF[] = { { "\r\n+USORD: %d,%d,\"%c\"", TYPE_PLUS }, { "\r\n+USORF: %d,\"" IPSTR "\",%d,%d,\"%c\"", TYPE_PLUS }, { "\r\n+URDFILE: %s,%d,\"%c\"", TYPE_PLUS }, }; static struct { const char* sta; const char* end; int type; } lut[] = { { "\r\nOK\r\n", NULL, TYPE_OK }, { "\r\nERROR\r\n", NULL, TYPE_ERROR }, { "\r\n+CME ERROR:", "\r\n", TYPE_ERROR }, { "\r\n+CMS ERROR:", "\r\n", TYPE_ERROR }, { "\r\nRING\r\n", NULL, TYPE_RING }, { "\r\nCONNECT\r\n", NULL, TYPE_CONNECT }, { "\r\nNO CARRIER\r\n", NULL, TYPE_NOCARRIER }, { "\r\nNO DIALTONE\r\n", NULL, TYPE_NODIALTONE }, { "\r\nBUSY\r\n", NULL, TYPE_BUSY }, { "\r\nNO ANSWER\r\n", NULL, TYPE_NOANSWER }, { "\r\n+", "\r\n", TYPE_PLUS }, { "\r\n@", NULL, TYPE_PROMPT }, // Sockets { "\r\n>", NULL, TYPE_PROMPT }, // SMS { "\n>", NULL, TYPE_PROMPT }, // File { "\r\nABORTED\r\n", NULL, TYPE_ABORTED }, // Current command aborted { "\r\n\r\n", "\r\n", TYPE_DBLNEWLINE }, // Double CRLF detected { "\r\n", "\r\n", TYPE_UNKNOWN }, // If all else fails, break up generic strings }; for (int i = 0; i < (int)(sizeof(lutF)/sizeof(*lutF)); i ++) { pipe->set(unkn); int ln = _parseFormated(pipe, len, lutF[i].fmt); if (ln == WAIT && fr) { return WAIT; } if ((ln != NOT_FOUND) && (unkn > 0)) { return TYPE_UNKNOWN | pipe->get(buf, unkn); } if (ln > 0) { return lutF[i].type | pipe->get(buf, ln); } } for (int i = 0; i < (int)(sizeof(lut)/sizeof(*lut)); i ++) { pipe->set(unkn); int ln = _parseMatch(pipe, len, lut[i].sta, lut[i].end); if (ln == WAIT && fr) { return WAIT; } // Double CRLF detected, discard it. // This resolves a case on G350 where "\r\n" is generated after +USORF response, but missing // on U260/U270, which would otherwise generate "\r\n\r\nOK\r\n" which is not parseable. if ((ln > 0) && (lut[i].type == TYPE_DBLNEWLINE) && (unkn == 0)) { return TYPE_UNKNOWN | pipe->get(buf, 2); } if ((ln != NOT_FOUND) && (unkn > 0)) { return TYPE_UNKNOWN | pipe->get(buf, unkn); } if (ln > 0) { return lut[i].type | pipe->get(buf, ln); } } // UNKNOWN unkn ++; len--; } return WAIT; } // ---------------------------------------------------------------- // Electron Serial Implementation // ---------------------------------------------------------------- MDMElectronSerial::MDMElectronSerial(int rxSize /*= 256*/, int txSize /*= 256*/) : ElectronSerialPipe(rxSize, txSize) { #ifdef MDM_DEBUG //_debugLevel = -1; #endif // Important to set _dev.lpm = LPM_ENABLED; when HW FLOW CONTROL enabled. } MDMElectronSerial::~MDMElectronSerial(void) { powerOff(); } int MDMElectronSerial::_send(const void* buf, int len) { return put((const char*)buf, len, true/*=blocking*/); } int MDMElectronSerial::getLine(char* buffer, int length) { int ret = _getLine(&_pipeRx, buffer, length); rxResume(); return ret; } void MDMElectronSerial::pause() { LOCK(); rxPause(); } void MDMElectronSerial::resumeRecv() { LOCK(); rxResume(); } #endif // !defined(HAL_CELLULAR_EXCLUDE)
変更されたテキスト
ファイルを開く
/* ****************************************************************************** * Copyright (c) 2015 Particle Industries, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see <http://www.gnu.org/licenses/>. ****************************************************************************** */ #ifndef HAL_CELLULAR_EXCLUDE /* Includes -----------------------------------------------------------------*/ #include <stdio.h> #include <stdint.h> #include <stdarg.h> #include <string.h> #include "mdm_hal.h" #include "timer_hal.h" #include "delay_hal.h" #include "pinmap_hal.h" #include "pinmap_impl.h" #include "gpio_hal.h" #include "mdmapn_hal.h" #include "stm32f2xx.h" #include "dns_client.h" #include "service_debug.h" #include "bytes2hexbuf.h" #include "hex_to_bytes.h" #include "concurrent_hal.h" #include <mutex> #include "net_hal.h" #include <limits> std::recursive_mutex mdm_mutex; /* Private typedef ----------------------------------------------------------*/ /* Private define -----------------------------------------------------------*/ /* Private macro ------------------------------------------------------------*/ #define PROFILE "0" //!< this is the psd profile used #define MAX_SIZE 1024 //!< max expected messages (used with RX) #define USO_MAX_WRITE 1024 //!< maximum number of bytes to write to socket (used with TX) // ID of the PDP context used to configure the default EPS bearer when registering in an LTE network // Note: There are no PDP contexts in LTE, SARA-R4 uses this naming for the sake of simplicity #define PDP_CONTEXT 1 // Enable hex mode for socket commands. SARA-R410M-01B has a bug which causes truncation of // data read from a socket if the data contains a null byte // #define SOCKET_HEX_MODE // Timeout for socket write operations #define SOCKET_WRITE_TIMEOUT 30000 // Timeout for +COPS command #define COPS_TIMEOUT (3 * 60 * 1000) // num sockets #define NUMSOCKETS ((int)(sizeof(_sockets)/sizeof(*_sockets))) //! test if it is a socket is ok to use #define ISSOCKET(s) (((s) >= 0) && ((s) < NUMSOCKETS) && (_sockets[s].handle != MDM_SOCKET_ERROR)) //! check for timeout #define TIMEOUT(t, ms) ((ms != TIMEOUT_BLOCKING) && ((HAL_Timer_Get_Milli_Seconds() - t) > ms)) //! registration ok check helper #define REG_OK(r) ((r == REG_HOME) || (r == REG_ROAMING)) //! registration done check helper (no need to poll further) #define REG_DONE(r) ((r == REG_HOME) || (r == REG_ROAMING) || (r == REG_DENIED)) //! helper to make sure that lock unlock pair is always balanced #define LOCK() std::lock_guard<std::recursive_mutex> __mdm_guard(mdm_mutex); //! helper to make sure that lock unlock pair is always balanced #define UNLOCK() static volatile uint32_t gprs_timeout_start; static volatile uint32_t gprs_timeout_duration; inline void ARM_GPRS_TIMEOUT(uint32_t dur) { gprs_timeout_start = HAL_Timer_Get_Milli_Seconds(); gprs_timeout_duration = dur; DEBUG("GPRS WD Set %d",(dur)); } inline bool IS_GPRS_TIMEOUT() { return gprs_timeout_duration && ((HAL_Timer_Get_Milli_Seconds()-gprs_timeout_start)>gprs_timeout_duration); } inline void CLR_GPRS_TIMEOUT() { gprs_timeout_duration = 0; DEBUG("GPRS WD Cleared, was %d", gprs_timeout_duration); } #ifdef MDM_DEBUG #if 0 // colored terminal output using ANSI escape sequences #define COL(c) "\033[" c #else #define COL(c) "" #endif #define DEF COL("39m") #define BLA COL("30m") #define RED COL("31m") #define GRE COL("32m") #define YEL COL("33m") #define BLU COL("34m") #define MAG COL("35m") #define CYA COL("36m") #define WHY COL("37m") void dumpAtCmd(const char* buf, int len) { DEBUG_D(" %3d \"", len); while (len --) { char ch = *buf++; if ((ch > 0x1F) && (ch < 0x7F)) { // is printable if (ch == '%') DEBUG_D("%%"); else if (ch == '"') DEBUG_D("\\\""); else if (ch == '\\') DEBUG_D("\\\\"); else DEBUG_D("%c", ch); } else { if (ch == '\a') DEBUG_D("\\a"); // BEL (0x07) else if (ch == '\b') DEBUG_D("\\b"); // Backspace (0x08) else if (ch == '\t') DEBUG_D("\\t"); // Horizontal Tab (0x09) else if (ch == '\n') DEBUG_D("\\n"); // Linefeed (0x0A) else if (ch == '\v') DEBUG_D("\\v"); // Vertical Tab (0x0B) else if (ch == '\f') DEBUG_D("\\f"); // Formfeed (0x0C) else if (ch == '\r') DEBUG_D("\\r"); // Carriage Return (0x0D) else DEBUG_D("\\x%02x", (unsigned char)ch); } } DEBUG_D("\"\r\n"); } void MDMParser::_debugPrint(int level, const char* color, const char* format, ...) { if (_debugLevel >= level) { if (color) DEBUG_D(color); va_list args; va_start(args, format); log_printf_v(LOG_LEVEL_TRACE, LOG_THIS_CATEGORY(), nullptr, format, args); va_end(args); if (color) DEBUG_D(DEF); DEBUG_D("\r\n"); } } #define MDM_ERROR(_fmt, ...) do {_debugPrint(0, RED, _fmt, ##__VA_ARGS__);}while(0) #define MDM_INFO(_fmt, ...) do {_debugPrint(1, GRE, _fmt, ##__VA_ARGS__);}while(0) #define MDM_TRACE(_fmt, ...) do {_debugPrint(2, DEF, _fmt, ##__VA_ARGS__);}while(0) #define MDM_TEST(_fmt, ...) do {_debugPrint(3, CYA, _fmt, ##__VA_ARGS__);}while(0) #else #define MDM_ERROR(...) // no tracing #define MDM_TEST(...) // no tracing #define MDM_INFO(...) // no tracing #define MDM_TRACE(...) // no tracing #endif /* Private variables --------------------------------------------------------*/ MDMParser* MDMParser::inst; /* Extern variables ---------------------------------------------------------*/ /* Private function prototypes ----------------------------------------------*/ MDMParser::MDMParser(void) { inst = this; memset(&_dev, 0, sizeof(_dev)); memset(&_net, 0, sizeof(_net)); _net.lac = 0xFFFF; _net.ci = 0xFFFFFFFF; _ip = NOIP; _init = false; _pwr = false; _activated = false; _attached = false; _attached_urc = false; // updated by GPRS detached/attached URC, // used to notify system of prolonged GPRS detach. _power_mode = 1; // default power mode is AT+UPSV=1 _cancel_all_operations = false; sms_cb = NULL; memset(_sockets, 0, sizeof(_sockets)); for (int socket = 0; socket < NUMSOCKETS; socket ++) _sockets[socket].handle = MDM_SOCKET_ERROR; #ifdef MDM_DEBUG _debugLevel = 3; _debugTime = HAL_Timer_Get_Milli_Seconds(); #endif } void MDMParser::setPowerMode(int mode) { _power_mode = mode; } void MDMParser::cancel(void) { MDM_INFO("\r\n[ Modem::cancel ] = = = = = = = = = = = = = = ="); _cancel_all_operations = true; } void MDMParser::resume(void) { MDM_INFO("\r\n[ Modem::resume ] = = = = = = = = = = = = = = ="); _cancel_all_operations = false; } void MDMParser::setSMSreceivedHandler(_CELLULAR_SMS_CB cb, void* data) { sms_cb = cb; sms_data = data; } void MDMParser::SMSreceived(int index) { sms_cb(sms_data, index); // call the SMS callback with the index of the new SMS } int MDMParser::send(const char* buf, int len) { #ifdef MDM_DEBUG if (_debugLevel >= 3) { DEBUG_D("%10.3f AT send ", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001); dumpAtCmd(buf,len); } #endif return _send(buf, len); } int MDMParser::sendFormated(const char* format, ...) { va_list args; va_start(args, format); const int ret = sendFormattedWithArgs(format, args); va_end(args); return ret; } int MDMParser::sendFormattedWithArgs(const char* format, va_list args) { if (_cancel_all_operations) { return 0; } va_list argsCopy; va_copy(argsCopy, args); char buf[128]; int n = vsnprintf(buf, sizeof(buf), format, args); if (n >= 0) { if ((size_t)n < sizeof(buf)) { n = send(buf, n); } else { char buf[n + 1]; // Use larger buffer n = vsnprintf(buf, sizeof(buf), format, argsCopy); if (n >= 0) { n = send(buf, n); } } } va_end(argsCopy); return n; } int MDMParser::waitFinalResp(_CALLBACKPTR cb /* = NULL*/, void* param /* = NULL*/, system_tick_t timeout_ms /*= 5000*/) { if (_cancel_all_operations) return WAIT; // If we went from a GPRS attached state to detached via URC, // a WDT was set and now expired. Notify system of disconnect. if (IS_GPRS_TIMEOUT()) { _ip = NOIP; _attached = false; CLR_GPRS_TIMEOUT(); // HAL_NET_notify_dhcp(false); HAL_NET_notify_disconnected(); } char buf[MAX_SIZE + 64 /* add some more space for framing */]; system_tick_t start = HAL_Timer_Get_Milli_Seconds(); do { int ret = getLine(buf, sizeof(buf)); #ifdef MDM_DEBUG if ((_debugLevel >= 3) && (ret != WAIT) && (ret != NOT_FOUND)) { int len = LENGTH(ret); int type = TYPE(ret); const char* s = (type == TYPE_UNKNOWN)? YEL "UNK" DEF : (type == TYPE_TEXT) ? MAG "TXT" DEF : (type == TYPE_OK ) ? GRE "OK " DEF : (type == TYPE_ERROR) ? RED "ERR" DEF : (type == TYPE_ABORTED) ? RED "ABT" DEF : (type == TYPE_PLUS) ? CYA " + " DEF : (type == TYPE_PROMPT) ? BLU " > " DEF : "..." ; DEBUG_D("%10.3f AT read %s", (HAL_Timer_Get_Milli_Seconds()-_debugTime)*0.001, s); dumpAtCmd(buf, len); (void)s; } #endif if ((ret != WAIT) && (ret != NOT_FOUND)) { int type = TYPE(ret); // handle unsolicited commands here if (type == TYPE_PLUS) { const char* cmd = buf+3; int a, b, c, d, r; char s[32]; // SMS Command --------------------------------- // +CNMI: <mem>,<index> if (sscanf(cmd, "CMTI: \"%*[^\"]\",%d", &a) == 1) { DEBUG_D("New SMS at index %d\r\n", a); if (sms_cb) SMSreceived(a); } else if ((sscanf(cmd, "CIEV: 9,%d", &a) == 1)) { DEBUG_D("CIEV matched: 9,%d\r\n", a); // Wait until the system is attached before attempting to act on GPRS detach if (_attached) { _attached_urc = (a==2)?1:0; if (!_attached_urc) ARM_GPRS_TIMEOUT(15*1000); // If detached, set WDT else CLR_GPRS_TIMEOUT(); // else if re-attached clear WDT. } // Socket Specific Command --------------------------------- // +USORD: <socket>,<length> } else if ((sscanf(cmd, "USORD: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSORD: <socket>,<length> } else if ((sscanf(cmd, "UUSORD: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +USORF: <socket>,<length> } else if ((sscanf(cmd, "USORF: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSORF: <socket>,<length> } else if ((sscanf(cmd, "UUSORF: %d,%d", &a, &b) == 2)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d has %d bytes pending\r\n", socket, a, b); if (socket != MDM_SOCKET_ERROR) _sockets[socket].pending = b; // +UUSOCL: <socket> } else if ((sscanf(cmd, "UUSOCL: %d", &a) == 1)) { int socket = _findSocket(a); DEBUG_D("Socket %d: handle %d closed by remote host\r\n", socket, a); if (socket != MDM_SOCKET_ERROR) { _socketFree(socket); } } // GSM/UMTS Specific ------------------------------------------- // +UUPSDD: <profile_id> if (sscanf(cmd, "UUPSDD: %s", s) == 1) { DEBUG_D("UUPSDD: %s matched\r\n", PROFILE); if ( !strcmp(s, PROFILE) ) { _ip = NOIP; _attached = false; DEBUG("PDP context deactivated remotely!\r\n"); // PDP context was remotely deactivated via URC, // Notify system of disconnect. HAL_NET_notify_dhcp(false); } } else { // +CREG|CGREG: <n>,<stat>[,<lac>,<ci>[,AcT[,<rac>]]] // reply to AT+CREG|AT+CGREG // +CREG|CGREG: <stat>[,<lac>,<ci>[,AcT[,<rac>]]] // URC b = (int)0xFFFF; c = (int)0xFFFFFFFF; d = -1; r = sscanf(cmd, "%s %*d,%d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d); if (r <= 1) r = sscanf(cmd, "%s %d,\"%x\",\"%x\",%d",s,&a,&b,&c,&d); if (r >= 2) { Reg *reg = !strcmp(s, "CREG:") ? &_net.csd : !strcmp(s, "CGREG:") ? &_net.psd : !strcmp(s, "CEREG:") ? &_net.eps : NULL; if (reg) { // network status if (a == 0) *reg = REG_NONE; // 0: not registered, home network else if (a == 1) *reg = REG_HOME; // 1: registered, home network else if (a == 2) *reg = REG_NONE; // 2: not registered, but MT is currently searching a new operator to register to else if (a == 3) *reg = REG_DENIED; // 3: registration denied else if (a == 4) *reg = REG_UNKNOWN; // 4: unknown else if (a == 5) *reg = REG_ROAMING; // 5: registered, roaming if ((r >= 3) && (b != (int)0xFFFF)) _net.lac = b; // location area code if ((r >= 4) && (c != (int)0xFFFFFFFF)) _net.ci = c; // cell ID // access technology if (r >= 5) { if (d == 0) _net.act = ACT_GSM; // 0: GSM else if (d == 1) _net.act = ACT_GSM; // 1: GSM COMPACT else if (d == 2) _net.act = ACT_UTRAN; // 2: UTRAN else if (d == 3) _net.act = ACT_EDGE; // 3: GSM with EDGE availability else if (d == 4) _net.act = ACT_UTRAN; // 4: UTRAN with HSDPA availability else if (d == 5) _net.act = ACT_UTRAN; // 5: UTRAN with HSUPA availability else if (d == 6) _net.act = ACT_UTRAN; // 6: UTRAN with HSDPA and HSUPA availability else if (d == 7) _net.act = ACT_LTE; // 7: LTE else if (d == 8) _net.act = ACT_LTE_CAT_M1; // 8: LTE Cat M1 else if (d == 9) _net.act = ACT_LTE_CAT_NB1; // 9: LTE Cat NB1 } } } } } // end ==TYPE_PLUS if (cb) { int len = LENGTH(ret); int ret = cb(type, buf, len, param); if (WAIT != ret) return ret; } if (type == TYPE_OK) return RESP_OK; if (type == TYPE_ERROR) return RESP_ERROR; if (type == TYPE_PROMPT) return RESP_PROMPT; if (type == TYPE_ABORTED) return RESP_ABORTED; // This means the current command was ABORTED, so retry your command if critical. } // relax a bit HAL_Delay_Milliseconds(10); } while (!TIMEOUT(start, timeout_ms) && !_cancel_all_operations); return WAIT; } int MDMParser::sendCommandWithArgs(const char* fmt, va_list args, _CALLBACKPTR cb, void* param, system_tick_t timeout) { LOCK(); sendFormattedWithArgs(fmt, args); const int ret = waitFinalResp(cb, param, timeout); UNLOCK(); return ret; } void MDMParser::lock() { mdm_mutex.lock(); } void MDMParser::unlock() { mdm_mutex.unlock(); } int MDMParser::_cbString(int type, const char* buf, int len, char* str) { if (str && (type == TYPE_UNKNOWN)) { if (sscanf(buf, "\r\n%s\r\n", str) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbInt(int type, const char* buf, int len, int* val) { if (val && (type == TYPE_UNKNOWN)) { if (sscanf(buf, "\r\n%d\r\n", val) == 1) /*nothing*/; } return WAIT; } // ---------------------------------------------------------------- bool MDMParser::connect( const char* apn, const char* username, const char* password, Auth auth) { bool ok = registerNet(apn); /* #ifdef MDM_DEBUG if (_debugLevel >= 1) { dumpNetStatus(&_net); } #endif */ if (!ok) { return false; } ok = pdp(apn); /* #ifdef MDM_DEBUG if (_debugLevel >= 1) { dumpNetStatus(&_net); } #endif */ if (!ok) { return false; } const MDM_IP ip = join(apn, username, password, auth); /* #ifdef MDM_DEBUG if (_debugLevel >= 1) { dumpIp(ip); } #endif */ if (ip == NOIP) { return false; } HAL_NET_notify_connected(); HAL_NET_notify_dhcp(true); return true; } bool MDMParser::disconnect() { if (!deactivate()) { return false; } if (!detach()) { return false; } HAL_NET_notify_disconnected(); return true; } void MDMParser::reset(void) { MDM_INFO("[ Modem reset ]"); unsigned delay = 100; if (_dev.dev == DEV_UNKNOWN || _dev.dev == DEV_SARA_R410) { delay = 10000; // SARA-R4: 10s } HAL_GPIO_Write(RESET_UC, 0); HAL_Delay_Milliseconds(delay); HAL_GPIO_Write(RESET_UC, 1); } bool MDMParser::_powerOn(void) { LOCK(); /* Initialize I/O */ STM32_Pin_Info* PIN_MAP_PARSER = HAL_Pin_Map(); // This pin tends to stay low when floating on the output of the buffer (PWR_UB) // It shouldn't hurt if it goes low temporarily on STM32 boot, but strange behavior // was noticed when it was left to do whatever it wanted. By adding a 100k pull up // resistor all flakey behavior has ceased (i.e., the modem had previously stopped // responding to AT commands). This is how we set it HIGH before enabling the OUTPUT. PIN_MAP_PARSER[PWR_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[PWR_UC].gpio_pin; HAL_Pin_Mode(PWR_UC, OUTPUT); // This pin tends to stay high when floating on the output of the buffer (RESET_UB), // but we need to ensure it gets set high before being set to an OUTPUT. // If this pin goes LOW, the modem will be reset and all configuration will be lost. PIN_MAP_PARSER[RESET_UC].gpio_peripheral->BSRRL = PIN_MAP_PARSER[RESET_UC].gpio_pin; HAL_Pin_Mode(RESET_UC, OUTPUT); _dev.dev = DEV_UNKNOWN; _dev.lpm = LPM_ENABLED; HAL_Pin_Mode(LVLOE_UC, OUTPUT); HAL_GPIO_Write(LVLOE_UC, 0); if (!_init) { MDM_INFO("[ ElectronSerialPipe::begin ] = = = = = = = ="); // Here we initialize the UART with hardware flow control enabled, even though some of // the modems don't support it (SARA-R4 at the time of writing). It is assumed that the // modem still keeps the CTS pin in a correct state even if doesn't support the CTS/RTS // flow control electronMDM.begin(115200, true /* hwFlowControl */); _init = true; } MDM_INFO("\r\n[ Modem::powerOn ] = = = = = = = = = = = = = ="); bool continue_cancel = false; bool retried_after_reset = false; int i = 10; while (i--) { // SARA-U2/LISA-U2 50..80us HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(50); HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(10); // SARA-G35 >5ms, LISA-C2 > 150ms, LEON-G2 >5ms, SARA-R4 >= 150ms HAL_GPIO_Write(PWR_UC, 0); HAL_Delay_Milliseconds(150); HAL_GPIO_Write(PWR_UC, 1); HAL_Delay_Milliseconds(100); // purge any messages purge(); // Save desire to cancel, but since we are already here // trying to power up the modem when we received a cancel // resume AT parser to ensure it's ready to receive // power down commands. if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can talk to the modem } // check interface sendFormated("AT\r\n"); int r = waitFinalResp(NULL,NULL,1000); if(RESP_OK == r) { _pwr = true; break; } else if (i==0 && !retried_after_reset) { retried_after_reset = true; // only perform reset & retry sequence once i = 10; reset(); } } if (i < 0) { MDM_ERROR("[ No Reply from Modem ]\r\n"); } else { // Determine type of the modem sendFormated("AT+CGMM\r\n"); waitFinalResp(_cbCGMM, &_dev); if (_dev.dev == DEV_SARA_R410) { // SARA-R410 doesn't support hardware flow control, reinitialize the UART electronMDM.begin(115200, false /* hwFlowControl */); // Power saving modes defined by the +UPSV command are not supported _dev.lpm = LPM_DISABLED; } } if (continue_cancel) { cancel(); goto failure; } // Flush any on-boot URCs that can cause syncing issues later waitFinalResp(NULL,NULL,200); // echo off sendFormated("AT E0\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // enable verbose error messages sendFormated("AT+CMEE=2\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // Configures sending of URCs from MT to DTE for indications sendFormated("AT+CMER=1,0,0,2,1\r\n"); if(RESP_OK != waitFinalResp()) goto failure; // set baud rate sendFormated("AT+IPR=115200\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // wait some time until baudrate is applied HAL_Delay_Milliseconds(100); // SARA-G > 40ms UNLOCK(); return true; failure: UNLOCK(); return false; } bool MDMParser::powerOn(const char* simpin) { LOCK(); memset(&_dev, 0, sizeof(_dev)); bool retried_after_reset = false; /* Power on the modem and perform basic initialization */ if (!_powerOn()) goto failure; /* The ATI command is undocumented, and in practice the response * time varies greatly. On inital power-on of the module, ATI * will respond with "OK" before a device type number, which * requires wasting time in a for() loop to solve. * Instead, use AT+CGMM and _dev.model for future use of module identification. * * identify the module * sendFormated("ATI\r\n"); * if (RESP_OK != waitFinalResp(_cbATI, &_dev.dev)) * goto failure; * if (_dev.dev == DEV_UNKNOWN) * goto failure; */ // check the sim card for (int i = 0; (i < 5) && (_dev.sim != SIM_READY) && !_cancel_all_operations; i++) { sendFormated("AT+CPIN?\r\n"); int ret = waitFinalResp(_cbCPIN, &_dev.sim); // having an error here is ok (sim may still be initializing) if ((RESP_OK != ret) && (RESP_ERROR != ret)) { goto failure; } else if (i==4 && (RESP_OK != ret) && !retried_after_reset) { retried_after_reset = true; // only perform reset & retry sequence once i = 0; if(!powerOff()) reset(); /* Power on the modem and perform basic initialization again */ if (!_powerOn()) goto failure; } // Enter PIN if needed if (_dev.sim == SIM_PIN) { if (!simpin) { MDM_ERROR("SIM PIN not available\r\n"); goto failure; } sendFormated("AT+CPIN=%s\r\n", simpin); if (RESP_OK != waitFinalResp(_cbCPIN, &_dev.sim)) goto failure; } else if (_dev.sim != SIM_READY) { // wait for up to one second while looking for slow "+CPIN: READY" URCs waitFinalResp(_cbCPIN, &_dev.sim, 1000); } } if (_dev.sim != SIM_READY) { if (_dev.sim == SIM_MISSING) { MDM_ERROR("SIM not inserted\r\n"); } goto failure; } if (_dev.dev == DEV_UNKNOWN) { MDM_ERROR("Unknown modem type"); goto failure; } UNLOCK(); return true; failure: if (_cancel_all_operations) { // fake out the has_credentials() function so we don't end up in listening mode _dev.sim = SIM_READY; // return true to prevent from entering Listening Mode // UNLOCK(); // return true; } UNLOCK(); return false; } bool MDMParser::init(DevStatus* status) { LOCK(); MDM_INFO("\r\n[ Modem::init ] = = = = = = = = = = = = = = ="); if (_dev.dev == DEV_SARA_R410) { // TODO: Without this delay, some commands, such as +CIMI, may return a SIM failure error. // This probably has something to do with the SIM initialization. Should we check the SIM // status via +USIMSTAT in addition to +CPIN? HAL_Delay_Milliseconds(250); } // Returns the product serial number, IMEI (International Mobile Equipment Identity) sendFormated("AT+CGSN\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.imei)) goto failure; if (_dev.sim != SIM_READY) { if (_dev.sim == SIM_MISSING) MDM_ERROR("SIM not inserted\r\n"); goto failure; } // get the manufacturer sendFormated("AT+CGMI\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.manu)) goto failure; // get the version sendFormated("AT+CGMR\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.ver)) goto failure; // Returns the ICCID (Integrated Circuit Card ID) of the SIM-card. // ICCID is a serial number identifying the SIM. sendFormated("AT+CCID\r\n"); if (RESP_OK != waitFinalResp(_cbCCID, _dev.ccid)) goto failure; // enable power saving if (_dev.lpm != LPM_DISABLED) { // enable power saving (requires flow control, cts at least) sendFormated("AT+UPSV=%d\r\n", _power_mode); if (RESP_OK != waitFinalResp()) goto failure; if (_power_mode != 0) { _dev.lpm = LPM_ACTIVE; } } // Setup SMS in text mode sendFormated("AT+CMGF=1\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // setup new message indication sendFormated("AT+CNMI=2,1\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // Request IMSI (International Mobile Subscriber Identification) sendFormated("AT+CIMI\r\n"); if (RESP_OK != waitFinalResp(_cbString, _dev.imsi)) goto failure; #ifdef SOCKET_HEX_MODE // Enable hex mode for socket commands sendFormated("AT+UDCONF=1,1\r\n"); if (waitFinalResp() != RESP_OK) { goto failure; } #endif if (status) memcpy(status, &_dev, sizeof(DevStatus)); UNLOCK(); return true; failure: UNLOCK(); return false; } bool MDMParser::powerOff(void) { LOCK(); bool ok = false; bool continue_cancel = false; if (_init && _pwr) { MDM_INFO("\r\n[ Modem::powerOff ] = = = = = = = = = = = = = ="); if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } for (int i=0; i<3; i++) { // try 3 times sendFormated("AT+CPWROFF\r\n"); int ret = waitFinalResp(NULL,NULL,40*1000); if (RESP_OK == ret) { _pwr = false; // todo - add if these are automatically done on power down //_activated = false; //_attached = false; // Give the modem some time to switch off cleanly HAL_Delay_Milliseconds(1000); ok = true; break; } else if (RESP_ABORTED == ret) { MDM_INFO("\r\n[ Modem::powerOff ] found ABORTED, retrying..."); } else { MDM_INFO("\r\n[ Modem::powerOff ] timeout, retrying..."); } } } // Close serial connection electronMDM.end(); _init = false; HAL_Pin_Mode(PWR_UC, INPUT); HAL_Pin_Mode(RESET_UC, INPUT); HAL_Pin_Mode(LVLOE_UC, INPUT); if (continue_cancel) cancel(); UNLOCK(); return ok; } int MDMParser::_cbCGMM(int type, const char* buf, int len, DevStatus* s) { if (type == TYPE_UNKNOWN && s) { static_assert(sizeof(DevStatus::model) == 16, "The format string below needs to be updated accordingly"); if (sscanf(buf, "\r\n%15s\r\n", s->model) == 1) { if (strstr(s->model, "SARA-G350")) { s->dev = DEV_SARA_G350; } else if (strstr(s->model, "SARA-U260")) { s->dev = DEV_SARA_U260; } else if (strstr(s->model, "SARA-U270")) { s->dev = DEV_SARA_U270; } else if (strstr(s->model, "SARA-U201")) { s->dev = DEV_SARA_U201; } else if (strstr(s->model, "SARA-R410")) { s->dev = DEV_SARA_R410; } } } return WAIT; } int MDMParser::_cbCPIN(int type, const char* buf, int len, Sim* sim) { if (sim) { if (type == TYPE_PLUS){ char s[16]; if (sscanf(buf, "\r\n+CPIN: %[^\r]\r\n", s) >= 1) *sim = (0 == strcmp("READY", s)) ? SIM_READY : SIM_PIN; } else if (type == TYPE_ERROR) { if (strstr(buf, "+CME ERROR: SIM not inserted")) *sim = SIM_MISSING; } } return WAIT; } int MDMParser::_cbCCID(int type, const char* buf, int len, char* ccid) { if ((type == TYPE_PLUS) && ccid) { if (sscanf(buf, "\r\n+CCID: %[^\r]\r\n", ccid) == 1) { //DEBUG_D("Got CCID: %s\r\n", ccid); } } return WAIT; } bool MDMParser::registerNet(const char* apn, NetStatus* status /*= NULL*/, system_tick_t timeout_ms /*= 180000*/) { LOCK(); if (_init && _pwr && _dev.dev != DEV_UNKNOWN) { MDM_INFO("\r\n[ Modem::register ] = = = = = = = = = = = = = ="); // Check to see if we are already connected. If so don't issue these // commands as they will knock us off the cellular network. bool ok = false; if (!(ok = checkNetStatus())) { #ifdef MDM_DEBUG // Show enabled RATs sendFormated("AT+URAT?\r\n"); waitFinalResp(); #endif // defined(MDM_DEBUG) if (_dev.dev == DEV_SARA_R410) { // Get default context settings sendFormated("AT+CGDCONT?\r\n"); CGDCONTparam ctx = {}; if (waitFinalResp(_cbCGDCONT, &ctx) != RESP_OK) { goto failure; } // TODO: SARA-R410-01B modules come preconfigured with AT&T's APN ("broadband"), which may // cause network registration issues with MVNO providers and third party SIM cards. As a // workaround the code below sets a blank APN if it detects that the current context is // configured to use the dual stack IPv4/IPv6 capability ("IPV4V6"), which is the case for // the factory default settings. Ideally, setting of a default APN should be based on IMSI if (strcmp(ctx.type, "IP") != 0 || strcmp(ctx.apn, apn ? apn : "") != 0) { // Stop the network registration and update the context settings sendFormated("AT+COPS=2\r\n"); if (waitFinalResp(nullptr, nullptr, COPS_TIMEOUT) != RESP_OK) { goto failure; } sendFormated("AT+CGDCONT=%d,\"IP\",\"%s\"\r\n", PDP_CONTEXT, apn ? apn : ""); if (waitFinalResp() != RESP_OK) { goto failure; } } // Make sure automatic network registration is enabled sendFormated("AT+COPS=0\r\n"); if (waitFinalResp(nullptr, nullptr, COPS_TIMEOUT) != RESP_OK) { goto failure; } // Set up the EPS network registration URC sendFormated("AT+CEREG=2\r\n"); if (waitFinalResp() != RESP_OK) { goto failure; } } else { // setup the GPRS network registration URC (Unsolicited Response Code) // 0: (default value and factory-programmed value): network registration URC disabled // 1: network registration URC enabled // 2: network registration and location information URC enabled sendFormated("AT+CGREG=2\r\n"); if (RESP_OK != waitFinalResp()) goto failure; // setup the network registration URC (Unsolicited Response Code) // 0: (default value and factory-programmed value): network registration URC disabled // 1: network registration URC enabled // 2: network registration and location information URC enabled sendFormated("AT+CREG=2\r\n"); if (RESP_OK != waitFinalResp()) goto failure; } // Now check every 15 seconds for 5 minutes to see if we're connected to the tower (GSM and GPRS) system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (!(ok = checkNetStatus(status)) && !TIMEOUT(start, timeout_ms) && !_cancel_all_operations) { system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while ((HAL_Timer_Get_Milli_Seconds() - start < 15000UL) && !_cancel_all_operations); // just wait //HAL_Delay_Milliseconds(15000); } if (_net.csd == REG_DENIED) MDM_ERROR("CSD Registration Denied\r\n"); if (_net.psd == REG_DENIED) MDM_ERROR("PSD Registration Denied\r\n"); if (_net.eps == REG_DENIED) MDM_ERROR("EPS Registration Denied\r\n"); // if (_net.csd == REG_DENIED || _net.psd == REG_DENIED) { // sendFormated("AT+CEER\r\n"); // waitFinalResp(); // } } UNLOCK(); return ok; } failure: UNLOCK(); return false; } bool MDMParser::checkNetStatus(NetStatus* status /*= NULL*/) { bool ok = false; LOCK(); memset(&_net, 0, sizeof(_net)); _net.lac = 0xFFFF; _net.ci = 0xFFFFFFFF; if (_dev.dev == DEV_SARA_R410) { // check EPS registration sendFormated("AT+CEREG?\r\n"); waitFinalResp(); } else { // check registration sendFormated("AT+CREG?\r\n"); waitFinalResp(); // don't fail as service could be not subscribed // check PSD registration sendFormated("AT+CGREG?\r\n"); waitFinalResp(); // don't fail as service could be not subscribed } if (REG_OK(_net.csd) || REG_OK(_net.psd) || REG_OK(_net.eps)) { sendFormated("AT+COPS?\r\n"); if (RESP_OK != waitFinalResp(_cbCOPS, &_net)) goto failure; // get the MSISDNs related to this subscriber sendFormated("AT+CNUM\r\n"); if (RESP_OK != waitFinalResp(_cbCNUM, _net.num)) goto failure; // get the signal strength indication sendFormated("AT+CSQ\r\n"); if (RESP_OK != waitFinalResp(_cbCSQ, &_net)) goto failure; } if (status) { memcpy(status, &_net, sizeof(NetStatus)); } // don't return true until fully registered if (_dev.dev == DEV_SARA_R410) { ok = REG_OK(_net.eps); } else { ok = REG_OK(_net.csd) && REG_OK(_net.psd); } UNLOCK(); return ok; failure: UNLOCK(); return false; } bool MDMParser::getSignalStrength(NetStatus &status) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::getSignalStrength ] = = = = = = = = = ="); sendFormated("AT+CSQ\r\n"); if (RESP_OK == waitFinalResp(_cbCSQ, &_net)) { ok = true; status = _net; } } UNLOCK(); return ok; } bool MDMParser::getDataUsage(MDM_DataUsage &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::getDataUsage ] = = = = = = = = = ="); sendFormated("AT+UGCNTRD\r\n"); if (RESP_OK == waitFinalResp(_cbUGCNTRD, &_data_usage)) { ok = true; data.cid = _data_usage.cid; data.tx_session = _data_usage.tx_session; data.rx_session = _data_usage.rx_session; data.tx_total = _data_usage.tx_total; data.rx_total = _data_usage.rx_total; } } UNLOCK(); return ok; } void MDMParser::_setBandSelectString(MDM_BandSelect &data, char* bands, int index /*= 0*/) { char band[5]; for (int x=index; x<data.count; x++) { sprintf(band, "%d", data.band[x]); strcat(bands, band); if ((x+1) < data.count) strcat(bands, ","); } } bool MDMParser::setBandSelect(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_INFO("\r\n[ Modem::setBandSelect ] = = = = = = = = = ="); char bands_to_set[22] = ""; _setBandSelectString(data, bands_to_set, 0); if (strcmp(bands_to_set,"") == 0) goto failure; // create default bands string MDM_BandSelect band_avail; if (!getBandAvailable(band_avail)) goto failure; char band_defaults[22] = ""; if (band_avail.band[0] == BAND_DEFAULT) _setBandSelectString(band_avail, band_defaults, 1); // create selected bands string MDM_BandSelect band_sel; if (!getBandSelect(band_sel)) goto failure; char bands_selected[22] = ""; _setBandSelectString(band_sel, bands_selected, 0); if (strcmp(bands_to_set, "0") == 0) { if (strcmp(bands_selected, band_defaults) == 0) { ok = true; goto success; } } if (strcmp(bands_selected, bands_to_set) != 0) { sendFormated("AT+UBANDSEL=%s\r\n", bands_to_set); if (RESP_OK == waitFinalResp(NULL,NULL,40000)) { ok = true; } } else { ok = true; } } success: UNLOCK(); return ok; failure: UNLOCK(); return false; } bool MDMParser::getBandSelect(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_BandSelect data_sel; MDM_INFO("\r\n[ Modem::getBandSelect ] = = = = = = = = = ="); sendFormated("AT+UBANDSEL?\r\n"); if (RESP_OK == waitFinalResp(_cbBANDSEL, &data_sel)) { ok = true; memcpy(&data, &data_sel, sizeof(MDM_BandSelect)); } } UNLOCK(); return ok; } bool MDMParser::getBandAvailable(MDM_BandSelect &data) { bool ok = false; LOCK(); if (_init && _pwr) { MDM_BandSelect data_avail; MDM_INFO("\r\n[ Modem::getBandAvailable ] = = = = = = = = = ="); sendFormated("AT+UBANDSEL=?\r\n"); if (RESP_OK == waitFinalResp(_cbBANDAVAIL, &data_avail)) { ok = true; memcpy(&data, &data_avail, sizeof(MDM_BandSelect)); } } UNLOCK(); return ok; } int MDMParser::_cbUGCNTRD(int type, const char* buf, int len, MDM_DataUsage* data) { if ((type == TYPE_PLUS) && data) { int a,b,c,d,e; // +UGCNTRD: 31,2704,1819,2724,1839\r\n // +UGCNTRD: <cid>,<tx_sess_bytes>,<rx_sess_bytes>,<tx_total_bytes>,<rx_total_bytes> if (sscanf(buf, "\r\n+UGCNTRD: %d,%d,%d,%d,%d\r\n", &a,&b,&c,&d,&e) == 5) { data->cid = a; data->tx_session = b; data->rx_session = c; data->tx_total = d; data->rx_total = e; } } return WAIT; } int MDMParser::_cbBANDAVAIL(int type, const char* buf, int len, MDM_BandSelect* data) { if ((type == TYPE_PLUS) && data) { int c; int b[5]; // \r\n+UBANDSEL: (0,850,900,1800,1900)\r\n if ((c = sscanf(buf, "\r\n+UBANDSEL: (%d,%d,%d,%d,%d)\r\n", &b[0],&b[1],&b[2],&b[3],&b[4])) > 0) { for (int i=0; i<c; i++) { data->band[i] = (MDM_Band)b[i]; } data->count = c; } } return WAIT; } int MDMParser::_cbBANDSEL(int type, const char* buf, int len, MDM_BandSelect* data) { if ((type == TYPE_PLUS) && data) { int c; int b[4]; // \r\n+UBANDSEL: 850\r\n // \r\n+UBANDSEL: 850,1900\r\n if ((c = sscanf(buf, "\r\n+UBANDSEL: %d,%d,%d,%d\r\n", &b[0],&b[1],&b[2],&b[3])) > 0) { for (int i=0; i<c; i++) { data->band[i] = (MDM_Band)b[i]; } data->count = c; } } return WAIT; } int MDMParser::_cbCOPS(int type, const char* buf, int len, NetStatus* status) { if ((type == TYPE_PLUS) && status){ int act = 99; // +COPS: <mode>[,<format>,<oper>[,<AcT>]] if (sscanf(buf, "\r\n+COPS: %*d,%*d,\"%[^\"]\",%d",status->opr,&act) >= 1) { if (act == 0) status->act = ACT_GSM; // 0: GSM, else if (act == 2) status->act = ACT_UTRAN; // 2: UTRAN } } return WAIT; } int MDMParser::_cbCNUM(int type, const char* buf, int len, char* num) { if ((type == TYPE_PLUS) && num){ int a; if ((sscanf(buf, "\r\n+CNUM: \"My Number\",\"%31[^\"]\",%d", num, &a) == 2) && ((a == 129) || (a == 145))) { } } return WAIT; } int MDMParser::_cbCSQ(int type, const char* buf, int len, NetStatus* status) { if ((type == TYPE_PLUS) && status){ int a,b; char _qual[] = { 49, 43, 37, 25, 19, 13, 7, 0 }; // see 3GPP TS 45.008 [20] subclause 8.2.4 // +CSQ: <rssi>,<qual> if (sscanf(buf, "\r\n+CSQ: %d,%d",&a,&b) == 2) { if (a != 99) status->rssi = -113 + 2*a; // 0: -113 1: -111 ... 30: -53 dBm with 2 dBm steps if ((b != 99) && (b < (int)sizeof(_qual))) status->qual = _qual[b]; // switch (status->act) { case ACT_GSM: case ACT_EDGE: status->rxlev = (a != 99) ? (2 * a) : a; status->rxqual = b; break; case ACT_UTRAN: status->rscp = (a != 99) ? (status->rssi + 116) : 255; status->ecno = (b != 99) ? std::min((7 + (7 - b) * 6), 44) : 255; break; case ACT_LTE: case ACT_LTE_CAT_M1: case ACT_LTE_CAT_NB1: status->rsrp = (a != 99) ? (a * 97)/31 : 255; // [0,31] -> [0,97] status->rsrq = (b != 99) ? (b * 34)/7 : 255; // [0, 7] -> [0,34] break; default: // Unknown access tecnhology status->asu = std::numeric_limits<int32_t>::min(); status->aqual = std::numeric_limits<int32_t>::min(); break; } } } return WAIT; } int MDMParser::_cbUACTIND(int type, const char* buf, int len, int* i) { if ((type == TYPE_PLUS) && i){ int a; if (sscanf(buf, "\r\n+UACTIND: %d", &a) == 1) { *i = a; } } return WAIT; } // ---------------------------------------------------------------- // setup the PDP context bool MDMParser::pdp(const char* apn) { bool ok = true; // bool is3G = _dev.dev == DEV_SARA_U260 || _dev.dev == DEV_SARA_U270; LOCK(); if (_init && _pwr) { // todo - refactor // This is setting up an external PDP context, join() creates an internal one // which is ultimately the one that's used by the system. So no need for this. #if 0 MDM_INFO("Modem::pdp\r\n"); DEBUG_D("Define the PDP context 1 with PDP type \"IP\" and APN \"%s\"\r\n", apn); sendFormated("AT+CGDCONT=1,\"IP\",\"%s\"\r\n", apn); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; if (is3G) { MDM_INFO("Define a QoS profile for PDP context 1"); /* with Traffic Class 3 (background), * maximum bit rate 64 kb/s both for UL and for DL, no Delivery Order requirements, * a maximum SDU size of 320 octets, an SDU error ratio of 10-4, a residual bit error * ratio of 10-5, delivery of erroneous SDUs allowed and Traffic Handling Priority 3. */ sendFormated("AT+CGEQREQ=1,3,64,64,,,0,320,\"1E4\",\"1E5\",1,,3\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; } MDM_INFO("Activate PDP context 1..."); sendFormated("AT+CGACT=1,1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 20000)) { sendFormated("AT+CEER\r\n"); waitFinalResp(); MDM_INFO("Test PDP context 1 for non-zero IP address..."); sendFormated("AT+CGPADDR=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) MDM_INFO("Read the PDP contexts’ parameters..."); sendFormated("AT+CGDCONT?\r\n"); // +CGPADDR: 1, "99.88.111.88" if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) if (is3G) { MDM_INFO("Read the negotiated QoS profile for PDP context 1..."); sendFormated("AT+CGEQNEG=1\r\n"); goto failure; } } MDM_INFO("Test PDP context 1 for non-zero IP address..."); sendFormated("AT+CGPADDR=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; MDM_INFO("Read the PDP contexts’ parameters..."); sendFormated("AT+CGDCONT?\r\n"); // +CGPADDR: 1, "99.88.111.88" if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; if (is3G) { MDM_INFO("Read the negotiated QoS profile for PDP context 1..."); sendFormated("AT+CGEQNEG=1\r\n"); if (RESP_OK != waitFinalResp(NULL, NULL, 2000)) goto failure; } _activated = true; // PDP #endif UNLOCK(); return ok; } // failure: UNLOCK(); return false; } // ---------------------------------------------------------------- // internet connection MDM_IP MDMParser::join(const char* apn /*= NULL*/, const char* username /*= NULL*/, const char* password /*= NULL*/, Auth auth /*= AUTH_DETECT*/) { LOCK(); if (_init && _pwr && _dev.dev != DEV_UNKNOWN) { MDM_INFO("\r\n[ Modem::join ] = = = = = = = = = = = = = = = ="); _ip = NOIP; if (_dev.dev == DEV_SARA_R410) { // Get local IP address associated with the default profile sendFormated("AT+CGPADDR=%d\r\n", PDP_CONTEXT); if (waitFinalResp(_cbCGPADDR, &_ip) != RESP_OK) { goto failure; } // FIXME: The existing code seems to use `_activated` and `_attached` flags kind of interchangeably _activated = true; } else { int a = 0; bool force = false; // If we are already connected, don't force a reconnect. // perform GPRS attach sendFormated("AT+CGATT=1\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000)) goto failure; // Check the if the PSD profile is activated (a=1) sendFormated("AT+UPSND=" PROFILE ",8\r\n"); if (RESP_OK != waitFinalResp(_cbUPSND, &a)) goto failure; if (a == 1) { _activated = true; // PDP activated if (force) { // deactivate the PSD profile if it is already activated sendFormated("AT+UPSDA=" PROFILE ",4\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,40*1000)) goto failure; a = 0; } } if (a == 0) { bool ok = false; _activated = false; // PDP deactived // try to lookup the apn settings from our local database by mccmnc const char* config = NULL; if (!apn && !username && !password) config = apnconfig(_dev.imsi); // Set up the dynamic IP address assignment. sendFormated("AT+UPSD=" PROFILE ",7,\"0.0.0.0\"\r\n"); if (RESP_OK != waitFinalResp()) goto failure; do { if (config) { apn = _APN_GET(config); username = _APN_GET(config); password = _APN_GET(config); DEBUG_D("Testing APN Settings(\"%s\",\"%s\",\"%s\")\r\n", apn, username, password); } // Set up the APN if (apn && *apn) { sendFormated("AT+UPSD=" PROFILE ",1,\"%s\"\r\n", apn); if (RESP_OK != waitFinalResp()) goto failure; } if (username && *username) { sendFormated("AT+UPSD=" PROFILE ",2,\"%s\"\r\n", username); if (RESP_OK != waitFinalResp()) goto failure; } if (password && *password) { sendFormated("AT+UPSD=" PROFILE ",3,\"%s\"\r\n", password); if (RESP_OK != waitFinalResp()) goto failure; } // try different Authentication Protocols // 0 = none // 1 = PAP (Password Authentication Protocol) // 2 = CHAP (Challenge Handshake Authentication Protocol) for (int i = AUTH_NONE; i <= AUTH_CHAP && !ok; i ++) { if ((auth == AUTH_DETECT) || (auth == i)) { // Set up the Authentication Protocol sendFormated("AT+UPSD=" PROFILE ",6,%d\r\n", i); if (RESP_OK != waitFinalResp()) goto failure; // Activate the PSD profile and make connection sendFormated("AT+UPSDA=" PROFILE ",3\r\n"); if (RESP_OK == waitFinalResp(NULL,NULL,150*1000)) { _activated = true; // PDP activated ok = true; } } } } while (!ok && config && *config); // maybe use next setting ? if (!ok) { MDM_ERROR("Your modem APN/password/username may be wrong\r\n"); goto failure; } } //Get local IP address sendFormated("AT+UPSND=" PROFILE ",0\r\n"); if (RESP_OK != waitFinalResp(_cbUPSND, &_ip)) goto failure; } UNLOCK(); _attached = true; // GPRS return _ip; } failure: UNLOCK(); return NOIP; } int MDMParser::_cbUDOPN(int type, const char* buf, int len, char* mccmnc) { if ((type == TYPE_PLUS) && mccmnc) { if (sscanf(buf, "\r\n+UDOPN: 0,\"%[^\"]\"", mccmnc) == 1) ; } return WAIT; } int MDMParser::_cbCGPADDR(int type, const char* buf, int len, MDM_IP* ip) { if (type == TYPE_PLUS && ip) { int cid, a, b, c, d; // +CGPADDR: <cid>,<PDP_addr> // TODO: IPv6 if (sscanf(buf, "\r\n+CGPADDR: %d,%d.%d.%d.%d", &cid, &a, &b, &c, &d) == 5) { *ip = IPADR(a, b, c, d); } } return WAIT; } int MDMParser::_cbCGDCONT(int type, const char* buf, int len, CGDCONTparam* param) { if (type == TYPE_PLUS || type == TYPE_UNKNOWN) { buf = (const char*)memchr(buf, '+', len); // Skip leading new line characters if (buf) { int id; CGDCONTparam p = {}; static_assert(sizeof(p.type) == 8 && sizeof(p.apn) == 32, "The format string below needs to be updated accordingly"); if (sscanf(buf, "+CGDCONT: %d,\"%7[^,],\"%31[^,],", &id, p.type, p.apn) == 3 && id == PDP_CONTEXT) { p.type[strlen(p.type) - 1] = '\0'; // Trim trailing quote character p.apn[strlen(p.apn) - 1] = '\0'; *param = p; } } } return WAIT; } int MDMParser::_cbCMIP(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_UNKNOWN) && ip) { int a,b,c,d; if (sscanf(buf, "\r\n" IPSTR, &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } int MDMParser::_cbUPSND(int type, const char* buf, int len, int* act) { if ((type == TYPE_PLUS) && act) { if (sscanf(buf, "\r\n+UPSND: %*d,%*d,%d", act) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbUPSND(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_PLUS) && ip) { int a,b,c,d; // +UPSND=<profile_id>,<param_tag>[,<dynamic_param_val>] if (sscanf(buf, "\r\n+UPSND: " PROFILE ",0,\"" IPSTR "\"", &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } int MDMParser::_cbUDNSRN(int type, const char* buf, int len, MDM_IP* ip) { if ((type == TYPE_PLUS) && ip) { int a,b,c,d; if (sscanf(buf, "\r\n+UDNSRN: \"" IPSTR "\"", &a,&b,&c,&d) == 4) *ip = IPADR(a,b,c,d); } return WAIT; } bool MDMParser::reconnect(void) { bool ok = false; LOCK(); if (_activated) { MDM_INFO("\r\n[ Modem::reconnect ] = = = = = = = = = = = = = ="); if (!_attached) { /* Activates the PDP context assoc. with this profile */ /* If GPRS is detached, this will force a re-attach */ sendFormated("AT+UPSDA=" PROFILE ",3\r\n"); if (RESP_OK == waitFinalResp(NULL, NULL, 150*1000)) { //Get local IP address sendFormated("AT+UPSND=" PROFILE ",0\r\n"); if (RESP_OK == waitFinalResp(_cbUPSND, &_ip)) { ok = true; _attached = true; } } } } UNLOCK(); return ok; } // TODO - refactor deactivate() and detach() // deactivate() can be called before detach() but not vice versa or // deactivate() will ERROR because its PDP context will already be // deactivated. // _attached and _activated flags are currently associated inversely // to what's happening. When refactoring, consider combining... bool MDMParser::deactivate(void) { bool ok = false; bool continue_cancel = false; LOCK(); if (_attached) { if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } MDM_INFO("\r\n[ Modem::deactivate ] = = = = = = = = = = = = ="); if (_ip != NOIP) { if (_dev.dev == DEV_SARA_R410) { // The default context cannot be deactivated _ip = NOIP; _attached = false; ok = true; } else { /* Deactivates the PDP context assoc. with this profile * ensuring that no additional data is sent or received * by the device. */ sendFormated("AT+UPSDA=" PROFILE ",4\r\n"); if (RESP_OK == waitFinalResp()) { _ip = NOIP; ok = true; _attached = false; } } } } if (continue_cancel) cancel(); UNLOCK(); return ok; } bool MDMParser::detach(void) { bool ok = false; bool continue_cancel = false; LOCK(); if (_activated) { if (_cancel_all_operations) { continue_cancel = true; resume(); // make sure we can use the AT parser } MDM_INFO("\r\n[ Modem::detach ] = = = = = = = = = = = = = = ="); if (_dev.dev == DEV_SARA_R410) { // TODO: There's no GPRS service in LTE, although the GRPS detach command still disables // the PSD connection. For now let's unregister from the network entirely, since the // behavior of the detach command in relation to LTE is not documented sendFormated("AT+COPS=2\r\n"); if (waitFinalResp(nullptr, nullptr, COPS_TIMEOUT) == RESP_OK) { _activated = false; ok = true; } } else { // if (_ip != NOIP) { // if we deactivate() first we won't have an IP /* Detach from the GPRS network and conserve network resources. */ /* Any active PDP context will also be deactivated. */ sendFormated("AT+CGATT=0\r\n"); if (RESP_OK != waitFinalResp(NULL,NULL,3*60*1000)) { ok = true; _activated = false; } // } } } if (continue_cancel) cancel(); UNLOCK(); return ok; } MDM_IP MDMParser::gethostbyname(const char* host) { MDM_IP ip = NOIP; LOCK(); if (_dev.dev == DEV_SARA_R410) { // Current uBlox firmware (L0.0.00.00.05.05) doesn't support +UDNSRN command, so we have to // use our own DNS client particle::getHostByName(host, &ip); } else { int a,b,c,d; if (sscanf(host, IPSTR, &a,&b,&c,&d) == 4) { ip = IPADR(a,b,c,d); } else { sendFormated("AT+UDNSRN=0,\"%s\"\r\n", host); if (RESP_OK != waitFinalResp(_cbUDNSRN, &ip, 30*1000)) { ip = NOIP; } } } UNLOCK(); return ip; } // ---------------------------------------------------------------- // sockets int MDMParser::_cbUSOCR(int type, const char* buf, int len, int* handle) { if ((type == TYPE_PLUS) && handle) { // +USOCR: socket if (sscanf(buf, "\r\n+USOCR: %d", handle) == 1) /*nothing*/; } return WAIT; } int MDMParser::_cbUSOCTL(int type, const char* buf, int len, int* handle) { if ((type == TYPE_PLUS) && handle) { // +USOCTL: socket,param_id,param_val if (sscanf(buf, "\r\n+USOCTL: %d,%*d,%*d", handle) == 1) /*nothing*/; } return WAIT; } /* Tries to close any currently unused socket handles */ int MDMParser::_socketCloseUnusedHandles() { bool ok = false; LOCK(); for (int s = 0; s < NUMSOCKETS; s++) { // If this HANDLE is not found to be in use, try to close it if (_findSocket(s) == MDM_SOCKET_ERROR) { if (_socketCloseHandleIfOpen(s)) { ok = true; // If any actually close, return true } } } UNLOCK(); return ok; } /* Tries to close the specified socket handle */ int MDMParser::_socketCloseHandleIfOpen(int socket_handle) { bool ok = false; LOCK(); // Check if socket_handle is open // AT+USOCTL=0,1 // +USOCTL: 0,1,0 int handle = MDM_SOCKET_ERROR; sendFormated("AT+USOCTL=%d,1\r\n", socket_handle); if ((RESP_OK == waitFinalResp(_cbUSOCTL, &handle)) && (handle != MDM_SOCKET_ERROR)) { DEBUG_D("Socket handle %d was open, now closing...\r\n", handle); // Close it if it's open // AT+USOCL=0 // OK sendFormated("AT+USOCL=%d\r\n", handle); if (RESP_OK == waitFinalResp()) { DEBUG_D("Socket handle %d was closed.\r\n", handle); ok = true; } } UNLOCK(); return ok; } int MDMParser::_socketSocket(int socket, IpProtocol ipproto, int port) { int rv = socket; LOCK(); if (ipproto == MDM_IPPROTO_UDP) { // sending port can only be set on 2G/3G modules if (port != -1) { sendFormated("AT+USOCR=17,%d\r\n", port); } else { sendFormated("AT+USOCR=17\r\n"); } } else /*(ipproto == MDM_IPPROTO_TCP)*/ { sendFormated("AT+USOCR=6\r\n"); } int handle = MDM_SOCKET_ERROR; if ((RESP_OK == waitFinalResp(_cbUSOCR, &handle)) && (handle != MDM_SOCKET_ERROR)) { DEBUG_D("Socket %d: handle %d was created\r\n", socket, handle); _sockets[socket].handle = handle; _sockets[socket].timeout_ms = TIMEOUT_BLOCKING; _sockets[socket].connected = (ipproto == MDM_IPPROTO_UDP); _sockets[socket].pending = 0; _sockets[socket].open = true; } else { rv = MDM_SOCKET_ERROR; } UNLOCK(); return rv; } int MDMParser::socketSocket(IpProtocol ipproto, int port) { int socket; static bool checkedOnce = false; LOCK(); if (!_attached) { if (!reconnect()) { socket = MDM_SOCKET_ERROR; } } if (_attached) { if (!checkedOnce) { checkedOnce = true; // prevent re-entry DEBUG_D("On first socketSocket use, free all open sockets\r\n"); // Clean up any open sockets, we may have power cycled the STM32 // while the modem remained connected. for (int s = 0; s < NUMSOCKETS; s++) { _socketCloseHandleIfOpen(s); // re-initialize the socket element _socketFree(s); } } // find an free socket socket = _findSocket(MDM_SOCKET_ERROR); DEBUG_D("socketSocket(%s)\r\n", (ipproto?"UDP":"TCP")); if (socket != MDM_SOCKET_ERROR) { int _socket = _socketSocket(socket, ipproto, port); if (_socket != MDM_SOCKET_ERROR) { socket = _socket; } else { // A socket should be available, but errored on trying to create one if (_socketCloseUnusedHandles()) { // find a new free socket and try again _socket = _findSocket(MDM_SOCKET_ERROR); socket = _socketSocket(_socket, ipproto, port); } else { // We tried to close unused handles, but also failed. socket = MDM_SOCKET_ERROR; } } } } UNLOCK(); return socket; } bool MDMParser::socketConnect(int socket, const char * host, int port) { MDM_IP ip = gethostbyname(host); if (ip == NOIP) return false; DEBUG_D("socketConnect(host: %s)\r\n", host); // connect to socket return socketConnect(socket, ip, port); } bool MDMParser::socketConnect(int socket, const MDM_IP& ip, int port) { bool ok = false; LOCK(); if (ISSOCKET(socket) && (!_sockets[socket].connected)) { DEBUG_D("socketConnect(%d,port:%d)\r\n", socket,port); sendFormated("AT+USOCO=%d,\"" IPSTR "\",%d\r\n", _sockets[socket].handle, IPNUM(ip), port); if (RESP_OK == waitFinalResp(nullptr, nullptr, 120000)) { // SARA-U2: < 20s, SARA-R4: < 120s ok = _sockets[socket].connected = true; } } UNLOCK(); return ok; } bool MDMParser::socketIsConnected(int socket) { bool ok = false; LOCK(); ok = ISSOCKET(socket) && _sockets[socket].connected; //DEBUG_D("socketIsConnected(%d) %s\r\n", socket, ok?"yes":"no"); UNLOCK(); return ok; } bool MDMParser::socketSetBlocking(int socket, system_tick_t timeout_ms) { bool ok = false; LOCK(); // DEBUG_D("socketSetBlocking(%d,%d)\r\n", socket,timeout_ms); if (ISSOCKET(socket)) { _sockets[socket].timeout_ms = timeout_ms; ok = true; } UNLOCK(); return ok; } bool MDMParser::socketClose(int socket) { bool ok = false; LOCK(); if (ISSOCKET(socket) && (_sockets[socket].connected || _sockets[socket].open)) { DEBUG_D("socketClose(%d)\r\n", socket); sendFormated("AT+USOCL=%d\r\n", _sockets[socket].handle); if (RESP_ERROR == waitFinalResp()) { sendFormated("AT+CEER\r\n"); // For logging visibility waitFinalResp(); } // Assume RESP_OK in most situations, and assume closed // already if we couldn't close it, even though this can // be false. Recovery added to socketSocket(); _sockets[socket].connected = false; _sockets[socket].open = false; ok = true; } UNLOCK(); return ok; } bool MDMParser::_socketFree(int socket) { bool ok = false; LOCK(); if ((socket >= 0) && (socket < NUMSOCKETS)) { if (_sockets[socket].handle != MDM_SOCKET_ERROR) { DEBUG_D("socketFree(%d)\r\n", socket); _sockets[socket].handle = MDM_SOCKET_ERROR; _sockets[socket].timeout_ms = TIMEOUT_BLOCKING; _sockets[socket].connected = false; _sockets[socket].pending = 0; _sockets[socket].open = false; } ok = true; } UNLOCK(); return ok; // only false if invalid socket } bool MDMParser::socketFree(int socket) { // make sure it is closed socketClose(socket); return _socketFree(socket); } int MDMParser::socketSend(int socket, const char * buf, int len) { //DEBUG_D("socketSend(%d,,%d)\r\n", socket,len); #ifndef SOCKET_HEX_MODE int cnt = len; while (cnt > 0) { int blk = USO_MAX_WRITE; if (cnt < blk) blk = cnt; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { sendFormated("AT+USOWR=%d,%d\r\n",_sockets[socket].handle,blk); if (RESP_PROMPT == waitFinalResp()) { HAL_Delay_Milliseconds(50); send(buf, blk); if (RESP_OK == waitFinalResp()) ok = true; } } UNLOCK(); } if (!ok) return MDM_SOCKET_ERROR; buf += blk; cnt -= blk; } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORD=%d,0\r\n", _sockets[socket].handle); // TCP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); return (len - cnt); #else int bytesLeft = len; while (bytesLeft > 0) { LOCK(); if (!ISSOCKET(socket)) { goto error; } // Maximum number of bytes that can be written via single +USOWR command size_t chunkSize = bytesLeft; if (chunkSize > USO_MAX_WRITE / 2) { // Half the maximum size in binary mode chunkSize = USO_MAX_WRITE / 2; } // Write command prefix char data[256]; // Hex data is written in chunks const int prefixSize = snprintf(data, sizeof(data), "AT+USOWR=%d,%u,\"", _sockets[socket].handle, (unsigned)chunkSize); if (prefixSize < 0 || prefixSize > (int)sizeof(data)) { goto error; } if (send(data, prefixSize) != prefixSize) { goto error; } // Write hex data do { size_t n = chunkSize; if (n > sizeof(data) / 2) { n = sizeof(data) / 2; } bytes2hexbuf_lower_case((const uint8_t*)buf, n, data); const size_t hexSize = n * 2; if (send(data, hexSize) != (int)hexSize) { goto error; } buf += n; bytesLeft -= n; chunkSize -= n; } while (chunkSize > 0); // Write command suffix if (send("\"\r\n", 3) != 3) { goto error; } if (waitFinalResp(nullptr, nullptr, SOCKET_WRITE_TIMEOUT) != RESP_OK) { goto error; } UNLOCK(); } return (len - bytesLeft); error: UNLOCK(); return MDM_SOCKET_ERROR; #endif // defined(SOCKET_HEX_MODE) } int MDMParser::socketSendTo(int socket, MDM_IP ip, int port, const char * buf, int len) { DEBUG_D("socketSendTo(%d," IPSTR ",%d,,%d)\r\n", socket,IPNUM(ip),port,len); #ifndef SOCKET_HEX_MODE int cnt = len; while (cnt > 0) { int blk = USO_MAX_WRITE; if (cnt < blk) blk = cnt; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { sendFormated("AT+USOST=%d,\"" IPSTR "\",%d,%d\r\n",_sockets[socket].handle,IPNUM(ip),port,blk); if (RESP_PROMPT == waitFinalResp()) { HAL_Delay_Milliseconds(50); send(buf, blk); if (RESP_OK == waitFinalResp()) ok = true; } } UNLOCK(); } if (!ok) return MDM_SOCKET_ERROR; buf += blk; cnt -= blk; } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORF=%d,0\r\n", _sockets[socket].handle); // UDP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); return (len - cnt); #else int bytesLeft = len; if (bytesLeft >= 0) { LOCK(); if (!ISSOCKET(socket)) { goto error; } // Maximum number of bytes that can be written via +USOST command if (bytesLeft > USO_MAX_WRITE / 2) { // Half the maximum size in binary mode MDM_ERROR("Maximum UDP packet size exceeded"); goto error; } // Write command prefix char data[256]; // Hex data is written in chunks const int prefixSize = snprintf(data, sizeof(data), "AT+USOST=%d,\"" IPSTR "\",%d,%d,\"", _sockets[socket].handle, IPNUM(ip), port, bytesLeft); if (prefixSize < 0 || prefixSize > (int)sizeof(data)) { goto error; } if (send(data, prefixSize) != prefixSize) { goto error; } // Write hex data do { size_t n = bytesLeft; if (n > sizeof(data) / 2) { n = sizeof(data) / 2; } bytes2hexbuf_lower_case((const uint8_t*)buf, n, data); const size_t hexSize = n * 2; if (send(data, hexSize) != (int)hexSize) { goto error; } buf += n; bytesLeft -= n; } while (bytesLeft > 0); // Write command suffix if (send("\"\r\n", 3) != 3) { goto error; } if (waitFinalResp(nullptr, nullptr, SOCKET_WRITE_TIMEOUT) != RESP_OK) { goto error; } UNLOCK(); } return (len - bytesLeft); error: UNLOCK(); return MDM_SOCKET_ERROR; #endif // defined(SOCKET_HEX_MODE) } int MDMParser::socketReadable(int socket) { int pending = MDM_SOCKET_ERROR; if (_cancel_all_operations) return MDM_SOCKET_ERROR; LOCK(); if (ISSOCKET(socket) && _sockets[socket].connected) { //DEBUG_D("socketReadable(%d)\r\n", socket); // allow to receive unsolicited commands waitFinalResp(NULL, NULL, 0); if (_sockets[socket].connected) pending = _sockets[socket].pending; } UNLOCK(); return pending; } int MDMParser::_cbUSORD(int type, const char* buf, int len, USORDparam* param) { #ifndef SOCKET_HEX_MODE if ((type == TYPE_PLUS) && param) { int sz, sk; if ((sscanf(buf, "\r\n+USORD: %d,%d,", &sk, &sz) == 2) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { memcpy(param->buf, &buf[len-1-sz], sz); param->len = sz; } else { param->len = 0; } } return WAIT; #else if ((type == TYPE_UNKNOWN || type == TYPE_PLUS) && param) { int socket, size; if (sscanf(buf, "+USORD: %d,%d,", &socket, &size) == 2 && buf[len - size * 2 - 2] == '\"' && buf[len - 1] == '\"') { particle::hexToBytes(buf + len - size * 2 - 1, param->buf, size); param->len = size; } else { param->len = 0; } } return WAIT; #endif // defined(SOCKET_HEX_MODE) } int MDMParser::socketRecv(int socket, char* buf, int len) { int cnt = 0; /* DEBUG_D("socketRecv(%d,%d)\r\n", socket, len); #ifdef MDM_DEBUG memset(buf, '\0', len); #endif */ system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (len) { // DEBUG_D("socketRecv: LEN: %d\r\n", len); #ifndef SOCKET_HEX_MODE int blk = MAX_SIZE; // still need space for headers and unsolicited commands #else int blk = MAX_SIZE / 4; #endif if (len < blk) blk = len; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { if (_sockets[socket].connected) { int available = socketReadable(socket); if (available<0) { // DEBUG_D("socketRecv: SOCKET CLOSED or NO AVAIL DATA\r\n"); // Socket may have been closed remotely during read, or no more data to read. // Zero the `len` to break out of the while(len), and set `ok` to true so // we return the `cnt` recv'd up until the socket was closed. len = 0; ok = true; } else { if (blk > available) // only read up to the amount available. When 0, blk = available;// skip reading and check timeout. if (blk > 0) { // DEBUG_D("socketRecv: _cbUSORD\r\n"); sendFormated("AT+USORD=%d,%d\r\n",_sockets[socket].handle, blk); USORDparam param; param.buf = buf; if (RESP_OK == waitFinalResp(_cbUSORD, ¶m)) { blk = param.len; _sockets[socket].pending -= blk; len -= blk; cnt += blk; buf += blk; ok = true; } } else if (!TIMEOUT(start, _sockets[socket].timeout_ms)) { // DEBUG_D("socketRecv: WAIT FOR URCs\r\n"); ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs } else { // DEBUG_D("socketRecv: TIMEOUT\r\n"); len = 0; ok = true; } } } else { // DEBUG_D("socketRecv: SOCKET NOT CONNECTED\r\n"); len = 0; ok = true; } } UNLOCK(); } if (!ok) { // DEBUG_D("socketRecv: ERROR\r\n"); return MDM_SOCKET_ERROR; } } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORD=%d,0\r\n", _sockets[socket].handle); // TCP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); // DEBUG_D("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt); return cnt; } int MDMParser::_cbUSORF(int type, const char* buf, int len, USORFparam* param) { #ifndef SOCKET_HEX_MODE if ((type == TYPE_PLUS) && param) { int sz, sk, p, a,b,c,d; int r = sscanf(buf, "\r\n+USORF: %d,\"" IPSTR "\",%d,%d,", &sk,&a,&b,&c,&d,&p,&sz); if ((r == 7) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { memcpy(param->buf, &buf[len-1-sz], sz); param->ip = IPADR(a,b,c,d); param->port = p; param->len = sz; } else { param->len = 0; } } return WAIT; #else if ((type == TYPE_UNKNOWN || type == TYPE_PLUS) && param) { int socket, size, port, ip1, ip2, ip3, ip4; if (sscanf(buf, "+USORF: %d,\"" IPSTR "\",%d,%d,", &socket, &ip1, &ip2, &ip3, &ip4, &port, &size) == 7 && buf[len - size * 2 - 2] == '\"' && buf[len - 1] == '\"') { particle::hexToBytes(buf + len - size * 2 - 1, param->buf, size); param->ip = IPADR(ip1, ip2, ip3, ip4); param->port = port; param->len = size; } else { param->len = 0; } } return WAIT; #endif // defined(SOCKET_HEX_MODE) } int MDMParser::socketRecvFrom(int socket, MDM_IP* ip, int* port, char* buf, int len) { int cnt = 0; // DEBUG_D("socketRecvFrom(%d,,%d)\r\n", socket, len); #ifdef MDM_DEBUG memset(buf, '\0', len); #endif system_tick_t start = HAL_Timer_Get_Milli_Seconds(); while (len) { #ifndef SOCKET_HEX_MODE int blk = MAX_SIZE; // still need space for headers and unsolicited commands #else int blk = MAX_SIZE / 4; #endif if (len < blk) blk = len; bool ok = false; { LOCK(); if (ISSOCKET(socket)) { if (blk > 0) { sendFormated("AT+USORF=%d,%d\r\n",_sockets[socket].handle, blk); USORFparam param; param.buf = buf; if (RESP_OK == waitFinalResp(_cbUSORF, ¶m)) { *ip = param.ip; *port = param.port; blk = param.len; _sockets[socket].pending -= blk; len -= blk; cnt += blk; buf += blk; len = 0; // done ok = true; } } else if (!TIMEOUT(start, _sockets[socket].timeout_ms)) { ok = (WAIT == waitFinalResp(NULL,NULL,0)); // wait for URCs } else { len = 0; // no more data and socket closed or timed-out ok = true; } } UNLOCK(); } if (!ok) { DEBUG_D("socketRecv: ERROR\r\n"); return MDM_SOCKET_ERROR; } } LOCK(); if (ISSOCKET(socket) && (_sockets[socket].pending == 0)) { sendFormated("AT+USORF=%d,0\r\n", _sockets[socket].handle); // UDP waitFinalResp(NULL, NULL, 10*1000); } UNLOCK(); // DEBUG_D("socketRecv: %d \"%*s\"\r\n", cnt, cnt, buf-cnt); return cnt; } int MDMParser::_findSocket(int handle) { for (int socket = 0; socket < NUMSOCKETS; socket ++) { if (_sockets[socket].handle == handle) return socket; } return MDM_SOCKET_ERROR; } // ---------------------------------------------------------------- int MDMParser::_cbCMGL(int type, const char* buf, int len, CMGLparam* param) { if ((type == TYPE_PLUS) && param && param->num) { // +CMGL: <ix>,... int ix; if (sscanf(buf, "\r\n+CMGL: %d,", &ix) == 1) { *param->ix++ = ix; param->num--; } } return WAIT; } int MDMParser::smsList(const char* stat /*= "ALL"*/, int* ix /*=NULL*/, int num /*= 0*/) { int ret = -1; LOCK(); sendFormated("AT+CMGL=\"%s\"\r\n", stat); CMGLparam param; param.ix = ix; param.num = num; if (RESP_OK == waitFinalResp(_cbCMGL, ¶m)) ret = num - param.num; UNLOCK(); return ret; } bool MDMParser::smsSend(const char* num, const char* buf) { bool ok = false; LOCK(); sendFormated("AT+CMGS=\"%s\"\r\n",num); if (RESP_PROMPT == waitFinalResp(NULL,NULL,150*1000)) { send(buf, strlen(buf)); const char ctrlZ = 0x1A; send(&ctrlZ, sizeof(ctrlZ)); ok = (RESP_OK == waitFinalResp()); } UNLOCK(); return ok; } bool MDMParser::smsDelete(int ix) { bool ok = false; LOCK(); sendFormated("AT+CMGD=%d\r\n",ix); ok = (RESP_OK == waitFinalResp()); UNLOCK(); return ok; } int MDMParser::_cbCMGR(int type, const char* buf, int len, CMGRparam* param) { if (param) { if (type == TYPE_PLUS) { if (sscanf(buf, "\r\n+CMGR: \"%*[^\"]\",\"%[^\"]", param->num) == 1) { } } else if ((type == TYPE_UNKNOWN) && (buf[len-2] == '\r') && (buf[len-1] == '\n')) { memcpy(param->buf, buf, len-2); param->buf[len-2] = '\0'; } } return WAIT; } bool MDMParser::smsRead(int ix, char* num, char* buf, int len) { bool ok = false; LOCK(); CMGRparam param; param.num = num; param.buf = buf; sendFormated("AT+CMGR=%d\r\n",ix); ok = (RESP_OK == waitFinalResp(_cbCMGR, ¶m)); UNLOCK(); return ok; } // ---------------------------------------------------------------- int MDMParser::_cbCUSD(int type, const char* buf, int len, char* resp) { if ((type == TYPE_PLUS) && resp) { // +USD: \"%*[^\"]\",\"%[^\"]\",,\"%*[^\"]\",%d,%d,%d,%d,\"*[^\"]\",%d,%d"..); if (sscanf(buf, "\r\n+CUSD: %*d,\"%[^\"]\",%*d", resp) == 1) { /*nothing*/ } } return WAIT; } bool MDMParser::ussdCommand(const char* cmd, char* buf) { bool ok = false; LOCK(); *buf = '\0'; // 2G/3G devices only sendFormated("AT+CUSD=1,\"%s\"\r\n",cmd); ok = (RESP_OK == waitFinalResp(_cbCUSD, buf)); UNLOCK(); return ok; } // ---------------------------------------------------------------- int MDMParser::_cbUDELFILE(int type, const char* buf, int len, void*) { if ((type == TYPE_ERROR) && strstr(buf, "+CME ERROR: FILE NOT FOUND")) return RESP_OK; // file does not exist, so all ok... return WAIT; } bool MDMParser::delFile(const char* filename) { bool ok = false; LOCK(); sendFormated("AT+UDELFILE=\"%s\"\r\n", filename); ok = (RESP_OK == waitFinalResp(_cbUDELFILE)); UNLOCK(); return ok; } int MDMParser::writeFile(const char* filename, const char* buf, int len) { bool ok = false; LOCK(); sendFormated("AT+UDWNFILE=\"%s\",%d\r\n", filename, len); if (RESP_PROMPT == waitFinalResp()) { send(buf, len); ok = (RESP_OK == waitFinalResp()); } UNLOCK(); return ok ? len : -1; } int MDMParser::readFile(const char* filename, char* buf, int len) { URDFILEparam param; param.filename = filename; param.buf = buf; param.sz = len; param.len = 0; LOCK(); sendFormated("AT+URDFILE=\"%s\"\r\n", filename, len); if (RESP_OK != waitFinalResp(_cbURDFILE, ¶m)) param.len = -1; UNLOCK(); return param.len; } int MDMParser::_cbURDFILE(int type, const char* buf, int len, URDFILEparam* param) { if ((type == TYPE_PLUS) && param && param->filename && param->buf) { char filename[48]; int sz; if ((sscanf(buf, "\r\n+URDFILE: \"%[^\"]\",%d,", filename, &sz) == 2) && (0 == strcmp(param->filename, filename)) && (buf[len-sz-2] == '\"') && (buf[len-1] == '\"')) { param->len = (sz < param->sz) ? sz : param->sz; memcpy(param->buf, &buf[len-1-sz], param->len); } } return WAIT; } // ---------------------------------------------------------------- bool MDMParser::setDebug(int level) { #ifdef MDM_DEBUG if ((_debugLevel >= -1) && (level >= -1) && (_debugLevel <= 3) && (level <= 3)) { _debugLevel = level; return true; } #endif return false; } void MDMParser::dumpDevStatus(DevStatus* status) { MDM_INFO("\r\n[ Modem::devStatus ] = = = = = = = = = = = = = ="); const char* txtDev[] = { "Unknown", "SARA-G350", "LISA-U200", "LISA-C200", "SARA-U260", "SARA-U270", "LEON-G200", "SARA-U201", "SARA-R410" }; if (status->dev < sizeof(txtDev)/sizeof(*txtDev) && (status->dev != DEV_UNKNOWN)) DEBUG_D(" Device: %s\r\n", txtDev[status->dev]); const char* txtLpm[] = { "Disabled", "Enabled", "Active" }; if (status->lpm < sizeof(txtLpm)/sizeof(*txtLpm)) DEBUG_D(" Power Save: %s\r\n", txtLpm[status->lpm]); const char* txtSim[] = { "Unknown", "Missing", "Pin", "Ready" }; if (status->sim < sizeof(txtSim)/sizeof(*txtSim) && (status->sim != SIM_UNKNOWN)) DEBUG_D(" SIM: %s\r\n", txtSim[status->sim]); if (*status->ccid) DEBUG_D(" CCID: %s\r\n", status->ccid); if (*status->imei) DEBUG_D(" IMEI: %s\r\n", status->imei); if (*status->imsi) DEBUG_D(" IMSI: %s\r\n", status->imsi); if (*status->meid) DEBUG_D(" MEID: %s\r\n", status->meid); // LISA-C if (*status->manu) DEBUG_D(" Manufacturer: %s\r\n", status->manu); if (*status->model) DEBUG_D(" Model: %s\r\n", status->model); if (*status->ver) DEBUG_D(" Version: %s\r\n", status->ver); } void MDMParser::dumpNetStatus(NetStatus *status) { MDM_INFO("\r\n[ Modem::netStatus ] = = = = = = = = = = = = = ="); const char* txtReg[] = { "Unknown", "Denied", "None", "Home", "Roaming" }; if (status->csd < sizeof(txtReg)/sizeof(*txtReg) && (status->csd != REG_UNKNOWN)) DEBUG_D(" CSD Registration: %s\r\n", txtReg[status->csd]); if (status->psd < sizeof(txtReg)/sizeof(*txtReg) && (status->psd != REG_UNKNOWN)) DEBUG_D(" PSD Registration: %s\r\n", txtReg[status->psd]); const char* txtAct[] = { "Unknown", "GSM", "Edge", "3G", "CDMA" }; if (status->act < sizeof(txtAct)/sizeof(*txtAct) && (status->act != ACT_UNKNOWN)) DEBUG_D(" Access Technology: %s\r\n", txtAct[status->act]); if (status->rssi) DEBUG_D(" Signal Strength: %d dBm\r\n", status->rssi); if (status->qual) DEBUG_D(" Signal Quality: %d\r\n", status->qual); if (*status->opr) DEBUG_D(" Operator: %s\r\n", status->opr); if (status->lac != 0xFFFF) DEBUG_D(" Location Area Code: %04X\r\n", status->lac); if (status->ci != 0xFFFFFFFF) DEBUG_D(" Cell ID: %08X\r\n", status->ci); if (*status->num) DEBUG_D(" Phone Number: %s\r\n", status->num); } void MDMParser::dumpIp(MDM_IP ip) { if (ip != NOIP) { DEBUG_D("\r\n[ Modem:IP " IPSTR " ] = = = = = = = = = = = = = =\r\n", IPNUM(ip)); } } // ---------------------------------------------------------------- int MDMParser::_parseMatch(Pipe<char>* pipe, int len, const char* sta, const char* end) { int o = 0; if (sta) { while (*sta) { if (++o > len) return WAIT; char ch = pipe->next(); if (*sta++ != ch) return NOT_FOUND; } } if (!end) return o; // no termination // at least any char if (++o > len) return WAIT; pipe->next(); // check the end int x = 0; while (end[x]) { if (++o > len) return WAIT; char ch = pipe->next(); x = (end[x] == ch) ? x + 1 : (end[0] == ch) ? 1 : 0; } return o; } int MDMParser::_parseFormated(Pipe<char>* pipe, int len, const char* fmt) { int o = 0; int num = 0; if (fmt) { while (*fmt) { if (++o > len) return WAIT; char ch = pipe->next(); if (*fmt == '%') { fmt++; if (*fmt == 'd') { // numeric fmt ++; num = 0; while (ch >= '0' && ch <= '9') { num = num * 10 + (ch - '0'); if (++o > len) return WAIT; ch = pipe->next(); } } else if (*fmt == 'c') { // char buffer (takes last numeric as length) fmt ++; while (num --) { if (++o > len) return WAIT; ch = pipe->next(); } } else if (*fmt == 's') { fmt ++; if (ch != '\"') return NOT_FOUND; do { if (++o > len) return WAIT; ch = pipe->next(); } while (ch != '\"'); if (++o > len) return WAIT; ch = pipe->next(); } } if (*fmt++ != ch) return NOT_FOUND; } } return o; } int MDMParser::_getLine(Pipe<char>* pipe, char* buf, int len) { int unkn = 0; int sz = pipe->size(); int fr = pipe->free(); if (len > sz) len = sz; while (len > 0) { static struct { const char* fmt; int type; } lutF[] = { { "\r\n+USORD: %d,%d,\"%c\"", TYPE_PLUS }, { "\r\n+USORF: %d,\"" IPSTR "\",%d,%d,\"%c\"", TYPE_PLUS }, { "\r\n+URDFILE: %s,%d,\"%c\"", TYPE_PLUS }, }; static struct { const char* sta; const char* end; int type; } lut[] = { { "\r\nOK\r\n", NULL, TYPE_OK }, { "\r\nERROR\r\n", NULL, TYPE_ERROR }, { "\r\n+CME ERROR:", "\r\n", TYPE_ERROR }, { "\r\n+CMS ERROR:", "\r\n", TYPE_ERROR }, { "\r\nRING\r\n", NULL, TYPE_RING }, { "\r\nCONNECT\r\n", NULL, TYPE_CONNECT }, { "\r\nNO CARRIER\r\n", NULL, TYPE_NOCARRIER }, { "\r\nNO DIALTONE\r\n", NULL, TYPE_NODIALTONE }, { "\r\nBUSY\r\n", NULL, TYPE_BUSY }, { "\r\nNO ANSWER\r\n", NULL, TYPE_NOANSWER }, { "\r\n+", "\r\n", TYPE_PLUS }, { "\r\n@", NULL, TYPE_PROMPT }, // Sockets { "\r\n>", NULL, TYPE_PROMPT }, // SMS { "\n>", NULL, TYPE_PROMPT }, // File { "\r\nABORTED\r\n", NULL, TYPE_ABORTED }, // Current command aborted { "\r\n\r\n", "\r\n", TYPE_DBLNEWLINE }, // Double CRLF detected { "\r\n", "\r\n", TYPE_UNKNOWN }, // If all else fails, break up generic strings }; for (int i = 0; i < (int)(sizeof(lutF)/sizeof(*lutF)); i ++) { pipe->set(unkn); int ln = _parseFormated(pipe, len, lutF[i].fmt); if (ln == WAIT && fr) { return WAIT; } if ((ln != NOT_FOUND) && (unkn > 0)) { return TYPE_UNKNOWN | pipe->get(buf, unkn); } if (ln > 0) { return lutF[i].type | pipe->get(buf, ln); } } for (int i = 0; i < (int)(sizeof(lut)/sizeof(*lut)); i ++) { pipe->set(unkn); int ln = _parseMatch(pipe, len, lut[i].sta, lut[i].end); if (ln == WAIT && fr) { return WAIT; } // Double CRLF detected, discard it. // This resolves a case on G350 where "\r\n" is generated after +USORF response, but missing // on U260/U270, which would otherwise generate "\r\n\r\nOK\r\n" which is not parseable. if ((ln > 0) && (lut[i].type == TYPE_DBLNEWLINE) && (unkn == 0)) { return TYPE_UNKNOWN | pipe->get(buf, 2); } if ((ln != NOT_FOUND) && (unkn > 0)) { return TYPE_UNKNOWN | pipe->get(buf, unkn); } if (ln > 0) { return lut[i].type | pipe->get(buf, ln); } } // UNKNOWN unkn ++; len--; } return WAIT; } // ---------------------------------------------------------------- // Electron Serial Implementation // ---------------------------------------------------------------- MDMElectronSerial::MDMElectronSerial(int rxSize /*= 256*/, int txSize /*= 256*/) : ElectronSerialPipe(rxSize, txSize) { #ifdef MDM_DEBUG //_debugLevel = -1; #endif // Important to set _dev.lpm = LPM_ENABLED; when HW FLOW CONTROL enabled. } MDMElectronSerial::~MDMElectronSerial(void) { powerOff(); } int MDMElectronSerial::_send(const void* buf, int len) { return put((const char*)buf, len, true/*=blocking*/); } int MDMElectronSerial::getLine(char* buffer, int length) { int ret = _getLine(&_pipeRx, buffer, length); rxResume(); return ret; } void MDMElectronSerial::pause() { LOCK(); rxPause(); } void MDMElectronSerial::resumeRecv() { LOCK(); rxResume(); } #endif // !defined(HAL_CELLULAR_EXCLUDE)
違いを見つける