From 863ec3c3cc8be49fe8dd67add2cfb526e512002f Mon Sep 17 00:00:00 2001 From: Ari Parkkila Date: Fri, 9 Feb 2018 13:24:27 +0200 Subject: [PATCH] Initial cellular feature --- .../easy_cellular/CellularConnectionUtil.cpp | 513 +++++++ .../easy_cellular/CellularConnectionUtil.h | 90 ++ .../easy_cellular/EasyCellularConnection.cpp | 115 ++ .../easy_cellular/EasyCellularConnection.h | 119 ++ .../cellular/framework/API/CellularDevice.h | 121 ++ .../framework/API/CellularInformation.h | 69 + .../framework/API/CellularMultiplexer.h | 51 + .../cellular/framework/API/CellularNetwork.h | 382 +++++ .../cellular/framework/API/CellularPower.h | 130 ++ features/cellular/framework/API/CellularSIM.h | 85 ++ features/cellular/framework/API/CellularSMS.h | 155 ++ features/cellular/framework/AT/ATHandler.cpp | 1088 ++++++++++++++ features/cellular/framework/AT/ATHandler.h | 471 ++++++ .../cellular/framework/AT/AT_CellularBase.cpp | 36 + .../cellular/framework/AT/AT_CellularBase.h | 53 + .../framework/AT/AT_CellularDevice.cpp | 248 ++++ .../cellular/framework/AT/AT_CellularDevice.h | 144 ++ .../framework/AT/AT_CellularInformation.cpp | 56 + .../framework/AT/AT_CellularInformation.h | 70 + .../framework/AT/AT_CellularMultiplexer.cpp | 43 + .../framework/AT/AT_CellularMultiplexer.h | 41 + .../framework/AT/AT_CellularNetwork.cpp | 888 +++++++++++ .../framework/AT/AT_CellularNetwork.h | 300 ++++ .../framework/AT/AT_CellularPower.cpp | 232 +++ .../cellular/framework/AT/AT_CellularPower.h | 112 ++ .../cellular/framework/AT/AT_CellularSIM.cpp | 114 ++ .../cellular/framework/AT/AT_CellularSIM.h | 67 + .../cellular/framework/AT/AT_CellularSMS.cpp | 1303 +++++++++++++++++ .../cellular/framework/AT/AT_CellularSMS.h | 245 ++++ .../framework/AT/AT_CellularStack.cpp | 278 ++++ .../cellular/framework/AT/AT_CellularStack.h | 310 ++++ .../cellular/framework/common/CellularList.h | 102 ++ .../cellular/framework/common/CellularLog.cpp | 69 + .../cellular/framework/common/CellularLog.h | 82 ++ .../framework/common/CellularTargets.h | 47 + .../framework/common/CellularUtil.cpp | 326 +++++ .../cellular/framework/common/CellularUtil.h | 116 ++ features/cellular/framework/mux/mbed_mux.cpp | 1237 ++++++++++++++++ features/cellular/framework/mux/mbed_mux.h | 565 +++++++ .../framework/mux/mbed_mux_data_service.cpp | 53 + .../targets/QUECTEL/BC95/QUECTEL_BC95.cpp | 55 + .../targets/QUECTEL/BC95/QUECTEL_BC95.h | 40 + .../BC95/QUECTEL_BC95_CellularNetwork.cpp | 60 + .../BC95/QUECTEL_BC95_CellularNetwork.h | 58 + .../BC95/QUECTEL_BC95_CellularPower.cpp | 50 + .../QUECTEL/BC95/QUECTEL_BC95_CellularPower.h | 44 + .../BC95/QUECTEL_BC95_CellularStack.cpp | 200 +++ .../QUECTEL/BC95/QUECTEL_BC95_CellularStack.h | 133 ++ .../targets/QUECTEL/BG96/QUECTEL_BG96.cpp | 46 + .../targets/QUECTEL/BG96/QUECTEL_BG96.h | 47 + .../BG96/QUECTEL_BG96_CellularNetwork.cpp | 115 ++ .../BG96/QUECTEL_BG96_CellularNetwork.h | 50 + .../BG96/QUECTEL_BG96_CellularStack.cpp | 262 ++++ .../QUECTEL/BG96/QUECTEL_BG96_CellularStack.h | 136 ++ .../targets/TELIT/HE910/TELIT_HE910.cpp | 76 + .../targets/TELIT/HE910/TELIT_HE910.h | 38 + .../HE910/TELIT_HE910_CellularMultiplexer.cpp | 66 + .../HE910/TELIT_HE910_CellularMultiplexer.h | 34 + .../HE910/TELIT_HE910_CellularNetwork.cpp | 44 + .../TELIT/HE910/TELIT_HE910_CellularNetwork.h | 53 + .../TELIT/HE910/TELIT_HE910_CellularPower.cpp | 69 + .../TELIT/HE910/TELIT_HE910_CellularPower.h | 52 + .../targets/UBLOX/C027/UBLOX_C027.cpp | 47 + .../framework/targets/UBLOX/C027/UBLOX_C027.h | 40 + .../UBLOX/C027/UBLOX_C027_CellularNetwork.cpp | 44 + .../UBLOX/C027/UBLOX_C027_CellularNetwork.h | 52 + .../UBLOX/C027/UBLOX_C027_CellularPower.cpp | 49 + .../UBLOX/C027/UBLOX_C027_CellularPower.h | 45 + 68 files changed, 12531 insertions(+) create mode 100644 features/cellular/easy_cellular/CellularConnectionUtil.cpp create mode 100644 features/cellular/easy_cellular/CellularConnectionUtil.h create mode 100644 features/cellular/easy_cellular/EasyCellularConnection.cpp create mode 100644 features/cellular/easy_cellular/EasyCellularConnection.h create mode 100644 features/cellular/framework/API/CellularDevice.h create mode 100644 features/cellular/framework/API/CellularInformation.h create mode 100644 features/cellular/framework/API/CellularMultiplexer.h create mode 100644 features/cellular/framework/API/CellularNetwork.h create mode 100644 features/cellular/framework/API/CellularPower.h create mode 100644 features/cellular/framework/API/CellularSIM.h create mode 100644 features/cellular/framework/API/CellularSMS.h create mode 100644 features/cellular/framework/AT/ATHandler.cpp create mode 100644 features/cellular/framework/AT/ATHandler.h create mode 100644 features/cellular/framework/AT/AT_CellularBase.cpp create mode 100644 features/cellular/framework/AT/AT_CellularBase.h create mode 100644 features/cellular/framework/AT/AT_CellularDevice.cpp create mode 100644 features/cellular/framework/AT/AT_CellularDevice.h create mode 100644 features/cellular/framework/AT/AT_CellularInformation.cpp create mode 100644 features/cellular/framework/AT/AT_CellularInformation.h create mode 100644 features/cellular/framework/AT/AT_CellularMultiplexer.cpp create mode 100644 features/cellular/framework/AT/AT_CellularMultiplexer.h create mode 100644 features/cellular/framework/AT/AT_CellularNetwork.cpp create mode 100644 features/cellular/framework/AT/AT_CellularNetwork.h create mode 100644 features/cellular/framework/AT/AT_CellularPower.cpp create mode 100644 features/cellular/framework/AT/AT_CellularPower.h create mode 100644 features/cellular/framework/AT/AT_CellularSIM.cpp create mode 100644 features/cellular/framework/AT/AT_CellularSIM.h create mode 100644 features/cellular/framework/AT/AT_CellularSMS.cpp create mode 100644 features/cellular/framework/AT/AT_CellularSMS.h create mode 100644 features/cellular/framework/AT/AT_CellularStack.cpp create mode 100644 features/cellular/framework/AT/AT_CellularStack.h create mode 100644 features/cellular/framework/common/CellularList.h create mode 100644 features/cellular/framework/common/CellularLog.cpp create mode 100644 features/cellular/framework/common/CellularLog.h create mode 100644 features/cellular/framework/common/CellularTargets.h create mode 100644 features/cellular/framework/common/CellularUtil.cpp create mode 100644 features/cellular/framework/common/CellularUtil.h create mode 100644 features/cellular/framework/mux/mbed_mux.cpp create mode 100644 features/cellular/framework/mux/mbed_mux.h create mode 100644 features/cellular/framework/mux/mbed_mux_data_service.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.h create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.h create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.h create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.h create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.h create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp create mode 100644 features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.cpp create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.h create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.cpp create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.h create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.cpp create mode 100644 features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.h create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.cpp create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.h create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.cpp create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.h create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.cpp create mode 100644 features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.h diff --git a/features/cellular/easy_cellular/CellularConnectionUtil.cpp b/features/cellular/easy_cellular/CellularConnectionUtil.cpp new file mode 100644 index 0000000000..390233b117 --- /dev/null +++ b/features/cellular/easy_cellular/CellularConnectionUtil.cpp @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + + +#include "CellularConnectionUtil.h" + +#ifndef MBED_TRACE_MAX_LEVEL +#define MBED_TRACE_MAX_LEVEL TRACE_LEVEL_INFO +#endif +#include "CellularLog.h" +/*#define log_debug printf +#define log_info printf +#define log_warn printf +#define log_error printf +*/ + +// timeout to wait for AT responses +#define TIMEOUT_POWER_ON (1*1000) +#define TIMEOUT_SIM_PIN (1*1000) +#define TIMEOUT_NETWORK (10*1000) +#define TIMEOUT_REGISTRATION (180*1000) + +static EventQueue at_queue(8 * EVENTS_EVENT_SIZE); +static CELLULAR_DEVICE cellularDevice(at_queue); + +#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\ + enum name { v1, v2, v3, v4, v5, v6, v7};\ + const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7}; + +CellularConnectionUtil::CellularConnectionUtil() : _serial(0), _state(STATE_POWER_ON), _next_state(_state), _status_callback(0), _network(0), _power(0), _queue(8 * EVENTS_EVENT_SIZE), _queue_thread(0), _cellularDevice(&cellularDevice) +{ +} + +CellularConnectionUtil::~CellularConnectionUtil() +{ + stop(); +} + +bool CellularConnectionUtil::open_power(FileHandle *fh) +{ + if (!_power) { + _power = _cellularDevice->open_power(fh); + if (!_power) { + return false; + } + } + + nsapi_error_t err = _power->on(); + if (err != NSAPI_ERROR_OK && err != NSAPI_ERROR_UNSUPPORTED) { + log_warn("Cellular start failed. Power off/on."); + err = _power->off(); + if (err != NSAPI_ERROR_OK && err != NSAPI_ERROR_UNSUPPORTED) { + log_error("Cellular power down failed!"); + } + return false; + } + return true; +} + +bool CellularConnectionUtil::open_sim() +{ +#ifdef MBED_CONF_APP_CELLULAR_SIM_PIN + nsapi_error_t err; + static CellularSIM *sim; + if (!sim) { + sim = _cellularDevice->open_sim(_serial); + } + if (!sim) { + return false; + } + CellularSIM::SimState state = CellularSIM::SimStateUnknown; + // wait until SIM is readable + // here you could add wait(secs) if you know start delay of your SIM + while (sim->get_sim_state(state) != NSAPI_ERROR_OK || state == CellularSIM::SimStateUnknown) { + log_debug("Waiting for SIM (state %d)...", state); + return false; + } + if (state == CellularSIM::SimStatePinNeeded) { + log_info("SIM pin required, entering pin: %s", MBED_CONF_APP_CELLULAR_SIM_PIN); + err = sim->set_pin(MBED_CONF_APP_CELLULAR_SIM_PIN); + if (err) { + log_error("SIM pin set failed with: %d, bailing out...", err); + return false; + } + // here you could add wait(secs) if you know delay of changing PIN on your SIM + for (int i = 0; i < MAX_SIM_READY_WAITING_TIME; i++) { + if (sim->get_sim_state(state) == NSAPI_ERROR_OK && state == CellularSIM::SimStateReady) { + break; + } + log_debug("SIM state: %d", state); + return false; + } + } + return state == CellularSIM::SimStateReady; +#else + log_info("Continue without SIM."); + return true; +#endif +} + +bool CellularConnectionUtil::run_self_tests() +{ + CellularInformation *info = _cellularDevice->open_information(_serial); + + char buf[2048]; + if (info->get_manufacturer(buf, sizeof(buf)) == NSAPI_ERROR_OK) { + log_info("Cellular device manufacturer: %s", buf); + } + if (info->get_model(buf, sizeof(buf)) == NSAPI_ERROR_OK) { + log_info("Cellular device model: %s", buf); + } + if (info->get_revision(buf, sizeof(buf)) == NSAPI_ERROR_OK) { + log_info("Cellular device revision: %s", buf); + } + return true; +} + +bool CellularConnectionUtil::open_network() +{ + if (!_network) { + _network = _cellularDevice->open_network(_serial); + } + return (_network != NULL); +} + +bool CellularConnectionUtil::set_network_registration(char *plmn) +{ + if (_network->set_registration(plmn) != NSAPI_ERROR_OK) { + log_error("Failed to set network registration."); + return false; + } + return true; +} + +bool CellularConnectionUtil::get_network_registration(CellularNetwork::RegistrationType type, CellularNetwork::RegistrationStatus &status, bool &is_registered) +{ + is_registered = false; + bool is_roaming = false; + if (_network->get_registration_status(type, status) != NSAPI_ERROR_OK) { + return false; + } + switch (status) { + case CellularNetwork::RegisteredRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredHomeNetwork: + is_registered = true; + break; + case CellularNetwork::RegisteredSMSOnlyRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredSMSOnlyHome: + log_warn("SMS only network registration!"); + is_registered = true; + break; + case CellularNetwork::RegisteredCSFBNotPreferredRoaming: + is_roaming = true; + // fall-through + case CellularNetwork::RegisteredCSFBNotPreferredHome: + log_warn("Not preferred network registration!"); + is_registered = true; + break; + case CellularNetwork::AttachedEmergencyOnly: + case CellularNetwork::RegistrationDenied: + case CellularNetwork::NotRegistered: + case CellularNetwork::Unknown: + case CellularNetwork::SearchingNetwork: + default: + break; + } + + if (is_roaming) { + log_warn("Roaming cellular network!"); + } + + return true; +} + +bool CellularConnectionUtil::get_attach_network(CellularNetwork::AttachStatus &status) +{ + nsapi_error_t err = _network->get_attach(status); + if (err != NSAPI_ERROR_OK) { + return false; + } + return true; +} + +bool CellularConnectionUtil::set_attach_network() +{ + nsapi_error_t attach_err = _network->set_attach(); + if (attach_err != NSAPI_ERROR_OK) { + return false; + } + return true; +} + +void CellularConnectionUtil::report_failure(const char* msg) +{ + log_error("Cellular network failed: %s", msg); + if (_status_callback) { + _status_callback(_state, _next_state); + } +} + +bool CellularConnectionUtil::continue_with_state(CellularState state) +{ + _state = state; + if (!_queue.call_in(0, callback(this, &CellularConnectionUtil::event))) { + stop(); + return NSAPI_ERROR_NO_MEMORY; + } + + return NSAPI_ERROR_OK; +} + +void CellularConnectionUtil::event() +{ + nsapi_error_t err; + int event_timeout = -1; + switch (_state) { + case STATE_POWER_ON: + cellularDevice.set_timeout(TIMEOUT_POWER_ON); + log_info("Cellular power ON (timeout %d ms)", TIMEOUT_POWER_ON); + if (open_power(_serial)) { + _next_state = STATE_SELF_TEST; + } else { + static int retry_count; + if (++retry_count < 10) { + log_warn("Power ON retry %d", retry_count); + event_timeout = 1000; + } else { + report_failure("Power"); + return; + } + } + break; + case STATE_SELF_TEST: + cellularDevice.set_timeout(TIMEOUT_POWER_ON); + log_info("Cellular self-test (timeout %d ms)", TIMEOUT_POWER_ON); + if (_power->set_at_mode() == NSAPI_ERROR_OK) { + _next_state = STATE_START_CELLULAR; + run_self_tests(); + } else { + static int retry_count = 0; + if (++retry_count < 10) { + log_warn("Waiting for cellular %d", retry_count); + event_timeout = 1000; + } else { + report_failure("Power"); + return; + } + } + break; + case STATE_START_CELLULAR: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + log_info("Start cellular (timeout %d ms)", TIMEOUT_NETWORK); + open_network(); + _next_state = STATE_SIM_PIN; + break; + case STATE_SIM_PIN: + cellularDevice.set_timeout(TIMEOUT_SIM_PIN); + log_info("Start cellular (timeout %d ms)", TIMEOUT_SIM_PIN); + if (open_sim()) { + _next_state = STATE_REGISTERING_NETWORK; + log_info("Check for network registration"); + } else { + static int retry_count; + if (++retry_count < 10) { + log_warn("Waiting for SIM %d", retry_count); + event_timeout = 1000; + } else { + report_failure("Entering SIM PIN"); + return; + } + } + break; + case STATE_REGISTERING_NETWORK: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + CellularNetwork::RegistrationStatus status; + bool is_registered; + for (int type = 0; type < CellularNetwork::C_MAX; type++) { + if (get_network_registration((CellularNetwork::RegistrationType)type, status, is_registered)) { + if (is_registered) { + log_info("Registered to cellular network (status %d)", status); + _next_state = STATE_ATTACH_NETWORK; + log_info("Check cellular network attach state"); + event_timeout = 0; + break; + } else { + if (status == CellularNetwork::RegistrationDenied) { + static int backoff_timeout = 1; + log_warn("Network registration denied! Retry after %d seconds.", backoff_timeout); + event_timeout = backoff_timeout * 1000; + backoff_timeout *= 2; + _next_state = STATE_REGISTERING_NETWORK; + break; + } else if (status == CellularNetwork::NotRegistered) { + log_info("(STATE_REGISTERING_NETWORK), not registered"); + if (event_timeout == -1) { + _next_state = STATE_REGISTER_NETWORK; + event_timeout = 0; + } + } else { + static int retry_count; + if (++retry_count < 18) { + log_info("Waiting for registration"); + event_timeout = 1*1000; + _next_state = STATE_REGISTERING_NETWORK; + } else { + if (event_timeout == -1) { + log_info("Start cellular registration"); + _next_state = STATE_REGISTER_NETWORK; + } + } + } + } + } + } + if (_state == _next_state && event_timeout == -1) { + event_timeout = 0; + } + break; + case STATE_REGISTER_NETWORK: + cellularDevice.set_timeout(TIMEOUT_REGISTRATION); + log_info("Register to cellular network (timeout %d ms)", TIMEOUT_REGISTRATION); + if (set_network_registration()) { + _next_state = STATE_REGISTERING_NETWORK; + } else { + static int retry_count; + if (++retry_count < 3) { + event_timeout = 1000; + } else { + report_failure("Registration"); + return; + } + } + break; + case STATE_ATTACHING_NETWORK: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + CellularNetwork::AttachStatus attach_status; + if (get_attach_network(attach_status)) { + if (attach_status == CellularNetwork::Attached) { + _next_state = STATE_CONNECT_NETWORK; + } else { + _next_state = STATE_ATTACH_NETWORK; + } + } else { + event_timeout = 0; + } + break; + case STATE_ATTACH_NETWORK: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + log_info("Attach to cellular network (timeout %d ms)", TIMEOUT_NETWORK); + if (set_attach_network()) { + _next_state = STATE_ATTACHING_NETWORK; + log_info("Cellular network attaching"); + } else { + event_timeout = 0; + } + break; + case STATE_CONNECT_NETWORK: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + log_info("Connect to cellular network (timeout %d ms)", TIMEOUT_NETWORK); + // Set APN + //network->set_credentials("internet"); + err = _network->connect(); + if (!err) { + _next_state = STATE_READY; + } else { + report_failure("Network connect"); + return; + } + break; + case STATE_READY: + cellularDevice.set_timeout(TIMEOUT_NETWORK); + log_debug("Cellular ready! (timeout %d ms)", TIMEOUT_NETWORK); + if (_status_callback) { + _status_callback(_state, _next_state); + } + break; + default: + MBED_ASSERT(0); + break; + } + + if (_next_state != _state || event_timeout >= 0) { + if (_next_state != _state) { + log_info("Cellular state from %d to %d", _state, _next_state); + if (_status_callback) { + if (!_status_callback(_state, _next_state)) { + return; + } + } + } else { + if (event_timeout == 0) { + static int retry_count = 0; + if (++retry_count < 3) { + log_info("Cellular event retry %d", retry_count); + } else { + report_failure("Cellular connection failed!"); + return; + } + } else { + log_info("Cellular event in %d milliseconds", event_timeout); + } + } + _state = _next_state; + if (event_timeout == -1) { + event_timeout = 0; + } + if (!_queue.call_in(event_timeout, callback(this, &CellularConnectionUtil::event))) { + report_failure("Cellular event failure!"); + return; + } + } +} + +bool CellularConnectionUtil::start(bool start_dispatch) +{ + log_info("CellularConnectionUtil::start"); + _power = _cellularDevice->open_power(_serial); + if (!_power) { + stop(); + return false; + } + _network = _cellularDevice->open_network(_serial); + if (!_network) { + stop(); + return false; + } + if (!_queue.call_in(0, callback(this, &CellularConnectionUtil::event))) { + stop(); + return false; + } + + at_queue.chain(&_queue); + + if (start_dispatch) { + log_info("Create cellular thread"); + _queue_thread = new Thread(); + if (!_queue_thread) { + stop(); + return false; + } + if (_queue_thread->start(callback(&_queue, &EventQueue::dispatch_forever)) != osOK) { + stop(); + return false; + } + } + log_info("CellularConnectionUtil::started"); + return true; +} + +void CellularConnectionUtil::stop() +{ + log_info("CellularConnectionUtil::stop"); + _cellularDevice->close_power(); + _cellularDevice->close_network(); + if (_queue_thread) { + _queue_thread->terminate(); + _queue_thread = NULL; + } +} + +void CellularConnectionUtil::set_serial(UARTSerial *serial) +{ + _serial = serial; +} + +void CellularConnectionUtil::set_callback(mbed::Callback status_callback) +{ + _status_callback = status_callback; +} + +EventQueue *CellularConnectionUtil::get_queue() +{ + return &_queue; +} + +CellularNetwork* CellularConnectionUtil::get_network() +{ + if (_state != STATE_READY) { + return NULL; + } + return _network; +} + +CellularDevice* CellularConnectionUtil::get_device() +{ + + if (_cellularDevice) { + return _cellularDevice; + } else { + return NULL; + } +} + diff --git a/features/cellular/easy_cellular/CellularConnectionUtil.h b/features/cellular/easy_cellular/CellularConnectionUtil.h new file mode 100644 index 0000000000..856f6efc2a --- /dev/null +++ b/features/cellular/easy_cellular/CellularConnectionUtil.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _CELLULAR_CONNECTION_UTIL_H +#define _CELLULAR_CONNECTION_UTIL_H + +#include +#include +#include + +#include "CellularNetwork.h" +#include "CellularPower.h" + +// modem type is defined as CELLULAR_DEVICE macro +#define _CELLULAR_STRINGIFY(a) #a +#define CELLULAR_STRINGIFY(a) _CELLULAR_STRINGIFY(a) +#include "CellularTargets.h" +#include CELLULAR_STRINGIFY(CELLULAR_DEVICE.h) + +class CellularConnectionUtil +{ +public: + enum CellularState { + STATE_POWER_ON = 0, + STATE_SELF_TEST = 1, + STATE_START_CELLULAR = 2, + STATE_SIM_PIN = 3, + STATE_REGISTER_NETWORK = 4, + STATE_REGISTERING_NETWORK = 5, + STATE_ATTACH_NETWORK = 7, + STATE_ATTACHING_NETWORK = 8, + STATE_CONNECT_NETWORK = 9, + STATE_READY = 10, + }; + +public: + CellularConnectionUtil(); + virtual ~CellularConnectionUtil(); + void set_serial(UARTSerial *serial); + void set_callback(mbed::Callback status_callback); + EventQueue* get_queue(); + bool start(bool start_dispatch = true); + void stop(); + CellularNetwork* get_network(); + CellularDevice* get_device(); + bool continue_with_state(CellularState state); + +protected: + bool open_power(FileHandle *fh); + bool open_sim(); + bool open_network(); + bool get_network_registration(CellularNetwork::RegistrationType type, CellularNetwork::RegistrationStatus &status, bool &is_registered); + bool set_network_registration(char *plmn = 0); + bool get_attach_network(CellularNetwork::AttachStatus &status); + bool set_attach_network(); + +private: + bool run_self_tests(); + void report_failure(const char* msg); + void event(); + + UARTSerial *_serial; + CellularState _state; + CellularState _next_state; + + mbed::Callback _status_callback; + + CellularNetwork *_network; + CellularPower *_power; + EventQueue _queue; + Thread *_queue_thread; + CellularDevice *_cellularDevice; +}; + + +#endif /* _CELLULAR_CONNECTION_UTIL_H */ diff --git a/features/cellular/easy_cellular/EasyCellularConnection.cpp b/features/cellular/easy_cellular/EasyCellularConnection.cpp new file mode 100644 index 0000000000..a08a1a58da --- /dev/null +++ b/features/cellular/easy_cellular/EasyCellularConnection.cpp @@ -0,0 +1,115 @@ +/* Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "CellularConnectionUtil.h" +#include "CellularTargets.h" +#include "CellularUtil.h" +#include "CellularConnectionUtil.h" + +#include "EasyCellularConnection.h" + +#include "CellularLog.h" + +static CellularConnectionUtil cellularConnection; +static rtos::Semaphore cellularSemaphore(0); +static UARTSerial cellularSerial(MDMTXD, MDMRXD, MBED_CONF_APP_CELLULAR_SERIAL_SPEED); + +static int cellular_status(int state, int next_state) +{ + if (state == CellularConnectionUtil::STATE_READY) { + MBED_ASSERT(cellularSemaphore.release() == osOK); + } + return 1; +} + +EasyCellularConnection::EasyCellularConnection() +{ + log_info("EasyCellularConnection start"); +#if defined (MDMRTS) && defined (MDMCTS) + log_info("Serial RTS/CTS flow control in use"); + cellularSerial.set_flow_control(SerialBase::RTSCTS, MDMRTS, MDMCTS); +#endif + cellularConnection.set_serial(&cellularSerial); + cellularConnection.set_callback(callback(cellular_status)); +} + +EasyCellularConnection::~EasyCellularConnection() +{ + cellularConnection.stop(); +} + +void EasyCellularConnection::set_credentials(const char *apn, const char *uname, const char *pwd) +{ +} + +/* if (cellularConnection.start()) { + int ret_wait = cellularSemaphore.wait(180*1000); + if (ret_wait == 1) { + network_interface = cellularConnection.get_network(); + if (network_interface) { + connect_success = true; + } + if (log_messages) { + printf("[EasyConnect] Cellular connection %s!\n", connect_success?"succesful":"failed"); + } + } + }*/ + +void EasyCellularConnection::set_sim_pin(const char *sim_pin) +{ + //go_to_state(); +} + +nsapi_error_t EasyCellularConnection::connect(const char *sim_pin, const char *apn, const char *uname, const char *pwd) +{ + return 0; +} + +nsapi_error_t EasyCellularConnection::connect() +{ + return 0; +} + +nsapi_error_t EasyCellularConnection::disconnect() +{ + return 0; +} + +bool EasyCellularConnection::is_connected() +{ + return 0; +} + +const char *EasyCellularConnection::get_ip_address() +{ + return 0; +} + +const char *EasyCellularConnection::get_netmask() +{ + return 0; +} + +const char *EasyCellularConnection::get_gateway() +{ + return 0; +} + +NetworkStack *EasyCellularConnection::get_stack() +{ + return 0; +} diff --git a/features/cellular/easy_cellular/EasyCellularConnection.h b/features/cellular/easy_cellular/EasyCellularConnection.h new file mode 100644 index 0000000000..c79504ec43 --- /dev/null +++ b/features/cellular/easy_cellular/EasyCellularConnection.h @@ -0,0 +1,119 @@ +/* Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EASY_CELLULAR_CONNECTION_H +#define EASY_CELLULAR_CONNECTION_H + +#include "netsocket/CellularBase.h" + +/** ExampleCellularBase class + * + * A simplified example about how to use cellular + */ +class EasyCellularConnection: public CellularBase { + +public: + EasyCellularConnection(); + virtual ~EasyCellularConnection(); + +public: + + /** Set the Cellular network credentials + * + * Please check documentation of connect() for default behaviour of APN settings. + * + * @param apn Access point name + * @param uname optionally, Username + * @param pwd optionally, password + */ + virtual void set_credentials(const char *apn, const char *uname = 0, + const char *pwd = 0); + + /** Set the pin code for SIM card + * + * @param sim_pin PIN for the SIM card + */ + virtual void set_sim_pin(const char *sim_pin); + + /** Start the interface + * + * Attempts to connect to a Cellular network. + * + * @param sim_pin PIN for the SIM card + * @param apn optionally, access point name + * @param uname optionally, Username + * @param pwd optionally, password + * @return NSAPI_ERROR_OK on success, or negative error code on failure + */ + virtual nsapi_error_t connect(const char *sim_pin, const char *apn = 0, + const char *uname = 0, + const char *pwd = 0); + + /** Start the interface + * + * Attempts to connect to a Cellular network. + * If the SIM requires a PIN, and it is not set/invalid, NSAPI_ERROR_AUTH_ERROR is returned. + * + * @return NSAPI_ERROR_OK on success, or negative error code on failure + */ + virtual nsapi_error_t connect(); + + /** Stop the interface + * + * @return 0 on success, or error code on failure + */ + virtual nsapi_error_t disconnect(); + + /** Check if the connection is currently established or not + * + * @return true/false If the cellular module have successfully acquired a carrier and is + * connected to an external packet data network using PPP, isConnected() + * API returns true and false otherwise. + */ + virtual bool is_connected(); + + /** Get the local IP address + * + * @return Null-terminated representation of the local IP address + * or null if no IP address has been received + */ + virtual const char *get_ip_address(); + + /** Get the local network mask + * + * @return Null-terminated representation of the local network mask + * or null if no network mask has been received + */ + virtual const char *get_netmask(); + + /** Get the local gateways + * + * @return Null-terminated representation of the local gateway + * or null if no network mask has been received + */ + virtual const char *get_gateway(); + +protected: + + /** Provide access to the NetworkStack object + * + * @return The underlying NetworkStack object + */ + virtual NetworkStack *get_stack(); +}; + +#endif // EASY_CELLULAR_CONNECTION_H + +/** @}*/ diff --git a/features/cellular/framework/API/CellularDevice.h b/features/cellular/framework/API/CellularDevice.h new file mode 100644 index 0000000000..4bd7013283 --- /dev/null +++ b/features/cellular/framework/API/CellularDevice.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_DEVICE_H_ +#define CELLULAR_DEVICE_H_ + +#include "FileHandle.h" + +#include "CellularSIM.h" +#include "CellularNetwork.h" +#include "CellularSMS.h" +#include "CellularPower.h" +#include "CellularMultiplexer.h" +#include "CellularInformation.h" + +namespace mbed { + +/** + * Class CellularDevice + * + * An abstract interface that defines opening and closing of cellular interfaces. + * Deleting/Closing of opened interfaces can be done only via this class. + */ +class CellularDevice +{ +public: + /** virtual Destructor + */ + virtual ~CellularDevice() {} + +public: + /** Create new CellularNetwork interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularNetwork. + */ + virtual CellularNetwork *open_network(FileHandle *fh) = 0; + + /** Create new CellularSMS interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularSMS. + */ + virtual CellularSMS *open_sms(FileHandle *fh) = 0; + + /** Create new CellularPower interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularPower. + */ + virtual CellularPower *open_power(FileHandle *fh) = 0; + + /** Create new CellularSIM interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularSIM. + */ + virtual CellularSIM *open_sim(FileHandle *fh) = 0; + + /** Create new CellularMultiplexer interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularMultiplexer. + */ + virtual CellularMultiplexer *open_multiplexer(FileHandle *fh) = 0; + + /** Create new CellularInformation interface. + * + * @param fh file handle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularInformation. + */ + virtual CellularInformation *open_information(FileHandle *fh) = 0; + + /** Closes the opened CellularNetwork by deleting the CellularNetwork instance. + */ + virtual void close_network() = 0; + + /** Closes the opened CellularNetwork by deleting the CellularSMS instance. + */ + virtual void close_sms() = 0; + + /** Closes the opened CellularNetwork by deleting the CellularPower instance. + */ + virtual void close_power() = 0; + + /** Closes the opened CellularNetwork by deleting the CellularSIM instance. + */ + virtual void close_sim() = 0; + + /** Closes the opened CellularNetwork by deleting the CellularMultiplexer instance. + */ + virtual void close_multiplexer() = 0; + + /** Closes the opened CellularNetwork by deleting the CellularInformation instance. + */ + virtual void close_information() = 0; + + /** Set the default response timeout. + * + * @param timeout milliseconds to wait response from modem + */ + virtual void set_timeout(int timeout) = 0; +}; + +} // namespace mbed + +#endif // CELLULAR_DEVICE_H_ diff --git a/features/cellular/framework/API/CellularInformation.h b/features/cellular/framework/API/CellularInformation.h new file mode 100644 index 0000000000..d2be876921 --- /dev/null +++ b/features/cellular/framework/API/CellularInformation.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_INFORMATION_H_ +#define CELLULAR_INFORMATION_H_ + +#include +#include "nsapi_types.h" + +namespace mbed { + +/** + * Class CellularInformation + * + * An abstract interface that provides information about cellular device. + */ +class CellularInformation +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** virtual Destructor + */ + virtual ~CellularInformation() {}; + +public: + /** Request manufacturer identification of cellular device + * + * @param buf manufacturer identification + * @param buf_size max length of manufacturer identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_manufacturer(char *buf, size_t buf_size) = 0; + + /** Request model identification of cellular device + * + * @param buf model identification + * @param buf_size max length of model identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_model(char *buf, size_t buf_size) = 0; + + /** Request revision identification of cellular device + * + * @param buf revision identification + * @param buf_size max length of revision identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_revision(char *buf, size_t buf_size) = 0; +}; + +} // namespace mbed + +#endif // CELLULAR_INFORMATION_H_ diff --git a/features/cellular/framework/API/CellularMultiplexer.h b/features/cellular/framework/API/CellularMultiplexer.h new file mode 100644 index 0000000000..3ea9604d86 --- /dev/null +++ b/features/cellular/framework/API/CellularMultiplexer.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_MULTIPLEXER_H_ +#define CELLULAR_MULTIPLEXER_H_ + +#include "nsapi_types.h" + +namespace mbed { + +/** + * Class CellularMultiplexer + * + * An abstract interface that provides a way to start multiplexer mode in modem. + */ +class CellularMultiplexer +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** virtual Destructor + */ + virtual ~CellularMultiplexer() {}; + +public: + + /** Starts modem multiplexer mode specified by 3GPP TS 27.010. + * + * @return zero on success, negative error code on failure + */ + virtual nsapi_error_t multiplexer_mode_start() = 0; +}; + +} // namespace mbed + +#endif // CELLULAR_MULTIPLEXER_H_ diff --git a/features/cellular/framework/API/CellularNetwork.h b/features/cellular/framework/API/CellularNetwork.h new file mode 100644 index 0000000000..5b872227bd --- /dev/null +++ b/features/cellular/framework/API/CellularNetwork.h @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_NETWORK_H_ +#define CELLULAR_NETWORK_H_ + +#include "CellularInterface.h" +#include "NetworkInterface.h" +#include "CellularList.h" + +namespace mbed { + +/** + * Class CellularNetwork + * + * An abstract interface for connecting to a network and getting information from it. + */ +class CellularNetwork : public NetworkInterface +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** + * virtual Destructor + */ + virtual ~CellularNetwork() {} + +public: + /* Definition for Supported CIoT EPS optimizations type. */ + enum Supported_UE_Opt { + SUPPORTED_UE_OPT_NO_SUPPORT = 0, /* No support. */ + SUPPORTED_UE_OPT_CONTROL_PLANE, /* Support for control plane CIoT EPS optimization. */ + SUPPORTED_UE_OPT_USER_PLANE, /* Support for user plane CIoT EPS optimization. */ + SUPPORTED_UE_OPT_BOTH, /* Support for both control plane CIoT EPS optimization and user plane CIoT EPS + optimization. */ + SUPPORTED_UE_OPT_MAX + }; + + /* Definition for Preferred CIoT EPS optimizations type. */ + enum Preferred_UE_Opt { + PREFERRED_UE_OPT_NO_PREFERENCE = 0, /* No preference. */ + PREFERRED_UE_OPT_CONTROL_PLANE, /* Preference for control plane CIoT EPS optimization. */ + PREFERRED_UE_OPT_USER_PLANE, /* Preference for user plane CIoT EPS optimization. */ + PREFERRED_UE_OPT_MAX + }; + + /* Network registration status */ + enum RegistrationStatus { + NotRegistered = 0, + RegisteredHomeNetwork, + SearchingNetwork, + RegistrationDenied, + Unknown, + RegisteredRoaming, + RegisteredSMSOnlyHome, + RegisteredSMSOnlyRoaming, + AttachedEmergencyOnly, + RegisteredCSFBNotPreferredHome, + RegisteredCSFBNotPreferredRoaming = 10 + }; + + /* Network registration type */ + enum RegistrationType { + C_EREG = 0, + C_GREG, + C_REG, + C_MAX + }; + + /* device attach status to network */ + enum AttachStatus { + Detached = 0, + Attached, + }; + + /* whether the additional exception reports are allowed to be sent when the maximum uplink rate is reached */ + enum RateControlExceptionReports { + NotAllowedToBeSent = 0, + AllowedToBeSent + }; + + /* specifies the time unit to be used for the maximum uplink rate */ + enum RateControlUplinkTimeUnit { + Unrestricted = 0, + Minute, + Hour, + Day, + Week + }; + + /* authentication type when activating or modifying the pdp context */ + enum AuthenticationType { + NOAUTH = 0, + PAP, + CHAP + }; + + // 3GPP TS 27.007 - 7.3 PLMN selection +COPS + struct operator_t { + enum Status { + Unknown, + Available, + Current, + Forbiden + }; + + enum RadioAccessTechnology { + RAT_GSM, + RAT_GSM_COMPACT, + RAT_UTRAN, + RAT_EGPRS, + RAT_HSDPA, + RAT_HSUPA, + RAT_HSDPA_HSUPA, + RAT_E_UTRAN, + RAT_CATM1, + RAT_NB1, + RAT_UNKNOWN + }; + + + Status op_status; + char op_long[16+9]; + char op_short[8+4]; + char op_num[8+4]; + RadioAccessTechnology op_rat; + operator_t *next; + + operator_t() { + op_status = Unknown; + op_rat = RAT_UNKNOWN; + next = NULL; + } + }; + + typedef CellularList operList_t; + + /* PDP Context information */ + struct pdpcontext_params_t { + char apn[100+1]; + char local_addr[63+1]; + char local_subnet_mask[63+1]; + char gateway_addr[63+1]; + char dns_primary_addr[63+1]; + char dns_secondary_addr[63+1]; + char p_cscf_prim_addr[63+1]; + char p_cscf_sec_addr[63+1]; + int cid; + int bearer_id; + int im_signalling_flag; + int lipa_indication; + int ipv4_mtu; + int wlan_offload; + int local_addr_ind; + int non_ip_mtu; + int serving_plmn_rate_control_value; + pdpcontext_params_t* next; + + pdpcontext_params_t() { + apn[0] = '\0'; + local_addr[0] = '\0'; + local_subnet_mask[0] = '\0'; + gateway_addr[0] = '\0'; + dns_primary_addr[0] = '\0'; + dns_secondary_addr[0] = '\0'; + p_cscf_prim_addr[0] = '\0'; + p_cscf_sec_addr[0] = '\0'; + cid = -1; + bearer_id = -1; + im_signalling_flag = -1; + lipa_indication = -1; + ipv4_mtu = -1; + wlan_offload = -1; + local_addr_ind = -1; + non_ip_mtu = -1; + serving_plmn_rate_control_value = -1; + next = NULL; + } + }; + typedef CellularList pdpContextList_t; + + /** Request registering to network. + * + * @param plmn format is in numeric format or 0 for automatic network registration + * @return zero on success + */ + virtual nsapi_error_t set_registration(char *plmn = 0) = 0; + + /** Gets the network registration status. + * + * @param type see RegistrationType values + * @param status see RegistrationStatus values + * @return zero on success + */ + virtual nsapi_error_t get_registration_status(RegistrationType type, RegistrationStatus &status) = 0; + + /** Set the cellular network APN and credentials + * + * @param apn Optional name of the network to connect to + * @param username Optional username for the APN + * @param password Optional password fot the APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t set_credentials(const char *apn, + const char *username = 0, const char *password = 0) = 0; + + /** Set the cellular network APN and credentials + * + * @param apn Name of the network to connect to + * @param type Authentication type to use + * @param username Optional username for the APN + * @param password Optional password fot the APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t set_credentials(const char *apn, AuthenticationType type, + const char *username = 0, const char *password = 0) = 0; + + /** Request attach to network. + * + * @param timeout milliseconds to wait for attach response + * @return zero on success + */ + virtual nsapi_error_t set_attach(int timeout = 10*1000) = 0; + + /** Request attach status from network. + * + * @param status see AttachStatus values + * @return zero on success + */ + virtual nsapi_error_t get_attach(AttachStatus &status) = 0; + + /** Get APN rate control. + * + * @remark optional params are not updated if not received from network, so use good defaults + * @param reports Additional exception reports at maximum rate reached are allowed to be sent [optional] + * @param time_unit Uplink time unit with values 0=unrestricted, 1=minute, 2=hour, 3=day, 4=week [optional] + * @param uplink_rate Maximum number of messages per timeUnit [optional] + * @return zero on success + */ + virtual nsapi_error_t get_rate_control(CellularNetwork::RateControlExceptionReports &reports, + CellularNetwork::RateControlUplinkTimeUnit &time_unit, int &uplink_rate) = 0; + + /** Get backoff timer value + * + * @param backoff_time Backoff timer value associated with PDP APN in seconds + * @return zero on success + */ + virtual nsapi_error_t get_backoff_time(int &backoff_time) = 0; + + /** Sets radio access technology. + * + * @param op_rat Radio access technology + * @return zero on success + */ + virtual nsapi_error_t set_access_technology(operator_t::RadioAccessTechnology op_rat) = 0; + + /** Scans for operators module can reach. + * + * @param operators Container of reachable operators and their access technologies + * @param ops_count Number of found operators + * @return zero on success + */ + virtual nsapi_error_t scan_plmn(operList_t &operators, int &ops_count) = 0; + + /** Set CIoT optimizations. + * + * @param supported_opt Supported CIoT EPS optimizations. + * @param preferred_opt Preferred CIoT EPS optimizations. + * @return zero on success + */ + virtual nsapi_error_t set_ciot_optimization_config(Supported_UE_Opt supported_opt, + Preferred_UE_Opt preferred_opt) = 0; + + /** Get CIoT optimizations. + * + * @param supported_opt Supported CIoT EPS optimizations. + * @param preferred_opt Preferred CIoT EPS optimizations. + * @return zero on success + */ + virtual nsapi_error_t get_ciot_optimization_config(Supported_UE_Opt& supported_opt, + Preferred_UE_Opt& preferred_opt) = 0; + + /** Start the interface. Attempts to connect to a cellular network. + * + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t connect() = 0; + + /** Start the interface. Attempts to connect to a cellular network. + * + * @param apn Optional name of the network to connect to + * @param username Optional username for your APN + * @param password Optional password for your APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t connect(const char *apn, + const char *username = 0, const char *password = 0) = 0; + + /** + * Set the pdn type to be used + * + * @param stack_type the stack type to be used. + * + * @return NSAPI_ERROR_OK on success + */ + virtual nsapi_error_t set_stack_type(nsapi_ip_stack_t stack_type) = 0; + + /** + * Get the pdn type in use + * + * @return stack type + */ + virtual nsapi_ip_stack_t get_stack_type() = 0; + + /** Get the relevant information for an active non secondary PDP context. + * + * @remark optional params are not updated if not received from network. + * @param params_list reference to linked list which is filled on successful call + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t get_pdpcontext_params(pdpContextList_t& params_list) = 0; + + /** Get extended signal quality parameters. + * + * @param rxlev signal strength level + * @param ber bit error rate + * @param rscp signal code power + * @param ecno ratio of the received energy per PN chip to the total received power spectral density + * @param rsrq signal received quality + * @param rsrp signal received power + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_extended_signal_quality(int &rxlev, int &ber, int &rscp, int &ecno, int &rsrq, int &rsrp) = 0; + + /** Get signal quality parameters. + * + * @param rssi signal strength level + * @param ber bit error rate + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_signal_quality(int &rssi, int &ber) = 0; + + /** Get cell id. + * + * @param cell_id cell id + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_cell_id(int &cell_id) = 0; + + /** Get the last 3GPP error code + * @return see 3GPP TS 27.007 error codes + */ + virtual uint8_t get_3gpp_error() = 0; + + /** Get the operator params + * + * @param format format of the operator field + * @param operator_params applicable operator param fields filled + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_operator_params(int &format, operator_t &operator_params) = 0; + +}; + +} // namespace mbed + +#endif // CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/API/CellularPower.h b/features/cellular/framework/API/CellularPower.h new file mode 100644 index 0000000000..2e15927c84 --- /dev/null +++ b/features/cellular/framework/API/CellularPower.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef CELLULAR_API_CELLULARPOWER_H_ +#define CELLULAR_API_CELLULARPOWER_H_ + +#include "nsapi_types.h" + +namespace mbed { + +/** + * Class CellularPower + * + * An abstract interface for connecting to a network and getting information from it. + */ +class CellularPower +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** + * virtual Destructor + */ + virtual ~CellularPower() {} + +public: + /* Access technology used in method opt_receive_period */ + enum EDRXAccessTechnology { + EDRXGSM_EC_GSM_IoT_mode = 1, + EDRXGSM_A_Gb_mode, + EDRXUTRAN_Iu_mode, + EDRXEUTRAN_WB_S1_mode, + EDRXEUTRAN_NB_S1_mode + }; + + /** Set cellular device power on. Default implementation is empty. + * Device power on/off is modem/board specific behavior and must be done on inherited class if needed. + * Power on is done by toggling power pin/button. + * + * @remark set_at_mode must be called to initialise modem + * + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * @return zero on success + */ + virtual nsapi_error_t on() = 0; + + /** Set cellular device power off. Default implementation is empty. + * Device power on/off is modem/board specific behavior and must be done on inherited class if needed. + * Power off is done by toggling power pin/button. + * + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t off() = 0; + + /** Set AT command mode. Blocking until success or failure. + * + * @remark must be called after power on to prepare correct AT mode + * + * @return zero on success + */ + virtual nsapi_error_t set_at_mode() = 0; + + /** Set cellular device power level by enabling/disabling functionality. + * + * @param func_level: + * 0 minimum functionality + * 1 full functionality. Enable (turn on) the transmit and receive RF circuits for all supported radio access technologies. + * For MTs supporting +CSRA, this equals the RATs indicated by the response of +CSRA=?. Current +CSRA setting is ignored. + * It is not required that the MT transmit and receive RF circuits are in a disabled state for this setting to have effect. + * 2 disable (turn off) MT transmit RF circuits only + * 3 disable (turn off) MT receive RF circuits only + * 4 disable (turn off) both MT transmit and receive RF circuits + * + * @remark See 3GPP TS 27.007 CFUN for more details + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t set_power_level(int func_level) = 0; + + /** Reset and wake-up cellular device. + * + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t reset() = 0; + + /** Opt for power save setting on cellular device. If both parameters are zero then disables PSM. + * + * @remark See 3GPP TS 27.007 PSM for details + * + * @param periodic_time Timeout in seconds IoT subsystem is not expecting messaging + * @param active_time Timeout in seconds IoT subsystem waits for response + * @return zero on success + */ + virtual nsapi_error_t opt_power_save_mode(int periodic_time, int active_time) = 0; + + /** Opt for discontinuous reception on cellular device. + * + * @remark See 3GPP TS 27.007 eDRX for details. + * + * @param mode disable or enable the use of eDRX + * @param act_type type of access technology + * @param edrx_value requested edxr value. Extended DRX parameters information element. + * + * @return zero on success + */ + virtual nsapi_error_t opt_receive_period(int mode, EDRXAccessTechnology act_type, uint8_t edrx_value) = 0; +}; + +} // namespace mbed + +#endif /* CELLULAR_API_CELLULARPOWER_H_ */ diff --git a/features/cellular/framework/API/CellularSIM.h b/features/cellular/framework/API/CellularSIM.h new file mode 100644 index 0000000000..00ca6dc58d --- /dev/null +++ b/features/cellular/framework/API/CellularSIM.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_SIM_H_ +#define CELLULAR_SIM_H_ + +#include "nsapi_types.h" + +namespace mbed { + +const int MAX_SIM_READY_WAITING_TIME = 30; + +/** + * Class CellularSIM + * + * An abstract interface for SIM card handling. + */ +class CellularSIM +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** + * virtual Destructor + */ + virtual ~CellularSIM() {}; + +public: + /* enumeration for possible SIM states */ + enum SimState { + SimStateReady = 0, + SimStatePinNeeded, + SimStatePukNeeded, + SimStateUnknown + }; + + /** Open the SIM card by setting the pin code for SIM. + * + * @param sim_pin PIN for the SIM card + * @return zero on success + */ + virtual nsapi_error_t set_pin(const char *sim_pin) = 0; + + /**Change sim pin code. + * + * @param sim_pin Current PIN for sim + * @param new_pin New PIN for sim + * @return zero on success + */ + virtual nsapi_error_t change_pin(const char *sim_pin, const char *new_pin) = 0; + + /** Change is pin query needed after boot + * + * @param sim_pin Valid PIN for SIM card + * @param query_pin False is PIN query not needed, True if PIN query needed after boot. + * @return zero on success + */ + virtual nsapi_error_t set_pin_query(const char *sim_pin, bool query_pin) = 0; + + /** Get sim card's state + * + * @param state current state of SIM + * @return zero on success + */ + virtual nsapi_error_t get_sim_state(SimState &state) = 0; +}; + +} // namespace mbed + +#endif // CELLULAR_SIM_H_ diff --git a/features/cellular/framework/API/CellularSMS.h b/features/cellular/framework/API/CellularSMS.h new file mode 100644 index 0000000000..a7a85e8701 --- /dev/null +++ b/features/cellular/framework/API/CellularSMS.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_SMS_H_ +#define CELLULAR_SMS_H_ + +#include "Callback.h" +#include "nsapi_types.h" + +namespace mbed { + +// including trailing '\0' +const uint16_t SMS_MAX_SIZE_WITH_CONCATENATION = 4096 + 1; +const uint16_t SMS_MAX_PHONE_NUMBER_SIZE = 20 + 1; +const uint16_t SMS_MAX_TIME_STAMP_SIZE = 20 + 1; + +const uint16_t SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE = 140; +const uint16_t SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE = 160; + +const uint16_t SMS_SIM_WAIT_TIME_MILLISECONDS = 200; + +const int SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ = -5001; + +/** + * Class CellularSMS + * + * An abstract interface for SMS sending and reading. + */ +class CellularSMS +{ +protected: + // friend of CellularDevice so that it's the only way to close/delete this class. + friend class CellularDevice; + + /** + * virtual Destructor + */ + virtual ~CellularSMS() {}; +public: + + /* Enumeration for possible SMS modes, PDU and Text */ + enum CellularSMSMmode { + CellularSMSMmodePDU = 0, + CellularSMSMmodeText + }; + + /** Does all the necessary initializations needed for receiving and sending sms. + * + * @param mode enumeration for choosing the correct mode: text/pdu + * @return zero on success + */ + virtual nsapi_error_t initialize(CellularSMSMmode mode) = 0; + + /** Send the SMS with the given parameters + * + * @param phone_number Phone number where to send sms + * @param message SMS message content + * @param msg_len Length of the message + * @return possible error code or length of the sent sms + */ + virtual nsapi_size_or_error_t send_sms(const char* phone_number, const char* message, int msg_len) = 0; + + /** Gets the oldest received sms. + * + * @param buf preallocated buffer for sms message content + * @param buf_len length of allocated buf + * @param phone_num preallocated buffer for phone number where sms was sent + * @param phone_len length of allocated phone_num buffer + * @param time_stamp preallocated buffer for TP-Service Centre Time Stamp (format: yy/MM/dd,hh:mm:ss-+zz). +-zz is timezone. + * The unit of time zone is a quarter of an hour relative to GMT. For example +32 would be GMT+8. + * @param time_len length of allocated time_stamp buffer + * @param buf_size if method return error NSAPI_ERROR_NO_MEMORY because the given buf was not big enough this will + * hold the size which is enough. Otherwise zero. + * @return possible error code or size of buf. Will return SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ + * if sms was multipart but not all parts are present/failed to read. + */ + virtual nsapi_size_or_error_t get_sms(char* buf, uint16_t buf_len, char* phone_num, uint16_t phone_len, + char* time_stamp, uint16_t time_len, int *buf_size) = 0; + + /** Callback which is called when new sms is received. SMS can be fetched via method get_sms(). + * + * @remark In PDU mode there can be multipart sms and callback is called for every received part. + * + * @param func Callback function which is called when new sms is received. + */ + virtual void set_sms_callback(Callback func) = 0; + + /** CPMS preferred message storage + * + * @param memr memory from which messages are read and deleted + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * @param memw memory to which writing and sending operations are made + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * @param mems memory to which received SMs are preferred to be stored + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * + * @return 1 for success, 0 for failure + */ + virtual nsapi_error_t set_cpms(const char *memr, const char *memw, const char *mems) = 0; + + /** CSCA - set Service Center Address + * + * @param sca Service Center Address to be used for mobile originated SMS transmissions. + * @param type 129 - national numbering scheme, 145 - international numbering scheme (contains the character "+") + * + * @return 1 for success, 0 for failure + */ + virtual nsapi_error_t set_csca(const char *sca, int type) = 0; + + /** Set command sets the current character set used by the device. "GSM", "IRA",.... + * + * @remark Current implementation support only ASCII so choose the correct character set. + * + * @param chr_set preferred character set list (comma separated). Modem might not support the wanted character set + * so chr_set list is looped from start until supported set is found. Used character set index is returned. + * See more from 3GPP TS 27.005. + * @return Used character set index from the given list in case of success. Otherwise negative errorcode. + */ + virtual nsapi_size_or_error_t set_cscs(const char *chr_set) = 0; + + /** Deletes all messages from the currently set memory/SIM + * + * @return possible error code + */ + virtual nsapi_error_t delete_all_messages() = 0; + + /** Some modems need extra time between AT commands and responses or there will be error -314, SIM busy. + * If SIM busy errors are an issue this time should be increased. It can also be set to zero to make + * operations faster and more energy efficient if no errors will follow. By default wait time is zero. + * + * @param sim_wait_time + */ + virtual void set_extra_sim_wait_time(int sim_wait_time) = 0; +}; + +} // namespace mbed + +#endif // CELLULAR_SMS_H_ diff --git a/features/cellular/framework/AT/ATHandler.cpp b/features/cellular/framework/AT/ATHandler.cpp new file mode 100644 index 0000000000..36cfe095b9 --- /dev/null +++ b/features/cellular/framework/AT/ATHandler.cpp @@ -0,0 +1,1088 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ATHandler.h" +#include "mbed_poll.h" +#include "FileHandle.h" +#include "Timer.h" +#include "mbed_wait_api.h" +#include "mbed_debug.h" + +using namespace mbed; +using namespace events; + +//#define MBED_TRACE_MAX_LEVEL TRACE_LEVEL_DEBUG +#include "CellularLog.h" + +// this is intentionally TRACE_LEVEL_INFO +#if MBED_TRACE_MAX_LEVEL <= TRACE_LEVEL_INFO +#define at_debug(format, ...) do { if (_debug_on) debug(format, ## __VA_ARGS__); } while (0) +#else +#define at_debug(...) +#endif + +const char *mbed::OK = "OK\r\n"; +const uint8_t OK_LENGTH = 4; +const char *mbed::CRLF = "\r\n"; +const uint8_t CRLF_LENGTH = 2; +const char *CME_ERROR = "+CME ERROR:"; +const uint8_t CME_ERROR_LENGTH = 11; +const char *CMS_ERROR = "+CMS ERROR:"; +const uint8_t CMS_ERROR_LENGTH = 11; +const char *ERROR_ = "ERROR\r\n"; +const uint8_t ERROR_LENGTH = 7; +const uint8_t MAX_RESP_LENGTH = CMS_ERROR_LENGTH; + +static const uint8_t map_3gpp_errors[][2] = { + { 103, 3 }, { 106, 6 }, { 107, 7 }, { 108, 8 }, { 111, 11 }, { 112, 12 }, { 113, 13 }, { 114, 14 }, + { 115, 15 }, { 122, 22 }, { 125, 25 }, { 172, 95 }, { 173, 96 }, { 174, 97 }, { 175, 99 }, { 176, 111 }, + { 177, 8 }, { 126, 26 }, { 127, 27 }, { 128, 28 }, { 129, 29 }, { 130, 30 }, { 131, 31 }, { 132, 32 }, + { 133, 33 }, { 134, 34 }, { 140, 40 }, { 141, 41 }, { 142, 42 }, { 143, 43 }, { 144, 44 }, { 145, 45 }, + { 146, 46 }, { 178, 65 }, { 179, 66 }, { 180, 48 }, { 181, 83 }, { 171, 49 }, +}; + +ATHandler::ATHandler(FileHandle *fh, EventQueue &queue, int timeout, const char *output_delimiter) : + _nextATHandler(0), + _fileHandle(fh), + _queue(queue), + _last_err(NSAPI_ERROR_OK), + _last_3gpp_error(0), + _oob_string_max_length(0), + _oobs(NULL), + _at_timeout(timeout), + _previous_at_timeout(timeout), + _fh_sigio_set(false), + _processing(false), + _ref_count(1), + _stop_tag(NULL), + _delimiter(','), + _prefix_matched(false), + _urc_matched(false), + _error_found(false), + _max_resp_length(MAX_RESP_LENGTH), +#if MBED_TRACE_MAX_LEVEL == TRACE_LEVEL_DEBUG + _debug_on(true), +#else + _debug_on(false), +#endif + _cmd_start(false) +{ + //enable_debug(true); + + if (output_delimiter) { + _output_delimiter_length = strlen(output_delimiter); + _output_delimiter = new char[_output_delimiter_length]; + for (unsigned i=0; i<_output_delimiter_length; i++) { + _output_delimiter[i] = output_delimiter[i]; + } + } else { + _output_delimiter_length = 0; + _output_delimiter = NULL; + } + + reset_buffer(); + memset(_info_resp_prefix, 0, sizeof(_info_resp_prefix)); + + _current_scope = NotSet; + set_tag(&_resp_stop, OK); + set_tag(&_info_stop, CRLF); + set_tag(&_elem_stop, ")"); + + _fileHandle->set_blocking(false); + + set_filehandle_sigio(); +} + +void ATHandler::enable_debug(bool enable) +{ + _debug_on = enable; +} + +ATHandler::~ATHandler() +{ + while (_oobs) { + struct oob_t *oob = _oobs; + _oobs = oob->next; + delete oob; + } + if (_output_delimiter) { + delete [] _output_delimiter; + } +} + +void ATHandler::inc_ref_count() +{ + _ref_count++; +} + +void ATHandler::dec_ref_count() +{ + _ref_count--; +} + +int ATHandler::get_ref_count() +{ + return _ref_count; +} + +FileHandle *ATHandler::get_file_handle() +{ + return _fileHandle; +} + +void ATHandler::set_file_handle(FileHandle *fh) +{ + _fileHandle = fh; +} + +void ATHandler::set_urc_handler(const char *prefix, mbed::Callback callback) +{ + struct oob_t *oob = new struct oob_t; + oob->matching_to_received = true; + size_t prefix_len = strlen(prefix); + if (prefix_len > _oob_string_max_length) { + _oob_string_max_length = prefix_len; + if (_oob_string_max_length > _max_resp_length) { + _max_resp_length = _oob_string_max_length; + } + } + oob->prefix = prefix; + oob->cb = callback; + oob->next = _oobs; + _oobs = oob; +} + +void ATHandler::event() +{ + // _processing must be set before filehandle write/read to avoid repetitive sigio events + if (!_processing) { + _processing = true; + (void) _queue.call(Callback(this, &ATHandler::process_oob)); + } +} + +void ATHandler::lock() +{ +#ifdef AT_HANDLER_MUTEX + _fileHandleMutex.lock(); +#endif + _processing = true; + clear_error(); +} + +void ATHandler::unlock() +{ + _processing = false; +#ifdef AT_HANDLER_MUTEX + _fileHandleMutex.unlock(); +#endif + if (_fileHandle->readable() || (_recv_pos < _recv_len)) { + (void) _queue.call(Callback(this, &ATHandler::process_oob)); + } +} + +nsapi_error_t ATHandler::unlock_return_error() +{ + nsapi_error_t err = _last_err; + unlock(); + return err; +} + +void ATHandler::set_at_timeout(uint32_t timeout_milliseconds, bool default_timeout) +{ + if (default_timeout) { + _previous_at_timeout = timeout_milliseconds; + _at_timeout = timeout_milliseconds; + } else if (timeout_milliseconds != _at_timeout) { + _previous_at_timeout = _at_timeout; + _at_timeout = timeout_milliseconds; + } +} + +void ATHandler::restore_at_timeout() +{ + if (_previous_at_timeout != _at_timeout) { + _at_timeout =_previous_at_timeout; + } +} + +void ATHandler::process_oob() +{ + lock(); + log_info("process_oob %d", (_fileHandle->readable() || (_recv_pos < _recv_len))); + if (_fileHandle->readable() || (_recv_pos < _recv_len)) { + _current_scope = NotSet; + Timer timer; + timer.start(); + do { + if (match_urc()) { + if (_fileHandle->readable() || (_recv_pos < _recv_len)) { + continue; + } + break; + } + // If no match found, look for CRLF and consume everything up to CRLF + if (mem_str(_recv_buff, sizeof(_recv_buff), CRLF, CRLF_LENGTH)) { + consume_to_tag(CRLF, true); + } else { + if (_fileHandle->readable()) { + fill_buffer(); + } else { + wait_us(100); // wait for more incoming data + } + + } + } while (timer.read_ms() < 100); + } + log_debug("process_oob exit"); + + flush(); // consume anything that could not be handled + + unlock(); +} + +// oob match settings +void ATHandler::set_oobs_matching_param(bool is_it_a_match_or_not) +{ + for (struct oob_t *oob = _oobs; oob; oob = oob->next) { + oob->matching_to_received = is_it_a_match_or_not; + } +} + +void ATHandler::set_filehandle_sigio() +{ + if (_fh_sigio_set) { + return; + } + _fileHandle->sigio(mbed::Callback(this, &ATHandler::event)); + _fh_sigio_set = true; +} + +void ATHandler::reset_buffer() +{ + log_debug("%s", __func__); + memset(_recv_buff, 0, sizeof(_recv_buff)); + _recv_pos = 0; _recv_len = 0; +} + +void ATHandler::rewind_buffer() +{ + log_debug("%s", __func__); + if (_recv_pos > 0 && _recv_len >= _recv_pos) { + _recv_len -= _recv_pos; + // move what is not read to beginning of buffer + memmove(_recv_buff, _recv_buff + _recv_pos, _recv_len); + _recv_pos = 0; + // reset rest of buffer + memset(_recv_buff + _recv_len, 0, sizeof(_recv_buff) - _recv_len); + } +} + +// we are always expecting to receive something so there is wait timeout +void ATHandler::fill_buffer() +{ + log_debug("%s", __func__); + // Reset buffer when full + if (sizeof(_recv_buff) == _recv_len) { + reset_buffer(); + } + + Timer timer; + timer.start(); + do { + ssize_t len = _fileHandle->read(_recv_buff + _recv_len, sizeof(_recv_buff) - _recv_len); + if (len > 0) { + _recv_len += len; + at_debug("\n----------readable----------: %d\n", _recv_len); + for (size_t i = _recv_pos; i < _recv_len; i++) { + at_debug("%c", _recv_buff[i]); + } + at_debug("\n----------readable----------\n"); + return; + } + +#ifdef MBED_CONF_RTOS_PRESENT + wait_us(1000); // let other threads run for 1 milisecond +#endif + } while (timer.read_ms() < _at_timeout); + + set_error(NSAPI_ERROR_DEVICE_ERROR); + log_error("AT TIMEOUT, scope: %d timeout: %d", _current_scope, _at_timeout); +} + +int16_t ATHandler::get_char() +{ + if (_recv_pos == _recv_len) { + log_debug("%s", __func__); + reset_buffer(); // try to read as much as possible + fill_buffer(); + if (get_last_error()) { + log_debug("%s: -1", __func__); + return -1; // timeout to read + } + } + + log_debug("%s: %c", __func__, _recv_buff[_recv_pos]); + + return _recv_buff[_recv_pos++]; +} + +void ATHandler::skip_param(uint32_t count) +{ + log_debug("%s", __func__); + if (_last_err || _stop_tag->found) { + return; + } + + for (uint32_t i=0; (i < count && !_stop_tag->found); i++) { + size_t match_pos = 0; + while (true) { + int16_t c = get_char(); + if (c == -1) { + set_error(NSAPI_ERROR_DEVICE_ERROR); + return; + } else if (c == _delimiter) { + break; + } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) { + match_pos++; + if (match_pos == _stop_tag->len) { + _stop_tag->found = true; + break; + } + } else if (match_pos) { + match_pos = 0; + } + } + } + return; +} + +void ATHandler::skip_param(ssize_t len, uint32_t count) +{ + log_debug("%s", __func__); + if (_last_err || _stop_tag->found) { + return; + } + + for (uint32_t i=0; i < count; i++) { + ssize_t read_len = 0; + while (read_len < len) { + int16_t c = get_char(); + if (c == -1) { + set_error(NSAPI_ERROR_DEVICE_ERROR); + return; + } + read_len++; + } + } + return; +} + +ssize_t ATHandler::read_bytes(uint8_t *buf, size_t len) +{ + log_debug("%s", __func__); + if (_last_err) { + return -1; + } + + size_t read_len = 0; + for (; read_lenfound && read_even_stop_tag == false)) { + return -1; + } + + uint8_t *pbuf = (uint8_t*)buf; + + size_t len = 0; + size_t match_pos = 0; + + consume_char('\"'); + + for (; len<(size + match_pos); len++) { + int16_t c = get_char(); + if (c == -1) { + pbuf[len] = '\0'; + set_error(NSAPI_ERROR_DEVICE_ERROR); + return -1; + } else if (c == _delimiter) { + pbuf[len] = '\0'; + break; + } else if (c == '\"') { + match_pos = 0; + len--; + continue; + } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) { + match_pos++; + if (match_pos == _stop_tag->len) { + _stop_tag->found = true; + // remove tag from string if it was matched + len -= (_stop_tag->len - 1); + pbuf[len] = '\0'; + break; + } + } else if (match_pos) { + match_pos = 0; + } + + pbuf[len] = c; + } + + // Do we need _stop_found set after reading by size -> is _stop_tag_by_len needed or not? + return len; +} + +int32_t ATHandler::read_int() +{ + log_debug("%s", __func__); + + if (_last_err || _stop_tag->found) { + return -1; + } + + char buff[BUFF_SIZE] = {0}; + char *first_no_digit; + + if (read_string(buff, (size_t)sizeof(buff)) == 0) { + return -1; + } + + return std::strtol(buff, &first_no_digit, 10); +} + +void ATHandler::set_delimiter(char delimiter) +{ + _delimiter = delimiter; +} + +void ATHandler::set_tag(tag_t* tag_dst, const char *tag_seq) +{ + if (tag_seq) { + size_t tag_len = strlen(tag_seq); + set_string(tag_dst->tag, tag_seq, tag_len); + tag_dst->len = tag_len; + tag_dst->found = false; + } else { + _stop_tag = NULL; + } +} + +void ATHandler::set_stop_tag(const char *stop_tag_seq) +{ + if (_last_err || !_stop_tag) { + return; + } + + set_tag(_stop_tag, stop_tag_seq); +} + +void ATHandler::set_scope(ScopeType scope_type, const char* stop_tag) +{ + log_debug("%s: %d", __func__, scope_type); + if (_current_scope != scope_type) { + _current_scope = scope_type; + switch (_current_scope) { + case RespType: + _stop_tag = &_resp_stop; + _stop_tag->found = false; + break; + case InfoType: + _stop_tag = &_info_stop; + _stop_tag->found = false; + consume_char(' '); + break; + case ElemType: + _stop_tag = &_elem_stop; + _stop_tag->found = false; + break; + case NotSet: + _stop_tag = NULL; + return; + default: + break; + } + } + + if (stop_tag) { + set_stop_tag(stop_tag); + } +} + +// should match from recv_pos? +bool ATHandler::match(const char* str, size_t size) +{ + log_debug("%s: %s", __func__, str); + rewind_buffer(); + + if ((_recv_len - _recv_pos) < size) { + return false; + } + + if (str && memcmp(_recv_buff + _recv_pos, str, size) == 0) { + // consume matching part + _recv_pos += size; + return true; + } + return false; +} + +bool ATHandler::match_urc() +{ + log_debug("%s", __func__); + rewind_buffer(); + size_t prefix_len = 0; + for (struct oob_t *oob = _oobs; oob; oob = oob->next) { + prefix_len = strlen(oob->prefix); + if (_recv_len >= prefix_len) { + if (match(oob->prefix, prefix_len)) { + log_debug("URC!"); + set_scope(InfoType); + oob->cb(); + information_response_stop(); + return true; + } + } + } + return false; +} + +bool ATHandler::match_error() +{ + log_debug("%s", __func__); + + if (match(CME_ERROR, CME_ERROR_LENGTH)) { + at_error(true, DeviceErrorTypeErrorCME); + return true; + } else if (match(CMS_ERROR, CMS_ERROR_LENGTH)) { + at_error(true, DeviceErrorTypeErrorCMS); + return true; + } else if (match(ERROR_, ERROR_LENGTH)) { + at_error(false, DeviceErrorTypeNoError); + return true; + } + + return false; +} + +void ATHandler::clear_error() +{ + _last_err = NSAPI_ERROR_OK; + _last_at_err.errCode = 0; + _last_at_err.errType = DeviceErrorTypeNoError; + _last_3gpp_error = 0; +} + +nsapi_error_t ATHandler::get_last_error() const +{ + return _last_err; +} + +device_err_t ATHandler::get_last_device_error() const +{ + return _last_at_err; +} + +void ATHandler::set_error(nsapi_error_t err) +{ + if (_last_err == NSAPI_ERROR_OK) { + _last_err = err; + } + + if (_last_err != err) { + log_warn("AT error code changed from %d to %d!", _last_err, err); + } +} + +uint8_t ATHandler::get_3gpp_error() +{ + return _last_3gpp_error; +} + +void ATHandler::set_3gpp_error(uint8_t err, DeviceErrorType error_type) +{ + if (_last_3gpp_error) { // don't overwrite likely root cause error + return; + } + + if (error_type == DeviceErrorTypeErrorCMS && err < 128) { + // CMS errors 0-127 maps straight to 3GPP errors + _last_3gpp_error = err; + } else { + for (size_t i=0; ifound = true; + return; + } + + if(match_error()) { + _error_found = true; + return; + } + + if (prefix && match(prefix, strlen(prefix))) { + _prefix_matched = true; + return; + } + + if (check_urc && match_urc()) { + _urc_matched = true; + } + + // If no match found, look for CRLF and consume everything up to and including CRLF + if (mem_str(_recv_buff, sizeof(_recv_buff), CRLF, CRLF_LENGTH)) { + // If no prefix, return on CRLF - means data to read + if (!prefix) { + return; + } + consume_to_tag(CRLF, true); + } else { + // If no prefix, no CRLF and no more chance to match for OK, ERROR or URC(since max resp length is already in buffer) + // return so data could be read + if (!prefix && ((_recv_len-_recv_pos) >= _max_resp_length)) { + return; + } + fill_buffer(); + } + } + + return; + // something went wrong so application need to recover and retry +} + +void ATHandler::resp_start(const char *prefix, bool stop) +{ + log_debug("%s: %s", __func__, prefix); + + if (_last_err) { + return; + } + + // Try get as much data as possible + rewind_buffer(); + fill_buffer(); + + if (prefix) { + if ((strlen(prefix) < sizeof(_info_resp_prefix))) { + strcpy(_info_resp_prefix, prefix); + } else { + MBED_ASSERT(0); + } + } + + set_scope(RespType); + + resp(prefix, true); + + if (!stop && prefix && _prefix_matched) { + set_scope(InfoType); + } +} + +// check urc because of error as urc +bool ATHandler::info_resp() +{ + log_debug("%s", __func__); + if (_last_err || _resp_stop.found) { + return false; + } + + if (_prefix_matched) { + _prefix_matched = false; + return true; + } + + // If coming here after another info response was started(looping), stop the previous one. + // Trying to handle stopping in this level instead of doing it in upper level. + if (get_scope() == InfoType) { + information_response_stop(); + } + + resp(_info_resp_prefix, false); + + if (_prefix_matched) { + set_scope(InfoType); + _prefix_matched = false; + return true; + } + + // On mismatch go to response scope + set_scope(RespType); + return false; +} + +bool ATHandler::info_elem(char start_tag) +{ + log_debug("%s: %c", __func__, start_tag); + if (_last_err) { + return false; + } + + // If coming here after another info response element was started(looping), stop the previous one. + // Trying to handle stopping in this level instead of doing it in upper level. + if (get_scope() == ElemType) { + information_response_element_stop(); + } + + consume_char(_delimiter); + + if (consume_char(start_tag)) { + _prefix_matched = true; + set_scope(ElemType); + return true; + } + + // On mismatch go to information response scope + set_scope(InfoType); + return false; +} + +bool ATHandler::consume_char(char ch) +{ + log_debug("%s: %c", __func__, ch); + int16_t read_char = get_char(); + // If we read something else than ch, recover it + if (read_char != ch && read_char != -1) { + _recv_pos--; + return false; + } + return true; +} + +bool ATHandler::consume_to_tag(const char *tag, bool consume_tag) +{ + log_debug("%s: %s", __func__, tag); + size_t match_pos = 0; + + while (true) { + int16_t c = get_char(); + if (c == -1) { + break; + } else if (c == tag[match_pos]) { + match_pos++; + if (match_pos == strlen(tag)) { + if (!consume_tag) { + _recv_pos -= strlen(tag); + } + return true; + } + } else if (match_pos) { + match_pos = 0; + } + } + log_error("consume_to_tag not found"); + return false; +} + +bool ATHandler::consume_to_stop_tag() +{ + log_debug("%s", __func__); + + if (!_stop_tag || (_stop_tag && _stop_tag->found) || _error_found) { + return true; + } + + if (consume_to_tag((const char*)_stop_tag->tag, true)) { + return true; + } + + log_error("consume_to_stop_tag not found"); + set_error(NSAPI_ERROR_DEVICE_ERROR); + return false; +} + +// consume by size needed? + +void ATHandler::resp_stop() +{ + // Do not return on error so that we can consume whatever there is in the buffer + /*if (_last_err) { + return; + }*/ + + log_debug("%s", __func__); + + if (_current_scope == ElemType) { + information_response_element_stop(); + set_scope(InfoType); + } + + if (_current_scope == InfoType) { + information_response_stop(); + } + + // Go for response stop_tag + if (consume_to_stop_tag()) { + set_scope(NotSet); + } + + // Restore stop tag to OK + set_tag(&_resp_stop, OK); + // Reset info resp prefix + memset(_info_resp_prefix, 0, sizeof(_info_resp_prefix)); +} + +void ATHandler::information_response_stop() +{ + log_debug("%s", __func__); + if (consume_to_stop_tag()) { + set_scope(RespType); + } +} + +void ATHandler::information_response_element_stop() +{ + log_debug("%s", __func__); + if (consume_to_stop_tag()) { + set_scope(InfoType); + } +} + +ATHandler::ScopeType ATHandler::get_scope() +{ + return _current_scope; +} + +void ATHandler::set_string(char *dest, const char *src, size_t src_len) +{ + memcpy(dest, src, src_len); + dest[src_len] = '\0'; +} + +const char* ATHandler::mem_str(const char* dest, size_t dest_len, const char* src, size_t src_len) +{ + for(size_t i = 0; i < dest_len-src_len; ++i) { + if(memcmp(dest+i, src, src_len) == 0) { + return dest+i; + } + } + return NULL; +} + +void ATHandler::cmd_start(const char* cmd) +{ + log_debug("AT> %s", cmd); + + if (_last_err != NSAPI_ERROR_OK) { + return; + } + + // write command + for (size_t i = 0; i < strlen(cmd); i++) { + if (write_char(cmd[i]) == false) { + // writing failed ---> write_char have set the last error, return... + return; + } + } + + _cmd_start = true; +} + +void ATHandler::write_int(int32_t param) +{ + // do common checks before sending subparameter + if (check_cmd_send() == false) { + return; + } + + // write the integer subparameter + const uint8_t str_len = 12; + char number_string[str_len]; + int32_t result = std::snprintf(number_string, str_len, "%ld", param); + if (result > 0 && result < str_len) { + for (size_t i = 0; number_string[i]; i++) { + if (write_char(number_string[i]) == false) { + // writing failed ---> write_char have set the last error, break out + break; + } + } + } else { + // must have been 64-bit integer, not supported + set_error(NSAPI_ERROR_PARAMETER); + } +} + +void ATHandler::write_string(const char* param, bool useQuotations) +{ + // do common checks before sending subparameter + if (check_cmd_send() == false) { + return; + } + + // we are writing string, surround it with quotes + if (useQuotations && write_char('\"') == false) { + return; + } + + for (size_t i = 0; i < strlen(param); i++) { + if (write_char(param[i]) == false) { + // writing failed ---> write_char have set the last error, return + break; + } + } + + if (useQuotations) { + // we are writing string, surround it with quotes + write_char('\"'); + } +} + +void ATHandler::cmd_stop() +{ + if (_last_err != NSAPI_ERROR_OK) { + return; + } + // Finish with CR + for (size_t i = 0; i < _output_delimiter_length; i++) { + if (write_char(_output_delimiter[i]) == false) { + break; + } + } +} + +size_t ATHandler::write_bytes(uint8_t *data, size_t len) +{ + if (_last_err != NSAPI_ERROR_OK) { + return 0; + } + + size_t i = 0; + for (; i < len; i++) { + if (write_char(data[i]) == false) { + // writing failed ---> write_char have set the last error, return + break; + } + } + + return i; +} + +bool ATHandler::write_char(char c) +{ + pollfh fhs; + fhs.fh = _fileHandle; + fhs.events = POLLOUT; + bool retVal = true; + + int count = poll(&fhs, 1, _at_timeout); + if (count > 0 && (fhs.revents & POLLOUT)) { + retVal = _fileHandle->write(&c, 1) == 1 ? true : false; + } else { + retVal = false; + } + + if (retVal == false) { + set_error(NSAPI_ERROR_DEVICE_ERROR); + } + + return retVal; +} + +// do common checks before sending subparameters +bool ATHandler::check_cmd_send() +{ + if (_last_err != NSAPI_ERROR_OK) { + return false; + } + + // Don't write delimiter if this is the first subparameter + if (_cmd_start) { + _cmd_start = false; + } else { + if (write_char(_delimiter) == false) { + // writing of delimiter failed, return. write_char already have set the _last_err + return false; + } + } + + return true; +} + +void ATHandler::flush() +{ + while (_fileHandle->readable()) { + reset_buffer(); + fill_buffer(); + } + reset_buffer(); +} diff --git a/features/cellular/framework/AT/ATHandler.h b/features/cellular/framework/AT/ATHandler.h new file mode 100644 index 0000000000..bb13e4dab4 --- /dev/null +++ b/features/cellular/framework/AT/ATHandler.h @@ -0,0 +1,471 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_HANDLER_H_ +#define AT_HANDLER_H_ + +#include "platform/mbed_retarget.h" +#include "stdio.h" + +#include "EventQueue.h" +#include "PlatformMutex.h" +#include "nsapi_types.h" + +#include "PlatformMutex.h" +#include "Callback.h" +#include "EventQueue.h" + +namespace mbed { + +class FileHandle; + +/** + * If application calls associated FileHandle only from single thread context + * then locking between AT command and response is not needed. However, + * note that many cellular functions are called indirectly e.g. via socket API. + * If you are unsure then AT_HANDLER_MUTEX must be defined. + */ +#define AT_HANDLER_MUTEX + +extern const char *OK; +extern const char *CRLF; + +#define BUFF_SIZE 16 + +/* AT Error types enumeration */ +enum DeviceErrorType { + DeviceErrorTypeNoError = 0, + DeviceErrorTypeError, // AT ERROR + DeviceErrorTypeErrorCMS, // AT ERROR CMS + DeviceErrorTypeErrorCME // AT ERROR CME +}; + +/* struct used when getting at response error. Defines error code and type */ +struct device_err_t { + DeviceErrorType errType; + int errCode; +}; + +/** Class ATHandler + * + * Class for sending AT commands and parsing AT responses. + */ +class ATHandler +{ + +public: + /** Constructor + * + * @param fh file handle used for reading AT responses and writing AT commands + * @param queue Event queue used to transfer sigio events to this thread + * @param timeout Timeout when reading for AT response + * @param output_delimiter Default delimiter used when parsing at responses + */ + ATHandler(FileHandle *fh, events::EventQueue &queue, int timeout, const char *output_delimiter = "\r"); + ~ATHandler(); + + /** Return used file handle. + * + * @return used file handle + */ + FileHandle *get_file_handle(); + + /** Set file handle which is used for reading AT responses and writing AT commands + * + * @param fh file handle used for reading AT responses and writing AT commands + */ + void set_file_handle(FileHandle *fh); + + /** Locks the mutex for file handle if AT_HANDLER_MUTEX is defined. + */ + void lock(); + + /** Unlocks the mutex for file handle if AT_HANDLER_MUTEX is defined. + */ + void unlock(); + + /** Locks the mutex for file handle if AT_HANDLER_MUTEX is defined and returns the last error. + * + * @return last error which happened when parsing AT responses + */ + nsapi_error_t unlock_return_error(); + + /** Set the urc callback for urc. If urc is found when parsing AT responses then call if called. + * + * @param prefix Register urc prefix for callback. Urc could be for example "+CMTI: " + * @param callback Callback which is called if urc is found in AT response + */ + void set_urc_handler(const char *prefix, mbed::Callback callback); + + ATHandler *_nextATHandler; // linked list + + /** returns the last error while parsing AT responses. + * + * @return last error + */ + nsapi_error_t get_last_error() const; + + /** returns the last device error while parsing AT responses. Actually AT error (CME/CMS). + * + * @return last error struct device_err_t + */ + device_err_t get_last_device_error() const; + + /** Increase reference count. Used for counting references to this instance. + */ + void inc_ref_count(); + + /** Decrease reference count. Used for counting references to this instance. + */ + void dec_ref_count(); + + /** Get the current reference count. Used for counting references to this instance. + * + * @return current reference count + */ + int get_ref_count(); + + /** Set timeout in milliseconds for AT commands + * + * @param timeout_milliseconds Timeout in milliseconds + * @param default_timeout Store as default timeout + */ + void set_at_timeout(uint32_t timeout_milliseconds, bool default_timeout = false); + + /** Restore timeout to previous timeout. Handy if there is a need to change timeout temporarily. + */ + void restore_at_timeout(); + + /** Clear pending error flag. By default error is cleared only in lock(). + */ + void clear_error(); + + /** Tries to find oob's from the AT response. Call the urc callback if one is found. + */ + void process_oob(); + + /** Set sigio for the current file handle. Sigio event goes through eventqueue so that it's handled in current thread. + */ + void set_filehandle_sigio(); + + /** + * Flushes the underlying stream + */ + void flush(); + +protected: + void event(); +#ifdef AT_HANDLER_MUTEX + PlatformMutex _fileHandleMutex; +#endif + FileHandle *_fileHandle; +private: + + void set_error(nsapi_error_t err); + void set_oobs_matching_param(bool); + + events::EventQueue &_queue; + nsapi_error_t _last_err; + nsapi_error_t _last_3gpp_error; + device_err_t _last_at_err; + uint16_t _oob_string_max_length; + char *_output_delimiter; + uint8_t _output_delimiter_length; + + struct oob_t { + bool matching_to_received; + const char *prefix; + mbed::Callback cb; + oob_t *next; + }; + oob_t *_oobs; + bool _response_terminated; + uint32_t _at_timeout; + uint32_t _previous_at_timeout; + + bool _fh_sigio_set; + + bool _processing; + int32_t _ref_count; + + //************************************* +public: + + /** Starts the command writing by clearing the last error and writing the given command. + * In case of failure when writing the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param cmd AT command to be written to modem + */ + void cmd_start(const char* cmd); + + /** Writes integer type AT command subparameter. Starts with the delimiter if not the first param after cmd_start. + * In case of failure when writing the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param param int to be written to modem as AT command subparameter + */ + void write_int(int32_t param); + + /** Writes string type AT command subparamater. Quotes are added to surround the given string. + * Starts with the delimiter if not the first param after cmd_start. + * In case of failure when writing the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param param string to be written to modem as AT command subparameter + * @param useQuotations flag indicating if the string should be or not included in quotation marks + */ + void write_string(const char* param, bool useQuotations = true); + + /** Stops the AT command by writing command line terminator CR to mark command as finished. + */ + void cmd_stop(); + + /** Write bytes without any subparameter delimiters, such as comma. + * In case of failure when writing the last error is set to NSAPI_ERROR_DEVICE_ERROR. + * + * @param data bytes to be written to modem + * @param len length of data string + * + * @return number of characters successfully written + */ + size_t write_bytes(uint8_t *data, size_t len); + + /** Sets the stop tag for the current scope(response/information response/element) + * Parameter's reading routines will stop the reading when such tag is found and will set the found flag. + * Consume routines will read everything until such tag is found. + * + * @param stop_tag_seq string to be set as stop tag + */ + void set_stop_tag(const char *stop_tag_seq); + + /** Sets the delimiter between parameters or between elements of the information response. + * Parameter's reading routines will stop when such char is read. + * + * @param delimiter char to be set as _delimiter + */ + void set_delimiter(char delimiter); + + /** Consumes the reading buffer up to the delimiter or stop_tag + * + * @param count number of parameters to be skipped + */ + void skip_param(uint32_t count = 1); + + /** Consumes the given length from the reading buffer + * + * @param len length to be consumed from reading buffer + * @param count number of parameters to be skipped + */ + void skip_param(ssize_t len, uint32_t count); + + /** Reads given number of bytes from receiving buffer without checking any subparameter delimiters, such as comma. + * + * @param buf output buffer for the read + * @param len maximum number of bytes to read + * @return number of successfully read bytes or -1 in case of error + */ + ssize_t read_bytes(uint8_t *buf, size_t len); + + /** Reads chars from reading buffer. Terminates with null. Skips the quotation marks. + * Stops on delimiter or stop tag. + * + * @param str output buffer for the read + * @param size maximum number of chars to output + * @param read_even_stop_tag if true then try to read even the stop tag was found previously + * @return length of output string or -1 in case of read timeout before delimiter or stop tag is found + */ + ssize_t read_string(char *str, size_t size, bool read_even_stop_tag = false); + + /** Reads as string and converts result to integer. Supports only positive integers. + * + * @return the positive integer or -1 in case of error. + */ + int32_t read_int(); + + /** This looks for necessary matches: prefix, OK, ERROR, URCs and sets the correct scope. + * + * @param prefix string to be matched from receiving buffer. If not NULL and match succeeds, then scope + * will be set as information response(info_type) + * @param stop flag to indicate if we go to information response scope or not. + * (needed when nothing is expected to be received anymore after the prefix match: + * sms case: "> ", bc95 reboot case) + */ + void resp_start(const char *prefix = NULL, bool stop = false); + + /** Ends all scopes starting from current scope. + * Consumes everything until the scope's stop tag is found, then + * goes to next scope, until response scope is ending. + * Possible sequence: + * element scope -> information response scope -> response scope + */ + void resp_stop(); + + /** Looks for matching the prefix given to resp_start() call. + * If needed it ends the scope of a previous information response. + * Sets the information response scope if new prefix is found and response scope if prefix is not found. + * + * @return true if new information response is found, false otherwise + */ + bool info_resp(); + + /** Looks for matching the start tag. + * If needed it ends the scope of a previous element. + * Sets the element scope if start tag is found and information response scope if start tag is not found. + * + * @param start_tag tag to be matched to begin parsing an element of an information response + * @return true if new element is found, false otherwise + */ + bool info_elem(char start_tag); + + /** Consumes the received content until current stop tag is found. + * + * @return true if stop tag is found, false otherwise + */ + bool consume_to_stop_tag(); + + /** Sets _debug_on flag. + * + * @param enable value to be set for _debug_on flag + */ + void enable_debug(bool enable); + + /** Return the last 3GPP error code. + * @return last 3GPP error code + */ + uint8_t get_3gpp_error(); + +private: + + // should fit any prefix and int + char _recv_buff[BUFF_SIZE]; + // reading position + size_t _recv_len; + // reading length + size_t _recv_pos; + + // resp_type: the part of the response that doesn't include the information response(+CMD1,+CMD2..) + // ends with OK or (CME)(CMS)ERROR + // info_type: the information response part of the response: starts with +CMD1 and ends with CRLF + // information response contains parameters or subsets of parameters(elements), both separated by comma + // elem_type: subsets of parameters that are part of information response, its parameters are separated by comma + enum ScopeType {RespType, InfoType, ElemType, NotSet}; + void set_scope(ScopeType scope_type, const char* stop_tag = NULL); + ScopeType _current_scope; + + struct tag_t { + char tag[7]; + size_t len; + bool found; + }; + + // tag to stop response scope + tag_t _resp_stop; + // tag to stop information response scope + tag_t _info_stop; + // tag to stop element scope + tag_t _elem_stop; + // reference to the stop tag of current scope(resp/info/elem) + tag_t *_stop_tag; + + // delimiter between parameters and also used for delimiting elements of information response + char _delimiter; + // set true on prefix match -> indicates start of an information response or of an element + bool _prefix_matched; + // set true on urc match + bool _urc_matched; + // set true on (CME)(CMS)ERROR + bool _error_found; + // Max length of OK,(CME)(CMS)ERROR and URCs + size_t _max_resp_length; + + // prefix set during resp_start and used to try matching possible information responses + char _info_resp_prefix[BUFF_SIZE]; + bool _debug_on; + bool _cmd_start; + + // Gets char from receiving buffer. + // Resets and fills the buffer if all are already read (receiving position equals receiving length). + int16_t get_char(); + // Sets to 0 the reading position, reading length and the whole buffer content. + void reset_buffer(); + // Reading position set to 0 and buffer's unread content moved to beginning + void rewind_buffer(); + // Reads from serial to receiving buffer. + // Returns on first successful read OR on timeout. + void fill_buffer(); + + void set_tag(tag_t* tag_dest, const char *tag_seq); + + // Rewinds the receiving buffer and compares it against given str. + bool match(const char* str, size_t size); + // Iterates URCs and check if they match the receiving buffer content. + // If URC match sets the scope to information response and after urc's cb returns + // finishes the information response scope(consumes to CRLF). + bool match_urc(); + // Checks if any of the error strings are matching the receiving buffer content. + bool match_error(); + // Checks is current char in buffer matches ch and consumes it, + // if no match lets the buffer unchanged. + bool consume_char(char ch); + // Consumes the received content until tag is found. + // Consumes the tag only if consume_tag flag is true. + bool consume_to_tag(const char *tag, bool consume_tag); + // Checks if receiving buffer contains OK, ERROR, URC or given prefix. + void resp(const char *prefix, bool check_urc); + + + ScopeType get_scope(); + + // Consumes to information response stop tag which is CRLF. Sets scope to response. + void information_response_stop(); + // Consumes to element stop tag. Sets scope to information response + void information_response_element_stop(); + + // Reads the error code if expected and sets it as last error. + void at_error(bool error_code, DeviceErrorType error_type); + + /** Convert AT error code to 3GPP error codes + * @param err AT error code read from CME/CMS ERROR responses + * @param error_type error type (CMS/CME/ERROR) + */ + void set_3gpp_error(uint8_t err, DeviceErrorType error_type); + + bool check_cmd_send(); + bool write_char(char c); + + /** Copy content of one char buffer to another buffer and sets NULL terminator + * + * @param dest destination char buffer + * @param src source char buffer + * @param src_len number of bytes to copy + * + */ + void set_string(char *dest, const char *src, size_t src_len); + + /** Finds occurence of one char buffer inside another char buffer. + * + * @param dest destination char buffer + * @param dest_len length of dest + * @param src string to be searched for + * @param src_len length of string to be searched for + * + * @return pointer to first occurrence of src in dest + */ + const char* mem_str(const char* dest, size_t dest_len, const char* src, size_t src_len); +}; + +} // namespace mbed + +#endif //AT_HANDLER_H_ diff --git a/features/cellular/framework/AT/AT_CellularBase.cpp b/features/cellular/framework/AT/AT_CellularBase.cpp new file mode 100644 index 0000000000..c4e5183c68 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularBase.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularBase.h" + +using namespace mbed; + +AT_CellularBase::AT_CellularBase(ATHandler& at) : _at(at) +{ + +} + +ATHandler& AT_CellularBase::get_at_handler() +{ + return _at; +} + +device_err_t AT_CellularBase::get_device_error() const +{ + return _at.get_last_device_error(); +} + diff --git a/features/cellular/framework/AT/AT_CellularBase.h b/features/cellular/framework/AT/AT_CellularBase.h new file mode 100644 index 0000000000..6fe69959c6 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularBase.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef AT_CELLULAR_BASE_H_ +#define AT_CELLULAR_BASE_H_ + +#include "ATHandler.h" + +namespace mbed { + +/** + * Class AT_CellularBase + * + * A base class for AT-classes. + */ +class AT_CellularBase +{ +public: + AT_CellularBase(ATHandler& at); + + /** Getter for at handler. Common method for all AT-classes. + * + * @return reference to ATHandler + */ + ATHandler& get_at_handler(); + + /** Gets the device error which happened when using AT commands/responses. This is at error + * returned by the device. Returned CME/CMS errors can be found from 3gpp documents 27007 and 27005. + * + * @return at error (CME/CMS) while communication with the device + */ + device_err_t get_device_error() const; + +protected: + ATHandler& _at; +}; + +} // namespace mbed + +#endif /* AT_CELLULAR_BASE_H_ */ diff --git a/features/cellular/framework/AT/AT_CellularDevice.cpp b/features/cellular/framework/AT/AT_CellularDevice.cpp new file mode 100644 index 0000000000..84b6ebdd31 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularDevice.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularDevice.h" + +using namespace events; +using namespace mbed; + +#define DEFAULT_AT_TIMEOUT 1000 // at default timeout in milliseconds + +AT_CellularDevice::AT_CellularDevice(EventQueue &queue) : + _atHandlers(0), _network(0), _sms(0), _sim(0), _power(0), _multiplexer(0), _information(0), _queue(queue), _default_timeout(DEFAULT_AT_TIMEOUT) +{ +} + +AT_CellularDevice::~AT_CellularDevice() +{ + close_network(); + close_sms(); + close_power(); + close_sim(); + close_multiplexer(); + close_information(); + + ATHandler *atHandler = _atHandlers; + while (atHandler) { + ATHandler *old = atHandler; + atHandler = atHandler->_nextATHandler; + delete old; + old = NULL; + } +} + +// each parser is associated with one filehandle (that is UART or a MUX channel) +ATHandler* AT_CellularDevice::get_at_handler(FileHandle *fileHandle) +{ + if (!fileHandle) { + return NULL; + } + ATHandler *atHandler = _atHandlers; + while (atHandler) { + if (atHandler->get_file_handle() == fileHandle) { + atHandler->inc_ref_count(); + return atHandler; + } + atHandler = atHandler->_nextATHandler; + } + + atHandler = new ATHandler(fileHandle, _queue, _default_timeout); + if (atHandler) { + atHandler->_nextATHandler = _atHandlers; + _atHandlers = atHandler; + } + + return atHandler; +} + +void AT_CellularDevice::release_at_handler(ATHandler* at_handler) +{ + if (!at_handler) { + return; + } + at_handler->dec_ref_count(); + if (at_handler->get_ref_count() == 0) { + // we can delete this at_handler + ATHandler *atHandler = _atHandlers; + ATHandler *prev = NULL; + while (atHandler) { + if (atHandler == at_handler) { + if (prev == NULL) { + _atHandlers = _atHandlers->_nextATHandler; + } + else { + prev->_nextATHandler = atHandler->_nextATHandler; + } + delete atHandler; + atHandler = NULL; + break; + } else { + prev = atHandler; + atHandler =atHandler->_nextATHandler; + } + } + } +} + +CellularNetwork *AT_CellularDevice::open_network(FileHandle *fh) +{ + if (!_network) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _network = new AT_CellularNetwork(*atHandler); + if (!_network) { + release_at_handler(atHandler); + } + } + } + return _network; +} + +CellularSMS *AT_CellularDevice::open_sms(FileHandle *fh) +{ + if (!_sms) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _sms = new AT_CellularSMS(*atHandler); + if (!_sms) { + release_at_handler(atHandler); + } + } + } + return _sms; +} + +CellularSIM *AT_CellularDevice::open_sim(FileHandle *fh) +{ + if (!_sim) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _sim = new AT_CellularSIM(*atHandler); + if (!_sim) { + release_at_handler(atHandler); + } + } + } + return _sim; +} + +CellularPower *AT_CellularDevice::open_power(FileHandle *fh) +{ + if (!_power) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _power = new AT_CellularPower(*atHandler); + if (!_power) { + release_at_handler(atHandler); + } + } + } + return _power; +} + +CellularMultiplexer *AT_CellularDevice::open_multiplexer(FileHandle *fh) +{ + if (!_multiplexer) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _multiplexer = new AT_CellularMultiplexer(*atHandler); + if (!_multiplexer) { + release_at_handler(atHandler); + } + } + } + + return _multiplexer; +} + +CellularInformation *AT_CellularDevice::open_information(FileHandle *fh) +{ + if (!_information) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _information = new AT_CellularInformation(*atHandler); + if (!_information) { + release_at_handler(atHandler); + } + } + } + return _information; +} + +void AT_CellularDevice::close_network() +{ + if (_network) { + release_at_handler(&_network->get_at_handler()); + delete _network; + _network = NULL; + } +} + +void AT_CellularDevice::close_sms() +{ + if (_sms) { + release_at_handler(&_sms->get_at_handler()); + delete _sms; + _sms = NULL; + } +} +void AT_CellularDevice::close_power() +{ + if (_power) { + release_at_handler(&_power->get_at_handler()); + delete _power; + _power = NULL; + } +} + +void AT_CellularDevice::close_sim() +{ + if (_sim) { + release_at_handler(&_sim->get_at_handler()); + delete _sim; + _sim = NULL; + } +} + +void AT_CellularDevice::close_multiplexer() +{ + if (_multiplexer) { + release_at_handler(&_multiplexer->get_at_handler()); + delete _multiplexer; + _multiplexer = NULL; + } +} + +void AT_CellularDevice::close_information() +{ + if (_information) { + release_at_handler(&_information->get_at_handler()); + delete _information; + _information = NULL; + } +} + +void AT_CellularDevice::set_timeout(int timeout) +{ + _default_timeout = timeout; + + ATHandler *atHandler = _atHandlers; + while (atHandler) { + atHandler->set_at_timeout(_default_timeout, true); // set as default timeout + atHandler = atHandler->_nextATHandler; + } +} diff --git a/features/cellular/framework/AT/AT_CellularDevice.h b/features/cellular/framework/AT/AT_CellularDevice.h new file mode 100644 index 0000000000..9185306d41 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularDevice.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_DEVICE_H_ +#define AT_CELLULAR_DEVICE_H_ + +#include "CellularDevice.h" + +#include "AT_CellularNetwork.h" +#include "AT_CellularSIM.h" +#include "AT_CellularSMS.h" +#include "AT_CellularPower.h" +#include "AT_CellularMultiplexer.h" +#include "AT_CellularInformation.h" + +#include "ATHandler.h" + +namespace mbed { + +class AT_CellularDevice : public CellularDevice +{ +public: + AT_CellularDevice(events::EventQueue &queue); + virtual ~AT_CellularDevice(); + +protected: + ATHandler *_atHandlers; + + /** Get the athandler. If the given fh is already used with existing athandler, instance to that athander is returned. + * Otherwise new athander is created. + * + * @param fh FileHandle to be used with the ATHandler + * @return pointer to ATHandler + */ + ATHandler *get_at_handler(FileHandle *fh); + + /** Releases the given at_handler. If last reference to at_hander then it's deleted. + * + * @param at_handler + */ + void release_at_handler(ATHandler* at_handler); + +public: // CellularDevice + /** Create new CellularNetwork interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularNetwork. + */ + virtual CellularNetwork *open_network(FileHandle *fh); + + /** Create new CellularSMS interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularSMS. + */ + virtual CellularSMS *open_sms(FileHandle *fh); + + /** Create new CellularPower interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularPower. + */ + virtual CellularPower *open_power(FileHandle *fh); + + /** Create new CellularSIM interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularSIM. + */ + virtual CellularSIM *open_sim(FileHandle *fh); + + /** Create new CellularMultiplexer interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularMultiplexer. + */ + virtual CellularMultiplexer *open_multiplexer(FileHandle *fh); + + /** Create new CellularInformation interface. + * + * @param fh filehandle used in communication to modem. Can be for example UART handle. + * @return New instance of interface CellularInformation. + */ + virtual CellularInformation *open_information(FileHandle *fh); + + /** Closes the opened CellularNetwork by deleting the CellularNetwork instance. + */ + virtual void close_network(); + + /** Closes the opened CellularNetwork by deleting the CellularSMS instance. + */ + virtual void close_sms(); + + /** Closes the opened CellularNetwork by deleting the CellularPower instance. + */ + virtual void close_power(); + + /** Closes the opened CellularNetwork by deleting the CellularSIM instance. + */ + virtual void close_sim(); + + /** Closes the opened CellularNetwork by deleting the CellularMultiplexer instance. + */ + virtual void close_multiplexer(); + + /** Closes the opened CellularNetwork by deleting the CellularInformation instance. + */ + virtual void close_information(); + + /** Set the default response timeout. + * + * @param timeout milliseconds to wait response from modem + */ + virtual void set_timeout(int timeout); + +protected: + AT_CellularNetwork *_network; + AT_CellularSMS *_sms; + AT_CellularSIM *_sim; + AT_CellularPower* _power; + AT_CellularMultiplexer* _multiplexer; + AT_CellularInformation* _information; + +protected: + events::EventQueue &_queue; + int _default_timeout; +}; + +} // namespace mbed +#endif // AT_CELLULAR_DEVICE_H_ diff --git a/features/cellular/framework/AT/AT_CellularInformation.cpp b/features/cellular/framework/AT/AT_CellularInformation.cpp new file mode 100644 index 0000000000..a0a10940f2 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularInformation.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularInformation.h" + +using namespace mbed; + +AT_CellularInformation::AT_CellularInformation(ATHandler &at) : AT_CellularBase(at) +{ +} + +AT_CellularInformation::~AT_CellularInformation() +{ +} + +nsapi_error_t AT_CellularInformation::get_manufacturer(char *buf, size_t buf_size) +{ + return get_info("AT+CGMI", buf, buf_size); +} + +nsapi_error_t AT_CellularInformation::get_model(char *buf, size_t buf_size) +{ + return get_info("AT+CGMM", buf, buf_size); +} + +nsapi_error_t AT_CellularInformation::get_revision(char *buf, size_t buf_size) +{ + return get_info("AT+CGMR", buf, buf_size); +} + +nsapi_error_t AT_CellularInformation::get_info(const char *cmd, char *buf, size_t buf_size) +{ + _at.lock(); + + _at.cmd_start(cmd); + _at.cmd_stop(); + _at.resp_start(); + _at.read_string(buf, buf_size-1); // stop tag OK\r\n + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/AT/AT_CellularInformation.h b/features/cellular/framework/AT/AT_CellularInformation.h new file mode 100644 index 0000000000..c8f51c0e37 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularInformation.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_INFORMATION_H_ +#define AT_CELLULAR_INFORMATION_H_ + +#include "CellularInformation.h" +#include "AT_CellularBase.h" + +namespace mbed { + +class AT_CellularInformation : public CellularInformation, public AT_CellularBase +{ +public: + AT_CellularInformation(ATHandler &atHandler); + virtual ~AT_CellularInformation(); + +public: + /** Request manufacturer identification of cellular device + * + * @param buf manufacturer identification + * @param buf_size max length of manufacturer identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_manufacturer(char *buf, size_t buf_size); + + /** Request model identification of cellular device + * + * @param buf model identification + * @param buf_size max length of model identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_model(char *buf, size_t buf_size); + + /** Request revision identification of cellular device + * + * @param buf revision identification + * @param buf_size max length of revision identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + virtual nsapi_size_or_error_t get_revision(char *buf, size_t buf_size); + +protected: + /** Request information text from cellular device + * + * @param cmd 3gpp command string + * @param buf manufacturer identification + * @param buf_size max length of manufacturer identification is 2048 characters + * @return on success read character count, on failure negative error code + */ + nsapi_error_t get_info(const char *cmd, char *buf, size_t buf_size); +}; + +} // namespace mbed + +#endif // AT_CELLULAR_INFORMATION_H_ diff --git a/features/cellular/framework/AT/AT_CellularMultiplexer.cpp b/features/cellular/framework/AT/AT_CellularMultiplexer.cpp new file mode 100644 index 0000000000..10cc70f3dc --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularMultiplexer.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularMultiplexer.h" +#include "CellularLog.h" + +using namespace mbed; + +AT_CellularMultiplexer::AT_CellularMultiplexer(ATHandler &at) : AT_CellularBase(at) +{ +} + +AT_CellularMultiplexer::~AT_CellularMultiplexer() +{ +} + +nsapi_error_t AT_CellularMultiplexer::multiplexer_mode_start() +{ + log_info("multiplexer_mode_start()"); + + _at.lock(); + + _at.cmd_start("AT+CMUX=0"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/AT/AT_CellularMultiplexer.h b/features/cellular/framework/AT/AT_CellularMultiplexer.h new file mode 100644 index 0000000000..520e5be3c2 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularMultiplexer.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_MULTIPLEXER_H_ +#define AT_CELLULAR_MULTIPLEXER_H_ + +#include "CellularMultiplexer.h" +#include "AT_CellularBase.h" + +namespace mbed { + +class AT_CellularMultiplexer : public CellularMultiplexer, public AT_CellularBase +{ +public: + AT_CellularMultiplexer(ATHandler &atHandler); + virtual ~AT_CellularMultiplexer(); + +public: + /** Starts modem multiplexer mode specified by 3GPP TS 27.010. + * + * @return zero on success, negative error code on failure + */ + virtual nsapi_error_t multiplexer_mode_start(); +}; + +} // namespace mbed +#endif /* AT_CELLULAR_MULTIPLEXER_H_ */ diff --git a/features/cellular/framework/AT/AT_CellularNetwork.cpp b/features/cellular/framework/AT/AT_CellularNetwork.cpp new file mode 100644 index 0000000000..51710a8f6a --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularNetwork.cpp @@ -0,0 +1,888 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "AT_CellularNetwork.h" +#include "nsapi_ppp.h" +#include "CellularUtil.h" +#include "CellularLog.h" + +using namespace std; +using namespace mbed_cellular_util; +using namespace mbed; + +AT_CellularNetwork::AT_CellularNetwork(ATHandler &atHandler) : AT_CellularBase(atHandler), + _stack(NULL), _uname(NULL), _pwd(NULL), _ip_stack_type_requested(DEFAULT_STACK), _ip_stack_type(DEFAULT_STACK), _cid(-1), + _op_act(operator_t::RAT_UNKNOWN), _authentication_type(CHAP), _last_reg_type(C_REG) +{ + + _at.set_urc_handler("NO CARRIER", callback(this, &AT_CellularNetwork::urc_no_carrier)); + + memset(_apn, 0, MAX_APN_LENGTH); + +#ifdef MBED_CONF_APP_CELLULAR_APN + strncpy(_apn, MBED_CONF_APP_CELLULAR_APN, MAX_APN_LENGTH); + log_debug("Using APN [%s] from json", _apn); +#endif + +} + +AT_CellularNetwork::~AT_CellularNetwork() +{ +} + +void AT_CellularNetwork::urc_no_carrier() +{ + if (_connection_status_cb) { + _connection_status_cb(NSAPI_ERROR_CONNECTION_LOST); + } +} + +nsapi_error_t AT_CellularNetwork::set_credentials(const char *apn, + const char *username, const char *password) +{ + strncpy(_apn, apn, MAX_APN_LENGTH); + _uname = username; + _pwd = password; + + return NSAPI_ERROR_OK; +} + +nsapi_error_t AT_CellularNetwork::set_credentials(const char *apn, + AuthenticationType type, const char *username, const char *password) +{ + strncpy(_apn, apn, MAX_APN_LENGTH); + _uname = username; + _pwd = password; + _authentication_type = type; + + return NSAPI_ERROR_OK; +} + +nsapi_error_t AT_CellularNetwork::connect(const char *apn, + const char *username, const char *password) +{ + strncpy(_apn, apn, MAX_APN_LENGTH); + _uname = username; + _pwd = password; + + return connect(); +} + +nsapi_error_t AT_CellularNetwork::connect() +{ + _at.lock(); + + nsapi_error_t err = set_context_to_be_activated(); + if (err != NSAPI_ERROR_OK) { + _at.unlock(); + log_error("Failed to activate network context!"); + return err; + } + + err = open_data_channel(); + if (err != NSAPI_ERROR_OK) { + _at.unlock(); + log_error("Failed to open data channel!"); + return err; + } + + _at.unlock(); + + return NSAPI_ERROR_OK; +} + +nsapi_error_t AT_CellularNetwork::open_data_channel() +{ + //old way: _at.send("ATD*99***%d#", _cid) && _at.recv("CONNECT"); + nsapi_error_t err = NSAPI_ERROR_NO_CONNECTION; +#if NSAPI_PPP_AVAILABLE + log_info("Open data channel in PPP mode"); + _at.cmd_start("AT+CGDATA=\"PPP\","); + _at.write_int(_cid); + _at.cmd_stop(); + + _at.resp_start("CONNECT", true); + if (_at.get_last_error()) { + log_warn("Failed to CONNECT"); + } + /* Initialize PPP + * mbed_ppp_init() is a blocking call, it will block until + * connected, or timeout after 30 seconds*/ + err = nsapi_ppp_connect(_at.get_file_handle(), _connection_status_cb, _uname, _pwd, _ip_stack_type); +#else + // do check for stack to validate that we have support for stack + _stack = get_stack(); + if (!_stack) { + return err; + } + log_info("Activate PDP context"); + _at.cmd_start("AT+CGACT=1,"); + _at.write_int(_cid); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + err = (_at.get_last_error() == NSAPI_ERROR_OK) ? NSAPI_ERROR_OK : NSAPI_ERROR_NO_CONNECTION; +#endif + return err; +} + +/** + * User initiated disconnect + * + * Disconnects from PPP connection only and brings down the underlying network + * interface + */ +nsapi_error_t AT_CellularNetwork::disconnect() +{ +#if NSAPI_PPP_AVAILABLE + return nsapi_ppp_disconnect(_at.get_file_handle()); +#else + return NSAPI_ERROR_OK; +#endif +} + +void AT_CellularNetwork::connection_status_cb(Callback cb) +{ + _connection_status_cb = cb; +} + +nsapi_error_t AT_CellularNetwork::set_context_to_be_activated() +{ + // try to find or create context with suitable stack + if (!get_context(_ip_stack_type_requested)) { + return NSAPI_ERROR_NO_CONNECTION; + } + + // if user has defined user name and password we need to call CGAUTH before activating or modifying context + if (_pwd && _uname) { + _at.cmd_start("AT+CGAUTH="); + _at.write_int(_cid); + _at.write_int(_authentication_type); + _at.write_string(_uname); + _at.write_string(_pwd); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + if (_at.get_last_error() != NSAPI_ERROR_OK) { + return NSAPI_ERROR_AUTH_FAILURE; + } + } + + return _at.get_last_error(); +} + +bool AT_CellularNetwork::set_new_context(nsapi_ip_stack_t stack, int cid) +{ + nsapi_ip_stack_t tmp_stack = stack; + char pdp_type[8+1] = {0}; + + switch (stack) { + case IPV4_STACK: + strncpy(pdp_type, "IP", sizeof(pdp_type)); + break; + case IPV6_STACK: + strncpy(pdp_type, "IPV6", sizeof(pdp_type)); + break; + case IPV4V6_STACK: + strncpy(pdp_type, "IPV4V6", sizeof(pdp_type)); + break; + default: + strncpy(pdp_type, "", sizeof(pdp_type)); + break; + } + + //apn: "If the value is null or omitted, then the subscription value will be requested." + bool success = false; + _at.cmd_start("AT+CGDCONT="); + _at.write_int(cid); + _at.write_string(pdp_type); + _at.write_string(_apn); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + success = (_at.get_last_error() == NSAPI_ERROR_OK); + + // Fall back to ipv4 + if (!success && tmp_stack == IPV4V6_STACK) { + tmp_stack = IPV4_STACK; + _at.cmd_start("AT+FCLASS=0;+CGDCONT="); + _at.write_int(cid); + _at.write_string("IP"); + _at.write_string(_apn); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + success = (_at.get_last_error() == NSAPI_ERROR_OK); + } + + if (success) { + _ip_stack_type = tmp_stack; + _cid = cid; + } + + return success; +} + +bool AT_CellularNetwork::get_context(nsapi_ip_stack_t requested_stack) +{ + _at.cmd_start("AT+CGDCONT?"); + _at.cmd_stop(); + _at.resp_start("+CGDCONT:"); + _cid = -1; + int cid_max = 0; // needed when creating new context + char apn[MAX_APN_LENGTH] = {0}; + int apn_len = 0; + + while (_at.info_resp()) { + int cid = _at.read_int(); + if (cid > cid_max) { + cid_max = cid; + } + char pdp_type_from_context[10]; + int pdp_type_len = _at.read_string(pdp_type_from_context, sizeof(pdp_type_from_context) - 1); + if (pdp_type_len > 0) { + apn_len = _at.read_string(apn, sizeof(apn) - 1); + if (apn_len >= 0) { + if (strlen(_apn) && strcmp(apn, _apn) != 0 ) { + continue; + } + nsapi_ip_stack_t pdp_stack = string_to_stack_type(pdp_type_from_context); + if (pdp_stack != DEFAULT_STACK) { + if (get_modem_stack_type(pdp_stack)) { + if (requested_stack == IPV4_STACK) { + if (pdp_stack == IPV4_STACK || pdp_stack == IPV4V6_STACK) { + _ip_stack_type = pdp_stack; + _cid = cid; + break; + } + } else if (requested_stack == IPV6_STACK) { + if (pdp_stack == IPV6_STACK || pdp_stack == IPV4V6_STACK) { + _ip_stack_type = pdp_stack; + _cid = cid; + break; + } + } else { // accept any but prefer to IPv6 + if (pdp_stack == IPV6_STACK || pdp_stack == IPV4V6_STACK) { + _ip_stack_type = pdp_stack; + _cid = cid; + break; + } + if (_ip_stack_type == DEFAULT_STACK) { + _ip_stack_type = pdp_stack; + _cid = cid; + } + } + } + } + } + } + } + _at.resp_stop(); + if (_cid == -1) { // no suitable context was found so create a new one + if (!set_new_context(_ip_stack_type, cid_max+1)) { + return false; + } + } + + // save the apn + if (apn_len > 0 && !strlen(_apn)) { + strncpy(_apn, apn, MAX_APN_LENGTH); + } + + log_debug("Context id %d", _cid); + return true; +} + +nsapi_ip_stack_t AT_CellularNetwork::string_to_stack_type(const char* pdp_type) +{ + nsapi_ip_stack_t stack = DEFAULT_STACK; + int len = strlen(pdp_type); + + if (len == 6 && memcmp(pdp_type, "IPV4V6", len) == 0) { + stack = IPV4V6_STACK; + } else if (len == 4 && memcmp(pdp_type, "IPV6", len) == 0) { + stack = IPV6_STACK; + } else if (len == 2 && memcmp(pdp_type, "IP", len) == 0) { + stack = IPV4_STACK; + } + return stack; +} + +nsapi_error_t AT_CellularNetwork::set_registration_urc(bool urc_on) +{ + RegistrationType reg_types[] = {C_EREG, C_GREG, C_REG}; + const char *cmd_on[] = {"AT+CEREG=2", "AT+CGREG=2", "AT+CREG=2"}; + const char *cmd_off[] = {"AT+CEREG=0", "AT+CGREG=0", "AT+CREG=0"}; + for (uint8_t i=0; i= 0 && i < C_MAX); + + RegistrationType reg_types[] = { C_EREG, C_GREG, C_REG}; + const char *cmd[] = { "AT+CEREG", "AT+CGREG", "AT+CREG"}; + const char *rsp[] = { "+CEREG: ", "+CGREG: ", "+CREG: "}; + + const int LAC_LENGTH = 5, CELL_ID_LENGTH = 9; + char lac_string[LAC_LENGTH] = {0}, cell_id_string[CELL_ID_LENGTH] = {0}; + bool lac_read = false, cell_id_read = false; + + _cell_id = -1; + _lac = -1; + + _at.lock(); + + if (!has_registration(reg_types[i])) { + _at.unlock(); + return NSAPI_ERROR_UNSUPPORTED; + } + + _at.cmd_start(cmd[i]); + _at.write_string("=2", false); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start(cmd[i]); + _at.write_string("?", false); + + _at.cmd_stop(); + _at.resp_start(rsp[i]); + _at.read_int(); // ignore urc mode subparam + status = (RegistrationStatus)_at.read_int(); + + int len = _at.read_string(lac_string, LAC_LENGTH); + if (memcmp(lac_string, "ffff", LAC_LENGTH-1) && len >= 0) { + lac_read = true; + } + + len = _at.read_string(cell_id_string, CELL_ID_LENGTH); + if (memcmp(cell_id_string, "ffffffff", CELL_ID_LENGTH-1) && len >= 0) { + cell_id_read = true; + } + + _AcT = (operator_t::RadioAccessTechnology)_at.read_int(); + + _at.resp_stop(); + + _at.cmd_start(cmd[i]); + _at.write_string("=0", false); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + nsapi_error_t ret = _at.get_last_error(); + _at.unlock(); + + if (lac_read) { + _lac = hex_str_to_int(lac_string, LAC_LENGTH); + log_debug("lac %s %d", lac_string, _lac ); + } + + if (cell_id_read) { + _cell_id = hex_str_to_int(cell_id_string, CELL_ID_LENGTH); + log_debug("cell_id %s %d", cell_id_string, _cell_id ); + } + + return ret; +} + +nsapi_error_t AT_CellularNetwork::get_cell_id(int &cell_id) +{ + RegistrationStatus tmp; + + nsapi_error_t error = get_registration_status(_last_reg_type, tmp); + + cell_id = _cell_id; + + return error; +} + +bool AT_CellularNetwork::has_registration(RegistrationType reg_type) +{ + (void)reg_type; + return true; +} + +nsapi_error_t AT_CellularNetwork::set_attach(int timeout) +{ + _at.lock(); + + _at.cmd_start("AT+CGATT?"); + _at.cmd_stop(); + _at.resp_start("+CGATT:"); + int attached_state = _at.read_int(); + _at.resp_stop(); + if (attached_state != 1) { + log_debug("Network attach"); + _at.cmd_start("AT+CGATT=1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::get_attach(AttachStatus &status) +{ + _at.lock(); + + _at.cmd_start("AT+CGATT?"); + _at.cmd_stop(); + + _at.resp_start("+CGATT:"); + if (_at.info_resp()) { + int attach_status = _at.read_int(); + status = (attach_status == 1) ? Attached : Detached; + } + _at.resp_stop(); + + return _at.unlock_return_error(); +} + + +nsapi_error_t AT_CellularNetwork::get_backoff_time(int &backoffTime) +{ + _at.lock(); + + // If apn is set + if (strlen(_apn)) { + _at.cmd_start("AT+CABTRDP="); + _at.write_string(_apn); + _at.cmd_stop(); + _at.resp_start("+CABTRDP:"); + if (_at.info_resp()) { + _at.skip_param(); + backoffTime = _at.read_int(); + } + _at.resp_stop(); + } + + return _at.unlock_return_error(); +} + +NetworkStack *AT_CellularNetwork::get_stack() +{ + // use lwIP/PPP if modem does not have IP stack +#if NSAPI_PPP_AVAILABLE + _stack = nsapi_ppp_get_stack(); +#else + _stack = NULL; +#endif + return _stack; +} + +const char *AT_CellularNetwork::get_ip_address() +{ +#if NSAPI_PPP_AVAILABLE + return nsapi_ppp_get_ip_addr(_at.get_file_handle()); +#else + if (!_stack) { + _stack = get_stack(); + } + if (_stack) { + return _stack->get_ip_address(); + } + return NULL; +#endif +} + +nsapi_error_t AT_CellularNetwork::set_stack_type(nsapi_ip_stack_t stack_type) +{ + + if (get_modem_stack_type(stack_type)) { + _ip_stack_type_requested = stack_type; + return NSAPI_ERROR_OK; + } else { + return NSAPI_ERROR_PARAMETER; + } + +} + +nsapi_ip_stack_t AT_CellularNetwork::get_stack_type() +{ + return _ip_stack_type; +} + +bool AT_CellularNetwork::get_modem_stack_type(nsapi_ip_stack_t requested_stack) +{ + if (requested_stack == _ip_stack_type) { + return true; + } else { + return false; + } +} + +nsapi_error_t AT_CellularNetwork::set_access_technology_impl(operator_t::RadioAccessTechnology opsAct) +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +nsapi_error_t AT_CellularNetwork::set_access_technology(operator_t::RadioAccessTechnology opAct) +{ + if (opAct == operator_t::RAT_UNKNOWN) { + return NSAPI_ERROR_UNSUPPORTED; + } + + _op_act = opAct; + + return set_access_technology_impl(opAct); +} + +nsapi_error_t AT_CellularNetwork::scan_plmn(operList_t &operators, int &opsCount) +{ + int idx = 0; + + _at.lock(); + + _at.cmd_start("AT+COPS=?"); + _at.cmd_stop(); + + _at.resp_start("+COPS:"); + + int ret, error_code = -1; + operator_t *op = NULL; + + while (_at.info_elem('(')) { + + op = operators.add_new(); + + op->op_status = (operator_t::Status)_at.read_int(); + _at.read_string(op->op_long, sizeof(op->op_long)); + _at.read_string(op->op_short, sizeof(op->op_short)); + _at.read_string(op->op_num, sizeof(op->op_num)); + + // Optional - try read an int + ret = _at.read_int(); + op->op_rat = (ret == error_code) ? operator_t::RAT_UNKNOWN:(operator_t::RadioAccessTechnology)ret; + + if ((_op_act == operator_t::RAT_UNKNOWN) || + ((op->op_rat != operator_t::RAT_UNKNOWN) && (op->op_rat == _op_act))) { + idx++; + } else { + operators.delete_last(); + } + } + + _at.resp_stop(); + + opsCount = idx; + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::set_ciot_optimization_config(Supported_UE_Opt supported_opt, + Preferred_UE_Opt preferred_opt) +{ + _at.lock(); + + _at.cmd_start("AT+CCIOTOPT="); + _at.write_int(_cid); + _at.write_int(supported_opt); + _at.write_int(preferred_opt); + _at.cmd_stop(); + + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::get_ciot_optimization_config(Supported_UE_Opt& supported_opt, + Preferred_UE_Opt& preferred_opt) +{ + _at.lock(); + + _at.cmd_start("AT+CCIOTOPT?"); + _at.cmd_stop(); + + _at.resp_start("+CCIOTOPT:"); + _at.read_int(); + if (_at.get_last_error() == NSAPI_ERROR_OK) { + supported_opt = (Supported_UE_Opt)_at.read_int(); + preferred_opt = (Preferred_UE_Opt)_at.read_int(); + } + + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::get_rate_control( + CellularNetwork::RateControlExceptionReports &reports, + CellularNetwork::RateControlUplinkTimeUnit &timeUnit, int &uplinkRate) +{ + + _at.lock(); + + _at.cmd_start("AT+CGAPNRC="); + _at.write_int(_cid); + _at.cmd_stop(); + + _at.resp_start("+CGAPNRC:"); + _at.read_int(); + if (_at.get_last_error() == NSAPI_ERROR_OK) { + bool comma_found = true; + int next_element = _at.read_int(); + if (next_element >= 0) { + reports = (RateControlExceptionReports)next_element; + log_debug("reports %d",reports); + next_element = _at.read_int(); + } else { + comma_found = false; + } + + if (comma_found && next_element >= 0) { + timeUnit = (RateControlUplinkTimeUnit)next_element; + log_debug("time %d",timeUnit); + next_element = _at.read_int(); + } else { + comma_found = false; + } + + if (comma_found && next_element >= 0) { + uplinkRate = next_element; + log_debug("rate %d",uplinkRate); + } + } + _at.resp_stop(); + nsapi_error_t ret = _at.get_last_error(); + _at.unlock(); + + return (ret == NSAPI_ERROR_OK) ? NSAPI_ERROR_OK : NSAPI_ERROR_PARAMETER; +} + +nsapi_error_t AT_CellularNetwork::get_pdpcontext_params(pdpContextList_t& params_list) +{ + const int ipv6_subnet_size = 128; + const int max_ipv6_size = 64; + char* ipv6_and_subnetmask = (char*)malloc(ipv6_subnet_size); + if (!ipv6_and_subnetmask) { + return NSAPI_ERROR_NO_MEMORY; + } + + char* temp = (char*)malloc(max_ipv6_size); + if (!temp) { + free(ipv6_and_subnetmask); + return NSAPI_ERROR_NO_MEMORY; + } + + _at.lock(); + + _at.cmd_start("AT+CGCONTRDP="); + _at.write_int(_cid); + _at.cmd_stop(); + + _at.resp_start("+CGCONTRDP:"); + pdpcontext_params_t *params = NULL; + while (_at.info_resp()) { // response can be zero or many +CGDCONT lines + params = params_list.add_new(); + if (!params) { + log_warn("Could not allocate new pdpcontext_params_t"); + params_list.delete_all(); + _at.resp_stop(); + free(temp); + free(ipv6_and_subnetmask); + return NSAPI_ERROR_NO_MEMORY; + } + + params->cid = _at.read_int(); + params->bearer_id = _at.read_int(); + _at.read_string(params->apn, sizeof(params->apn)); + + // rest are optional params + ipv6_and_subnetmask[0] = '\0'; + temp[0] = '\0'; + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->local_addr, sizeof(params->local_addr), params->local_subnet_mask, sizeof(params->local_subnet_mask)); + ipv6_and_subnetmask[0] = '\0'; + + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->gateway_addr, sizeof(params->gateway_addr), temp, max_ipv6_size); + prefer_ipv6(params->gateway_addr, sizeof(params->gateway_addr), temp, max_ipv6_size); + ipv6_and_subnetmask[0] = '\0'; + temp[0] = '\0'; + + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->dns_primary_addr, sizeof(params->dns_primary_addr), temp, max_ipv6_size); + prefer_ipv6(params->dns_primary_addr, sizeof(params->dns_primary_addr), temp, max_ipv6_size); + ipv6_and_subnetmask[0] = '\0'; + temp[0] = '\0'; + + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->dns_secondary_addr, sizeof(params->dns_secondary_addr), temp, max_ipv6_size); + prefer_ipv6(params->dns_secondary_addr, sizeof(params->dns_secondary_addr), temp, max_ipv6_size); + ipv6_and_subnetmask[0] = '\0'; + temp[0] = '\0'; + + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->p_cscf_prim_addr, sizeof(params->p_cscf_prim_addr), temp, max_ipv6_size); + prefer_ipv6(params->p_cscf_prim_addr, sizeof(params->p_cscf_prim_addr), temp, max_ipv6_size); + ipv6_and_subnetmask[0] = '\0'; + temp[0] = '\0'; + + _at.read_string(ipv6_and_subnetmask, ipv6_subnet_size); + separate_ip_addresses(ipv6_and_subnetmask, params->p_cscf_sec_addr, sizeof(params->p_cscf_sec_addr), temp, max_ipv6_size); + prefer_ipv6(params->p_cscf_sec_addr, sizeof(params->p_cscf_sec_addr), temp, max_ipv6_size); + + params->im_signalling_flag = _at.read_int(); + params->lipa_indication = _at.read_int(); + params->ipv4_mtu = _at.read_int(); + params->wlan_offload = _at.read_int(); + params->local_addr_ind = _at.read_int(); + params->non_ip_mtu = _at.read_int(); + params->serving_plmn_rate_control_value = _at.read_int(); + } + _at.resp_stop(); + + free(temp); + free(ipv6_and_subnetmask); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::get_extended_signal_quality(int &rxlev, int &ber, int &rscp, int &ecno, int &rsrq, int &rsrp) +{ + _at.lock(); + + _at.cmd_start("AT+CESQ"); + _at.cmd_stop(); + + _at.resp_start("+CESQ:"); + rxlev = _at.read_int(); + ber = _at.read_int(); + rscp = _at.read_int(); + ecno = _at.read_int(); + rsrq = _at.read_int(); + rsrp = _at.read_int(); + _at.resp_stop(); + if (rxlev < 0 || ber < 0 || rscp < 0 || ecno < 0 || rsrq < 0 || rsrp < 0) { + _at.unlock(); + return NSAPI_ERROR_DEVICE_ERROR; + } + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularNetwork::get_signal_quality(int &rssi, int &ber) +{ + _at.lock(); + + _at.cmd_start("AT+CSQ"); + _at.cmd_stop(); + + _at.resp_start("+CSQ:"); + rssi = _at.read_int(); + ber = _at.read_int(); + _at.resp_stop(); + if (rssi < 0 || ber < 0) { + _at.unlock(); + return NSAPI_ERROR_DEVICE_ERROR; + } + + return _at.unlock_return_error(); +} + +/** Get the last 3GPP error code + * @return see 3GPP TS 27.007 error codes + */ +uint8_t AT_CellularNetwork::get_3gpp_error() +{ + return _at.get_3gpp_error(); +} + + +nsapi_error_t AT_CellularNetwork::get_operator_params(int &format, operator_t &operator_params) +{ + _at.lock(); + + _at.cmd_start("AT+COPS?"); + _at.cmd_stop(); + + _at.resp_start("+COPS: "); + _at.read_int(); //ignore mode + format = _at.read_int(); + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + + switch (format) { + case 0: + _at.read_string(operator_params.op_long, 16+9); + break; + + case 1: + _at.read_string(operator_params.op_short, 8+4); + break; + + default: + _at.read_string(operator_params.op_num, 8+4); + break; + } + + operator_params.op_rat = (operator_t::RadioAccessTechnology)_at.read_int(); + } + + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/AT/AT_CellularNetwork.h b/features/cellular/framework/AT/AT_CellularNetwork.h new file mode 100644 index 0000000000..e855dac7d0 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularNetwork.h @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_NETWORK_H_ +#define AT_CELLULAR_NETWORK_H_ + +#include "CellularNetwork.h" +#include "AT_CellularBase.h" +#include "NetworkStack.h" + +namespace mbed { + +#define AT_NETWORK_TRIALS 5 +#define MAX_APN_LENGTH 63 + +class AT_CellularNetwork : public CellularNetwork, public AT_CellularBase +{ + +public: + + AT_CellularNetwork(ATHandler &atHandler); + virtual ~AT_CellularNetwork(); + +public: // NetworkInterface + + /** Set the cellular network APN and credentials + * + * @param apn Optional name of the network to connect to + * @param username Optional username for the APN + * @param password Optional password fot the APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t set_credentials(const char *apn, + const char *username = 0, const char *password = 0); + + /** Set the cellular network APN and credentials + * + * @param apn Name of the network to connect to + * @param type Authentication type to use + * @param username Optional username for the APN + * @param password Optional password fot the APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t set_credentials(const char *apn, AuthenticationType type, + const char *username = 0, const char *password = 0); + + /** Start the interface. Attempts to connect to a cellular network. + * + * @param apn Optional name of the network to connect to + * @param username Optional username for your APN + * @param password Optional password for your APN + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t connect(const char *apn, + const char *username = 0, const char *password = 0); + + /** Start the interface. Attempts to connect to a cellular network. + * + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t connect(); + + /** Stop the interface + * + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t disconnect(); + + /** Provide access to the NetworkStack object + * + * @return The underlying NetworkStack object + */ + virtual NetworkStack *get_stack(); + +public: // CellularNetwork + /** Request registering to network. + * + * @param plmn format is in numeric format or 0 for automatic network registration + * @return zero on success + */ + virtual nsapi_error_t set_registration(char *plmn = 0); + + /** + * Gets the network registration status. + * @param type see RegistrationType values + * @param status see RegistrationStatus values + * @return zero on success + */ + virtual nsapi_error_t get_registration_status(RegistrationType type, RegistrationStatus &status); + + /** Request attach to network. + * + * @param timeout milliseconds to wait for attach response + * @return zero on success + */ + virtual nsapi_error_t set_attach(int timeout = 10*1000); + + /** Request attach status from network. + * + * @param status see AttachStatus values + * @return zero on success + */ + virtual nsapi_error_t get_attach(AttachStatus &status); + + /** Get APN rate control. + * + * @remark optional params are not updated if not received from network, so use good defaults + * + * @param reports Additional exception reports at maximum rate reached are allowed to be sent [optional] + * @param time_unit Uplink time unit with values 0=unrestricted, 1=minute, 2=hour, 3=day, 4=week [optional] + * @param uplink_rate Maximum number of messages per timeUnit [optional] + * @return zero on success + */ + virtual nsapi_error_t get_rate_control(CellularNetwork::RateControlExceptionReports &reports, + CellularNetwork::RateControlUplinkTimeUnit &time_unit, int &uplink_rate); + + /** Get backoff timer value + * + * @param backoff_time Backoff timer value associated with PDP APN in seconds + * @return zero on success + */ + virtual nsapi_error_t get_backoff_time(int &backoff_time); + + /** Get notified if the connection gets lost + * + * @param cb user defined callback + */ + void connection_status_cb(Callback cb); + + /** Get the local IP address + * + * @return Null-terminated representation of the local IP address + * or null if no IP address has been received + */ + virtual const char *get_ip_address(); + + /** Sets radio access technology. + * + * @param op_rat Radio access technology + * @return zero on success + */ + virtual nsapi_error_t set_access_technology(operator_t::RadioAccessTechnology op_rat); + + /** Scans for operators module can reach. + * + * @param operators Container of reachable operators and their access technologies + * @param ops_count Number of found operators + * @return zero on success + */ + virtual nsapi_error_t scan_plmn(operList_t &operators, int &ops_count); + + /** Set CIoT optimizations. + * + * @param supported_opt Supported CIoT EPS optimizations. + * @param preferred_opt Preferred CIoT EPS optimizations. + * @return zero on success + */ + virtual nsapi_error_t set_ciot_optimization_config(Supported_UE_Opt supported_opt, + Preferred_UE_Opt preferred_opt); + + /** Get CIoT optimizations. + * + * @param supported_opt Supported CIoT EPS optimizations. + * @param preferred_opt Preferred CIoT EPS optimizations. + * @return zero on success + */ + virtual nsapi_error_t get_ciot_optimization_config(Supported_UE_Opt& supported_opt, + Preferred_UE_Opt& preferred_opt); + + + /** Set the pdn type to be used + * + * @param stack_type the stack type to be used. + * @return NSAPI_ERROR_OK on success + */ + virtual nsapi_error_t set_stack_type(nsapi_ip_stack_t stack_type); + + /** Get the pdn type in use + * + * @return stack type + */ + virtual nsapi_ip_stack_t get_stack_type(); + + /** Get the relevant information for an active non secondary PDP context. + * + * @remark optional params are not updated if not received from network. + * + * @param params_list reference to linked list which is filled on successful call + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t get_pdpcontext_params(pdpContextList_t& params_list); + + /** Get extended signal quality parameters. + * + * @param rxlev signal strength level + * @param ber bit error rate + * @param rscp signal code power + * @param ecno ratio of the received energy per PN chip to the total received power spectral density + * @param rsrq signal received quality + * @param rsrp signal received power + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_extended_signal_quality(int &rxlev, int &ber, int &rscp, int &ecno, int &rsrq, int &rsrp); + + /** Get signal quality parameters. + * + * @param rssi signal strength level + * @param ber bit error rate + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_signal_quality(int &rssi, int &ber); + + /** Get cell id. + * + * @param cell_id cell id + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_cell_id(int &cell_id); + + /** Get the last 3GPP error code + * @return see 3GPP TS 27.007 error codes + */ + virtual uint8_t get_3gpp_error(); + + /** Get the operator params + * + * @param format format of the operator field + * @param operator_params applicable operator param fields filled + * @return NSAPI_ERROR_OK on success, negative error code on failure + */ + virtual nsapi_error_t get_operator_params(int &format, operator_t &operator_params); + +protected: + + /** Check if modem supports the given stack type. + * + * @return true if supported + */ + virtual bool get_modem_stack_type(nsapi_ip_stack_t requested_stack); + + /** Check if modem supports given registration type. + * + * @param reg_type enum RegistrationType + * @return true if given registration type is supported by modem + */ + virtual bool has_registration(RegistrationType reg_type); + + /** Sets access technology to be scanned. + * + * @param op_rat Access technology + * + * @return zero on success + */ + virtual nsapi_error_t set_access_technology_impl(operator_t::RadioAccessTechnology op_rat); + +private: + // "NO CARRIER" urc + void urc_no_carrier(); + nsapi_error_t set_context_to_be_activated(); + nsapi_ip_stack_t string_to_stack_type(const char* pdp_type); + + nsapi_error_t open_data_channel(); + bool get_context(nsapi_ip_stack_t supported_stack); + bool set_new_context(nsapi_ip_stack_t stack, int cid); + nsapi_error_t set_registration_urc(bool on); + +protected: + NetworkStack *_stack; + char _apn[MAX_APN_LENGTH]; + const char *_uname; + const char *_pwd; + nsapi_ip_stack_t _ip_stack_type_requested; + nsapi_ip_stack_t _ip_stack_type; + int _cid; + Callback _connection_status_cb; + operator_t::RadioAccessTechnology _op_act; + AuthenticationType _authentication_type; + int _lac; + int _cell_id; + operator_t::RadioAccessTechnology _AcT; + RegistrationType _last_reg_type; +}; + +} // namespace mbed + +#endif // AT_CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/AT/AT_CellularPower.cpp b/features/cellular/framework/AT/AT_CellularPower.cpp new file mode 100644 index 0000000000..9202ace38a --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularPower.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularPower.h" +#include "CellularUtil.h" +#include "CellularLog.h" + +static const int PSMTimerBits = 5; + +using namespace mbed_cellular_util; +using namespace mbed; + +AT_CellularPower::AT_CellularPower(ATHandler &at) : AT_CellularBase(at) +{ +} + +AT_CellularPower::~AT_CellularPower() +{ +} + +nsapi_error_t AT_CellularPower::on() +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +nsapi_error_t AT_CellularPower::off() +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +nsapi_error_t AT_CellularPower::set_at_mode() +{ + _at.lock(); + _at.flush(); + _at.cmd_start("ATE0"); // echo off + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+CMEE=1"); // verbose responses + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularPower::set_power_level(int func_level) +{ + _at.lock(); + _at.cmd_start("AT+CFUN="); + _at.write_int(func_level); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularPower::reset() +{ + _at.lock(); + _at.cmd_start("AT+CFUN=");// reset to full power levels + _at.write_int(1); + _at.write_int(1); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularPower::opt_power_save_mode(int periodic_time, int active_time) +{ + _at.lock(); + + if (periodic_time == 0 && active_time == 0) { + // disable PSM + _at.cmd_start("AT+CPSMS="); + _at.write_int(0); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } else { + /** + Table 10.5.163a/3GPP TS 24.008: GPRS Timer 3 information element + + Bits 5 to 1 represent the binary coded timer value. + + Bits 6 to 8 defines the timer value unit for the GPRS timer as follows: + 8 7 6 + 0 0 0 value is incremented in multiples of 10 minutes + 0 0 1 value is incremented in multiples of 1 hour + 0 1 0 value is incremented in multiples of 10 hours + 0 1 1 value is incremented in multiples of 2 seconds + 1 0 0 value is incremented in multiples of 30 seconds + 1 0 1 value is incremented in multiples of 1 minute + 1 1 0 value is incremented in multiples of 320 hours (NOTE 1) + 1 1 1 value indicates that the timer is deactivated (NOTE 2). + */ + char pt[8+1];// timer value encoded as 3GPP IE + const int ie_value_max = 0x1f; + uint32_t periodic_timer = 0; + if (periodic_time <= 2*ie_value_max) { // multiples of 2 seconds + periodic_timer = periodic_time/2; + strcpy(pt, "01100000"); + } else { + if (periodic_time <= 30*ie_value_max) { // multiples of 30 seconds + periodic_timer = periodic_time/30; + strcpy(pt, "10000000"); + } else { + if (periodic_time <= 60*ie_value_max) { // multiples of 1 minute + periodic_timer = periodic_time/60; + strcpy(pt, "10100000"); + } else { + if (periodic_time <= 10*60*ie_value_max) { // multiples of 10 minutes + periodic_timer = periodic_time/(10*60); + strcpy(pt, "00000000"); + } else { + if (periodic_time <= 60*60*ie_value_max) { // multiples of 1 hour + periodic_timer = periodic_time/(60*60); + strcpy(pt, "00100000"); + } else { + if (periodic_time <= 10*60*60*ie_value_max) { // multiples of 10 hours + periodic_timer = periodic_time/(10*60*60); + strcpy(pt, "01000000"); + } else { // multiples of 320 hours + int t = periodic_time / (320*60*60); + if (t > ie_value_max) { + t = ie_value_max; + } + periodic_timer = t; + strcpy(pt, "11000000"); + } + } + } + } + } + } + + uint_to_binary_str(periodic_timer, &pt[3], sizeof(pt)-3, PSMTimerBits); + pt[8] = '\0'; + + /** + Table 10.5.172/3GPP TS 24.008: GPRS Timer information element + + Bits 5 to 1 represent the binary coded timer value. + + Bits 6 to 8 defines the timer value unit for the GPRS timer as follows: + + 8 7 6 + 0 0 0 value is incremented in multiples of 2 seconds + 0 0 1 value is incremented in multiples of 1 minute + 0 1 0 value is incremented in multiples of decihours + 1 1 1 value indicates that the timer is deactivated. + + Other values shall be interpreted as multiples of 1 minute in this version of the protocol. + */ + char at[8+1]; + uint32_t active_timer; // timer value encoded as 3GPP IE + if (active_time <= 2*ie_value_max) { // multiples of 2 seconds + active_timer = active_time/2; + strcpy(at, "00000000"); + } else { + if (active_time <= 60*ie_value_max) { // multiples of 1 minute + active_timer = (1<<5) | (active_time/60); + strcpy(at, "00100000"); + } else { // multiples of decihours + int t = active_time / (6*60); + if (t > ie_value_max) { + t = ie_value_max; + } + active_timer = t; + strcpy(at, "01000000"); + } + } + + uint_to_binary_str(active_timer, &at[3], sizeof(at)-3, PSMTimerBits); + pt[8] = '\0'; + + // request for both GPRS and LTE + _at.cmd_start("AT+CPSMS="); + _at.write_int(1); + _at.write_string(pt); + _at.write_string(at); + _at.write_string(pt); + _at.write_string(at); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + + if (_at.get_last_error() != NSAPI_ERROR_OK) { + log_warn("Power save mode not enabled!"); + } else { + // network may not agree with power save options but + // that should be fine as timeout is not longer than requested + } + } + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularPower::opt_receive_period(int mode, EDRXAccessTechnology act_type, uint8_t edrx_value) +{ + char edrx[5]; + uint_to_binary_str(edrx_value, edrx, 5, 4); + edrx[4] = '\0'; + + _at.lock(); + + _at.cmd_start("AT+CEDRXS="); + _at.write_int(mode); + _at.write_int(act_type); + _at.write_string(edrx); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/AT/AT_CellularPower.h b/features/cellular/framework/AT/AT_CellularPower.h new file mode 100644 index 0000000000..f102feb7cf --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularPower.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_POWER_H_ +#define AT_CELLULAR_POWER_H_ + +#include "CellularPower.h" +#include "AT_CellularBase.h" + +namespace mbed { + +class AT_CellularPower : public CellularPower, public AT_CellularBase +{ +public: + AT_CellularPower(ATHandler &atHandler); + virtual ~AT_CellularPower(); + +public: + /** Set cellular device power on. Default implementation is empty. + * Device power on/off is modem/board specific behavior and must be done on inherited class if needed. + * Power on is done by toggling power pin/button. + * + * @remark set_at_mode must be called to initialise modem + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t on(); + + /** Set cellular device power off. Default implementation is empty. + * Device power on/off is modem/board specific behavior and must be done on inherited class if needed. + * Power off is done by toggling power pin/button. + * + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t off(); + + /** Set AT command mode. Blocking until success or failure. + * + * @remark must be called after power on to prepare correct AT mode + * + * @return zero on success + */ + virtual nsapi_error_t set_at_mode(); + + /** Set cellular device power level by enabling/disabling functionality. + * + * @param func_level: + * 0 minimum functionality + * 1 full functionality. Enable (turn on) the transmit and receive RF circuits for all supported radio access technologies. + * For MTs supporting +CSRA, this equals the RATs indicated by the response of +CSRA=?. Current +CSRA setting is ignored. + * It is not required that the MT transmit and receive RF circuits are in a disabled state for this setting to have effect. + * 2 disable (turn off) MT transmit RF circuits only + * 3 disable (turn off) MT receive RF circuits only + * 4 disable (turn off) both MT transmit and receive RF circuits + * + * @remark See 3GPP TS 27.007 CFUN for more details + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t set_power_level(int func_level); + + /** Reset and wake-up cellular device. + * + * @remark Should not be called if MUX is enabled and started. If called then start-up sequence must be done again. + * + * @return zero on success + */ + virtual nsapi_error_t reset(); + + /** Opt for power save setting on cellular device. If both parameters are zero then disables PSM. + * + * @remark See 3GPP TS 27.007 PSM for details + * + * @param periodic_time Timeout in seconds IoT subsystem is not expecting messaging + * @param active_time Timeout in seconds IoT subsystem waits for response + * @return zero on success + */ + virtual nsapi_error_t opt_power_save_mode(int periodic_time, int active_time); + + /** Opt for discontinuous reception on cellular device. + * + * @remark See 3GPP TS 27.007 eDRX for details. + * + * @param mode disable or enable the use of eDRX + * @param act_type type of access technology + * @param edrx_value requested edxr value. Extended DRX parameters information element. + * @return zero on success + */ + virtual nsapi_error_t opt_receive_period(int mode, EDRXAccessTechnology act_type, uint8_t edrx_value); +}; + +} // namespace mbed + +#endif /* AT_CELLULAR_POWER_H_ */ diff --git a/features/cellular/framework/AT/AT_CellularSIM.cpp b/features/cellular/framework/AT/AT_CellularSIM.cpp new file mode 100644 index 0000000000..3bbd0ccc83 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularSIM.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularSIM.h" +#include "CellularLog.h" + +using namespace mbed; + +AT_CellularSIM::AT_CellularSIM(ATHandler &at) : AT_CellularBase(at) +{ +} + +AT_CellularSIM::~AT_CellularSIM() +{ +} + +nsapi_error_t AT_CellularSIM::get_sim_state(SimState &state) +{ + char simstr[16]; + _at.lock(); + _at.flush(); + _at.cmd_start("AT+CPIN?"); + _at.cmd_stop(); + _at.resp_start("+CPIN:"); + ssize_t len = _at.read_string(simstr, sizeof (simstr)); + if (len != -1) { + if (len >= 5 && memcmp(simstr, "READY", 5) == 0) { + state = SimStateReady; + } else if (len >= 6 && memcmp(simstr, "SIM PIN", 6) == 0) { + state = SimStatePinNeeded; + } else if (len >= 6 && memcmp(simstr, "SIM PUK", 6) == 0) { + state = SimStatePukNeeded; + } else { + simstr[len] = '\0'; + log_error("Unknown SIM state %s", simstr); + state = SimStateUnknown; + } + } else { + log_warn("SIM not readable."); + state = SimStateUnknown; // SIM may not be ready yet or +CPIN may be unsupported command + } + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSIM::set_pin(const char *sim_pin) +{ + // if SIM is already in ready state then settings the PIN + // will return error so let's check the state before settings the pin. + SimState state; + if (get_sim_state(state) == NSAPI_ERROR_OK && state == SimStateReady) { + return NSAPI_ERROR_OK; + } + + _at.lock(); + _at.cmd_start("AT+CPIN="); + _at.write_string(sim_pin); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSIM::change_pin(const char *sim_pin, const char *new_pin) +{ + _at.lock(); + _at.cmd_start("AT+CPWD="); + _at.write_string("SC"); + _at.write_string(sim_pin); + _at.write_string(new_pin); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSIM::set_pin_query(const char *sim_pin, bool query_pin) +{ + _at.lock(); + if (query_pin) { + /* use the SIM locked */ + _at.cmd_start("AT+CLCK="); + _at.write_string("SC"); + _at.write_int(1); + _at.write_string(sim_pin); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } else { + /* use the SIM unlocked */ + _at.cmd_start("AT+CLCK="); + _at.write_string("SC"); + _at.write_int(0); + _at.write_string(sim_pin); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/AT/AT_CellularSIM.h b/features/cellular/framework/AT/AT_CellularSIM.h new file mode 100644 index 0000000000..5c50925c76 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularSIM.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_SIM_H_ +#define AT_CELLULAR_SIM_H_ + +#include "CellularSIM.h" +#include "AT_CellularBase.h" + +namespace mbed { + +class AT_CellularSIM : public CellularSIM, public AT_CellularBase +{ + +public: + AT_CellularSIM(ATHandler &atHandler); + virtual ~AT_CellularSIM(); + +public: + /** Open the SIM card by setting the pin code for SIM. + * + * @param sim_pin PIN for the SIM card + * @return zero on success + */ + virtual nsapi_error_t set_pin(const char *sim_pin); + + /**Change sim pin code. + * + * @param sim_pin Current PIN for sim + * @param new_pin New PIN for sim + * @return zero on success + */ + virtual nsapi_error_t change_pin(const char *sim_pin, const char *new_pin); + + /** Change is pin query needed after boot + * + * @param sim_pin Valid PIN for SIM card + * @param query_pin False is PIN query not needed, True if PIN query needed after boot. + * @return zero on success + */ + virtual nsapi_error_t set_pin_query(const char *sim_pin, bool query_pin); + + /** Get sim card's state + * + * @param state current state of SIM + * @return zero on success + */ + virtual nsapi_error_t get_sim_state(SimState &state); +}; + +} // namespace mbed + +#endif // AT_CELLULAR_SIM_H_ diff --git a/features/cellular/framework/AT/AT_CellularSMS.cpp b/features/cellular/framework/AT/AT_CellularSMS.cpp new file mode 100644 index 0000000000..73bcd26868 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularSMS.cpp @@ -0,0 +1,1303 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "mbed_wait_api.h" +#include "AT_CellularSMS.h" +#include "CellularUtil.h" +#include "CellularLog.h" + +using namespace mbed_cellular_util; +using namespace mbed; +using namespace std; + +#define CTRL_Z "\x1a" +#define ESC "\x1b" + +const uint8_t SMS_STATUS_SIZE = 12 + 1; +const uint8_t FIRST_OCTET_DELIVER_SUBMIT = 17; +const uint8_t TP_VALIDITY_PERIOD_24_HOURS = 167; +const uint8_t TP_PROTOCOL_IDENTIFIER = 0; +const uint8_t SMS_DATA_CODING_SCHEME = 0; + +const uint8_t SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE = 134; +const uint8_t SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE = 153; +#define NVAM '?' // Not Valid ascii, ISO-8859-1 mark + +// mapping table from 7-bit GSM to ascii (ISO-8859-1) +static const int gsm_to_ascii[] = { + 64, // 0 + 163, // 1 + 36, // 2 + 165, // 3 + 232, // 4 + 233, // 5 + 249, // 6 + 236, // 7 + 242, // 8 + 199, // 9 + 10, // 10 + 216, // 11 + 248, // 12 + 13, // 13 + 197, // 14 + 229, // 15 + NVAM, // 16 + 95, // 17 + NVAM, // 18 + NVAM, // 19 + NVAM, // 20 + NVAM, // 21 + NVAM, // 22 + NVAM, // 23 + NVAM, // 24 + NVAM, // 25 + NVAM, // 26 + 27, // 27 + 198, // 28 + 230, // 29 + 223, // 30 + 201, // 31 + 32, // 32 + 33, // 33 + 34, // 34 + 35, // 35 + 164, // 36 + 37, // 37 + 38, // 38 + 39, // 39 + 40, // 40 + 41, // 41 + 42, // 42 + 43, // 43 + 44, // 44 + 45, // 45 + 46, // 46 + 47, // 47 + 48, // 48 + 49, // 49 + 50, // 50 + 51, // 51 + 52, // 52 + 53, // 53 + 54, // 54 + 55, // 55 + 56, // 56 + 57, // 57 + 58, // 58 + 59, // 59 + 60, // 60 + 61, // 61 + 62, // 62 + 63, // 63 + 161, // 64 + 65, // 65 + 66, // 66 + 67, // 67 + 68, // 68 + 69, // 69 + 70, // 70 + 71, // 71 + 72, // 72 + 73, // 73 + 74, // 74 + 75, // 75 + 76, // 76 + 77, // 77 + 78, // 78 + 79, // 79 + 80, // 80 + 81, // 81 + 82, // 82 + 83, // 83 + 84, // 84 + 85, // 85 + 86, // 86 + 87, // 87 + 88, // 88 + 89, // 89 + 90, // 90 + 196, // 91 + 214, // 92 + 209, // 93 + 220, // 94 + 167, // 95 + 191, // 96 + 97, // 97 + 98, // 98 + 99, // 99 + 100, // 100 + 101, // 101 + 102, // 102 + 103, // 103 + 104, // 104 + 105, // 105 + 106, // 106 + 107, // 107 + 108, // 108 + 109, // 109 + 110, // 110 + 111, // 111 + 112, // 112 + 113, // 113 + 114, // 114 + 115, // 115 + 116, // 116 + 117, // 117 + 118, // 118 + 119, // 119 + 120, // 120 + 121, // 121 + 122, // 122 + 228, // 123 + 246, // 124 + 241, // 125 + 252, // 126 + 224 // 127 +}; + +const int GSM_TO_ASCII_TABLE_SIZE = sizeof(gsm_to_ascii)/sizeof(gsm_to_ascii[0]); + +AT_CellularSMS::AT_CellularSMS(ATHandler &at) : AT_CellularBase(at), _cb(0), _mode(CellularSMSMmodeText), + _use_8bit_encoding(false), _sim_wait_time(0), _sms_message_ref_number(1), _sms_info(NULL) +{ + /* URCs, handled out of band */ + _at.set_urc_handler("+CMTI: ", callback(this, &AT_CellularSMS::cmti_urc)); + _at.set_urc_handler("+CMT: ", callback(this, &AT_CellularSMS::cmt_urc)); +} + +AT_CellularSMS::~AT_CellularSMS() +{ +} + +void AT_CellularSMS::cmt_urc() +{ + log_debug("CMT_URC called"); + //+CMT: ,[],[,,,,,,,] + _at.consume_to_stop_tag(); + // call user defined callback function + if (_cb) { + _cb(); + } else { + log_warn("cmt_urc, no user defined callback for receiving sms!"); + } +} + +void AT_CellularSMS::cmti_urc() +{ + //+CMTI: ,, + log_debug("CMTI_URC called"); + // call user defined callback function + if (_cb) { + _cb(); + } else { + log_warn("cmti_urc, no user defined callback for receiving sms!"); + } +} + +nsapi_error_t AT_CellularSMS::set_cnmi() +{ + _at.lock(); + _at.cmd_start("AT+CNMI=2,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::set_cmgf(int msg_format) +{ + _at.lock(); + _at.cmd_start("AT+CMGF="); + _at.write_int(msg_format); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::set_csmp(int fo, int vp, int pid, int dcs) +{ + _at.lock(); + _at.cmd_start("AT+CSMP="); + _at.write_int(fo); + _at.write_int(vp); + _at.write_int(pid); + _at.write_int(dcs); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::set_csdh(int show_header) +{ + _at.lock(); + _at.cmd_start("AT+CSDH="); + _at.write_int(show_header); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::initialize(CellularSMSMmode mode) +{ + _at.lock(); + set_cnmi(); //set new SMS indication + set_cmgf(mode); //set message format/PDU + + if (mode == CellularSMSMmodeText) { + set_csmp(FIRST_OCTET_DELIVER_SUBMIT, TP_VALIDITY_PERIOD_24_HOURS, TP_PROTOCOL_IDENTIFIER, + SMS_DATA_CODING_SCHEME); //set Set Text Mode Parameters(default values for SMS-SUBMIT and RECEIVE) + set_csdh(1);//set header extra info as it's needed + } + + _mode = mode; + + return _at.unlock_return_error(); +} + +void AT_CellularSMS::set_extra_sim_wait_time(int sim_wait_time) +{ + _sim_wait_time = sim_wait_time; +} + +char* AT_CellularSMS::create_pdu(const char* phone_number, const char* message, uint8_t message_length, uint8_t msg_parts, + uint8_t msg_part_number) +{ + int totalPDULength = 0; + int number_len = strlen(phone_number); + + totalPDULength += number_len; + if (number_len&0x01) {// if phone number length is not even length we must pad it and so +1 + totalPDULength += 1; + } + + totalPDULength += 16; // all other than phone number and message length + if (msg_parts > 1) {// add more space for UDH + totalPDULength += 12; + } + // there might be need for padding so some more space + totalPDULength +=2; + + // message 7-bit padded and it will be converted to hex so it will take twice as much space + totalPDULength += (message_length - (message_length/8))*2; + + char* pdu = (char*)calloc(totalPDULength, sizeof(char)); + if (!pdu) { + return NULL; + } + + int x = 0; + // See more how to create PDU from 3GPP specification 23040 + // first two define that we use service center number which is set with +CSCA + pdu[x++] = '0'; + pdu[x++] = '0'; + // First Octet of the TPDU. 41 means SMS SUBMIT, no validity period, no status report, use User Data Header. + // 01 means SMS SUBMIT, no validity period, no status report, NO User Data Header. + if (msg_parts > 1) { // concatenated, must use UDH + pdu[x++] = '4'; + } else { + pdu[x++] = '0'; + } + pdu[x++] = '1'; + // assign a message reference automatically. We have defined TP-RD bit as 0 so duplicates are not rejected. + pdu[x++] = '0'; + pdu[x++] = '0'; + // [6] and [7] Length of the Destination Phone Number + int_to_hex_str(number_len, pdu+x); + x+=2; + // Type of the Destination Phone Number + pdu[x++] = '8'; + pdu[x++] = '1'; + + // phone number as reverse nibble encoded + int i = 0; + for (; i < number_len; i += 2) { + if (i+1 == number_len) { + pdu[x++] = 'f'; + } else { + pdu[x++] = phone_number[i+1]; + } + pdu[x++] = phone_number[i]; + } + + // Protocol Identifier + pdu[x++] = '0'; + pdu[x++] = '0'; + // Data Coding Scheme, GSM 7-bit default alphabet = '00', 8-bit '04' + pdu[x++] = '0'; + + if (_use_8bit_encoding) { + pdu[x++] = '4'; + } else { + pdu[x++] = '0'; + } + + // possible to use 16 bit identifier, can't be defined yet from outside + bool use_16_bit_identifier = false; + uint8_t udhlen = 0; + // Length can be update after we have created PDU, store position for later use. + int lengthPos = x; + x +=2; + + int paddingBits = 0; + if (msg_parts > 1) { // concatenated, must use UDH + // user data header length in chars + pdu[x++] = '0'; + if (use_16_bit_identifier) { + udhlen = 7; // udh length in chars (6) + udhl length in chars + pdu[x++] = '6'; + } else { + udhlen = 6; // udh length in chars (5) + udhl length in chars + pdu[x++] = '5'; + } + // Information element identifier + pdu[x++] = '0'; + if (use_16_bit_identifier) { + pdu[x++] = '8'; + } else { + pdu[x++] = '0'; + } + // Information element data length + pdu[x++] = '0'; + if (use_16_bit_identifier) { + pdu[x++] = '4'; + } else { + pdu[x++] = '3'; + } + // A reference number (must be the same for all parts of the same larger messages) + int_to_hex_str(_sms_message_ref_number&0xFF, pdu+x); + x +=2; + if (use_16_bit_identifier) { + int_to_hex_str((_sms_message_ref_number>>16)&0xFF, pdu+x); + x +=2; + } + // How many parts does this message have? + int_to_hex_str(msg_parts, pdu+x); + x +=2; + // this is a part number + int_to_hex_str(msg_part_number, pdu+x); + x +=2; + + // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary + paddingBits = (udhlen * 8 ) % 7; + if (paddingBits) { + paddingBits = 7 - paddingBits; + udhlen += 1; + } + } + + if (_use_8bit_encoding) { + char_str_to_hex_str(message, message_length, pdu+x); + } else { + // we might need to send zero length sms + if (message_length) { + if (pack_7_bit_gsm_and_hex(message, message_length, pdu+x, paddingBits) == 0) { + free(pdu); + return NULL; + } + } + } + + // now we know the correct length of the UDL (User Data Length) + int_to_hex_str(message_length + udhlen, pdu+lengthPos); + + return pdu; +} + +nsapi_size_or_error_t AT_CellularSMS::send_sms(const char* phone_number, const char* message, int msg_len) +{ + int single_sms_max_length = _use_8bit_encoding ? SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE : + SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE; + if ((_mode == CellularSMSMmodeText && msg_len > single_sms_max_length) || !phone_number) { + return NSAPI_ERROR_PARAMETER; + } + + _at.lock(); + + int write_size = 0; + int remove_plus_sign = (phone_number[0] == '+') ? 1 : 0; + + wait_ms(_sim_wait_time); + + if (_mode == CellularSMSMmodeText) { + _at.cmd_start("AT+CMGS="); + _at.write_string(phone_number+remove_plus_sign); + _at.cmd_stop(); + + wait_ms(_sim_wait_time); + _at.resp_start("> ", true); + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + write_size = _at.write_bytes((uint8_t*)message, msg_len); + if (write_size < msg_len) { + // sending can be cancelled by giving character (IRA 27). + _at.cmd_start(ESC); + _at.cmd_stop(); + _at.unlock(); + return write_size; + } + // (IRA 26) must be used to indicate the ending of the message body. + _at.cmd_start(CTRL_Z); + _at.cmd_stop(); + _at.resp_start("+CMGS: "); + _at.resp_stop(); + } + } else { + // supports uncompressed 8 bit data and GSM 7 bit default alphabet data. Current implementation uses only + // GSM 7 bit default but support is done for 8 bit data. + int sms_count; + int concatenated_sms_length = _use_8bit_encoding ? SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE : + SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE; + + if (msg_len <= single_sms_max_length) { + // single message + sms_count = 1; + } else { + // concatenated message + sms_count = msg_len/concatenated_sms_length; + if (msg_len%concatenated_sms_length != 0) { + sms_count++; + } + } + + int remaining_len = msg_len; + int pdu_len; + char *pdu_str; + for (int i = 0; i< sms_count; i++) { + + if (sms_count == 1) { + pdu_len = msg_len; + } else { + pdu_len = remaining_len > concatenated_sms_length ? concatenated_sms_length : remaining_len; + } + + pdu_str = create_pdu(phone_number+remove_plus_sign, message + i*concatenated_sms_length, pdu_len, + sms_count, i+1); + if (!pdu_str) { + _at.unlock(); + return NSAPI_ERROR_NO_MEMORY; + } + pdu_len = strlen(pdu_str); + + // specification says that service center number should not be included so we subtract -2 from pdu_len as we use '00' for automatic service center number + _at.cmd_start("AT+CMGS="); + _at.write_int((pdu_len-2)/2); + _at.cmd_stop(); + + wait_ms(_sim_wait_time); + _at.resp_start("> ", true); + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + write_size = _at.write_bytes((uint8_t*)pdu_str, pdu_len); + if (write_size < pdu_len) { + // sending can be cancelled by giving character (IRA 27). + _at.cmd_start(ESC); + _at.cmd_stop(); + _at.unlock(); + free(pdu_str); + return write_size; + } + + // (IRA 26) must be used to indicate the ending of the message body. + _at.cmd_start(CTRL_Z); + _at.cmd_stop(); + _at.resp_start("+CMGS: "); + _at.resp_stop(); + } + free(pdu_str); + remaining_len -= concatenated_sms_length; + if (_at.get_last_error() != NSAPI_ERROR_OK) { + return _at.unlock_return_error(); + } + } + } + + _sms_message_ref_number++; + nsapi_error_t ret = _at.get_last_error(); + _at.unlock(); + + return (ret == NSAPI_ERROR_OK) ? write_size : ret; +} + +void AT_CellularSMS::set_sms_callback(Callback func) +{ + _cb = func; +} + +nsapi_error_t AT_CellularSMS::set_cpms(const char *memr, const char *memw, const char *mems) +{ + _at.lock(); + _at.cmd_start("AT+CPMS="); + _at.write_string(memr); + _at.write_string(memw); + _at.write_string(mems); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::set_csca(const char *sca, int type) +{ + _at.lock(); + _at.cmd_start("AT+CSCA="); + _at.write_string(sca); + _at.write_int(type); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_size_or_error_t AT_CellularSMS::set_cscs(const char *chr_set) +{ + _at.lock(); + _at.cmd_start("AT+CSCS="); + _at.write_string(chr_set); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::set_csms(int msg_service) +{ + _at.lock(); + _at.cmd_start("AT+CSMS="); + _at.write_int(msg_service); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularSMS::delete_sms(sms_info_t* sms) +{ + _at.lock(); + for (int i = 0; i < sms->parts; i++) { + _at.cmd_start("AT+CMGD="); + _at.write_int(sms->msg_index[i]); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } + + return _at.unlock_return_error(); +} + +// we need this as for example concatenated sms can get different sms reference numbers +// if for example last part take much more time to arrive. This situation happened while testing. +// What this means that after this we can't read another sms because we always read the oldest sms +// that was corrupted. So we need to have delete all messages. +nsapi_error_t AT_CellularSMS::delete_all_messages() +{ + _at.lock(); + _at.cmd_start("AT+CMGD=1,4"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +// read msg in text mode +nsapi_size_or_error_t AT_CellularSMS::read_sms_from_index(int msg_index, char* buf, uint16_t len, char* phone_num, + char* time_stamp) +{ + /* + * +CMGR: ,,,[,,,,,,,]OK + */ + wait_ms(_sim_wait_time); + _at.cmd_start("AT+CMGR="); + _at.write_int(msg_index); + _at.cmd_stop(); + + // TODO: NOTE: If the selected can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), + // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter. + // for now we support sms reading of received messages + int buf_len = 0; + if (_at.get_last_error() == NSAPI_ERROR_OK) { + char status[SMS_STATUS_SIZE]; + // first we read msg status and with that we can decide how the rest of message format + _at.resp_start("+CMGR: "); + + if (_at.info_resp()) { + _at.read_string(status, SMS_STATUS_SIZE); + uint16_t status_len = strlen(status); + if (((status_len == sizeof("REC READ") - 1) && memcmp("REC READ", status, status_len) == 0) + || ((status_len == sizeof("REC UNREAD") - 1) && memcmp("REC UNREAD", status, status_len) == 0)) { + // Received message + if (phone_num) { + _at.read_string(phone_num, SMS_MAX_PHONE_NUMBER_SIZE); + } + else { + _at.skip_param(); // , + } + _at.skip_param(); // + if (time_stamp) { + _at.read_string(time_stamp, SMS_MAX_TIME_STAMP_SIZE); + } + (void)_at.consume_to_stop_tag(); // consume until + if (buf) { + _at.read_string(buf, len); + buf_len = strlen(buf); + } + } + } + _at.resp_stop(); + } + + return (_at.get_last_error() == NSAPI_ERROR_OK) ? buf_len : _at.get_last_error(); +} + +// read msg in PDU mode +nsapi_size_or_error_t AT_CellularSMS::read_sms(sms_info_t* sms, char* buf, char* phone_num, char* time_stamp) +{ + // +CMGR: ,[], + int index = -1; + if (sms->parts == sms->parts_added) { + char *pdu; // we need a temp buffer as payload is hexencoded ---> can't use buf as it might be enough for message but not hexenconded pdu. + int status = -1; + int msg_len = 0; + index = 0; + int pduSize = 0; + + for (int i = 0; i < sms->parts; i++) { + wait_ms(_sim_wait_time); + _at.cmd_start("AT+CMGR="); + _at.write_int(sms->msg_index[i]); + _at.cmd_stop(); + _at.resp_start("+CMGR: "); + + if (_at.info_resp()) { + status = _at.read_int(); + _at.skip_param(); // + if ((_at.get_last_error() == NSAPI_ERROR_OK) && (status == 0 || status == 1)) { + msg_len = _at.read_int(); + if (msg_len > 0) { + pduSize = msg_len*2 + 20;// *2 as it's hex encoded and +20 as service center number is not included in size given by CMGR + pdu = (char*)calloc(pduSize, sizeof(char)); + if (!pdu) { + _at.resp_stop(); + return NSAPI_ERROR_NO_MEMORY; + } + _at.read_string(pdu, pduSize, true); + if (_at.get_last_error() == NSAPI_ERROR_OK) { + msg_len = get_data_from_pdu(pdu, NULL, NULL, phone_num, buf+index); + if (msg_len >= 0) { // we need to allow zero length messages + index += msg_len; + } else { + free(pdu); + _at.resp_stop(); + return -1; + } + } + free(pdu); + } + } + } + _at.resp_stop(); + } + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + if (time_stamp) { + strcpy(time_stamp, sms->date); + } + buf[index] = '\0'; + } + } + else { + log_warn("NOT all concatenated parts were received..."); + index = SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ; + } + + return index; +} + +nsapi_size_or_error_t AT_CellularSMS::get_sms(char* buf, uint16_t len, char* phone_num, uint16_t phone_len, + char* time_stamp, uint16_t time_len, int *buf_size) +{ + // validate buffer sizes already here to avoid any necessary function calls and locking of _at + if ((phone_num && phone_len < SMS_MAX_PHONE_NUMBER_SIZE) || (time_stamp && time_len < SMS_MAX_TIME_STAMP_SIZE) || + buf == NULL) { + return NSAPI_ERROR_PARAMETER; + } + + _at.lock(); + + nsapi_size_or_error_t err = list_messages(); + if (err == NSAPI_ERROR_OK) { + // we return the oldest sms and delete it after successful read + sms_info_t* info = get_oldest_sms_index(); + + if (info) { + if (info->msg_size+1 > len) { // +1 for '\0' + log_warn("Given buf too small, len is: %d but is must be: %d", len, info->msg_size); + if (buf_size) { + *buf_size = info->msg_size; + } + free_linked_list(); + _at.unlock(); + return NSAPI_ERROR_PARAMETER; + } + + if (_mode == CellularSMSMmodePDU) { + err = read_sms(info, buf, phone_num, time_stamp); + } else { + err = read_sms_from_index(info->msg_index[0], buf, len, phone_num, time_stamp); + } + + if (err > 0) { + int delerr = delete_sms(info); + if (delerr) { + err = delerr; + } + } + } else { + // No messages were found, return -1 + err = -1; + } + } + + free_linked_list(); + + _at.unlock(); + + // update error only when there really was an error, otherwise we return the length + if (_at.get_last_error()) { + err = _at.get_last_error(); + } + return err; +} + + nsapi_size_or_error_t AT_CellularSMS::get_data_from_pdu(const char* pdu, sms_info_t *info, int *part_number, + char *phone_number, char *msg) +{ + int index = 0; + int tmp; + bool userDataHeader; + int oaLength; + int dataScheme; + nsapi_size_or_error_t err = NSAPI_ERROR_OK; + + // read Length of the SMSC information + oaLength = hex_str_to_int(pdu, 2); + index += 2; // length we just read + index += oaLength*2; // skip service center number + + // read first the lower part of first octet as there is message type + index++; + tmp = hex_str_to_int(pdu+index, 1); + //wait_ms(200); + if ((tmp & 0x03) == 0) {// SMS-DELIVER type, last two bits should be zero + // UDH present? Check from first octets higher part + tmp = hex_str_to_int(pdu + (--index), 1); + userDataHeader = ((tmp & 0x04) == 0) ? false : true; + + index +=2; // we just read the high bits of first octet so move +2 + // originating address length + oaLength = hex_str_to_int(pdu+index, 2); + index +=2; // add index over address length + index +=2; // skip number type + if (phone_number) { + // phone number as reverse nibble encoded + int a = 0; + for (; a < oaLength; a +=2) { + if (a+1 == oaLength) { + phone_number[a] = pdu[index+a+1]; + } else { + phone_number[a] = pdu[index+a+1]; + phone_number[a+1] = pdu[index+a]; + } + } + phone_number[oaLength] = '\0'; + } + + index += oaLength; + if (oaLength&0x01) { // if phone number length is odd then it has padded F so skip that + index++; + } + index +=2; // skip TP-Protocol identifier + + dataScheme = hex_str_to_int(pdu+index, 2); + index +=2; // skip TP-Data-Coding-Scheme + + // next one is date, it's length is 7 octets according to 3GPP TS 23.040 + // create time string + if (info) { + int i = 0; + // year + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + info->date[i++] = '/'; + // month + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + info->date[i++] = '/'; + // Day + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + info->date[i++] = ','; + // Hour + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + info->date[i++] = ':'; + // Minute + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + info->date[i++] = ':'; + // Second + info->date[i++] = pdu[index+1]; + info->date[i++] = pdu[index]; + index+=2; + // timezone related to GMT. pdu[index+1] most significant bit indicates the sign related to gmt + tmp = hex_str_to_int(pdu+index+1, 1); + if (tmp&0x08) { + info->date[i++] = '-'; + } else { + info->date[i++] = '+'; + } + + // pdu[index+1 & 0x07 is the most significant bits of the timezone + // pdu [index] is the least significant bits + info->date[i++] = '0' + (tmp & 0x07); + info->date[i++] = pdu[index]; + info->date[i] = '\0'; + index+=2; + } else { + index+=14; + } + + index +=2; + int paddingBits = 0; + int parts = 1; + int partnro = 1; + if (userDataHeader) { + // we need to read User Defined Header to know what part number this message is. + index += read_udh_from_pdu(pdu+index, info, partnro, parts, paddingBits); + } + + if (part_number) { + *part_number = partnro; + } + + if (msg) { + // we are reading the message + err = read_pdu_payload(pdu+index, dataScheme, msg, paddingBits, partnro == parts); + } + else { + if (dataScheme == 0x00) { + // when listing messages we need to calculated length. Other way would be unpacking the whole message. + err = strlen(pdu+index) >> 1; + err *= 8; + err /= 7; + } else if (dataScheme == 0x04) { + err = strlen(pdu+index) >> 1; + } else { + return NSAPI_ERROR_UNSUPPORTED; + } + } + + return err; + } + else { + // message was not DELIVER so discard it + return NSAPI_ERROR_UNSUPPORTED; + } +} + + // read params from User DEfined Header +int AT_CellularSMS::read_udh_from_pdu(const char* pdu, sms_info_t *info, int &part_number, int &parts, + int &padding_bits) { + + int index = 0; + int udhLength = hex_str_to_int(pdu, 2); + index +=2; + + // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary + padding_bits = ((udhLength+1) * 8 ) % 7; // +1 is for udhLength itself + if (padding_bits) { + padding_bits = 7 - padding_bits; + } else { + padding_bits = 0; + } + + int tmp = hex_str_to_int(pdu+index, 2); + index +=4; + + if (tmp == 0) { // 8-bit reference number + if (info) { + info->msg_ref_number = (uint16_t)hex_str_to_int(pdu+index, 2); + } + index +=2; + } else { // 16-bit reference number + if (info) { + info->msg_ref_number = (uint16_t)hex_str_to_int(pdu+index+2, 2); + tmp = hex_str_to_int(pdu+index, 2); + info->msg_ref_number |= (tmp << 8); + } + index +=4; + } + + parts = hex_str_to_int(pdu+index, 2); + if (info) { + info->parts = parts; + } + index +=2; + + part_number = hex_str_to_int(pdu+index, 2); + index +=2; + + return (udhLength*2 + 2); // udh in hex and udhl +} + +nsapi_size_or_error_t AT_CellularSMS::read_pdu_payload(const char* pdu, int scheme, char *msg, int padding_bits, + bool last_part) +{ + if (scheme == 0x00) { + // 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii + return unpack_7_bit_gsm_to_str(pdu, strlen(pdu)/2, msg, padding_bits, last_part); + + } else if (scheme == 0x04) { + // 8bit scheme so just convert hexstring to charstring + return hex_str_to_char_str(pdu, strlen(pdu), msg); + } else { + log_error("Received unsupported data coding scheme: 0x%02x", scheme); + return NSAPI_ERROR_UNSUPPORTED; + } +} + +void AT_CellularSMS::free_linked_list() +{ + sms_info_t* info = _sms_info; + sms_info_t* old; + while (info) { + old = info; + info = info->next_info; + delete old; + } + _sms_info = NULL; +} + +void AT_CellularSMS::add_info(sms_info_t* info, int index, int part_number) { + // check for same message reference id. If found, update it and delete the given info. + // if NOT found then add to the end of the list. + + if (!_sms_info) { + info->msg_index[part_number-1] = index; // part numbering starts from 1 so -1 to put to right index + _sms_info = info; + return; + } + sms_info_t* current = _sms_info; + sms_info_t* prev; + bool found_msg = false; + while (current) { + prev = current; + // sms messages can have same reference number so additional checks are needed. + // TODO: should we include phone number also? + if (current->msg_ref_number == info->msg_ref_number && current->parts > current->parts_added && + info->parts > info->parts_added) { + // multipart sms, update msg size and index + current->msg_size += info->msg_size; + current->msg_index[part_number-1] = index; // part numbering starts from 1 so -1 to put to right index + current->parts_added++; + // update oldest part as date + if (compare_time_strings(info->date, current->date) == -1) { + strcpy(current->date, info->date); + } + found_msg = true; + break; + } + current = current->next_info; + } + + if (found_msg) { + // info was added to existing item in linked list, must be deleted + delete info; + } else { + // message not found, add to linked list + info->msg_index[part_number-1] = index; + prev->next_info = info; + } +} + +// reads all the messages to the linked list AT_CellularSMS::_sms_info +nsapi_error_t AT_CellularSMS::list_messages() +{ + // TODO: NOTE: If the selected can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs), + // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter. + // for now we assume that only SMS-DELIVER messages are read. + if (_mode == CellularSMSMmodePDU) { + _at.cmd_start("AT+CMGL=4"); + } else { + _at.cmd_start("AT+CMGL=\"ALL\""); + } + _at.cmd_stop(); + + sms_info_t* info = NULL; + // init for 1 so that in text mode we will add to the correct place without any additional logic in addInfo() in text mode + int part_number = 1; + int index = 0; + int length = 0; + char *pdu = NULL; + + _at.resp_start("+CMGL: "); + while (_at.info_resp()) { + info = new sms_info_t(); + if (!info) { + _at.resp_stop(); + return NSAPI_ERROR_NO_MEMORY; + } + + if (_mode == CellularSMSMmodePDU) { + //+CMGL: ,,[],[ + // +CMGL:,,[], + //[...]] + index = _at.read_int(); + _at.skip_param(2); // ,[] + length = _at.read_int(); + length = length*2 + 20;// *2 as it's hex encoded and +20 as service center number is not included in size given by CMGL + pdu = (char*)calloc(length, sizeof(char)); + if (!pdu) { + delete info; + _at.resp_stop(); + return NSAPI_ERROR_NO_MEMORY; + } + _at.read_string(pdu, length, true); + if (_at.get_last_error() == NSAPI_ERROR_OK) { + info->msg_size = get_data_from_pdu(pdu, info, &part_number); + } + } else { + // +CMGL: ,,,[],[][,,][ + // +CMGL: ,,,[],[][,,][...]] + index = _at.read_int(); + (void)_at.consume_to_stop_tag(); // consume until + (void)_at.consume_to_stop_tag(); // consume until + } + + if (index > 0) { + add_info(info, index, part_number); + } else { + delete info; + info = NULL; + } + free(pdu); + pdu = NULL; + } + + + _at.resp_stop(); + + return _at.get_last_error(); +} + +AT_CellularSMS::sms_info_t* AT_CellularSMS::get_oldest_sms_index() +{ + /* + * Different scenarios when finding the oldest concatenated sms + * + * 1. Find first parts first and it was received first + * 2. Find first parts first and it was NOT received first -> older timestamp might exist in some other part + * 3. Find other than first part first and it was received first + * 4. Find other than first part first and it was NOT received first -> older timestamp might exist in some other part + * + * So must take all message to a linked list and loop that for the oldest + */ + + // if text mode we need to read sms with +CMGR because time stamp is optional while looping with +CMGL + sms_info_t* retVal = NULL; + sms_info_t* current = _sms_info; + nsapi_size_or_error_t err = 0; + while (current) { + if (_mode == CellularSMSMmodeText) { + wait_ms(_sim_wait_time); + err = read_sms_from_index(current->msg_index[0], NULL, 0, NULL, current->date); + if (err != 0) { + return NULL; + } + } + + if (retVal == NULL) { + retVal = current; + } else if (compare_time_strings(current->date, retVal->date) == -1) { + // found older sms, update return value to oldest + retVal = current; + } + current = current->next_info; + } + + return retVal; +} + +// if time_string_1 is greater (more fresh date) then return 1, same 0, smaller -1. Error -2 +int AT_CellularSMS::compare_time_strings(const char* time_string_1, const char* time_string_2) +{ + time_t t1; + time_t t2; + + bool success = create_time(time_string_1, &t1) && create_time(time_string_2, &t2); + int retVal = -2; + + if (success) { + double diff = difftime(t1, t2); + + if (diff > 0) { + retVal = 1; + } else if (diff == 0) { + retVal = 0; + } else { + retVal = -1; + } + } + + return retVal; +} + +bool AT_CellularSMS::create_time(const char* time_string, time_t* time) +{ + const int kNumberOfElements = 8; + tm time_struct = { 0 }; + int gmt = 0; + char sign; + bool retVal = false; + + if (sscanf(time_string, "%d/%d/%d,%d:%d:%d%c%d", &time_struct.tm_year, &time_struct.tm_mon, &time_struct.tm_mday, + &time_struct.tm_hour, &time_struct.tm_min, &time_struct.tm_sec, &sign, &gmt) == kNumberOfElements) { + *time = mktime(&time_struct); + // add timezone as seconds. gmt is in quarter of hours. + int x = 60 * 60 * gmt * 0.25; + if (sign == '+') { + *time += x; + } else { + *time -= x; + } + retVal = true; + } + + return retVal; +} + +uint16_t AT_CellularSMS::pack_7_bit_gsm_and_hex(const char* str, uint16_t len, char *buf, + int number_of_padding_bit) +{ + uint16_t strCnt = 0; + uint16_t i = 0; + uint8_t shift; + char tmp; + + // convert to 7bit gsm first + char* gsm_str = (char*)malloc(len); + if (!gsm_str) { + return 0; + } + for (uint16_t y = 0; y < len; y++) { + for (int x=0; x < GSM_TO_ASCII_TABLE_SIZE; x++) { + if (gsm_to_ascii[x] == str[y]) { + gsm_str[y] = x; + } + } + } + + // then packing and converting to hex + if (number_of_padding_bit) { + tmp = gsm_str[strCnt]<>shift); + } else { + tmp = (gsm_str[strCnt]>>shift) | (gsm_str[strCnt+1] <<(7-shift)); + } + + char_str_to_hex_str(&tmp, 1, buf+(i*2)); + + if (shift == 6) { + strCnt++; + } + strCnt++; + i++; + } + + free(gsm_str); + + return i; +} + + uint16_t AT_CellularSMS::unpack_7_bit_gsm_to_str(const char* str, int len, char *buf, int padding_bits, + bool last_part) +{ + int strCount = 0; + uint16_t decodedCount = 0; + uint8_t shift; + char tmp; + char tmp1; + + if (padding_bits) { + hex_str_to_char_str(str, 2, &tmp); + buf[decodedCount] = gsm_to_ascii[(tmp>>padding_bits) & 0x7F]; + strCount++; + decodedCount++; + } + + while (strCount < len) { + shift = (strCount-padding_bits)%7; + hex_str_to_char_str(str + strCount*2, 2, &tmp); + if (shift == 0) { + buf[decodedCount] = gsm_to_ascii[tmp & 0x7F]; + } else if (shift == 6) { + hex_str_to_char_str(str + (strCount-1)*2, 2, &tmp1); + buf[decodedCount] = gsm_to_ascii[(((tmp1>>2)) | (tmp << 6)) & 0x7F]; + // we are unpacking the last byte and so tmp is not complete as it's not completed by the next byte. + // unless this is a multipart message and not the last part. + if (!((strCount+1 == len) && last_part)) { + hex_str_to_char_str(str + strCount*2, 2, &tmp); + decodedCount++; + buf[decodedCount] = gsm_to_ascii[(tmp>>1) & 0x7F]; + } + } else { + hex_str_to_char_str(str + (strCount-1)*2, 2, &tmp1); + buf[decodedCount] = gsm_to_ascii[(((tmp1>>(8- shift))) | ((tmp << shift))) & 0x7F]; + } + + strCount++; + decodedCount++; + } + + return decodedCount; +} diff --git a/features/cellular/framework/AT/AT_CellularSMS.h b/features/cellular/framework/AT/AT_CellularSMS.h new file mode 100644 index 0000000000..5747514926 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularSMS.h @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_SMS_H_ +#define AT_CELLULAR_SMS_H_ + +#include "CellularSMS.h" +#include "AT_CellularBase.h" +#include +#include + +namespace mbed { + +class AT_CellularSMS: public CellularSMS, public AT_CellularBase +{ + +public: + AT_CellularSMS(ATHandler &atHandler); + virtual ~AT_CellularSMS(); + +public: + // from CellularSMS + + /** Does all the necessary initializations needed for receiving and sending sms. + * + * @param mode enumeration for choosing the correct mode: text/pdu + * @return zero on success + */ + virtual nsapi_error_t initialize(CellularSMSMmode mode); + + /** Send the SMS with the given parameters + * + * @param phone_number Phone number where to send sms + * @param message SMS message content + * @param msg_len Length of the message + * @return possible error code or length of the sent sms + */ + virtual nsapi_size_or_error_t send_sms(const char* phone_number, const char* message, int msg_len); + + /** Gets the oldest received sms. + * + * @param buf preallocated buffer for sms message content + * @param buf_len length of allocated buf + * @param phone_num preallocated buffer for phone number where sms was sent + * @param phone_len length of allocated phone_num buffer + * @param time_stamp preallocated buffer for TP-Service Centre Time Stamp (format: yy/MM/dd,hh:mm:ss-+zz). +-zz is timezone. + * The unit of time zone is a quarter of an hour relative to GMT. For example +32 would be GMT+8. + * @param time_len length of allocated time_stamp buffer + * @param buf_size if method return error NSAPI_ERROR_NO_MEMORY because the given buf was not big enough this will + * hold the size which is enough. Otherwise zero. + * @return possible error code or size of buf. Will return SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ + * if sms was multipart but not all parts are present/failed to read. + */ + virtual nsapi_size_or_error_t get_sms(char* buf, uint16_t buf_len, char* phone_num, uint16_t phone_len, + char* time_stamp, uint16_t time_len, int *buf_size); + + /** Callback which is called when new sms is received. SMS can be fetched via method get_sms(). + * + * @remark In PDU mode there can be multipart sms and callback is called for every received part. + * + * @param func Callback function which is called when new sms is received. + */ + virtual void set_sms_callback(Callback func); + + /** CPMS preferred message storage + * + * @param memr memory from which messages are read and deleted + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * @param memw memory to which writing and sending operations are made + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * @param mems memory to which received SMs are preferred to be stored + * "SM" - SIM SMS memory storage (default) + * "ME" - NVM SMS storage + * + * @return zero for success + */ + virtual nsapi_error_t set_cpms(const char *memr, const char *memw, const char *mems); + + /** CSCA - set Service Center Address + * + * @param sca Service Center Address to be used for mobile originated SMS transmissions. + * @param type 129 - national numbering scheme, 145 - international numbering scheme (contains the character "+") + * + * @return zero for success + */ + virtual nsapi_error_t set_csca(const char *sca, int type); + + /** Set command sets the current character set used by the device. "GSM", "IRA",.... + * + * @remark Current implementation support only ASCII so choose the correct character set. + * + * @param chr_set preferred character set list (comma separated). Modem might not support the wanted character set + * so chr_set list is looped from start until supported set is found. Used character set index is returned. + * See more from 3GPP TS 27.005. + * @return Used character set index from the given list in case of success. Otherwise negative errorcode. + */ + virtual nsapi_size_or_error_t set_cscs(const char *chr_set); + + /** Deletes all messages from the currently set memory/SIM + * + * @return zero for success + */ + virtual nsapi_error_t delete_all_messages(); + + /** Some modems need extra time between AT commands and responses or there will be error -314, SIM busy. + * If SIM busy errors are an issue this time should be increased. It can also be set to zero to make + * operations faster and more energy efficient if no errors will follow. By default wait time is zero. + * + * @param sim_wait_time + */ + virtual void set_extra_sim_wait_time(int sim_wait_time); + +private: + + struct sms_info_t { + char date[SMS_MAX_TIME_STAMP_SIZE]; + uint16_t msg_index[50]; // can hold up to 50 concatenated msg parts, indexes are in correct order. So max sms size is 50*140 = 7kb + uint16_t msg_size; + uint8_t parts; + uint8_t parts_added; + uint16_t msg_ref_number; + struct sms_info_t *next_info; + sms_info_t() : msg_size(0), parts(1), parts_added(1), msg_ref_number(0), next_info(0){}; + }; + + // application callback function for received sms + Callback _cb; + CellularSMSMmode _mode; + bool _use_8bit_encoding; + uint32_t _sim_wait_time; + uint16_t _sms_message_ref_number; + sms_info_t *_sms_info; + + // SMS urc's + void cmt_urc(); + void cmti_urc(); + + /** Set command selects the format of messages used with send, list, read and write commands. + * + * @param msg_format 0 PDU mode, 1 text mode + * @return zero for success + */ + nsapi_error_t set_cmgf(int msg_format); + + /** Select message service (AT+CSMS select message service) + * + * @param msg_service 0 or 1. See more from 3GPP TS 27.005 + * + * @return zero for success + */ + nsapi_error_t set_csms(int msg_service); + + /* Set how receiving of new messages from the network is indicated to the TE. + * + * @return zero for success + */ + nsapi_error_t set_cnmi(); + + /** Set Text Mode Parameters + * + * @param fo See more from 3GPP TS 27.005 for all params. + * @param vp + * @param pid + * @param dcs + * @return zero for success + */ + nsapi_error_t set_csmp(int fo, int vp, int pid, int dcs); + + /** CSDH - Set command controls whether detailed header information is shown in text mode (AT+CMGF=1) result codes. + * + * @param show_header 1 to show detailed header in text mode, 0 for not showing. + * @return zero for success + */ + nsapi_error_t set_csdh(int show_header); + + /** Delete SMS in the given message position(s) in the storage + * + * @param sms struct containing index array to delete + * @return zero for success + */ + nsapi_error_t delete_sms(sms_info_t* sms); + + /** + * Internal helper methods + */ + nsapi_error_t list_messages(); + int read_sms_params(char *, char *); + void free_linked_list(); + void add_info(sms_info_t* info, int index, int part_number); + int read_udh_from_pdu(const char* pdu, sms_info_t *info, int &part_number, int &parts, int &padding_bits); + nsapi_size_or_error_t get_data_from_pdu(const char* pdu, sms_info_t *info, int *part_number, + char *phone_number = NULL, char *msg = NULL); + nsapi_size_or_error_t read_pdu_payload(const char* pdu, int scheme, char *msg, int padding_bits, bool last_part); + sms_info_t* get_oldest_sms_index(); + bool create_time(const char* time_string, time_t* time); + int compare_time_strings(const char* time_string_1, const char* time_string_2); + char* create_pdu(const char* phone_number, const char* message, uint8_t message_length, uint8_t msg_parts, + uint8_t msg_part_number); + nsapi_size_or_error_t read_sms_from_index(int msg_index, char* buf, uint16_t len, char* phone_num, + char* time_stamp); + nsapi_size_or_error_t read_sms(sms_info_t* sms, char* buf, char* phone_num, char* time_stamp); + + /** Packs the given str from ascii to 7bit gsm format and converts it to hex to the given buf. + * + * @param str string which is to be converted + * @param len length of the str buffer + * @param buf preallocated buffer which holds the converted string in hex format after successful call + * @param number_of_padding_bit padding bits needed to keep the octet boundary + * @return length of buffer buf or zero on failure + */ + uint16_t pack_7_bit_gsm_and_hex(const char* str, uint16_t len, char *buf, int number_of_padding_bit); + + /** Unpacks the given hex- and 7-bit gsm encoded str to ascii string + * + * @param str string which converted to ascii string to buf + * @param len length of the str divided by two as str is hexencoded + * @param buf preallocated destination buffer + * @param padding_bits number of padding bits which were needed to hold the octet boundary + * @param last_part true is last part of the encoded message + * @return length of the destination buffer buf + * + */ + uint16_t unpack_7_bit_gsm_to_str(const char* str, int len, char *buf, int padding_bits, + bool last_part); +}; + +} // namespace mbed + +#endif // AT_CELLULAR_SMS_H_ diff --git a/features/cellular/framework/AT/AT_CellularStack.cpp b/features/cellular/framework/AT/AT_CellularStack.cpp new file mode 100644 index 0000000000..de5684d609 --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularStack.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AT_CellularStack.h" +#include "CellularUtil.h" +#include "CellularLog.h" + +using namespace mbed_cellular_util; +using namespace mbed; + +AT_CellularStack::AT_CellularStack(ATHandler &at, int cid, nsapi_ip_stack_t stack_type) : AT_CellularBase(at), _socket(NULL),_socket_count(0),_cid(cid), _stack_type(stack_type) +{ +} + +AT_CellularStack::~AT_CellularStack() +{ + for (int i = 0; i < _socket_count; i++) { + if (_socket[i]) { + delete _socket[i]; + } + } + + delete [] _socket; +} + +/** NetworkStack + */ + +const char * AT_CellularStack::get_ip_address() +{ + _at.lock(); + + _at.cmd_start("AT+CGPADDR="); + _at.write_int(_cid); + _at.cmd_stop(); + + _at.resp_start("+CGPADDR:"); + + if (_at.info_resp()) { + + _at.skip_param(); + + int len = _at.read_string(_ip, NSAPI_IPv4_SIZE-1); + if (len == -1) { + _ip[0] = '\0'; + _at.unlock(); + // no IPV4 address, return + return NULL; + } + + // in case stack type is not IPV4 only, try to look also for IPV6 address + if (_stack_type != IPV4_STACK) { + len = _at.read_string(_ip, PDP_IPV6_SIZE-1); + } + } + + _at.resp_stop(); + _at.unlock(); + + // we have at least IPV4 address + convert_ipv6(_ip); + + return _ip; +} + +nsapi_error_t AT_CellularStack::socket_open(nsapi_socket_t *handle, nsapi_protocol_t proto) +{ + if (!is_protocol_supported(proto)) { + return NSAPI_ERROR_UNSUPPORTED; + } + + int max_socket_count = get_max_socket_count(); + + if (!_socket) { + _socket = new CellularSocket*[max_socket_count]; + if (!_socket) { + return NSAPI_ERROR_NO_SOCKET; + } + _socket_count = max_socket_count; + for (int i = 0; i < max_socket_count; i++) { + _socket[i] = 0; + } + } + + int index = -1; + for (int i = 0; i < max_socket_count; i++) { + if (!_socket[i]) { + index = i; + break; + } + } + + if (index == -1) { + return NSAPI_ERROR_NO_SOCKET; + } + + // create local socket structure, socket on modem is created when app calls sendto/recvfrom + _socket[index] = new CellularSocket; + CellularSocket *psock; + psock = _socket[index]; + memset(psock, 0, sizeof(CellularSocket)); + SocketAddress addr(0, get_dynamic_ip_port()); + psock->id = index; + psock->localAddress = addr; + psock->proto = proto; + *handle = psock; + + return NSAPI_ERROR_OK; +} + +nsapi_error_t AT_CellularStack::socket_close(nsapi_socket_t handle) +{ + int err = NSAPI_ERROR_DEVICE_ERROR; + + struct CellularSocket *socket = (struct CellularSocket *)handle; + int sock_id = socket->id; + int max_socket_count = get_max_socket_count(); + + int index = -1; + for (int i = 0; i < max_socket_count; i++) { + if (_socket[i] && _socket[i]->id == sock_id) { + index = i; + break; + } + } + + if (index != -1) { + _socket[index] = NULL; + err = NSAPI_ERROR_OK; + } + + _at.lock(); + + //_atHandler.setTimeout(...) + + err = socket_close_impl(sock_id); + + _at.unlock(); + + delete socket; + + return err; +} + +nsapi_error_t AT_CellularStack::socket_bind(nsapi_socket_t handle, const SocketAddress &addr) +{ + struct CellularSocket *socket = (CellularSocket *)handle; + socket->localAddress = addr; + + _at.lock(); + + if (!socket->created) { + create_socket_impl(socket); + } + + return _at.unlock_return_error(); +} + +nsapi_error_t AT_CellularStack::socket_listen(nsapi_socket_t handle, int backlog) +{ + return 0; +} + +nsapi_error_t AT_CellularStack::socket_connect(nsapi_socket_t handle, const SocketAddress &addr) +{ + CellularSocket *socket = (CellularSocket *)handle; + socket->remoteAddress = addr; + socket->connected = true; + + return NSAPI_ERROR_OK; +} + +nsapi_error_t AT_CellularStack::socket_accept(void *server, void **socket, SocketAddress *addr) +{ + return 0; +} + +nsapi_size_or_error_t AT_CellularStack::socket_send(nsapi_socket_t handle, const void *data, unsigned size) +{ + CellularSocket *socket = (CellularSocket *)handle; + if (!socket->connected) { + return NSAPI_ERROR_DEVICE_ERROR; + } + return socket_sendto(handle, socket->remoteAddress, data, size); +} + +nsapi_size_or_error_t AT_CellularStack::socket_sendto(nsapi_socket_t handle, const SocketAddress &addr, const void *data, unsigned size) +{ + CellularSocket *socket = (CellularSocket *)handle; + + nsapi_size_or_error_t ret_val = NSAPI_ERROR_OK; + + if (!socket->created) { + _at.lock(); + + ret_val = create_socket_impl(socket); + + _at.unlock(); + if (ret_val != NSAPI_ERROR_OK) { + return ret_val; + } + } + + unsigned max_packet_size = get_max_packet_size(); + + /* Check parameters */ + if (addr.get_ip_version() == NSAPI_UNSPEC || + size > max_packet_size) { + return NSAPI_ERROR_DEVICE_ERROR; + } + + _at.lock(); + + ret_val = socket_sendto_impl(socket, addr, data, size); + + _at.unlock(); + + return ret_val; +} + +nsapi_size_or_error_t AT_CellularStack::socket_recv(nsapi_socket_t handle, void *data, unsigned size) +{ + return socket_recvfrom(handle, NULL, data, size); +} + +nsapi_size_or_error_t AT_CellularStack::socket_recvfrom(nsapi_socket_t handle, SocketAddress *addr, void *buffer, unsigned size) +{ + CellularSocket *socket = (CellularSocket *)handle; + + nsapi_size_or_error_t ret_val = NSAPI_ERROR_OK; + + if (!socket->created) { + _at.lock(); + + ret_val = create_socket_impl(socket); + + _at.unlock(); + if (ret_val != NSAPI_ERROR_OK) { + return ret_val; + } + } + + unsigned max_packet_size = get_max_packet_size(); + + /* Check parameters */ + if (size < max_packet_size) { + log_warn("Socket receive buffer smaller than max packet size! size:%d max_packet_size:%d", size, max_packet_size); + } + + _at.lock(); + + ret_val = socket_recvfrom_impl(socket, addr, buffer, size); + + _at.unlock(); + + return ret_val; +} + +void AT_CellularStack::socket_attach(nsapi_socket_t handle, void (*callback)(void *), void *data) +{ + CellularSocket *socket = (CellularSocket *)handle; + socket->_cb = callback; + socket->_data = data; +} diff --git a/features/cellular/framework/AT/AT_CellularStack.h b/features/cellular/framework/AT/AT_CellularStack.h new file mode 100644 index 0000000000..05adb9911e --- /dev/null +++ b/features/cellular/framework/AT/AT_CellularStack.h @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AT_CELLULAR_STACK_H_ +#define AT_CELLULAR_STACK_H_ + +#include "AT_CellularBase.h" +#include "NetworkStack.h" + +namespace mbed { + +// and : each is a string type that identifies the MT in the address space applicable to the PDP. +// The string is given as dot-separated numeric (0-255) parameter of the form: +// a1.a2.a3.a4 for IPv4 and +// a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12.a13.a14.a15.a16 for IPv6. +#define PDP_IPV6_SIZE 63+1 + +/** + * Class AT_CellularStack. + * + * Implements NetworkStack and introduces interface for modem specific stack implementations. + */ +class AT_CellularStack : public NetworkStack, public AT_CellularBase +{ + +public: + AT_CellularStack(ATHandler &at, int cid, nsapi_ip_stack_t stack_type); + virtual ~AT_CellularStack(); + +public: // NetworkStack + + /** Get the local IP address + * + * @return Null-terminated representation of the local IP address + * or null if not yet connected + */ + virtual const char *get_ip_address(); + + /** Opens a socket + * + * Creates a network socket and stores it in the specified handle. + * The handle must be passed to following calls on the socket. + * + * A stack may have a finite number of sockets, in this case + * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. + * + * @param handle Destination for the handle to a newly created socket + * @param proto Protocol of socket to open, NSAPI_TCP or NSAPI_UDP + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_open(nsapi_socket_t *handle, nsapi_protocol_t proto); + + /** Close the socket + * + * Closes any open connection and deallocates any memory associated + * with the socket. + * + * @param handle Socket handle + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_close(nsapi_socket_t handle); + + /** Bind a specific address to a socket + * + * Binding a socket specifies the address and port on which to recieve + * data. If the IP address is zeroed, only the port is bound. + * + * @param handle Socket handle + * @param address Local address to bind + * @return 0 on success, negative error code on failure. + */ + virtual nsapi_error_t socket_bind(nsapi_socket_t handle, const SocketAddress &address); + + /** Listen for connections on a TCP socket + * + * Marks the socket as a passive socket that can be used to accept + * incoming connections. + * + * @param handle Socket handle + * @param backlog Number of pending connections that can be queued + * simultaneously + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_listen(nsapi_socket_t handle, int backlog); + + /** Connects TCP socket to a remote host + * + * Initiates a connection to a remote server specified by the + * indicated address. + * + * @param handle Socket handle + * @param address The SocketAddress of the remote host + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_connect(nsapi_socket_t handle, const SocketAddress &address); + + /** Accepts a connection on a TCP socket + * + * The server socket must be bound and set to listen for connections. + * On a new connection, creates a network socket and stores it in the + * specified handle. The handle must be passed to following calls on + * the socket. + * + * A stack may have a finite number of sockets, in this case + * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. + * + * This call is non-blocking. If accept would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param server Socket handle to server to accept from + * @param handle Destination for a handle to the newly created socket + * @param address Destination for the remote address or NULL + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_accept(nsapi_socket_t server, + nsapi_socket_t *handle, SocketAddress *address=0); + + /** Send data over a TCP socket + * + * The socket must be connected to a remote host. Returns the number of + * bytes sent from the buffer. + * + * This call is non-blocking. If send would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param handle Socket handle + * @param data Buffer of data to send to the host + * @param size Size of the buffer in bytes + * @return Number of sent bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_send(nsapi_socket_t handle, + const void *data, nsapi_size_t size); + + /** Receive data over a TCP socket + * + * The socket must be connected to a remote host. Returns the number of + * bytes received into the buffer. + * + * This call is non-blocking. If recv would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param handle Socket handle + * @param data Destination buffer for data received from the host + * @param size Size of the buffer in bytes + * @return Number of received bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_recv(nsapi_socket_t handle, + void *data, nsapi_size_t size); + + /** Send a packet over a UDP socket + * + * Sends data to the specified address. Returns the number of bytes + * sent from the buffer. + * + * This call is non-blocking. If sendto would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param handle Socket handle + * @param address The SocketAddress of the remote host + * @param data Buffer of data to send to the host + * @param size Size of the buffer in bytes + * @return Number of sent bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_sendto(nsapi_socket_t handle, const SocketAddress &address, + const void *data, nsapi_size_t size); + + /** Receive a packet over a UDP socket + * + * Receives data and stores the source address in address if address + * is not NULL. Returns the number of bytes received into the buffer. + * + * This call is non-blocking. If recvfrom would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param handle Socket handle + * @param address Destination for the source address or NULL + * @param buffer Destination buffer for data received from the host + * @param size Size of the buffer in bytes + * @return Number of received bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_recvfrom(nsapi_socket_t handle, SocketAddress *address, + void *buffer, nsapi_size_t size); + + /** Register a callback on state change of the socket + * + * The specified callback will be called on state changes such as when + * the socket can recv/send/accept successfully and on when an error + * occurs. The callback may also be called spuriously without reason. + * + * The callback may be called in an interrupt context and should not + * perform expensive operations such as recv/send calls. + * + * @param handle Socket handle + * @param callback Function to call on state change + * @param data Argument to pass to callback + */ + virtual void socket_attach(nsapi_socket_t handle, void (*callback)(void *), void *data); + +protected: + + class CellularSocket + { + public: + // Socket id from cellular device + int id; + // Being connected means remote ip address and port are set + bool connected; + nsapi_protocol_t proto; + SocketAddress remoteAddress; + SocketAddress localAddress; + void (*_cb)(void *); + void *_data; + bool created; + bool rx_avail; // used to synchronise reading from modem + }; + + /** + * Gets maximum number of sockets modem supports + */ + virtual int get_max_socket_count() = 0; + + /** + * Gets maximum packet size + */ + virtual int get_max_packet_size() = 0; + + /** + * Checks if modem supports the given protocol + * + * @param protocol Protocol type + */ + virtual bool is_protocol_supported(nsapi_protocol_t protocol) = 0; + + /** + * Implements modem specific AT command set for socket closing + * + * @param sock_id Socket id + */ + virtual nsapi_error_t socket_close_impl(int sock_id) = 0; + + /** + * Implements modem specific AT command set for creating socket + * + * @param socket Cellular socket handle + */ + virtual nsapi_error_t create_socket_impl(CellularSocket *socket) = 0; + + /** + * Implements modem specific AT command set for sending data + * + * @param socket Cellular socket handle + * @param address The SocketAddress of the remote host + * @param data Buffer of data to send to the host + * @param size Size of the buffer in bytes + * @return Number of sent bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_sendto_impl(CellularSocket *socket, const SocketAddress &address, + const void *data, nsapi_size_t size) = 0; + + /** + * Implements modem specific AT command set for receiving data + * + * @param socket Socket handle + * @param address Destination for the source address or NULL + * @param buffer Destination buffer for data received from the host + * @param size Size of the buffer in bytes + * @return Number of received bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_recvfrom_impl(CellularSocket *socket, SocketAddress *address, + void *buffer, nsapi_size_t size) = 0; + + // socket container + CellularSocket **_socket; + + // number of socket slots allocated in socket container + int _socket_count; + + // IP address + char _ip[PDP_IPV6_SIZE]; + + // PDP context id + int _cid; + + // stack type from PDP context + nsapi_ip_stack_t _stack_type; +}; + +} // namespace mbed + +#endif // AT_CELLULAR_STACK_H_ diff --git a/features/cellular/framework/common/CellularList.h b/features/cellular/framework/common/CellularList.h new file mode 100644 index 0000000000..50f9ab7fc4 --- /dev/null +++ b/features/cellular/framework/common/CellularList.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_LIST_H_ +#define CELLULAR_LIST_H_ + +#include + +namespace mbed { + +/** Class CellularList + * + * Templated linked list class for common usage. + * + */ +template class CellularList +{ +private: + T *_head, *_tail; +public: + CellularList() + { + _head=NULL; + _tail=NULL; + } + + T* add_new() + { + T *temp=new T; + if (!temp) { + return NULL; + } + temp->next = NULL; + if (_head == NULL) { + _head = temp; + } else { + _tail->next=temp; + } + _tail = temp; + + return _tail; + } + + void delete_last() + { + T* previous = NULL; + T *current=_head; + + if (!current) { + return; + } + + while (current->next != NULL) { + previous=current; + current=current->next; + } + + if (previous) { + _tail=previous; + previous->next=NULL; + } else { + _head = NULL; + _tail = NULL; + } + + delete current; + } + + void delete_all() + { + T *temp = _head; + while (temp) { + _head = _head->next; + delete temp; + temp = _head; + } + } + + + T *get_head() + { + return _head; + } +}; + +} // namespace mbed + +#endif // CELLULAR_LIST_H_ diff --git a/features/cellular/framework/common/CellularLog.cpp b/features/cellular/framework/common/CellularLog.cpp new file mode 100644 index 0000000000..078468e6e4 --- /dev/null +++ b/features/cellular/framework/common/CellularLog.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) , Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "CellularLog.h" +#include "us_ticker_api.h" +#include "serial_api.h" +#include "rtos/Mutex.h" + +extern serial_t stdio_uart; +extern int stdio_uart_inited; + +namespace mbed { + +uint32_t cellular_log_time = us_ticker_read() / 1000L; + +/** + * Local mutex object for synchronization + */ +static rtos::Mutex mutex; + +/** + * Lock provided for serial printing used by trace library + */ +static void serial_lock() +{ + mutex.lock(); +} + +/** + * Releasing lock provided for serial printing used by trace library + */ +static void serial_unlock() +{ + mutex.unlock(); +} + +void log_init(PinName tx, PinName rx, int baud) +{ +#ifdef FEATURE_COMMON_PAL + mbed_trace_mutex_wait_function_set(serial_lock); + mbed_trace_mutex_release_function_set(serial_unlock); + mbed_trace_init(); +#else + /*UARTSerial trace_serial(tx, rx, baud); + FILE *trace = fdopen(&trace_serial, "w");*/ + serial_init(&stdio_uart, tx, rx); + serial_baud(&stdio_uart, baud); + stdio_uart_inited = 1; +#endif + log_info("\r\n\r\n**************************************"); + log_info("Hellular says Cellular Hello!"); + log_info("Build time %s %s", __DATE__, __TIME__); +} +} // namespace mbed diff --git a/features/cellular/framework/common/CellularLog.h b/features/cellular/framework/common/CellularLog.h new file mode 100644 index 0000000000..08ad196cda --- /dev/null +++ b/features/cellular/framework/common/CellularLog.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_LOG_H_ +#define CELLULAR_LOG_H_ + +#include +#ifdef FEATURE_COMMON_PAL +#include +#endif // FEATURE_COMMON_PAL +#include "PinNames.h" +#include "us_ticker_api.h" +#include "mbed_debug.h" + +namespace mbed { + +/** this print is some deep information for debug purpose */ +#define TRACE_LEVEL_DEBUG 0x10 +/** Info print, for general purpose prints */ +#define TRACE_LEVEL_INFO 0x08 +/** warning prints, which shouldn't causes any huge problems */ +#define TRACE_LEVEL_WARN 0x04 +/** Error prints, which causes probably problems, e.g. out of mem. */ +#define TRACE_LEVEL_ERROR 0x02 +/** special level for cmdline. Behaviours like "plain mode" */ +#define TRACE_LEVEL_CMD 0x01 + +#define TRACE_GROUP "cellular" + +#ifndef MBED_TRACE_MAX_LEVEL +#define MBED_TRACE_MAX_LEVEL TRACE_LEVEL_ERROR +#endif + +#ifdef FEATURE_COMMON_PAL +#include "mbed-trace/mbed_trace.h" +#define log_debug tr_debug +#define log_info tr_info +#define log_warn tr_warn +#define log_error tr_error +#else +extern uint32_t cellular_log_time; +#if (MBED_TRACE_MAX_LEVEL >= TRACE_LEVEL_DEBUG) +#define log_debug(format, ...) do { debug("DEBUG" " [" TRACE_GROUP " %ums] " format "\r\n", (us_ticker_read()-cellular_log_time) / 1000L, ## __VA_ARGS__); } while (0) +#else +#define log_debug(...) +#endif +#if (MBED_TRACE_MAX_LEVEL >= TRACE_LEVEL_INFO) +#define log_info(format, ...) do { debug("INFO" " [" TRACE_GROUP " %ums] " format "\r\n", (us_ticker_read()-cellular_log_time) / 1000L, ## __VA_ARGS__); } while (0) +#else +#define log_info(...) +#endif +#if (MBED_TRACE_MAX_LEVEL >= TRACE_LEVEL_WARN) +#define log_warn(format, ...) do { debug("WARN" " [" TRACE_GROUP " %ums] " format "\r\n", (us_ticker_read()-cellular_log_time) / 1000L, ## __VA_ARGS__); } while (0) +#else +#define log_warn(...) +#endif +#if (MBED_TRACE_MAX_LEVEL >= TRACE_LEVEL_ERROR) +#define log_error(format, ...) do { debug("ERROR" " [" TRACE_GROUP " %ums] " format "\r\n", (us_ticker_read()-cellular_log_time) / 1000L, ## __VA_ARGS__); } while (0) +#else +#define log_error(...) +#endif +#endif + +extern void log_init(PinName tx, PinName rx, int baud); + +} // namespace mbed + +#endif // CELLULAR_LOG_H_ diff --git a/features/cellular/framework/common/CellularTargets.h b/features/cellular/framework/common/CellularTargets.h new file mode 100644 index 0000000000..6cc65e74fc --- /dev/null +++ b/features/cellular/framework/common/CellularTargets.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_TARGETS_H_ +#define CELLULAR_TARGETS_H_ + +namespace mbed { + +#define UART 1 +#define MUX 2 + +#ifndef CELLULAR_DEVICE +#if defined(TARGET_ADV_WISE_1570) || defined(TARGET_MTB_ADV_WISE_1570) +#define CELLULAR_DEVICE QUECTEL_BC95 +#elif TARGET_MTS_DRAGONFLY_F411RE +#define CELLULAR_DEVICE TELIT_HE910 +#elif TARGET_MTB_MTS_DRAGONFLY +#define CELLULAR_DEVICE TELIT_HE910 +#elif TARGET_UBLOX_C030 +#define CELLULAR_DEVICE UBLOX_C027 +#elif TARGET_UBLOX_C027 +#define CELLULAR_DEVICE UBLOX_C027 + +#else +#error Cellular target not defined, see cellular/targets.h +//#define CELLULAR_TARGET +//#define MDMTXD +//#define MDMRXD +#endif +#endif + +} // namespace mbed +#endif // CELLULAR_TARGETS_H_ diff --git a/features/cellular/framework/common/CellularUtil.cpp b/features/cellular/framework/common/CellularUtil.cpp new file mode 100644 index 0000000000..406563951a --- /dev/null +++ b/features/cellular/framework/common/CellularUtil.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) , Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "CellularUtil.h" +#include +#include + +namespace mbed_cellular_util { + +void convert_ipv6(char* ip) +{ + if (!ip) { + return; + } + + int len = strlen(ip); + int pos = 0; + int i; + + for (i = 0; i < len; i++) { + if (ip[i] == '.') { + pos++; + } + if (pos > 3) { + break; + } + } + + // more that 3 periods mean that it was ipv6 but in format of a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12.a13.a14.a15.a16 + // we need to convert it to hexadecimal format separated with colons + if (pos > 3) { + pos = 0; + int ip_pos = 0; + char b; + bool set_colon = false; + for (i = 0; i < len; i++) { + if (ip[i] == '.') { + b = (char)strtol (ip+ip_pos, NULL, 10); // convert to char to int so we can change it to hex string + pos += char_str_to_hex_str(&b, 1, ip+pos, !set_colon); // omit leading zeroes with using set_colon flag + if (set_colon) { + ip[pos++] = ':'; + set_colon = false; + } else { + set_colon = true; + } + ip_pos = i+1; // skip the '.' + } + + // handle the last part which does not end with '.' but '\0' + if (i == len -1) { + b = (char)strtol(ip+ip_pos, NULL, 10); + pos += char_str_to_hex_str(&b, 1, ip+pos, !set_colon); + ip[pos] = '\0'; + } + } + } +} + +// For example "32.1.13.184.0.0.205.48.0.0.0.0.0.0.0.0" +void separate_ip4like_addresses(char* orig, char* ip, size_t ip_size, char* ip2, size_t ip2_size) +{ + // ipv4-like notation + int len = strlen(orig); + int count = 0, i = 0, pos = 0; + char *temp; + + for (; i < len; i++) { + if (orig[i] == '.') { + count++; + if (count == 4) { + pos = i; + } else if (count == 16) { + pos = i; + } + } + } + + if (count == 3) { // normal ipv4, copy to ip + if (ip_size > strlen(orig)) { + memcpy(ip, orig, strlen(orig)); + ip[strlen(orig)] = '\0'; + } else { + ip[0] = '\0'; + } + if (ip2) { + ip2[0] = '\0'; + } + } else if (count == 7) { // ipv4 and subnet mask. Need to separate those. + temp = &orig[pos]; + if ((uint8_t)ip_size > temp-orig) { + memcpy(ip, orig, temp-orig); + ip[temp-orig] = '\0'; + } + temp++; // skip the '.' + if (ip2 && (ip2_size > strlen(temp))) { + memcpy(ip2, temp, strlen(temp)); + ip2[strlen(temp)] = '\0'; + } + } else if (count == 15) { // only one ipv6 address in ipv4-like notation + if (ip_size > strlen(orig)) { + memcpy(ip, orig, strlen(orig)); + ip[strlen(orig)] = '\0'; + convert_ipv6(ip); + } else { + ip[0] = '\0'; + } + if (ip2) { + ip2[0] = '\0'; + } + } else if (count == 31){ // ipv6 + ipv6subnet mask in ipv4-like notation separated by dot '.' + temp = &orig[pos]; + if ((uint8_t)ip_size > temp-orig) { + memcpy(ip, orig, temp-orig); + ip[temp-orig] = '\0'; + convert_ipv6(ip); + } + temp++; // skip the '.' + if (ip2 && (ip2_size > strlen(temp))) { + memcpy(ip2, temp, strlen(temp)); + ip2[strlen(temp)] = '\0'; + convert_ipv6(ip2); + } + } +} + +void separate_ip_addresses(char* orig, char* ip, size_t ip_size, char* ip2, size_t ip2_size) +{ + // orig can include ipv4, ipv6, both or two ip4/ipv6 addresses. + // also format depends on possible AT+CGPIAF + if (!orig || !ip) { + if (ip) { + ip[0] = '\0'; + } + if (ip2) { + ip2[0] = '\0'; + } + return; + } + // 1. try to found ':'. If it's found then we know that possible addresses are separated with space + char *temp; + temp = strchr(orig, ':'); + + if (temp != NULL) { + // found ':' + temp = strstr(orig, " "); + // found space as separator and it wasn't in beginning --> contains 2 ip addresses + if (temp && temp != orig) { + if ((uint8_t)ip_size > temp-orig) { + memcpy(ip, orig, temp-orig); + ip[temp-orig] = '\0'; + } else { + ip[0] = '\0'; + } + temp++; // skip the space + if (ip2 && (ip2_size > strlen(temp))) { + memcpy(ip2, temp, strlen(temp)); + ip2[strlen(temp)] = '\0'; + } else { + ip2[0] = '\0'; + } + } else { + // Space was the first char or no space found ---> only one ip, copy to ip + size_t size = strlen(orig); + if (temp) { + size = strlen(temp); + } + + if (ip_size > size) { + memcpy(ip, orig, size); + ip[size] = '\0'; + } else { + ip[0] = '\0'; + } + if (ip2) { + ip2[0] = '\0'; + } + } + } else { + temp = strstr(orig, " "); + // found space as separator and it wasn't in beginning --> contains 2 ip addresses + if (temp && temp != orig) { + separate_ip4like_addresses(temp++, ip2, ip2_size, NULL, 0); + orig[temp-orig-1] = '\0'; + separate_ip4like_addresses(orig, ip, ip_size, NULL, 0); + orig[temp-orig-1] = ' '; // put space back to keep orig as original + } + else { + separate_ip4like_addresses(orig, ip, ip_size, ip2, ip2_size); + } + } +} + +void prefer_ipv6(char* ip, size_t ip_size, char* ip2, size_t ip2_size) +{ + if (!ip || !ip2) { + return; + } + // assume that that ipv6 is already in formatted to use ':' + // 1. try to found ':'. If it's found then we know that this is ipv6 + char *temp; + temp = strchr(ip, ':'); + if (temp) { + // ip has ipv6 address, we can leave + return; + } else { + // ip was not ipv6, check if ip2 is + temp = strchr(ip2, ':'); + if (temp) { + // ipv6 was found in ip2 but not in ip ---> we must swap them. Sadly ip and ip2 might not be pointers + // so we can't just swap them, must use copy. + if (strlen(ip) < ip2_size && strlen(ip2) < ip_size && strlen(ip) < 64) { + char tmp[64]; + strncpy(tmp, ip, strlen(ip)); + tmp[strlen(ip)] = '\0'; + strncpy(ip, ip2, strlen(ip2)); + ip[strlen(ip2)] = '\0'; + strncpy(ip2, tmp, strlen(tmp)); + ip2[strlen(tmp)] = '\0'; + } + } + } +} + +void int_to_hex_str(uint8_t num, char* buf) +{ + char charNum = num; + char_str_to_hex_str(&charNum, 1, buf); +} + +int hex_str_to_int(const char *hex_string, int hex_string_length) +{ + const int base = 16; + int character_as_integer, integer_output = 0; + + for (int i=0;i= '0' && hex_string[i] <= '9') { + character_as_integer = hex_string[i] - '0'; + } else if (hex_string[i] >= 'A' && hex_string[i] <= 'F') { + character_as_integer = hex_string[i] - 'A' + 10; + } else { + character_as_integer = hex_string[i] - 'a' + 10; + } + integer_output *= base; + integer_output += character_as_integer; + } + + return integer_output; +} + +int hex_str_to_char_str(const char* str, uint16_t len, char *buf) +{ + int strcount = 0; + for (int i = 0; i+1 < len; i += 2) { + int upper = hex_str_to_int(str+i, 1); + int lower = hex_str_to_int(str+i+1, 1); + buf[strcount] = ((upper<<4) & 0xF0) | (lower & 0x0F); + strcount++; + } + + return strcount; +} + +void uint_to_binary_str(uint32_t num, char* str, int str_size, int bit_cnt) +{ + if (!str || str_size < bit_cnt) { + return; + } + int tmp, pos = 0; + + for (int i = 31; i >= 0; i--) { + tmp = num >> i; + if (i < bit_cnt) { + if (tmp&1) { + str[pos] = 1 + '0'; + } else { + str[pos] = 0 + '0'; + } + pos++; + } + } +} + +int char_str_to_hex_str(const char* str, uint16_t len, char *buf, bool omit_leading_zero) +{ + if (!str || !buf) { + return 0; + } + + char *ptr = buf; + int i=0; + while (i < len) { + if (omit_leading_zero == true && i == 0 && !(str[i]>>4 & 0x0F)) { + *ptr++ = hex_values[(str[i]) & 0x0F]; + } else { + *ptr++ = hex_values[((str[i])>>4) & 0x0F]; + *ptr++ = hex_values[(str[i]) & 0x0F]; + } + i++; + } + return ptr-buf; +} + +uint16_t get_dynamic_ip_port() +{ + static uint16_t port; + port++; + if (port < 49152) { + port = 49152; + } + return port; +} + +} // namespace mbed_cellular_util diff --git a/features/cellular/framework/common/CellularUtil.h b/features/cellular/framework/common/CellularUtil.h new file mode 100644 index 0000000000..296557435d --- /dev/null +++ b/features/cellular/framework/common/CellularUtil.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_UTIL_H_ +#define CELLULAR_UTIL_H_ + +#include +#include + +namespace mbed_cellular_util { + +// some helper macros +#define EMPTY_CHECK(val) (val ## 1) +#define EMPTY(val) (EMPTY_CHECK(val) == 1) +#define _CELLULAR_STRINGIFY(a) #a +#define CELLULAR_STRINGIFY(a) _CELLULAR_STRINGIFY(a) + +static const char hex_values[] = "0123456789ABCDEF"; + +/** Converts the given ip address to proper IPv6 address if needed. + * Conversion is done only if it's NOT IPv4 and separated with colons. + * AT command +CGPADDR can give IP address in format of a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11.a12.a13.a14.a15.a16 for IPv6 + * where ax are in decimal format. In this case function converts decimals to hex with separated with colons. + * + * @param ip ip address which can be IPv4 or IPv6 in different formats from AT command +CGPADDR. Converted result uses same buffer. + */ +void convert_ipv6(char* ip); + +/** Separates ip addresses from the given 'orig' string. 'orig' may contain zero, one or two ip addresses in various formats. + * See AT command +CGPIAF from 3GPP TS 27.007 for details. Does also needed conversions for ipv6 addresses. + * + * @param orig original string which contains zero, one or two ip addressees in various formats + * @param ip preallocated buffer which might contain ip address after return + * @param ip_size size of preallocated buffer ip + * @param ip2 preallocated buffer which might contain ip address after return + * @param ip2_size size of preallocated buffer ip2 + * + */ +void separate_ip_addresses(char* orig, char* ip, size_t ip_size, char* ip2, size_t ip2_size); + +/** Swaps the arrays if param ip does not contain ipv6 address but param ip2 does. + * + * @param ip ip address + * @param ip_size size of buffer ip + * @param ip2 ip address + * @param ip2_size size of buffer ip2 + */ +void prefer_ipv6(char* ip, size_t ip_size, char* ip2, size_t ip2_size); + +/** Converts the given int to two hex characters + * + * @param num number to be converted to hex string + * @param buf preallocated buffer which will contain 2 char length hex value + */ +void int_to_hex_str(uint8_t num, char* buf); + +/** Converts the given buffer 'str' to hex buffer 'buf. First 'len' char's are converted to two hex bytes. + * + * @param str char buffer which is to be converted to hex + * @param len how many chars from str are to be converted + * @param buf destination buffer for hex converted chars. Buffer should be double the size of str to fit hex-encoded string. + * @param omit_leading_zero if true then possible leading zeroes are omitted + */ +int char_str_to_hex_str(const char* str, uint16_t len, char *buf, bool omit_leading_zero = false); + +/** Converts the given hex string to integer + * + * @param hex_string hex string from where chars are converted to int + * @param hex_string_length length of the param hex_string + * @return hex_string converted to int + */ +int hex_str_to_int(const char *hex_string, int hex_string_length); + +/** Converts the given hex string str to char string to buf + * + * @param str hex string which is converted to char string to buf + * @param len length of the param str/how many hex are converted + * @param buf preallocated buffer where result conversion is stored + * @return length of the buf + */ +int hex_str_to_char_str(const char* str, uint16_t len, char *buf); + +/** Converts the given uint to binary string. Fills the given str starting from [0] with the number of bits defined by bit_cnt + * For example uint_to_binary_string(9, str, 10) would fill str "0000001001" + * For example uint_to_binary_string(9, str, 3) would fill str "001" + * + * @param num uint to converts to binary string + * @param str buffer for converted binary string + * @param str_size size of the str buffer + * @param bit_cnt defines how many bits are filled to buffer started from lsb + */ +void uint_to_binary_str(uint32_t num, char* str, int str_size, int bit_cnt); + +/** Get dynamic port for socket + * + * @return next port number above 49152 + */ +uint16_t get_dynamic_ip_port(); + +} // namespace mbed_cellular_util + +#endif diff --git a/features/cellular/framework/mux/mbed_mux.cpp b/features/cellular/framework/mux/mbed_mux.cpp new file mode 100644 index 0000000000..08b40849ec --- /dev/null +++ b/features/cellular/framework/mux/mbed_mux.cpp @@ -0,0 +1,1237 @@ +/* + * Copyright (c) , Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "mbed_mux.h" +#include "mbed_retarget.h" + +using namespace rtos; +using namespace events; + +namespace mbed +{ + +#define FLAG_SEQUENCE_OCTET 0xF9u /* Flag field used in the basic option mode. */ +#define ADDRESS_MUX_START_REQ_OCTET 0x03u /* Address field of the start multiplexer request frame. */ +#define ADDRESS_MUX_START_RESP_OCTET 0x03u /* Address field value of the start multiplexer response frame. */ + +#define FCS_INPUT_LEN 3u /* Length of the input for FCS calculation in number of bytes. */ +#define SABM_FRAME_LEN 6u /* Length of the SABM frame in number of bytes. */ +#define UA_FRAME_LEN 6u /* Length of the UA frame in number of bytes. */ +#define DM_FRAME_LEN 6u /* Length of the DM frame in number of bytes. */ +#define UIH_FRAME_MIN_LEN 6u /* Minimum length of user frame. */ + +#define T1_TIMER_VALUE 300u /* T1 timer value. */ +#define RETRANSMIT_COUNT 3u /* Retransmission count for the tx frames requiring a response. */ + +#define FRAME_TYPE_SABM 0x2Fu /* SABM frame type coding in the frame control field. */ +#define FRAME_TYPE_UA 0x63u /* UA frame type coding in the frame control field. */ +#define FRAME_TYPE_DM 0x0Fu /* DM frame type coding in the frame control field. */ +#define FRAME_TYPE_DISC 0x43u /* DISC frame type coding in the frame control field. */ +#define FRAME_TYPE_UIH 0xEFu /* UIH frame type coding in the frame control field. */ + +#define PF_BIT (1u << 4) /* P/F bit position in the frame control field. */ +#define EA_BIT (1u << 0) /* E/A bit position in the frame address field. */ +#define CR_BIT (1u << 1) /* C/R bit position in the frame address field. */ + +#define DLCI_ID_LOWER_BOUND 1u /* Lower bound value of DLCI id.*/ +#define DLCI_ID_UPPER_BOUND 63u /* Upper bound value of DLCI id.*/ + +#define FRAME_FLAG_SEQUENCE_FIELD_INDEX 0 /* Index of the frame flag sequence field. */ +#define FRAME_ADDRESS_FIELD_INDEX 1u /* Index of the frame address field. */ +#define FRAME_CONTROL_FIELD_INDEX 2u /* Index of the frame control field. */ +#define FRAME_LENGTH_FIELD_INDEX 3u /* Index of the frame length indicator field. */ +#define FRAME_INFORMATION_FIELD_INDEX 4u /* Index of the frame information field. */ + +#define FRAME_START_READ_LEN 1u /* Frame start read length in number of bytes. */ +#define FRAME_HEADER_READ_LEN 3u /* Frame header read length in number of bytes. */ +#define FRAME_TRAILER_LEN 2u /* Frame trailer read length in number of bytes. */ + +#define LENGTH_INDICATOR_OCTET 1u /* Length indicator field value used in frame. */ + +/* Definition for frame header type. */ +struct frame_hdr_t { + uint8_t flag_seq; /* Flag sequence field. */ + uint8_t address; /* Address field. */ + uint8_t control; /* Control field. */ + uint8_t length; /* Length field. */ + uint8_t information[1]; /* Begin of the information field if present. */ +}; + +uint8_t Mux::_shared_memory = 0; +FileHandle* Mux::_serial = NULL; +EventQueue* Mux::_event_q = NULL; + +MuxDataService Mux::_mux_objects[MBED_CONF_MUX_DLCI_COUNT]; +Semaphore Mux::_semaphore(0); +PlatformMutex Mux::_mutex; +Mux::tx_context_t Mux::_tx_context; +Mux::rx_context_t Mux::_rx_context; +Mux::state_t Mux::_state; +const uint8_t Mux::_crctable[MUX_CRC_TABLE_LEN] = { + 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, + 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, + 0x38, 0xA9, 0xDB, 0x4A, 0x3F, 0xAE, 0xDC, 0x4D, 0x36, 0xA7, 0xD5, 0x44, 0x31, 0xA0, 0xD2, 0x43, + 0x24, 0xB5, 0xC7, 0x56, 0x23, 0xB2, 0xC0, 0x51, 0x2A, 0xBB, 0xC9, 0x58, 0x2D, 0xBC, 0xCE, 0x5F, + + 0x70, 0xE1, 0x93, 0x02, 0x77, 0xE6, 0x94, 0x05, 0x7E, 0xEF, 0x9D, 0x0C, 0x79, 0xE8, 0x9A, 0x0B, + 0x6C, 0xFD, 0x8F, 0x1E, 0x6B, 0xFA, 0x88, 0x19, 0x62, 0xF3, 0x81, 0x10, 0x65, 0xF4, 0x86, 0x17, + 0x48, 0xD9, 0xAB, 0x3A, 0x4F, 0xDE, 0xAC, 0x3D, 0x46, 0xD7, 0xA5, 0x34, 0x41, 0xD0, 0xA2, 0x33, + 0x54, 0xC5, 0xB7, 0x26, 0x53, 0xC2, 0xB0, 0x21, 0x5A, 0xCB, 0xB9, 0x28, 0x5D, 0xCC, 0xBE, 0x2F, + + 0xE0, 0x71, 0x03, 0x92, 0xE7, 0x76, 0x04, 0x95, 0xEE, 0x7F, 0x0D, 0x9C, 0xE9, 0x78, 0x0A, 0x9B, + 0xFC, 0x6D, 0x1F, 0x8E, 0xFB, 0x6A, 0x18, 0x89, 0xF2, 0x63, 0x11, 0x80, 0xF5, 0x64, 0x16, 0x87, + 0xD8, 0x49, 0x3B, 0xAA, 0xDF, 0x4E, 0x3C, 0xAD, 0xD6, 0x47, 0x35, 0xA4, 0xD1, 0x40, 0x32, 0xA3, + 0xC4, 0x55, 0x27, 0xB6, 0xC3, 0x52, 0x20, 0xB1, 0xCA, 0x5B, 0x29, 0xB8, 0xCD, 0x5C, 0x2E, 0xBF, + + 0x90, 0x01, 0x73, 0xE2, 0x97, 0x06, 0x74, 0xE5, 0x9E, 0x0F, 0x7D, 0xEC, 0x99, 0x08, 0x7A, 0xEB, + 0x8C, 0x1D, 0x6F, 0xFE, 0x8B, 0x1A, 0x68, 0xF9, 0x82, 0x13, 0x61, 0xF0, 0x85, 0x14, 0x66, 0xF7, + 0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, + 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF +}; + +void Mux::module_init() +{ + _state.is_mux_open = 0; + _state.is_mux_open_pending = 0; + _state.is_mux_open_running = 0; + _state.is_dlci_open_pending = 0; + _state.is_dlci_open_running = 0; + _state.is_system_thread_context = 0; + _state.is_tx_callback_context = 0; + _state.is_user_tx_pending = 0; + _state.is_user_rx_ready = 0; + + _rx_context.offset = 0; + _rx_context.read_length = 0; + _rx_context.rx_state = RX_FRAME_START; + + _tx_context.tx_state = TX_IDLE; + _tx_context.tx_callback_context = 0; + + frame_hdr_t* frame_hdr = + reinterpret_cast(&(Mux::_tx_context.buffer[FRAME_FLAG_SEQUENCE_FIELD_INDEX])); + frame_hdr->flag_seq = FLAG_SEQUENCE_OCTET; + + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + for (uint8_t i = 0; i != end; ++i) { + _mux_objects[i]._dlci = MUX_DLCI_INVALID_ID; + } +} + + +void Mux::frame_retransmit_begin() +{ + _tx_context.bytes_remaining = _tx_context.offset; + _tx_context.offset = 0; +} + + +void Mux::on_timeout() +{ + _mutex.lock(); + + _state.is_system_thread_context = 1u; + + switch (_tx_context.tx_state) { + case TX_RETRANSMIT_DONE: + if (_tx_context.retransmit_counter != 0) { + --(_tx_context.retransmit_counter); + frame_retransmit_begin(); + tx_state_change(TX_RETRANSMIT_ENQUEUE, tx_retransmit_enqueu_entry_run, null_action); + } else { + /* Retransmission limit reached, change state and release the suspended call thread with appropriate + status code. */ + _shared_memory = MUX_ESTABLISH_TIMEOUT; + const osStatus os_status = _semaphore.release(); + MBED_ASSERT(os_status == osOK); + tx_state_change(TX_IDLE, tx_idle_entry_run, null_action); + } + break; + default: + /* No implementation required. */ + break; + } + + _state.is_system_thread_context = 0; + + _mutex.unlock(); +} + + +void Mux::dm_response_construct() +{ + frame_hdr_t* frame_hdr = + reinterpret_cast(&(Mux::_tx_context.buffer[FRAME_FLAG_SEQUENCE_FIELD_INDEX])); + + frame_hdr->address = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX]; + frame_hdr->control = (FRAME_TYPE_DM | PF_BIT); + frame_hdr->length = LENGTH_INDICATOR_OCTET; + frame_hdr->information[0] = fcs_calculate(&(Mux::_tx_context.buffer[FRAME_ADDRESS_FIELD_INDEX]), FCS_INPUT_LEN); + (++frame_hdr)->flag_seq = FLAG_SEQUENCE_OCTET; + + _tx_context.bytes_remaining = DM_FRAME_LEN; +} + + +void Mux::on_rx_frame_sabm() +{ + /* Peer iniated open/establishment is not supported. */ + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +void Mux::on_rx_frame_ua() +{ + switch (_tx_context.tx_state) { + osStatus os_status; + uint8_t rx_dlci_id; + uint8_t tx_dlci_id; + bool is_cr_bit_set; + bool is_pf_bit_set; + case TX_RETRANSMIT_DONE: + is_cr_bit_set = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] & CR_BIT; + is_pf_bit_set = _rx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & PF_BIT; + + if (is_cr_bit_set && is_pf_bit_set) { + tx_dlci_id = _tx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + rx_dlci_id = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + if (tx_dlci_id == rx_dlci_id) { + _event_q->cancel(_tx_context.timer_id); + _shared_memory = MUX_ESTABLISH_SUCCESS; + if (rx_dlci_id != 0) { + dlci_id_append(rx_dlci_id); + } + + os_status = _semaphore.release(); + MBED_ASSERT(os_status == osOK); + tx_state_change(TX_IDLE, tx_idle_entry_run, null_action); + } + } + break; + default: + /* No implementation required. */ + break; + } + + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +void Mux::on_rx_frame_dm() +{ + switch (_tx_context.tx_state) { + osStatus os_status; + uint8_t rx_dlci_id; + uint8_t tx_dlci_id; + bool is_cr_bit_set; + bool is_pf_bit_set; + case TX_RETRANSMIT_DONE: + is_cr_bit_set = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] & CR_BIT; + is_pf_bit_set = _rx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & PF_BIT; + + if (is_cr_bit_set && is_pf_bit_set) { + tx_dlci_id = _tx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + rx_dlci_id = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + if (tx_dlci_id == rx_dlci_id) { + _event_q->cancel(_tx_context.timer_id); + _shared_memory = MUX_ESTABLISH_REJECT; + os_status = _semaphore.release(); + MBED_ASSERT(os_status == osOK); + tx_state_change(TX_IDLE, tx_idle_entry_run, null_action); + } + } + break; + default: + /* No implementation required. */ + break; + } + + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +void Mux::tx_internal_resp_entry_run() +{ + write_do(); +} + + +void Mux::dm_response_send() +{ + dm_response_construct(); + tx_state_change(TX_INTERNAL_RESP, tx_internal_resp_entry_run, tx_idle_exit_run); +} + + +void Mux::on_rx_frame_disc() +{ + /* Follow the specification: DM response generated for those DLCI IDs which are not established. */ + + switch (_tx_context.tx_state) { + uint8_t dlci_id; + bool is_cr_bit_clear; + bool is_pf_bit_set; + case TX_IDLE: + if (!_state.is_mux_open) { + dm_response_send(); + } else { + is_cr_bit_clear = !(_rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] & CR_BIT); + is_pf_bit_set = _rx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & PF_BIT; + + if (is_cr_bit_clear && is_pf_bit_set) { + dlci_id = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + if (dlci_id != 0) { + if (!is_dlci_in_use(dlci_id)) { + dm_response_send(); + } else { + /* DLCI close not supported and silently discarded. */ + } + } else { + /* Mux close not supported and silently discarded. */ + } + } + } + break; + default: + /* @note: Silently discarded as the current implementation does not support pending DM frame generation. */ + break; + } + + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +void Mux::on_rx_frame_uih() +{ + const uint8_t length = (_rx_context.buffer[FRAME_LENGTH_FIELD_INDEX] >> 1); + if (length != 0) { + /* Proceed with processing for non 0 length user data frames. */ + + const bool is_cr_bit_clear = !(_rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] & CR_BIT); + const bool is_pf_bit_clear = !(_rx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & PF_BIT); + if (is_cr_bit_clear && is_pf_bit_clear) { + /* Proceed with processing upon P/F and C/R bits being clear. */ + + const uint8_t dlci_id = _rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX] >> 2; + if (dlci_id != MUX_DLCI_INVALID_ID) { + /* Proceed with processing for non internal invalidate ID type. */ + + MuxDataService* obj = file_handle_get(dlci_id); + if (obj != NULL) { + /* Established DLCI exists, proceed with processing. */ + + _state.is_user_rx_ready = 1u; + _rx_context.offset = 0; + _rx_context.read_length = length; + + rx_state_change(RX_SUSPEND, null_action); + obj->_sigio_cb(); + + return; + } + } + } + } + + /* Default behaviour upon Rx of non valid user data frame. */ + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +void Mux::on_rx_frame_not_supported() +{ + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); +} + + +Mux::FrameRxType Mux::frame_rx_type_resolve() +{ + const uint8_t frame_type = (_rx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & ~PF_BIT); + + if (frame_type == FRAME_TYPE_SABM) { + return FRAME_RX_TYPE_SABM; + } else if (frame_type == FRAME_TYPE_UA) { + return FRAME_RX_TYPE_UA; + } else if (frame_type == FRAME_TYPE_DM) { + return FRAME_RX_TYPE_DM; + } else if (frame_type == FRAME_TYPE_DISC) { + return FRAME_RX_TYPE_DISC; + } else if (frame_type == FRAME_TYPE_UIH) { + return FRAME_RX_TYPE_UIH; + } else { + return FRAME_RX_TYPE_NOT_SUPPORTED; + } +} + + +void Mux::tx_state_change(TxState new_state, tx_state_entry_func_t entry_func, tx_state_exit_func_t exit_func) +{ + exit_func(); + _tx_context.tx_state = new_state; + entry_func(); +} + + +void Mux::event_queue_enqueue() +{ + const int id = _event_q->call(Mux::on_deferred_call); + MBED_ASSERT(id != 0); +} + + +void Mux::tx_retransmit_done_entry_run() +{ + _tx_context.timer_id = _event_q->call_in(T1_TIMER_VALUE, Mux::on_timeout); + MBED_ASSERT(_tx_context.timer_id != 0); +} + + +bool Mux::is_dlci_in_use(uint8_t dlci_id) +{ + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + for (uint8_t i = 0; i != end; ++i) { + if (_mux_objects[i]._dlci== dlci_id) { + return true; + } + } + + return false; +} + + +void Mux::on_post_tx_frame_sabm() +{ + switch (_tx_context.tx_state) { + case TX_RETRANSMIT_ENQUEUE: + tx_state_change(TX_RETRANSMIT_DONE, tx_retransmit_done_entry_run, null_action); + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + break; + } +} + + +void Mux::pending_self_iniated_mux_open_start() +{ + /* Construct the frame, start the tx sequence, set and reset relevant state contexts. */ + _state.is_mux_open_running = 1u; + _state.is_mux_open_pending = 0; + + sabm_request_construct(0); + tx_state_change(TX_RETRANSMIT_ENQUEUE, tx_retransmit_enqueu_entry_run, tx_idle_exit_run); + _tx_context.retransmit_counter = RETRANSMIT_COUNT; +} + + +void Mux::pending_self_iniated_dlci_open_start() +{ + /* Construct the frame, start the tx sequence, set and reset relevant state contexts. */ + _state.is_dlci_open_running = 1u; + _state.is_dlci_open_pending = 0; + + sabm_request_construct(_shared_memory); + tx_state_change(TX_RETRANSMIT_ENQUEUE, tx_retransmit_enqueu_entry_run, tx_idle_exit_run); + _tx_context.retransmit_counter = RETRANSMIT_COUNT; +} + + +void Mux::tx_idle_exit_run() +{ + _tx_context.offset = 0; +} + + +uint8_t Mux::tx_callback_index_advance() +{ + /* Increment and get the index bit accounting the roll over. */ + uint8_t index = ((_tx_context.tx_callback_context & 0xF0u) >> 4); + index <<= 1; + if ((index & 0x0Fu) == 0) { + index = 1u; + } + + /* Clear existing index bit and assign the new incremented index bit. */ + _tx_context.tx_callback_context &= 0x0Fu; + _tx_context.tx_callback_context |= (index << 4); + + return index; +} + + +uint8_t Mux::tx_callback_pending_mask_get() +{ + return (_tx_context.tx_callback_context & 0x0Fu); +} + + +void Mux::tx_callback_pending_bit_clear(uint8_t bit) +{ + _tx_context.tx_callback_context &= ~bit; +} + + +void Mux::tx_callback_pending_bit_set(uint8_t dlci_id) +{ + uint8_t i = 0; + uint8_t bit = 1u; + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + + do { + if (_mux_objects[i]._dlci== dlci_id) { + break; + } + + ++i; + bit <<= 1; + } while (i != end); + + MBED_ASSERT(i != end); + + _tx_context.tx_callback_context |= bit; +} + + +MuxDataService& Mux::tx_callback_lookup(uint8_t bit) +{ + uint8_t i = 0; + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + + do { + bit >>= 1; + if (bit == 0) { + break; + } + + ++i; + } while (i != end); + + MBED_ASSERT(i != end); + + return _mux_objects[i]; +} + + +void Mux::tx_callback_dispatch(uint8_t bit) +{ + MuxDataService& obj = tx_callback_lookup(bit); + obj._sigio_cb(); +} + + +void Mux::tx_callbacks_run() +{ + uint8_t current_tx_index; + uint8_t tx_callback_pending_mask = tx_callback_pending_mask_get(); + + while (tx_callback_pending_mask != 0) { + /* Locate 1st pending TX callback. */ + do { + current_tx_index = tx_callback_index_advance(); + } while ((current_tx_index & tx_callback_pending_mask) == 0); + + /* Clear pending bit and dispatch TX callback. */ + tx_callback_pending_bit_clear(current_tx_index); + + tx_callback_dispatch(current_tx_index); + + /* No valid use case exists for TX cycle activation within TX callback as per design user TX is started within + this while loop using @ref is_user_tx_pending bit and per system design DLCI establishment is not allowed + within callback context as this would leave to system thread context dead lock as DLCI establishment includes + semaphore wait. We will enforce this by MBED_ASSERT below. Note that @ref dlci_establish will have + MBED_ASSERT to enforce calling that API from system thread context thus the assert below is not absolutely + necessary. */ + MBED_ASSERT(_tx_context.tx_state == TX_IDLE); + + if (_state.is_user_tx_pending) { + /* User TX was requested within callback context dispatched above. */ + + /* TX buffer was constructed within @ref user_data_tx, now start the TX cycle. */ + _state.is_user_tx_pending = 0; + tx_state_change(TX_NORETRANSMIT, tx_noretransmit_entry_run, tx_idle_exit_run); + if (_tx_context.tx_state != TX_IDLE) { + /* TX cycle not finished within current context, stop callback processing as we will continue when TX + cycle finishes and this function is re-entered. */ + break; + } + } + tx_callback_pending_mask = tx_callback_pending_mask_get(); + } +} + + +void Mux::tx_idle_entry_run() +{ + if (_state.is_mux_open_pending) { + pending_self_iniated_mux_open_start(); + } else if (_state.is_dlci_open_pending) { + pending_self_iniated_dlci_open_start(); + } else { + /* TX callback processing block below could be entered recursively within same thread context. Protection bit + is used to prevent that. */ + if (!_state.is_tx_callback_context) { + /* Lock this code block from multiple entries within same thread context by using a protection bit. Check + and process possible pending user TX callback request as long there is something pending. Round robin + shceduling used for dispatching pending TX callback in order to prevent starvation of pending callbacks. + */ + _state.is_tx_callback_context = 1u; + tx_callbacks_run(); + _state.is_tx_callback_context = 0; + } + } +} + + +void Mux::on_post_tx_frame_dm() +{ + switch (_tx_context.tx_state) { + case TX_INTERNAL_RESP: + tx_state_change(TX_IDLE, tx_idle_entry_run, null_action); + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + break; + } +} + + +void Mux::on_post_tx_frame_uih() +{ + switch (_tx_context.tx_state) { + case TX_NORETRANSMIT: + tx_state_change(TX_IDLE, tx_idle_entry_run, null_action); + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + break; + } +} + + +Mux::FrameTxType Mux::frame_tx_type_resolve() +{ + const uint8_t frame_type = (_tx_context.buffer[FRAME_CONTROL_FIELD_INDEX] & ~PF_BIT); + + if (frame_type == FRAME_TYPE_SABM) { + return FRAME_TX_TYPE_SABM; + } else if (frame_type == FRAME_TYPE_DM) { + return FRAME_TX_TYPE_DM; + } else if (frame_type == FRAME_TYPE_UIH) { + return FRAME_TX_TYPE_UIH; + } else { + MBED_ASSERT(false); + return FRAME_TX_TYPE_MAX; + } +} + + +void Mux::null_action() +{ + +} + + +void Mux::rx_header_read_entry_run() +{ + _rx_context.offset = FRAME_ADDRESS_FIELD_INDEX; + _rx_context.read_length = FRAME_HEADER_READ_LEN; +} + + +void Mux::rx_state_change(RxState new_state, rx_state_entry_func_t entry_func) +{ + entry_func(); + _rx_context.rx_state = new_state; +} + + +ssize_t Mux::on_rx_read_state_frame_start() +{ + ssize_t read_err; + _rx_context.buffer[_rx_context.offset] = static_cast(~FLAG_SEQUENCE_OCTET); + do { + read_err = _serial->read(&(_rx_context.buffer[_rx_context.offset]), FRAME_START_READ_LEN); + } while ((_rx_context.buffer[_rx_context.offset] != FLAG_SEQUENCE_OCTET) && (read_err != -EAGAIN)); + + if (_rx_context.buffer[_rx_context.offset] == FLAG_SEQUENCE_OCTET) { + _rx_context.offset += FRAME_START_READ_LEN; + _rx_context.read_length = FRAME_HEADER_READ_LEN; + + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); + } + + return read_err; +} + + +ssize_t Mux::on_rx_read_state_header_read() +{ + ssize_t read_err; + + do { + read_err = _serial->read(&(_rx_context.buffer[_rx_context.offset]), _rx_context.read_length); + if (read_err != -EAGAIN) { + if ((_rx_context.offset == FRAME_ADDRESS_FIELD_INDEX) && + (_rx_context.buffer[_rx_context.offset] == FLAG_SEQUENCE_OCTET)) { + /* Overlapping block move 1-index possible trailing data after the flag sequence octet. */ + + memmove(&(_rx_context.buffer[_rx_context.offset]), + &(_rx_context.buffer[_rx_context.offset + 1u]), + (read_err - 1u)); + + _rx_context.offset += (read_err - 1u); + _rx_context.read_length -= (read_err - 1u); + } else { + _rx_context.offset += read_err; + _rx_context.read_length -= read_err; + } + } + } while ((_rx_context.read_length != 0) && (read_err != -EAGAIN)); + + if (_rx_context.offset == (FRAME_HEADER_READ_LEN + FRAME_START_READ_LEN)) { + /* Decode remaining frame read length and change state. Current implementation supports only 1-byte length + field, enforce this with MBED_ASSERT along with buffer over write. */ + + MBED_ASSERT((_rx_context.buffer[FRAME_LENGTH_FIELD_INDEX] & LENGTH_INDICATOR_OCTET)); + _rx_context.read_length = (_rx_context.buffer[FRAME_LENGTH_FIELD_INDEX] >> 1) + FRAME_TRAILER_LEN; + MBED_ASSERT(_rx_context.read_length <= + (MBED_CONF_MUX_BUFFER_SIZE - (FRAME_START_READ_LEN + FRAME_HEADER_READ_LEN))); + + rx_state_change(RX_TRAILER_READ, null_action); + } + + return read_err; +} + + +bool Mux::is_rx_fcs_valid() +{ + const uint8_t expected_fcs = fcs_calculate(&(_rx_context.buffer[FRAME_ADDRESS_FIELD_INDEX]), FCS_INPUT_LEN); + const uint8_t actual_fcs = + _rx_context.buffer[FRAME_INFORMATION_FIELD_INDEX + (_rx_context.buffer[FRAME_LENGTH_FIELD_INDEX] >> 1)]; + + return (expected_fcs == actual_fcs); +} + + +ssize_t Mux::on_rx_read_state_trailer_read() +{ + typedef void (*rx_frame_decoder_func_t)(); + static const rx_frame_decoder_func_t rx_frame_decoder_func[FRAME_RX_TYPE_MAX] = { + on_rx_frame_sabm, + on_rx_frame_ua, + on_rx_frame_dm, + on_rx_frame_disc, + on_rx_frame_uih, + on_rx_frame_not_supported + }; + + ssize_t read_err; + do { + read_err = _serial->read(&(_rx_context.buffer[_rx_context.offset]), _rx_context.read_length); + if (read_err != -EAGAIN) { + _rx_context.offset += read_err; + _rx_context.read_length -= read_err; + } + } while ((_rx_context.read_length != 0) && (read_err != -EAGAIN)); + + if (_rx_context.read_length == 0) { + /* Complete frame received, verify FCS and if valid proceed with processing. */ + + if (is_rx_fcs_valid()) { + const Mux::FrameRxType frame_type = frame_rx_type_resolve(); + const rx_frame_decoder_func_t func = rx_frame_decoder_func[frame_type]; + + func(); + } else { + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); + } + } + + return read_err; +} + + +ssize_t Mux::on_rx_read_state_suspend() +{ + return -EAGAIN; +} + + +void Mux::rx_event_do(RxEvent event) +{ + typedef ssize_t (*rx_read_func_t)(); + static const rx_read_func_t rx_read_func[RX_STATE_MAX] = { + on_rx_read_state_frame_start, + on_rx_read_state_header_read, + on_rx_read_state_trailer_read, + on_rx_read_state_suspend, + }; + + switch (event) { + ssize_t read_err; + rx_read_func_t func; + case RX_READ: + do { + func = rx_read_func[_rx_context.rx_state]; + read_err = func(); + } while (read_err != -EAGAIN); + + break; + case RX_RESUME: + MBED_ASSERT(_rx_context.rx_state == RX_SUSPEND); + + rx_state_change(RX_HEADER_READ, rx_header_read_entry_run); + event_queue_enqueue(); + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + break; + } +} + + +void Mux::write_do() +{ + switch (_tx_context.tx_state) { + ssize_t write_err; + case TX_NORETRANSMIT: + case TX_RETRANSMIT_ENQUEUE: + case TX_INTERNAL_RESP: + write_err = 1; + do { + write_err = _serial->write(&(_tx_context.buffer[_tx_context.offset]), _tx_context.bytes_remaining); + MBED_ASSERT(write_err >= 0); + + _tx_context.bytes_remaining -= write_err; + _tx_context.offset += write_err; + } while ((_tx_context.bytes_remaining != 0) && (write_err > 0)); + + if (_tx_context.bytes_remaining == 0) { + /* Frame write complete, execute correct post processing function for clean-up. */ + + typedef void (*post_tx_frame_func_t)(); + static const post_tx_frame_func_t post_tx_func[FRAME_TX_TYPE_MAX] = { + on_post_tx_frame_sabm, + on_post_tx_frame_dm, + on_post_tx_frame_uih + }; + const Mux::FrameTxType frame_type = frame_tx_type_resolve(); + const post_tx_frame_func_t func = post_tx_func[frame_type]; + func(); + } + + break; + default: + /* No implementation required. */ + break; + } +} + + +void Mux::on_deferred_call() +{ + _mutex.lock(); + + _state.is_system_thread_context = 1u; + + rx_event_do(RX_READ); + write_do(); + + _state.is_system_thread_context = 0; + + _mutex.unlock(); +} + + +void Mux::on_sigio() +{ + event_queue_enqueue(); +} + + +void Mux::eventqueue_attach(EventQueue *event_queue) +{ + _event_q = event_queue; +} + + +void Mux::serial_attach(FileHandle *serial) +{ + _serial = serial; + + _serial->sigio(Mux::on_sigio); + _serial->set_blocking(false); +} + + +uint8_t Mux::fcs_calculate(const uint8_t *buffer, uint8_t input_len) +{ + uint8_t fcs = 0xFFu; + + while (input_len-- != 0) { + fcs = _crctable[fcs^*buffer++]; + } + + /* Ones complement. */ + fcs = 0xFFu - fcs; + + return fcs; +} + + +void Mux::sabm_request_construct(uint8_t dlci_id) +{ + frame_hdr_t *frame_hdr = + reinterpret_cast(&(Mux::_tx_context.buffer[FRAME_FLAG_SEQUENCE_FIELD_INDEX])); + + frame_hdr->address = EA_BIT | CR_BIT | (dlci_id << 2); + frame_hdr->control = (FRAME_TYPE_SABM | PF_BIT); + frame_hdr->length = LENGTH_INDICATOR_OCTET; + frame_hdr->information[0] = fcs_calculate(&(Mux::_tx_context.buffer[FRAME_ADDRESS_FIELD_INDEX]), FCS_INPUT_LEN); + (++frame_hdr)->flag_seq = FLAG_SEQUENCE_OCTET; + + _tx_context.bytes_remaining = SABM_FRAME_LEN; +} + + +bool Mux::is_dlci_q_full() +{ + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + for (uint8_t i = 0; i != end; ++i) { + if (_mux_objects[i]._dlci== MUX_DLCI_INVALID_ID) { + return false; + } + } + + return true; +} + + +void Mux::dlci_id_append(uint8_t dlci_id) +{ + uint8_t i = 0; + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + do { + if (_mux_objects[i]._dlci== MUX_DLCI_INVALID_ID) { + _mux_objects[i]._dlci= dlci_id; + + break; + } + + ++i; + } while (i != end); + + /* Internal implementation error if append was not done as Q size is checked prior call. */ + MBED_ASSERT(i != end); +} + + +MuxDataService * Mux::file_handle_get(uint8_t dlci_id) +{ + MuxDataService* obj = NULL; + const uint8_t end = sizeof(_mux_objects) / sizeof(_mux_objects[0]); + for (uint8_t i = 0; i != end; ++i) { + if (_mux_objects[i]._dlci== dlci_id) { + obj = &(_mux_objects[i]); + + break; + } + } + + return obj; +} + + +Mux::MuxReturnStatus Mux::dlci_establish(uint8_t dlci_id, MuxEstablishStatus &status, FileHandle **obj) +{ + if ((dlci_id < DLCI_ID_LOWER_BOUND) || (dlci_id > DLCI_ID_UPPER_BOUND)) { + return MUX_STATUS_INVALID_RANGE; + } + + _mutex.lock(); + + MBED_ASSERT(!_state.is_system_thread_context); + + if (!_state.is_mux_open) { + _mutex.unlock(); + + return MUX_STATUS_MUX_NOT_OPEN; + } + if (is_dlci_q_full()) { + _mutex.unlock(); + + return MUX_STATUS_NO_RESOURCE; + } + if (is_dlci_in_use(dlci_id)) { + _mutex.unlock(); + + return MUX_STATUS_NO_RESOURCE; + } + if (_state.is_dlci_open_pending) { + _mutex.unlock(); + + return MUX_STATUS_INPROGRESS; + } + if (_state.is_dlci_open_running) { + _mutex.unlock(); + + return MUX_STATUS_INPROGRESS; + } + + switch (_tx_context.tx_state) { + int32_t ret_wait; + case TX_IDLE: + /* Construct the frame, start the tx sequence, and suspend the call thread upon write sequence success. */ + sabm_request_construct(dlci_id); + _tx_context.retransmit_counter = RETRANSMIT_COUNT; + tx_state_change(TX_RETRANSMIT_ENQUEUE, tx_retransmit_enqueu_entry_run, tx_idle_exit_run); + _state.is_dlci_open_running = 1u; + + _mutex.unlock(); + ret_wait = _semaphore.wait(); + MBED_ASSERT(ret_wait == 1); + status = static_cast(_shared_memory); + if (status == MUX_ESTABLISH_SUCCESS) { + *obj = file_handle_get(dlci_id); + MBED_ASSERT(*obj != NULL); + } + + break; + case TX_INTERNAL_RESP: + case TX_NORETRANSMIT: + _state.is_dlci_open_pending = 1u; + _shared_memory = dlci_id; + + _mutex.unlock(); + ret_wait = _semaphore.wait(); + MBED_ASSERT(ret_wait == 1); + status = static_cast(_shared_memory); + if (status == MUX_ESTABLISH_SUCCESS) { + *obj = file_handle_get(dlci_id); + MBED_ASSERT(*obj != NULL); + } + + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + + break; + } + + _state.is_dlci_open_running = 0; + + return MUX_STATUS_SUCCESS; +} + + +void Mux::tx_retransmit_enqueu_entry_run() +{ + write_do(); +} + + +Mux::MuxReturnStatus Mux::mux_start(Mux::MuxEstablishStatus &status) +{ + _mutex.lock(); + + if (_state.is_mux_open) { + _mutex.unlock(); + + return MUX_STATUS_NO_RESOURCE; + } + if (_state.is_mux_open_pending) { + _mutex.unlock(); + + return MUX_STATUS_INPROGRESS; + } + if (_state.is_mux_open_running ) { + _mutex.unlock(); + + return MUX_STATUS_INPROGRESS; + } + + switch (_tx_context.tx_state) { + int32_t ret_wait; + case TX_IDLE: + /* Construct the frame, start the tx sequence, and suspend the call thread upon write sequence success. */ + sabm_request_construct(0); + _tx_context.retransmit_counter = RETRANSMIT_COUNT; + tx_state_change(TX_RETRANSMIT_ENQUEUE, tx_retransmit_enqueu_entry_run, tx_idle_exit_run); + _state.is_mux_open_running = 1u; + + _mutex.unlock(); + ret_wait = _semaphore.wait(); + MBED_ASSERT(ret_wait == 1); + status = static_cast(_shared_memory); + if (status == MUX_ESTABLISH_SUCCESS) { + _state.is_mux_open = 1u; + } + + break; + case TX_INTERNAL_RESP: + _state.is_mux_open_pending = 1u; + + _mutex.unlock(); + ret_wait = _semaphore.wait(); + MBED_ASSERT(ret_wait == 1); + status = static_cast(_shared_memory); + if (status == MUX_ESTABLISH_SUCCESS) { + _state.is_mux_open = 1u; + } + + break; + default: + /* Code that should never be reached. */ + MBED_ASSERT(false); + break; + }; + + _state.is_mux_open_running = 0; + + return MUX_STATUS_SUCCESS; +} + + +void Mux::user_information_construct(uint8_t dlci_id, const void* buffer, size_t size) +{ + frame_hdr_t *frame_hdr = + reinterpret_cast(&(Mux::_tx_context.buffer[FRAME_FLAG_SEQUENCE_FIELD_INDEX])); + + frame_hdr->address = EA_BIT | CR_BIT | (dlci_id << 2); + frame_hdr->control = FRAME_TYPE_UIH; + frame_hdr->length = (LENGTH_INDICATOR_OCTET | (size << 1)); + + memmove(&(frame_hdr->information[0]), buffer, size); + + uint8_t* fcs_pos = (&(frame_hdr->information[0]) + size); + *fcs_pos = fcs_calculate(&(Mux::_tx_context.buffer[FRAME_ADDRESS_FIELD_INDEX]), FCS_INPUT_LEN); + *(++fcs_pos) = FLAG_SEQUENCE_OCTET; + + _tx_context.bytes_remaining = UIH_FRAME_MIN_LEN + size; +} + + +void Mux::tx_noretransmit_entry_run() +{ + write_do(); +} + + +ssize_t Mux::user_data_tx(uint8_t dlci_id, const void* buffer, size_t size) +{ + MBED_ASSERT(size <= + (MBED_CONF_MUX_BUFFER_SIZE - (FRAME_START_READ_LEN + FRAME_HEADER_READ_LEN + FRAME_TRAILER_LEN))); + MBED_ASSERT(buffer != NULL); + + if (size == 0) { + return 0; + } + + _mutex.lock(); + + ssize_t write_ret; + switch (_tx_context.tx_state) { + case TX_IDLE: + if (!_state.is_tx_callback_context) { + /* Proper state to start TX cycle within current context. */ + + user_information_construct(dlci_id, buffer, size); + tx_state_change(TX_NORETRANSMIT, tx_noretransmit_entry_run, tx_idle_exit_run); + + write_ret = size; + } else { + /* Current context is TX callback context. */ + + if (!_state.is_user_tx_pending) { + /* Signal callback context to start TX cycle and construct the frame. */ + + /* @note: If user is starting a TX to a DLCI, which is after the current DLCI TX callback within the + * stored sequence this will result to dispatching 1 unnecessary TX callback, if this is a issue one + * should clear the TX callback pending bit marker for this DLCI in this place. */ + + _state.is_user_tx_pending = 1u; + user_information_construct(dlci_id, buffer, size); + + write_ret = size; + } else { + /* TX cycle allready scheduled, set TX callback pending and inform caller by return value that no + action was taken. */ + + tx_callback_pending_bit_set(dlci_id); + write_ret = 0; + } + } + + break; + default: + /* TX allready in use, set TX callback pending and inform caller by return value that no action was taken. + */ + tx_callback_pending_bit_set(dlci_id); + write_ret = 0; + + break; + } + + _mutex.unlock(); + + return write_ret; +} + + +size_t Mux::min(uint8_t size_1, size_t size_2) +{ + return (size_1 < size_2) ? size_1 : size_2; +} + + +ssize_t Mux::user_data_rx(void* buffer, size_t size) +{ + MBED_ASSERT(buffer != NULL); + + _mutex.lock(); + + if (_state.is_user_rx_ready) { + const size_t read_length = min((_rx_context.read_length - _rx_context.offset), size); + memcpy(buffer, &(_rx_context.buffer[FRAME_INFORMATION_FIELD_INDEX + _rx_context.offset]), read_length); + _rx_context.offset += read_length; + + if (_rx_context.offset == _rx_context.read_length) { + /* Current Rx buffer consumed, disable user reading and resume the Rx path. */ + + _state.is_user_rx_ready = 0; + rx_event_do(RX_RESUME); + } + + _mutex.unlock(); + + return static_cast(read_length); + } else { + _mutex.unlock(); + + return -EAGAIN; + } +} + + +} // namespace mbed diff --git a/features/cellular/framework/mux/mbed_mux.h b/features/cellular/framework/mux/mbed_mux.h new file mode 100644 index 0000000000..2baa383903 --- /dev/null +++ b/features/cellular/framework/mux/mbed_mux.h @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2006-2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MUX_H +#define MUX_H + +#include +#include "FileHandle.h" +#include "PlatformMutex.h" +#include "Semaphore.h" +#include "EventQueue.h" + +#define MUX_DLCI_INVALID_ID 0 /* Invalid DLCI ID. Used to invalidate MuxDataService object. */ +#define MUX_CRC_TABLE_LEN 256u /* CRC table length in number of bytes. */ + +#ifndef MBED_CONF_MUX_DLCI_COUNT +#define MBED_CONF_MUX_DLCI_COUNT 3u /* Number of supported DLCI IDs. */ +#endif +#ifndef MBED_CONF_MUX_BUFFER_SIZE +#define MBED_CONF_MUX_BUFFER_SIZE 31u /* Size of TX/Rx buffers in number of bytes. */ +#endif + +/* More RAM needs to allocated if more than 4 DLCI ID to be supported see @ref tx_callback_context for details. */ +MBED_STATIC_ASSERT(MBED_CONF_MUX_DLCI_COUNT <= 4u, ""); + + +/* @todo: +I assume that we need to export some kind of #defines for EVENT_SIZE and MAX_EVENT_COUNT (max number of events that can +be queued at the same time by the module inside EventQueue, so that the application designer can calculate the RAM +storage requirements correctly at compile time. +*/ + +namespace mbed { + +class MuxDataService : public FileHandle +{ + friend class Mux; +public: + + /** Enqueue user data for transmission. + * + * @note: This is API is only meant to be used for the multiplexer (user) data service tx. Supplied buffer can be + * reused/freed upon call return. + * + * @param buffer Begin of the user data. + * @param size The number of bytes to write. + * @return The number of bytes written. + */ + virtual ssize_t write(const void* buffer, size_t size); + + /** Read user data into a buffer. + * + * @note: This is API is only meant to be used for the multiplexer (user) data service rx. + * + * @param buffer The buffer to read in to. + * @param size The number of bytes to read. + * @return The number of bytes read, -EAGAIN if no data available for read. + */ + virtual ssize_t read(void *buffer, size_t size); + + /** Not supported by the implementation. */ + virtual off_t seek(off_t offset, int whence = SEEK_SET); + + /** Not supported by the implementation. */ + virtual int close(); + + /** Register a callback on completion of enqueued write and read operations. + * + * @note: The registered callback is called within thread context supplied in eventqueue_attach. + * + * @param func Function to call upon event generation. + */ + virtual void sigio(Callback func); + + /** Constructor. */ + MuxDataService() : _dlci(MUX_DLCI_INVALID_ID) {}; + +private: + + /* Deny copy constructor. */ + MuxDataService(const MuxDataService& obj); + + /* Deny assignment operator. */ + MuxDataService& operator=(const MuxDataService& obj); + + uint8_t _dlci; /* DLCI number. Valid range 1 - 63. */ + Callback _sigio_cb; /* Registered signal callback. */ +}; + + +class Mux +{ + friend class MuxDataService; +public: + + /* Definition for multiplexer establishment status type. */ + enum MuxEstablishStatus { + MUX_ESTABLISH_SUCCESS = 0, /* Peer accepted the request. */ + MUX_ESTABLISH_REJECT, /* Peer rejected the request. */ + MUX_ESTABLISH_TIMEOUT, /* Timeout occurred for the request. */ + MUX_ESTABLISH_MAX /* Enumeration upper bound. */ + }; + + /* @ref MuxEstablishStatus type assigned to _shared_memory variable, which has sizeof(uint8_t) storage class. Enforce + expected behaviour compile time. */ + MBED_STATIC_ASSERT(sizeof(MuxEstablishStatus) == sizeof(uint8_t), ""); + + /* Definition for multiplexer establishment return code type. */ + enum MuxReturnStatus { + MUX_STATUS_SUCCESS = 0, + MUX_STATUS_INPROGRESS, + MUX_STATUS_INVALID_RANGE, + MUX_STATUS_MUX_NOT_OPEN, + MUX_STATUS_NO_RESOURCE, + MUX_STATUS_MAX + }; + + /** Module init. */ + static void module_init(); + + /** Establish the multiplexer control channel. + * + * @note: Relevant request specific parameters are fixed at compile time within multiplexer component. + * @note: Call returns when response from the peer is received, timeout or write error occurs. + * + * @param status Operation completion code. + * + * @return MUX_STATUS_SUCCESS Operation completed, check status for completion code. + * @return MUX_STATUS_INPROGRESS Operation not started, control channel open already in progress. + * @return MUX_STATUS_NO_RESOURCE Operation not started, multiplexer control channel already open. + */ + static MuxReturnStatus mux_start(MuxEstablishStatus &status); + + /** Establish a DLCI. + * + * @note: Relevant request specific parameters are fixed at compile time within multiplexer component. + * @note: Call returns when response from the peer is received or timeout occurs. + * + * @warning: Not allowed to be called from callback context. + * + * @param dlci_id ID of the DLCI to establish. Valid range 1 - 63. + * @param status Operation completion code. + * @param obj Valid object upon status having MUX_ESTABLISH_SUCCESS, NULL upon failure. + * + * @return MUX_STATUS_SUCCESS Operation completed, check status for completion code. + * @return MUX_STATUS_INPROGRESS Operation not started, DLCI establishment already in progress. + * @return MUX_STATUS_INVALID_RANGE Operation not started, DLCI ID not in valid range. + * @return MUX_STATUS_MUX_NOT_OPEN Operation not started, no established multiplexer control channel exists. + * @return MUX_STATUS_NO_RESOURCE Operation not started, dlci_id, or all available DLCI ID resources, + * already in use. + */ + static MuxReturnStatus dlci_establish(uint8_t dlci_id, MuxEstablishStatus &status, FileHandle **obj); + + /** Attach serial interface to the object. + * + * @param serial Serial interface to be used. + */ + static void serial_attach(FileHandle *serial); + + /** Attach EventQueue interface to the object. + * + * @param event_queue Event queue interface to be used. + */ + static void eventqueue_attach(events::EventQueue *event_queue); + +private: + + /* Definition for Rx event type. */ + enum RxEvent { + RX_READ = 0, + RX_RESUME, + RX_EVENT_MAX + }; + + /* Definition for Tx state machine. */ + enum TxState { + TX_IDLE = 0, + TX_RETRANSMIT_ENQUEUE, + TX_RETRANSMIT_DONE, + TX_INTERNAL_RESP, + TX_NORETRANSMIT, + TX_STATE_MAX + }; + + /* Definition for Rx state machine. */ + enum RxState { + RX_FRAME_START = 0, + RX_HEADER_READ, + RX_TRAILER_READ, + RX_SUSPEND, + RX_STATE_MAX + }; + + /* Definition for frame type within rx path. */ + enum FrameRxType { + FRAME_RX_TYPE_SABM = 0, + FRAME_RX_TYPE_UA, + FRAME_RX_TYPE_DM, + FRAME_RX_TYPE_DISC, + FRAME_RX_TYPE_UIH, + FRAME_RX_TYPE_NOT_SUPPORTED, + FRAME_RX_TYPE_MAX + }; + + /* Definition for frame type within tx path. */ + enum FrameTxType { + FRAME_TX_TYPE_SABM = 0, + FRAME_TX_TYPE_DM, + FRAME_TX_TYPE_UIH, + FRAME_TX_TYPE_MAX + }; + + /** Registered time-out expiration event. */ + static void on_timeout(); + + /** Registered deferred call event in safe (thread context) supplied in eventqueue_attach. */ + static void on_deferred_call(); + + /** Registered sigio callback from FileHandle. */ + static void on_sigio(); + + /** Calculate fcs. + * + * @param buffer Input buffer. + * @param input_len Input length in number of bytes. + * + * @return Calculated fcs. + */ + static uint8_t fcs_calculate(const uint8_t *buffer, uint8_t input_len); + + /** Construct sabm request message. + * + * @param dlci_id ID of the DLCI to establish + */ + static void sabm_request_construct(uint8_t dlci_id); + + /** Construct dm response message. + */ + static void dm_response_construct(); + + /** Construct user information frame. + * + * @param dlci_id ID of the DLCI to establish + * @param buffer + * @param size + */ + static void user_information_construct(uint8_t dlci_id, const void *buffer, size_t size); + + /** Do write operation if pending data available. + */ + static void write_do(); + + /** Generate Rx event. + * + * @param event Rx event + */ + static void rx_event_do(RxEvent event); + + /** Rx event frame start read state. + * + * @return Number of bytes read, -EAGAIN if no data available. + */ + static ssize_t on_rx_read_state_frame_start(); + + /** Rx event header read state. + * + * @return Number of bytes read, -EAGAIN if no data available. + */ + static ssize_t on_rx_read_state_header_read(); + + /** Rx event trailer read state. + * + * @return Number of bytes read, -EAGAIN if no data available. + */ + static ssize_t on_rx_read_state_trailer_read(); + + /** Rx event suspend read state. + * + * @return Number of bytes read, -EAGAIN if no data available. + */ + static ssize_t on_rx_read_state_suspend(); + + /** Process received SABM frame. */ + static void on_rx_frame_sabm(); + + /** Process received UA frame. */ + static void on_rx_frame_ua(); + + /** Process received DM frame. */ + static void on_rx_frame_dm(); + + /** Process received DISC frame. */ + static void on_rx_frame_disc(); + + /** Process received UIH frame. */ + static void on_rx_frame_uih(); + + /** Process received frame, which is not supported. */ + static void on_rx_frame_not_supported(); + + /** Process valid received frame. */ + static void valid_rx_frame_decode(); + + /** SABM frame tx path post processing. */ + static void on_post_tx_frame_sabm(); + + /** DM frame tx path post processing. */ + static void on_post_tx_frame_dm(); + + /** UIH frame tx path post processing. */ + static void on_post_tx_frame_uih(); + + /** Resolve rx frame type. + * + * @return Frame type. + */ + static Mux::FrameRxType frame_rx_type_resolve(); + + /** Resolve tx frame type. + * + * @return Frame type. + */ + static Mux::FrameTxType frame_tx_type_resolve(); + + /** Begin the frame retransmit sequence. */ + static void frame_retransmit_begin(); + + /** TX state entry functions. */ + static void tx_retransmit_enqueu_entry_run(); + static void tx_retransmit_done_entry_run(); + static void tx_idle_entry_run(); + static void tx_internal_resp_entry_run(); + static void tx_noretransmit_entry_run(); + typedef void (*tx_state_entry_func_t)(); + + /** TX state exit function. */ + static void tx_idle_exit_run(); + typedef void (*tx_state_exit_func_t)(); + + /** Change Tx state machine state. + * + * @param new_state State to transit. + * @param entry_func State entry function. + * @param exit_func State exit function. + */ + static void tx_state_change(TxState new_state, tx_state_entry_func_t entry_func, tx_state_exit_func_t exit_func); + + /** RX state entry functions. */ + static void rx_header_read_entry_run(); + typedef void (*rx_state_entry_func_t)(); + + /** Null action function. */ + static void null_action(); + + /** Change Rx state machine state. + * + * @param new_state State to transit. + * @param entry_func State entry function. + */ + static void rx_state_change(RxState new_state, rx_state_entry_func_t entry_func); + + /** Begin DM frame transmit sequence. */ + static void dm_response_send(); + + /** Append DLCI ID to storage. + * + * @param dlci_id ID of the DLCI to append. + */ + static void dlci_id_append(uint8_t dlci_id); + + /** Get file handle based on DLCI ID. + * + * @param dlci_id ID of the DLCI used as the key + * + * @return Valid object reference or NULL if not found. + */ + static MuxDataService* file_handle_get(uint8_t dlci_id); + + /** Evaluate is DLCI ID in use. + * + * @param dlci_id ID of the DLCI yo evaluate + * + * @return True if in use, false otherwise. + */ + static bool is_dlci_in_use(uint8_t dlci_id); + + /** Evaluate is DLCI ID queue full. + * + * @return True if full, false otherwise. + */ + static bool is_dlci_q_full(); + + /** Begin pending self iniated multiplexer open sequence. */ + static void pending_self_iniated_mux_open_start(); + + /** Begin pending self iniated DLCI establishment sequence. */ + static void pending_self_iniated_dlci_open_start(); + + /** Begin pending peer iniated DLCI establishment sequence. + * + * @param dlci_id ID of the DLCI to establish. + */ + static void pending_peer_iniated_dlci_open_start(uint8_t dlci_id); + + /** Enqueue user data for transmission. + * + * @note: This is API is only meant to be used for the multiplexer (user) data service tx. Supplied buffer can be + * reused/freed upon call return. + * + * @param dlci_id ID of the DLCI to use. + * @param buffer Begin of the user data. + * @param size The number of bytes to write. + * @return The number of bytes written, negative error on failure. + */ + static ssize_t user_data_tx(uint8_t dlci_id, const void* buffer, size_t size); + + /** Read user data into a buffer. + * + * @note: This is API is only meant to be used for the multiplexer (user) data service rx. + * + * @param buffer The buffer to read in to. + * @param size The number of bytes to read. + * @return The number of bytes read, -EAGAIN if no data availabe for read. + */ + static ssize_t user_data_rx(void* buffer, size_t size); + + /** Clear TX callback pending bit. + * + * @param bit Bit to clear. + */ + static void tx_callback_pending_bit_clear(uint8_t bit); + + /** Set TX callback pending bit for supplied DLCI ID. + * + * @param dlci_id DLCI ID for bit to set. + */ + static void tx_callback_pending_bit_set(uint8_t dlci_id); + + /** Advance the current TX callback index bit. + * + * @return The current TX callback index bit after advanced. + */ + static uint8_t tx_callback_index_advance(); + + /** Get the TX callback pending bitmask. + * + * @return TX callback pending bitmask. + */ + static uint8_t tx_callback_pending_mask_get(); + + /** Dispatch TX callback based on supplied bit. + * + * @param bit Bit indetifier of callback to dispatch. + */ + static void tx_callback_dispatch(uint8_t bit); + + /** Run main processing loop for resolving pending TX callbacks and dispatching them if they exists. + */ + static void tx_callbacks_run(); + + /** Get data service object based on supplied bit id. + * + * @param bit Bit indetifier of data service object to get. + * @return Data service object reference. + */ + static MuxDataService& tx_callback_lookup(uint8_t bit); + + /** Get minimum of 2 supplied paramaters. + * + * @param size_1 1st param for comparisation. + * @param size_2 2nd param for comparisation. + * @return Minimum of supplied paramaters. + */ + static size_t min(uint8_t size_1, size_t size_2); + + /** Enqueue callback to event queue. + */ + static void event_queue_enqueue(); + + /** Verify is FCS valid in RX frame. + * + * @return True upon valid FCS, false otherwise. + */ + static bool is_rx_fcs_valid(); + + /* Deny object creation. */ + Mux(); + + /* Deny copy constructor. */ + Mux(const Mux& obj); + + /* Deny assignment operator. */ + Mux& operator=(const Mux& obj); + + /* Definition for Tx context type. */ + struct tx_context_t { + int timer_id; /* Timer id. */ + union { + uint32_t align_4_byte; /* Force 4-byte alignment. */ + uint8_t buffer[MBED_CONF_MUX_BUFFER_SIZE]; /* Rx buffer. */ + }; + uint8_t retransmit_counter; /* Frame retransmission counter. */ + uint8_t bytes_remaining; /* Bytes remaining in the buffer to write. */ + uint8_t offset; /* Offset in the buffer where to write from. */ + uint8_t tx_callback_context; /* Context for the TX callback dispatching logic as follows: + - 4 LO bits contain the pending callback mask + - 4 HI bits contain the current bit used for masking */ + TxState tx_state; /* Tx state machine current state. */ + + }; + + /* Definition for Rx context type. */ + struct rx_context_t { + union { + uint32_t align_4_byte; /* Force 4-byte alignment. */ + uint8_t buffer[MBED_CONF_MUX_BUFFER_SIZE]; /* Rx buffer. */ + }; + uint8_t offset; /* Offset in the buffer where to read to. */ + uint8_t read_length; /* Amount to read in number of bytes. */ + RxState rx_state; /* Rx state machine current state. */ + }; + + /* Definition for state type. */ + struct state_t { + uint16_t is_mux_open : 1; /* True when multiplexer is open. */ + uint16_t is_mux_open_pending : 1; /* True when multiplexer open is pending. */ + uint16_t is_mux_open_running : 1; /* True when multiplexer open is running. */ + uint16_t is_dlci_open_pending : 1; /* True when DLCI open is pending. */ + uint16_t is_dlci_open_running : 1; /* True when DLCI open is running. */ + uint16_t is_system_thread_context : 1; /* True when current context is system thread context. */ + uint16_t is_tx_callback_context : 1; /* True when current context is TX callback context. */ + uint16_t is_user_tx_pending : 1; /* True when user TX is pending. */ + uint16_t is_user_rx_ready : 1; /* True when user RX is ready/available. */ + }; + + static FileHandle* _serial; /* Serial used. */ + static events::EventQueue* _event_q; /* Event queue used. */ + static rtos::Semaphore _semaphore; /* Semaphore used. */ + static PlatformMutex _mutex; /* Mutex used. */ + static MuxDataService _mux_objects[MBED_CONF_MUX_DLCI_COUNT]; /* Number of supported DLCIs. */ + static tx_context_t _tx_context; /* Tx context. */ + static rx_context_t _rx_context; /* Rx context. */ + static state_t _state; /* General state context. */ + static const uint8_t _crctable[MUX_CRC_TABLE_LEN]; /* CRC table used for frame FCS. */ + static uint8_t _shared_memory; /* Shared memory used passing data between user and + system threads. */ +}; + +} // namespace mbed + +#endif diff --git a/features/cellular/framework/mux/mbed_mux_data_service.cpp b/features/cellular/framework/mux/mbed_mux_data_service.cpp new file mode 100644 index 0000000000..48f7c86922 --- /dev/null +++ b/features/cellular/framework/mux/mbed_mux_data_service.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) , Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "mbed_mux.h" + +namespace mbed +{ + +ssize_t MuxDataService::write(const void* buffer, size_t size) +{ + return Mux::user_data_tx(_dlci, buffer, size); +} + + +ssize_t MuxDataService::read(void *buffer, size_t size) +{ + return Mux::user_data_rx(buffer, size); +} + + +off_t MuxDataService::seek(off_t offset, int whence) +{ + MBED_ASSERT(false); + return 0; +} + + +int MuxDataService::close() +{ + MBED_ASSERT(false); + return 0; +} + + +void MuxDataService::sigio(Callback func) +{ + _sigio_cb = func; +} + +} // namespace mbed diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.cpp b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.cpp new file mode 100644 index 0000000000..30736b4549 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "QUECTEL_BC95_CellularNetwork.h" +#include "QUECTEL_BC95_CellularPower.h" + +#include "QUECTEL_BC95.h" + +#define CONNECT_DELIM "\r\n" +#define CONNECT_BUFFER_SIZE (1280 + 80 + 80) // AT response + sscanf format +#define CONNECT_TIMEOUT 8000 + +#define MAX_STARTUP_TRIALS 5 +#define MAX_RESET_TRIALS 5 + +using namespace events; +using namespace mbed; + +QUECTEL_BC95::QUECTEL_BC95(EventQueue &queue) : AT_CellularDevice(queue) +{ +} + +QUECTEL_BC95::~QUECTEL_BC95() +{ +} + +CellularNetwork *QUECTEL_BC95::open_network(FileHandle *fh) +{ + if (!_network) { + _network = new QUECTEL_BC95_CellularNetwork(*get_at_handler(fh)); + } + return _network; +} + +CellularPower *QUECTEL_BC95::open_power(FileHandle *fh) +{ + if (!_power) { + _power = new QUECTEL_BC95_CellularPower(*get_at_handler(fh)); + } + return _power; +} diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.h b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.h new file mode 100644 index 0000000000..9dbce43544 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BC95_H_ +#define QUECTEL_BC95_H_ + +#include "AT_CellularDevice.h" + +namespace mbed { + +class QUECTEL_BC95 : public AT_CellularDevice +{ +public: + + QUECTEL_BC95(events::EventQueue &queue); + virtual ~QUECTEL_BC95(); + +public: // CellularDevice + virtual CellularNetwork *open_network(FileHandle *fh); + virtual CellularPower *open_power(FileHandle *fh); + +public: // NetworkInterface + void handle_urc(FileHandle *fh); +}; +} // namespace mbed +#endif // QUECTEL_BC95_H_ diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.cpp b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.cpp new file mode 100644 index 0000000000..8c8871d348 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "QUECTEL_BC95_CellularNetwork.h" +#include "QUECTEL_BC95_CellularStack.h" + +using namespace mbed; + +QUECTEL_BC95_CellularNetwork::QUECTEL_BC95_CellularNetwork(ATHandler &atHandler) : AT_CellularNetwork(atHandler) +{ + //TODO Set default to NB1 or leave it UNKNOWN + //_ops_act = operator_t::OPS_NB1; +} + +QUECTEL_BC95_CellularNetwork::~QUECTEL_BC95_CellularNetwork() +{ +} + +NetworkStack *QUECTEL_BC95_CellularNetwork::get_stack() +{ + if (!_stack) { + _stack = new QUECTEL_BC95_CellularStack(_at, _cid, _ip_stack_type); + } + return _stack; +} + +bool QUECTEL_BC95_CellularNetwork::get_modem_stack_type(nsapi_ip_stack_t requested_stack) +{ + return requested_stack == IPV4_STACK ? true : false; +} + +bool QUECTEL_BC95_CellularNetwork::has_registration(RegistrationType reg_tech) +{ + return (reg_tech == C_EREG); +} + +nsapi_error_t QUECTEL_BC95_CellularNetwork::set_access_technology_impl(operator_t::RadioAccessTechnology opRat) +{ + if (opRat != operator_t::RAT_NB1) { + //TODO: Set as unknown or force to NB1? + _op_act = operator_t::RAT_UNKNOWN; + return NSAPI_ERROR_UNSUPPORTED; + } + + return NSAPI_ERROR_OK; +} diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.h b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.h new file mode 100644 index 0000000000..d8303d7f64 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularNetwork.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BC95_CELLULAR_NETWORK_H_ +#define QUECTEL_BC95_CELLULAR_NETWORK_H_ + +#include "AT_CellularNetwork.h" + +namespace mbed { + +class QUECTEL_BC95_CellularNetwork : public AT_CellularNetwork +{ +public: + QUECTEL_BC95_CellularNetwork(ATHandler &atHandler); + virtual ~QUECTEL_BC95_CellularNetwork(); + + /** Provide access to the NetworkStack object + * + * @return The underlying NetworkStack object + */ + virtual NetworkStack *get_stack(); + + /** + * Sets radio access technology. + * + * @param opRat Radio access technology + * + * @return zero on success + */ + virtual nsapi_error_t set_access_technology_impl(operator_t::RadioAccessTechnology opRat); + +protected: + virtual bool get_modem_stack_type(nsapi_ip_stack_t requested_stack); + + /** + * Check if modem supports given registration type. + * + * @param reg_type enum RegistrationType + * @return true if given registration type is supported by modem + */ + virtual bool has_registration(RegistrationType reg_type); +}; +} // namespace mbed +#endif // QUECTEL_BC95_CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.cpp b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.cpp new file mode 100644 index 0000000000..60d6234cd0 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.cpp @@ -0,0 +1,50 @@ +/* Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "onboard_modem_api.h" +using namespace mbed; + +QUECTEL_BC95_CellularPower::QUECTEL_BC95_CellularPower(ATHandler &atHandler) : AT_CellularPower(atHandler) +{ + +} + +QUECTEL_BC95_CellularPower::~QUECTEL_BC95_CellularPower() +{ + +} + +nsapi_error_t QUECTEL_BC95_CellularPower::set_at_mode() +{ + _at.lock(); + _at.flush(); + _at.cmd_start("AT+CMEE="); // verbose responses + _at.write_int(1); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + return _at.unlock_return_error(); +} + +nsapi_error_t QUECTEL_BC95_CellularPower::reset() +{ + _at.lock(); + _at.cmd_start("AT+NRB"); // reset to full power levels + _at.cmd_stop(); + _at.resp_start("REBOOTING", true); + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.h b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.h new file mode 100644 index 0000000000..bc0429c6b0 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularPower.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2017 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TELIT_HE910_CELLULAR_POWER_H_ +#define TELIT_HE910_CELLULAR_POWER_H_ + +#include "AT_CellularPower.h" + +namespace mbed { + +class QUECTEL_BC95_CellularPower : public AT_CellularPower +{ +public: + QUECTEL_BC95_CellularPower(ATHandler &atHandler); + virtual ~QUECTEL_BC95_CellularPower(); + +public: //from CellularPower + /** + * Set AT command mode. + * @remark must be called after power on to prepare correct AT mode + * @return blocking until success or failure + */ + virtual nsapi_error_t set_at_mode(); + + /** + * Reset and wake-up cellular device. + * @return zero on success + */ + virtual nsapi_error_t reset(); +}; +} // namespace mbed +#endif // TELIT_HE910_CELLULAR_POWER_H_ diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.cpp b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.cpp new file mode 100644 index 0000000000..c9e63c8331 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "QUECTEL_BC95_CellularStack.h" +#include "CellularUtil.h" + +using namespace mbed; +using namespace mbed_cellular_util; + +QUECTEL_BC95_CellularStack::QUECTEL_BC95_CellularStack(ATHandler &atHandler, int cid, nsapi_ip_stack_t stack_type) : AT_CellularStack(atHandler, cid, stack_type) +{ + _at.set_urc_handler("+NSONMI:", mbed::Callback(this, &QUECTEL_BC95_CellularStack::urc_nsonmi)); +} + +QUECTEL_BC95_CellularStack::~QUECTEL_BC95_CellularStack() +{ +} + +nsapi_error_t QUECTEL_BC95_CellularStack::socket_listen(nsapi_socket_t handle, int backlog) +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +nsapi_error_t QUECTEL_BC95_CellularStack::socket_accept(void *server, void **socket, SocketAddress *addr) +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +void QUECTEL_BC95_CellularStack::urc_nsonmi() +{ + int sock_id =_at.read_int(); + + for (int i = 0; i < get_max_socket_count(); i++) { + CellularSocket *sock = _socket[i]; + if (sock && sock->id == sock_id) { + if (sock->_cb) { + sock->_cb(sock->_data); + } + break; + } + } +} + +int QUECTEL_BC95_CellularStack::get_max_socket_count() +{ + return BC95_SOCKET_MAX; +} + +int QUECTEL_BC95_CellularStack::get_max_packet_size() +{ + return BC95_MAX_PACKET_SIZE; +} + +bool QUECTEL_BC95_CellularStack::is_protocol_supported(nsapi_protocol_t protocol) +{ + return (protocol == NSAPI_UDP); +} + +nsapi_error_t QUECTEL_BC95_CellularStack::socket_close_impl(int sock_id) +{ + _at.cmd_start("AT+NSOCL="); + _at.write_int(sock_id); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.get_last_error(); +} + +nsapi_error_t QUECTEL_BC95_CellularStack::create_socket_impl(CellularSocket *socket) +{ + int sock_id; + bool socketOpenWorking = false; + + if (socket->proto == NSAPI_UDP) { + + _at.cmd_start("AT+NSOCR=DGRAM,17,"); + _at.write_int(socket->localAddress.get_port()); + _at.write_int(1); + _at.cmd_stop(); + _at.resp_start(); + sock_id = _at.read_int(); + _at.resp_stop(); + + bool socketOpenWorking = (_at.get_last_error() == NSAPI_ERROR_OK); + + if (!socketOpenWorking) { + _at.cmd_start("AT+NSOCL=0"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+NSOCR=DGRAM,17,"); + _at.write_int(socket->localAddress.get_port()); + _at.write_int(1); + _at.cmd_stop(); + _at.resp_start(); + sock_id = _at.read_int(); + _at.resp_stop(); + + socketOpenWorking = (_at.get_last_error() == NSAPI_ERROR_OK); + } + } + + if (!socketOpenWorking) { + return NSAPI_ERROR_NO_SOCKET; + } + + // Check for duplicate socket id delivered by modem + for (int i = 0; i < BC95_SOCKET_MAX; i++) { + CellularSocket *sock = _socket[i]; + if (sock && sock->created && sock->id == sock_id) { + return NSAPI_ERROR_NO_SOCKET; + } + } + + socket->id = sock_id; + socket->created = true; + + return NSAPI_ERROR_OK; +} + +nsapi_size_or_error_t QUECTEL_BC95_CellularStack::socket_sendto_impl(CellularSocket *socket, const SocketAddress &address, + const void *data, nsapi_size_t size) +{ + int sent_len = 0; + + char hexstr[BC95_MAX_PACKET_SIZE*2 + 1] = {0}; + char_str_to_hex_str((const char*)data, size, hexstr); + + _at.cmd_start("AT+NSOST="); + _at.write_int(socket->id); + _at.write_string(address.get_ip_address(), false); + _at.write_int(address.get_port()); + _at.write_int(size); + _at.write_string(hexstr, false); + _at.cmd_stop(); + _at.resp_start(); + socket->id = _at.read_int(); + sent_len = _at.read_int(); + _at.resp_stop(); + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + return sent_len; + } + + return _at.get_last_error(); +} + +nsapi_size_or_error_t QUECTEL_BC95_CellularStack::socket_recvfrom_impl(CellularSocket *socket, SocketAddress *address, + void *buffer, nsapi_size_t size) +{ + nsapi_size_or_error_t recv_len=0; + int port; + char ip_address[NSAPI_IP_SIZE]; + char hexstr[BC95_MAX_PACKET_SIZE*2 + 1] = {0}; + + _at.cmd_start("AT+NSORF="); + _at.write_int(socket->id); + _at.write_int(size); + _at.cmd_stop(); + _at.resp_start(); + // receiving socket id + _at.skip_param(); + _at.read_string(ip_address, sizeof(ip_address)); + port = _at.read_int(); + recv_len = _at.read_int(); + _at.read_string(hexstr, sizeof(hexstr)); + // remaining length + _at.skip_param(); + + if (!recv_len || (_at.get_last_error() != NSAPI_ERROR_OK)) { + return NSAPI_ERROR_WOULD_BLOCK; + } + + if (address) { + address->set_ip_address(ip_address); + address->set_port(port); + } + + if (recv_len > 0) { + hex_str_to_char_str((const char*) hexstr, recv_len*2, (char*)buffer); + } + + return recv_len; +} diff --git a/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.h b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.h new file mode 100644 index 0000000000..ad07be5dbb --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BC95/QUECTEL_BC95_CellularStack.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BC95_CELLULARSTACK_H_ +#define QUECTEL_BC95_CELLULARSTACK_H_ + +#include "AT_CellularStack.h" + +#define BC95_SOCKET_MAX 7 +#define BC95_MAX_PACKET_SIZE 512 + +namespace mbed { + +class QUECTEL_BC95_CellularStack : public AT_CellularStack +{ +public: + QUECTEL_BC95_CellularStack(ATHandler &atHandler, int cid, nsapi_ip_stack_t stack_type); + virtual ~QUECTEL_BC95_CellularStack(); + +public: // NetworkStack + + /** Listen for connections on a TCP socket + * + * Marks the socket as a passive socket that can be used to accept + * incoming connections. + * + * @param handle Socket handle + * @param backlog Number of pending connections that can be queued + * simultaneously + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_listen(nsapi_socket_t handle, int backlog); + + /** Accepts a connection on a TCP socket + * + * The server socket must be bound and set to listen for connections. + * On a new connection, creates a network socket and stores it in the + * specified handle. The handle must be passed to following calls on + * the socket. + * + * A stack may have a finite number of sockets, in this case + * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. + * + * This call is non-blocking. If accept would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param server Socket handle to server to accept from + * @param handle Destination for a handle to the newly created socket + * @param address Destination for the remote address or NULL + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_accept(nsapi_socket_t server, + nsapi_socket_t *handle, SocketAddress *address=0); + +protected: + + /** + * Gets maximum number of sockets modem supports + */ + virtual int get_max_socket_count(); + + /** + * Gets maximum packet size + */ + virtual int get_max_packet_size(); + + /** + * Checks if modem supports given protocol + * + * @param protocol Protocol type + */ + virtual bool is_protocol_supported(nsapi_protocol_t protocol); + + /** + * Implements modem specific AT command set for socket closing + * + * @param sock_id Socket id + */ + virtual nsapi_error_t socket_close_impl(int sock_id); + + /** + * Implements modem specific AT command set for creating socket + * + * @param socket Cellular socket handle + */ + virtual nsapi_error_t create_socket_impl(CellularSocket *socket); + + /** + * Implements modem specific AT command set for sending data + * + * @param socket Cellular socket handle + * @param address The SocketAddress of the remote host + * @param data Buffer of data to send to the host + * @param size Size of the buffer in bytes + * @return Number of sent bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_sendto_impl(CellularSocket *socket, const SocketAddress &address, + const void *data, nsapi_size_t size); + + /** + * Implements modem specific AT command set for receiving data + * + * @param handle Socket handle + * @param address Destination for the source address or NULL + * @param buffer Destination buffer for data received from the host + * @param size Size of the buffer in bytes + * @return Number of received bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_recvfrom_impl(CellularSocket *socket, SocketAddress *address, + void *buffer, nsapi_size_t size); + +private: + // URC handlers + void urc_nsonmi(); +}; +} // namespace mbed +#endif /* QUECTEL_BC95_CELLULARSTACK_H_ */ diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp new file mode 100644 index 0000000000..08c1b6fa1a --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +using namespace mbed; +using namespace events; + +#define CONNECT_DELIM "\r\n" +#define CONNECT_BUFFER_SIZE (1280 + 80 + 80) // AT response + sscanf format +#define CONNECT_TIMEOUT 8000 + +#define MAX_STARTUP_TRIALS 5 +#define MAX_RESET_TRIALS 5 + +QUECTEL_BG96::QUECTEL_BG96(EventQueue &queue) : AT_CellularDevice(queue) +{ +} + +QUECTEL_BG96::~QUECTEL_BG96() +{ +} + +CellularNetwork *QUECTEL_BG96::open_network(FileHandle *fh) +{ + if (!_network) { + _network = new QUECTEL_BG96_CellularNetwork(*get_at_handler(fh)); + } + return _network; +} diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h new file mode 100644 index 0000000000..2fe2ea0b87 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BG96_H_ +#define QUECTEL_BG96_H_ + +#include "AT_CellularDevice.h" + +namespace mbed { + +#ifdef TARGET_MCU_K64F +#define CELLULAR_SERIAL_TX PTC17 +#define CELLULAR_SERIAL_RX PTC16 +#else +#define CELLULAR_SERIAL_TX PC_1 +#define CELLULAR_SERIAL_RX PC_0 +#endif + +class QUECTEL_BG96 : public AT_CellularDevice +{ +public: + + QUECTEL_BG96(events::EventQueue &queue); + virtual ~QUECTEL_BG96(); + +public: // CellularDevice + virtual CellularNetwork *open_network(FileHandle *fh); + +public: // NetworkInterface + void handle_urc(FileHandle *fh); +}; +} // namespace mbed +#endif // QUECTEL_BG96_H_ diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.cpp b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.cpp new file mode 100644 index 0000000000..986c7e2d76 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +using namespace mbed; + +QUECTEL_BG96_CellularNetwork::QUECTEL_BG96_CellularNetwork(ATHandler &atHandler) : AT_CellularNetwork(atHandler) +{ +} + +QUECTEL_BG96_CellularNetwork::~QUECTEL_BG96_CellularNetwork() +{ +} + +bool QUECTEL_BG96_CellularNetwork::get_modem_stack_type(nsapi_ip_stack_t requested_stack) +{ + if ((requested_stack == IPV4_STACK) || + (requested_stack == IPV6_STACK) || + (requested_stack == IPV4V6_STACK)) { + return true; + } + + return false; +} + +NetworkStack *QUECTEL_BG96_CellularNetwork::get_stack() +{ + if (!_stack) { + _stack = new QUECTEL_BG96_CellularStack(_at, _cid, _ip_stack_type); + } + return _stack; +} + +nsapi_error_t QUECTEL_BG96_CellularNetwork::set_access_technology_impl(operator_t::RadioAccessTechnology opsAct) +{ + _at.lock(); + + switch (opsAct) { + case operator_t::RAT_CATM1: + _at.cmd_start("AT+QCFG=\"nwscanseq\",020301"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"nwscanmode\",3,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"iotopmode\",0,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + break; + case operator_t::RAT_NB1: + _at.cmd_start("AT+QCFG=\"nwscanseq\",030201"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"nwscanmode\",3,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"iotopmode\",1,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + break; + case operator_t::RAT_GSM: + case operator_t::RAT_GSM_COMPACT: + case operator_t::RAT_UTRAN: + case operator_t::RAT_EGPRS: + _at.cmd_start("AT+QCFG=\"nwscanseq\",010203"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"nwscanmode\",1,1"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + break; + default: + _at.cmd_start("AT+QCFG=\"nwscanseq\",020301"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"nwscanmode\",0,1"); //auto mode + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.cmd_start("AT+QCFG=\"iotopmode\",2,1"); //auto mode + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + _at.unlock(); + _op_act = operator_t::RAT_UNKNOWN; + return NSAPI_ERROR_UNSUPPORTED; + } + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.h b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.h new file mode 100644 index 0000000000..d2a93e837d --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularNetwork.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BG96_CELLULAR_NETWORK_H_ +#define QUECTEL_BG96_CELLULAR_NETWORK_H_ + +#include "AT_CellularNetwork.h" + +namespace mbed { + +class QUECTEL_BG96_CellularNetwork : public AT_CellularNetwork +{ +public: + QUECTEL_BG96_CellularNetwork(ATHandler &atHandler); + virtual ~QUECTEL_BG96_CellularNetwork(); + + /** Provide access to the NetworkStack object + * + * @return The underlying NetworkStack object + */ + virtual NetworkStack *get_stack(); + + /** + * Sets radio access technology. + * + * @param opRat Access technology + * + * @return zero on success + */ + virtual nsapi_error_t set_access_technology_impl(operator_t::RadioAccessTechnology opRat); + +protected: + virtual bool get_modem_stack_type(nsapi_ip_stack_t requested_stack); +}; +} // namespace mbed +#endif // QUECTEL_BG96_CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp new file mode 100644 index 0000000000..f130caa939 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "CellularLog.h" + +using namespace mbed; + +QUECTEL_BG96_CellularStack::QUECTEL_BG96_CellularStack(ATHandler &atHandler, int cid, nsapi_ip_stack_t stack_type) : AT_CellularStack(atHandler, cid, stack_type) +{ + _at.set_urc_handler("+QIURC: ", mbed::Callback(this, &QUECTEL_BG96_CellularStack::urc_qiurc)); +} + +QUECTEL_BG96_CellularStack::~QUECTEL_BG96_CellularStack() +{ +} + +nsapi_error_t QUECTEL_BG96_CellularStack::socket_listen(nsapi_socket_t handle, int backlog) +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +nsapi_error_t QUECTEL_BG96_CellularStack::socket_accept(void *server, void **socket, SocketAddress *addr) +{ + return NSAPI_ERROR_UNSUPPORTED; +} + +void QUECTEL_BG96_CellularStack::urc_qiurc() +{ + int sock_id=0; + + _at.lock(); + (void) _at.skip_param(); + sock_id = _at.read_int(); + _at.unlock(); + + for (int i = 0; i < get_max_socket_count(); i++) { + CellularSocket *sock = _socket[i]; + if (sock && sock->id == sock_id) { + if (sock->_cb) { + sock->_cb(sock->_data); + } + break; + } + } +} + +int QUECTEL_BG96_CellularStack::get_max_socket_count() +{ + return BG96_SOCKET_MAX; +} + +int QUECTEL_BG96_CellularStack::get_max_packet_size() +{ + return BG96_MAX_PACKET_SIZE; +} + +bool QUECTEL_BG96_CellularStack::is_protocol_supported(nsapi_protocol_t protocol) +{ + return (protocol == NSAPI_UDP); +} + +nsapi_error_t QUECTEL_BG96_CellularStack::socket_close_impl(int sock_id) +{ + _at.cmd_start("AT+QICLOSE="); + _at.write_int(sock_id); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.get_last_error(); +} + +void QUECTEL_BG96_CellularStack::handle_open_socket_response(int &modem_connect_id, int &err) +{ + // OK + _at.resp_start(); + _at.resp_stop(); + // QIOPEN -> should be handled as URC? + _at.set_at_timeout(BG96_CREATE_SOCKET_TIMEOUT); + _at.resp_start("+QIOPEN:"); + _at.restore_at_timeout(); + modem_connect_id = _at.read_int(); + err = _at.read_int(); +} +nsapi_error_t QUECTEL_BG96_CellularStack::create_socket_impl(CellularSocket *socket) +{ + int modem_connect_id; + int request_connect_id = socket->id; + int remote_port = 0; + int err = -1; + + if (socket->proto == NSAPI_UDP && !socket->connected) { + _at.cmd_start("AT+QIOPEN="); + _at.write_int(_cid); + _at.write_int(request_connect_id); + _at.write_string("UDP SERVICE"); + _at.write_string("127.0.0.1"); + _at.write_int(remote_port); + _at.write_int(socket->localAddress.get_port()); + _at.write_int(0); + _at.cmd_stop(); + + handle_open_socket_response(modem_connect_id, err); + + if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { + _at.cmd_start("AT+QICLOSE="); + _at.write_int(modem_connect_id); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+QIOPEN="); + _at.write_int(_cid); + _at.write_int(request_connect_id); + _at.write_string("UDP SERVICE"); + _at.write_string("127.0.0.1"); + _at.write_int(remote_port); + _at.write_int(socket->localAddress.get_port()); + _at.write_int(0); + _at.cmd_stop(); + + handle_open_socket_response(modem_connect_id, err); + } + } else if (socket->proto == NSAPI_UDP && socket->connected) { + _at.cmd_start("AT+QIOPEN="); + _at.write_int(_cid); + _at.write_int(request_connect_id); + _at.write_string("UDP"); + _at.write_string(socket->remoteAddress.get_ip_address()); + _at.write_int(socket->remoteAddress.get_port()); + _at.cmd_stop(); + + handle_open_socket_response(modem_connect_id, err); + + if ((_at.get_last_error() == NSAPI_ERROR_OK) && err) { + _at.cmd_start("AT+QICLOSE="); + _at.write_int(modem_connect_id); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+QIOPEN="); + _at.write_int(_cid); + _at.write_int(request_connect_id); + _at.write_string("UDP"); + _at.write_string(socket->remoteAddress.get_ip_address()); + _at.write_int(socket->remoteAddress.get_port()); + _at.cmd_stop(); + + handle_open_socket_response(modem_connect_id, err); + } + } + + // If opened successfully BUT not requested one, close it + if (!err && (modem_connect_id != request_connect_id)) { + _at.cmd_start("AT+QICLOSE="); + _at.write_int(modem_connect_id); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + } + + nsapi_error_t ret_val = _at.get_last_error(); + + socket->created = ((ret_val == NSAPI_ERROR_OK) && (modem_connect_id == request_connect_id)); + + return ret_val; +} + +nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_sendto_impl(CellularSocket *socket, const SocketAddress &address, + const void *data, nsapi_size_t size) +{ + int sent_len = 0; + int sent_len_before = 0; + int sent_len_after = 0; + + // Get the sent count before sending + _at.cmd_start("AT+QISEND="); + _at.write_int(socket->id); + _at.write_int(0); + _at.cmd_stop(); + + _at.resp_start("+QISEND:"); + sent_len_before = _at.read_int(); + _at.resp_stop(); + + // Send + _at.cmd_start("AT+QISEND="); + _at.write_int(socket->id); + _at.write_int(size); + _at.write_string(address.get_ip_address()); + _at.write_int(address.get_port()); + _at.cmd_stop(); + + _at.resp_start(">"); + _at.write_bytes((uint8_t*)data, size); + _at.resp_start(); + _at.set_stop_tag("\r\n"); + _at.resp_stop(); + + // Get the sent count after sending + _at.cmd_start("AT+QISEND="); + _at.write_int(socket->id); + _at.write_int(0); + _at.cmd_stop(); + + _at.resp_start("+QISEND:"); + sent_len_after = _at.read_int(); + _at.resp_stop(); + + if (_at.get_last_error() == NSAPI_ERROR_OK) { + sent_len = sent_len_after - sent_len_before; + return sent_len; + } + + return _at.get_last_error(); +} + +nsapi_size_or_error_t QUECTEL_BG96_CellularStack::socket_recvfrom_impl(CellularSocket *socket, SocketAddress *address, + void *buffer, nsapi_size_t size) +{ + nsapi_size_or_error_t recv_len=0; + int port; + char ip_address[NSAPI_IP_SIZE + 1]; + + _at.cmd_start("AT+QIRD="); + _at.write_int(socket->id); + _at.cmd_stop(); + + _at.resp_start("+QIRD:"); + recv_len = _at.read_int(); + _at.read_string(ip_address, sizeof(ip_address)); + port = _at.read_int(); + _at.read_bytes((uint8_t*)buffer, recv_len); + _at.resp_stop(); + + if (!recv_len || (_at.get_last_error() != NSAPI_ERROR_OK)) { + return NSAPI_ERROR_WOULD_BLOCK; + } + + if (address) { + address->set_ip_address(ip_address); + address->set_port(port); + } + + return recv_len; +} diff --git a/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h new file mode 100644 index 0000000000..f9ebe03e40 --- /dev/null +++ b/features/cellular/framework/targets/QUECTEL/BG96/QUECTEL_BG96_CellularStack.h @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QUECTEL_BG96_CELLULARSTACK_H_ +#define QUECTEL_BG96_CELLULARSTACK_H_ + +#include "AT_CellularStack.h" + +namespace mbed { + +#define BG96_SOCKET_MAX 12 +#define BG96_MAX_PACKET_SIZE 1460 +#define BG96_CREATE_SOCKET_TIMEOUT 150000 //150 seconds + +class QUECTEL_BG96_CellularStack : public AT_CellularStack +{ +public: + QUECTEL_BG96_CellularStack(ATHandler &atHandler, int cid, nsapi_ip_stack_t stack_type); + virtual ~QUECTEL_BG96_CellularStack(); + +public: // NetworkStack + + /** Listen for connections on a TCP socket + * + * Marks the socket as a passive socket that can be used to accept + * incoming connections. + * + * @param handle Socket handle + * @param backlog Number of pending connections that can be queued + * simultaneously + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_listen(nsapi_socket_t handle, int backlog); + + /** Accepts a connection on a TCP socket + * + * The server socket must be bound and set to listen for connections. + * On a new connection, creates a network socket and stores it in the + * specified handle. The handle must be passed to following calls on + * the socket. + * + * A stack may have a finite number of sockets, in this case + * NSAPI_ERROR_NO_SOCKET is returned if no socket is available. + * + * This call is non-blocking. If accept would block, + * NSAPI_ERROR_WOULD_BLOCK is returned immediately. + * + * @param server Socket handle to server to accept from + * @param handle Destination for a handle to the newly created socket + * @param address Destination for the remote address or NULL + * @return 0 on success, negative error code on failure + */ + virtual nsapi_error_t socket_accept(nsapi_socket_t server, + nsapi_socket_t *handle, SocketAddress *address=0); + +protected: + + /** + * Gets maximum number of sockets modem supports + */ + virtual int get_max_socket_count(); + + /** + * Gets maximum packet size + */ + virtual int get_max_packet_size(); + + /** + * Checks if modem supports given protocol + * + * @param protocol Protocol type + */ + virtual bool is_protocol_supported(nsapi_protocol_t protocol); + + /** + * Implements modem specific AT command set for socket closing + * + * @param sock_id Socket id + */ + virtual nsapi_error_t socket_close_impl(int sock_id); + + /** + * Implements modem specific AT command set for creating socket + * + * @param socket Cellular socket handle + */ + virtual nsapi_error_t create_socket_impl(CellularSocket *socket); + + /** + * Implements modem specific AT command set for sending data + * + * @param socket Cellular socket handle + * @param address The SocketAddress of the remote host + * @param data Buffer of data to send to the host + * @param size Size of the buffer in bytes + * @return Number of sent bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_sendto_impl(CellularSocket *socket, const SocketAddress &address, + const void *data, nsapi_size_t size); + + /** + * Implements modem specific AT command set for receiving data + * + * @param handle Socket handle + * @param address Destination for the source address or NULL + * @param buffer Destination buffer for data received from the host + * @param size Size of the buffer in bytes + * @return Number of received bytes on success, negative error + * code on failure + */ + virtual nsapi_size_or_error_t socket_recvfrom_impl(CellularSocket *socket, SocketAddress *address, + void *buffer, nsapi_size_t size); + +private: + // URC handlers + void urc_qiurc(); + + void handle_open_socket_response(int &modem_connect_id, int &err); +}; +} // namespace mbed +#endif /* QUECTEL_BG96_CELLULARSTACK_H_ */ diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp new file mode 100644 index 0000000000..e992526d2d --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TELIT_HE910.h" +#include "TELIT_HE910_CellularPower.h" +#include "TELIT_HE910_CellularNetwork.h" +#include "TELIT_HE910_CellularMultiplexer.h" + +using namespace mbed; +using namespace events; + +TELIT_HE910::TELIT_HE910(EventQueue &queue) : AT_CellularDevice(queue) +{ + +} + +TELIT_HE910::~TELIT_HE910() +{ +} + +CellularPower *TELIT_HE910::open_power(FileHandle *fh) +{ + if (!_power) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _power = new TELIT_HE910_CellularPower(*atHandler); + if (!_power) { + release_at_handler(atHandler); + } + } + } + return _power; +} + +CellularNetwork *TELIT_HE910::open_network(FileHandle *fh) +{ + if (!_network) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _network = new TELIT_HE910_CellularNetwork(*atHandler); + if (!_network) { + release_at_handler(atHandler); + } + } + } + return _network; +} + +CellularMultiplexer *TELIT_HE910::open_multiplexer(FileHandle *fh) +{ + if (!_multiplexer) { + ATHandler *atHandler = get_at_handler(fh); + if (atHandler) { + _multiplexer = new AT_CellularMultiplexer(*atHandler); + if (!_multiplexer) { + release_at_handler(atHandler); + } + } + } + + return _multiplexer; +} diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h new file mode 100644 index 0000000000..7434b08de4 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CELLULAR_TARGETS_TELIT_HE910_TELIT_HE910_H_ +#define CELLULAR_TARGETS_TELIT_HE910_TELIT_HE910_H_ + +#include "AT_CellularDevice.h" + +namespace mbed { + +class TELIT_HE910 : public AT_CellularDevice +{ + +public: + TELIT_HE910(events::EventQueue &queue); + virtual ~TELIT_HE910(); + +public: // from CellularDevice + virtual CellularPower *open_power(FileHandle *fh); + virtual CellularNetwork *open_network(FileHandle *fh); + virtual CellularMultiplexer *open_multiplexer(FileHandle *fh); +}; +} // namespace mbed +#endif /* CELLULAR_TARGETS_TELIT_HE910_TELIT_HE910_H_ */ diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.cpp b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.cpp new file mode 100644 index 0000000000..c19917f4b3 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TELIT_HE910_CellularMultiplexer.h" + +using namespace mbed; + +TELIT_HE910_CellularMultiplexer::TELIT_HE910_CellularMultiplexer(ATHandler &atHandler) +: AT_CellularMultiplexer(atHandler) +{ +} + +TELIT_HE910_CellularMultiplexer::~TELIT_HE910_CellularMultiplexer() +{ +} + +nsapi_error_t TELIT_HE910_CellularMultiplexer::multiplexer_mode_start() +{ + _at.lock(); + + _at.cmd_start("AT#SELINT=2"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("ATE0V1&K3&D2"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+IPR=115200"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT&W0&P0"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT#CMUXMODE=5"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + _at.cmd_start("AT+CMUX=0"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.h b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.h new file mode 100644 index 0000000000..f41ed1bad7 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularMultiplexer.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TELIT_HE910_CELLULARMULTIPLEXER_H_ +#define TELIT_HE910_CELLULARMULTIPLEXER_H_ + +#include "AT_CellularMultiplexer.h" + +namespace mbed { + +class TELIT_HE910_CellularMultiplexer : public AT_CellularMultiplexer { +public: + TELIT_HE910_CellularMultiplexer(ATHandler &atHandler); + virtual ~TELIT_HE910_CellularMultiplexer(); + + // override from AT_CellularMultiplexer + virtual nsapi_error_t multiplexer_mode_start(); +}; +} // namespace mbed +#endif /* TELIT_HE910_CELLULARMULTIPLEXER_H_ */ diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.cpp b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.cpp new file mode 100644 index 0000000000..59b04dc464 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +using namespace mbed; + +TELIT_HE910_CellularNetwork::TELIT_HE910_CellularNetwork(ATHandler &atHandler) : AT_CellularNetwork(atHandler) +{ +} + +TELIT_HE910_CellularNetwork::~TELIT_HE910_CellularNetwork() +{ +} + +bool TELIT_HE910_CellularNetwork::get_modem_stack_type(nsapi_ip_stack_t requested_stack) +{ + return requested_stack == IPV4_STACK ? true : false; +} + +bool TELIT_HE910_CellularNetwork::has_registration(RegistrationType reg_type) +{ + return (reg_type == C_REG || reg_type == C_GREG); +} + +nsapi_error_t TELIT_HE910_CellularNetwork::set_access_technology_impl(operator_t::RadioAccessTechnology opRat) +{ + _op_act = operator_t::RAT_UNKNOWN; + return NSAPI_ERROR_UNSUPPORTED; +} diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.h b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.h new file mode 100644 index 0000000000..9d73af6a65 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularNetwork.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TELIT_HE910_CELLULAR_NETWORK_H_ +#define TELIT_HE910_CELLULAR_NETWORK_H_ + +#include "AT_CellularNetwork.h" + +namespace mbed { + +class TELIT_HE910_CellularNetwork : public AT_CellularNetwork +{ +public: + TELIT_HE910_CellularNetwork(ATHandler &atHandler); + virtual ~TELIT_HE910_CellularNetwork(); + +protected: + + virtual bool get_modem_stack_type(nsapi_ip_stack_t requested_stack); + + /** + * Check if modem supports given registration type. + * + * @param rat enum RegistrationType + * @return true if given registration type is supported by modem + */ + virtual bool has_registration(RegistrationType rat); + + /** + * Sets access technology to be scanned. + * + * @param opRat Access technology + * + * @return zero on success + */ + virtual nsapi_error_t set_access_technology_impl(operator_t::RadioAccessTechnology opRat); +}; +} // namespace mbed +#endif // TELIT_HE910_CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.cpp b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.cpp new file mode 100644 index 0000000000..63a2b42027 --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "TELIT_HE910_CellularPower.h" +// for modem powering up&down +#include "onboard_modem_api.h" + +using namespace mbed; + +TELIT_HE910_CellularPower::TELIT_HE910_CellularPower(ATHandler &atHandler) : AT_CellularPower(atHandler) +{ + +} + +TELIT_HE910_CellularPower::~TELIT_HE910_CellularPower() +{ + +} + +nsapi_error_t TELIT_HE910_CellularPower::on() +{ +#if MODEM_ON_BOARD + ::onboard_modem_init(); + ::onboard_modem_power_up(); +#endif + return NSAPI_ERROR_OK; +} + +nsapi_error_t TELIT_HE910_CellularPower::off() +{ +#if MODEM_ON_BOARD + ::onboard_modem_power_down(); +#endif + return NSAPI_ERROR_OK; +} + +/** + * Set AT command mode. + * @remark must be called after power on to prepare correct AT mode + * @return blocking until success or failure + */ +nsapi_error_t TELIT_HE910_CellularPower::set_at_mode() +{ + nsapi_error_t err = AT_CellularPower::set_at_mode(); + if (err != NSAPI_ERROR_OK) { + return err; + } + _at.lock(); + _at.cmd_start("AT&K0;&C1;&D0"); + _at.cmd_stop(); + _at.resp_start(); + _at.resp_stop(); + + return _at.unlock_return_error(); +} diff --git a/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.h b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.h new file mode 100644 index 0000000000..050773e49b --- /dev/null +++ b/features/cellular/framework/targets/TELIT/HE910/TELIT_HE910_CellularPower.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TELIT_HE910_CELLULAR_POWER_H_ +#define TELIT_HE910_CELLULAR_POWER_H_ + +#include "AT_CellularPower.h" + +namespace mbed { + +class TELIT_HE910_CellularPower : public AT_CellularPower +{ +public: + TELIT_HE910_CellularPower(ATHandler &atHandler); + virtual ~TELIT_HE910_CellularPower(); + +public: //from CellularPower + /** + * Set cellular device power on. + * @return zero on success + */ + virtual nsapi_error_t on(); + + /** + * Set cellular device power off. + * @return zero on success + */ + virtual nsapi_error_t off(); + + /** + * Set AT command mode. + * @remark must be called after power on to prepare correct AT mode + * @return blocking until success or failure + */ + virtual nsapi_error_t set_at_mode(); +}; +} // namespace mbed +#endif // TELIT_HE910_CELLULAR_POWER_H_ diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.cpp b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.cpp new file mode 100644 index 0000000000..a4997cc744 --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UBLOX_C027_CellularNetwork.h" +#include "UBLOX_C027_CellularPower.h" +#include "UBLOX_C027.h" + +using namespace mbed; +using namespace events; + +UBLOX_C027::UBLOX_C027(EventQueue &queue) : AT_CellularDevice(queue) +{ +} + +UBLOX_C027::~UBLOX_C027() +{ +} + +CellularNetwork *UBLOX_C027::open_network(FileHandle *fh) +{ + if (!_network) { + _network = new UBLOX_C027_CellularNetwork(*get_at_handler(fh)); + } + return _network; +} + +CellularPower *UBLOX_C027::open_power(FileHandle *fh) +{ + if (!_power) { + _power = new UBLOX_C027_CellularPower(*get_at_handler(fh)); + } + return _power; +} diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.h b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.h new file mode 100644 index 0000000000..5e4f2f15cd --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UBLOX_C027_H_ +#define UBLOX_C027_H_ + +#include "AT_CellularDevice.h" + +namespace mbed { + +class UBLOX_C027 : public AT_CellularDevice +{ + +public: + + UBLOX_C027(events::EventQueue &queue); + virtual ~UBLOX_C027(); + +public: // CellularDevice + virtual CellularNetwork *open_network(FileHandle *fh); + virtual CellularPower *open_power(FileHandle *fh); + +public: // NetworkInterface +}; +} // namespace mbed +#endif // UBLOX_C027_H_ diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.cpp b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.cpp new file mode 100644 index 0000000000..873be64e04 --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "UBLOX_C027_CellularNetwork.h" + +using namespace mbed; + +UBLOX_C027_CellularNetwork::UBLOX_C027_CellularNetwork(ATHandler &atHandler) : AT_CellularNetwork(atHandler) +{ +} + +UBLOX_C027_CellularNetwork::~UBLOX_C027_CellularNetwork() +{ +} + +bool UBLOX_C027_CellularNetwork::get_modem_stack_type(nsapi_ip_stack_t requested_stack) +{ + return requested_stack == IPV4_STACK ? true : false; +} + +bool UBLOX_C027_CellularNetwork::has_registration(RegistrationType reg_type) +{ + return (reg_type == C_REG || reg_type == C_GREG); +} + +nsapi_error_t UBLOX_C027_CellularNetwork::set_access_technology_impl(operator_t::RadioAccessTechnology opRat) +{ + _op_act = operator_t::RAT_UNKNOWN; + return NSAPI_ERROR_UNSUPPORTED; +} diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.h b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.h new file mode 100644 index 0000000000..65ad5e34c3 --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularNetwork.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UBLOX_C027_CELLULAR_NETWORK_H_ +#define UBLOX_C027_CELLULAR_NETWORK_H_ + +#include "AT_CellularNetwork.h" + +namespace mbed { + +class UBLOX_C027_CellularNetwork : public AT_CellularNetwork +{ +public: + UBLOX_C027_CellularNetwork(ATHandler &atHandler); + virtual ~UBLOX_C027_CellularNetwork(); + +protected: + virtual bool get_modem_stack_type(nsapi_ip_stack_t requested_stack); + + /** + * Check if modem supports given registration type. + * + * @param rat enum RegistrationType + * @return true if given registration type is supported by modem + */ + virtual bool has_registration(RegistrationType rat); + + /** + * Sets access technology to be scanned. + * + * @param opRat Access technology + * + * @return zero on success + */ + virtual nsapi_error_t set_access_technology_impl(operator_t::RadioAccessTechnology opRat); +}; +} // namespace mbed +#endif // UBLOX_C027_CELLULAR_NETWORK_H_ diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.cpp b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.cpp new file mode 100644 index 0000000000..a52854bf84 --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "onboard_modem_api.h" + +#include "UBLOX_C027_CellularPower.h" + +using namespace mbed; + +UBLOX_C027_CellularPower::UBLOX_C027_CellularPower(ATHandler &atHandler) : AT_CellularPower(atHandler) +{ + +} + +UBLOX_C027_CellularPower::~UBLOX_C027_CellularPower() +{ + +} + +nsapi_error_t UBLOX_C027_CellularPower::on() +{ +#if MODEM_ON_BOARD + ::onboard_modem_init(); + ::onboard_modem_power_up(); +#endif + return NSAPI_ERROR_OK; +} + +nsapi_error_t UBLOX_C027_CellularPower::off() +{ +#if MODEM_ON_BOARD + ::onboard_modem_power_down(); +#endif + return NSAPI_ERROR_OK; +} diff --git a/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.h b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.h new file mode 100644 index 0000000000..0f5b0a0873 --- /dev/null +++ b/features/cellular/framework/targets/UBLOX/C027/UBLOX_C027_CellularPower.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017, Arm Limited and affiliates. + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef UBLOX_C027_CELLULARPOWER_H_ +#define UBLOX_C027_CELLULARPOWER_H_ + +#include "AT_CellularPower.h" + +namespace mbed { + +class UBLOX_C027_CellularPower : public AT_CellularPower +{ +public: + UBLOX_C027_CellularPower(ATHandler &atHandler); + virtual ~UBLOX_C027_CellularPower(); + +public: //from CellularPower + /** + * Set cellular device power on. + * @return zero on success + */ + virtual nsapi_error_t on(); + + /** + * Set cellular device power off. + * @return zero on success + */ + virtual nsapi_error_t off(); +}; +} // namespace mbed +#endif // UBLOX_C027_CELLULARPOWER_H_