diff --git a/esp_modem/CMakeLists.txt b/esp_modem/CMakeLists.txt new file mode 100644 index 000000000..50fa1a4b7 --- /dev/null +++ b/esp_modem/CMakeLists.txt @@ -0,0 +1,12 @@ +set(srcs "src/esp_modem.c" + "src/esp_modem_dce_service" + "src/esp_modem_netif.c" + "src/esp_modem_compat.c" + "src/sim800.c" + "src/sim7600.c" + "src/bg96.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS include + PRIV_INCLUDE_DIRS private_include + REQUIRES driver) diff --git a/esp_modem/Kconfig b/esp_modem/Kconfig new file mode 100644 index 000000000..932c1c091 --- /dev/null +++ b/esp_modem/Kconfig @@ -0,0 +1,9 @@ +menu "ESP-MODEM" + + config EXAMPLE_COMPONENT_MODEM_APN + string "Set Access Point Name (APN)" + default "CMNET" + help + Logical name which is used to select the GGSN or the external packet data network. + +endmenu diff --git a/esp_modem/component.mk b/esp_modem/component.mk new file mode 100644 index 000000000..d3afe7768 --- /dev/null +++ b/esp_modem/component.mk @@ -0,0 +1,3 @@ +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_PRIV_INCLUDEDIRS := private_include +COMPONENT_SRCDIRS := src diff --git a/esp_modem/include/bg96.h b/esp_modem/include/bg96.h new file mode 100644 index 000000000..f31d0a25c --- /dev/null +++ b/esp_modem/include/bg96.h @@ -0,0 +1,33 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_modem_dce_service.h" +#include "esp_modem.h" + +/** + * @brief Create and initialize BG96 object + * + * @param dte Modem DTE object + * @return modem_dce_t* Modem DCE object + */ +modem_dce_t *bg96_init(modem_dte_t *dte); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem.h b/esp_modem/include/esp_modem.h new file mode 100644 index 000000000..4440b7a5a --- /dev/null +++ b/esp_modem/include/esp_modem.h @@ -0,0 +1,174 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_modem_dce.h" +#include "esp_modem_dte.h" +#include "esp_event.h" +#include "driver/uart.h" +#include "esp_modem_compat.h" + +/** + * @brief Declare Event Base for ESP Modem + * + */ +ESP_EVENT_DECLARE_BASE(ESP_MODEM_EVENT); + +/** + * @brief ESP Modem Event + * + */ +typedef enum { + ESP_MODEM_EVENT_PPP_START = 0, /*!< ESP Modem Start PPP Session */ + ESP_MODEM_EVENT_PPP_STOP = 3, /*!< ESP Modem Stop PPP Session*/ + ESP_MODEM_EVENT_UNKNOWN = 4 /*!< ESP Modem Unknown Response */ +} esp_modem_event_t; + +/** + * @brief ESP Modem DTE Configuration + * + */ +typedef struct { + uart_port_t port_num; /*!< UART port number */ + uart_word_length_t data_bits; /*!< Data bits of UART */ + uart_stop_bits_t stop_bits; /*!< Stop bits of UART */ + uart_parity_t parity; /*!< Parity type */ + modem_flow_ctrl_t flow_control; /*!< Flow control type */ + uint32_t baud_rate; /*!< Communication baud rate */ + int tx_io_num; /*!< TXD Pin Number */ + int rx_io_num; /*!< RXD Pin Number */ + int rts_io_num; /*!< RTS Pin Number */ + int cts_io_num; /*!< CTS Pin Number */ + int rx_buffer_size; /*!< UART RX Buffer Size */ + int tx_buffer_size; /*!< UART TX Buffer Size */ + int pattern_queue_size; /*!< UART Pattern Queue Size */ + int event_queue_size; /*!< UART Event Queue Size */ + uint32_t event_task_stack_size; /*!< UART Event Task Stack size */ + int event_task_priority; /*!< UART Event Task Priority */ + int line_buffer_size; /*!< Line buffer size for command mode */ +} esp_modem_dte_config_t; + +/** + * @brief Type used for reception callback + * + */ +typedef esp_err_t (*esp_modem_on_receive)(void *buffer, size_t len, void *context); + +/** + * @brief ESP Modem DTE Default Configuration + * + */ +#define ESP_MODEM_DTE_DEFAULT_CONFIG() \ + { \ + .port_num = UART_NUM_1, \ + .data_bits = UART_DATA_8_BITS, \ + .stop_bits = UART_STOP_BITS_1, \ + .parity = UART_PARITY_DISABLE, \ + .baud_rate = 115200, \ + .flow_control = MODEM_FLOW_CONTROL_NONE,\ + .tx_io_num = 25, \ + .rx_io_num = 26, \ + .rts_io_num = 27, \ + .cts_io_num = 23, \ + .rx_buffer_size = 1024, \ + .tx_buffer_size = 512, \ + .pattern_queue_size = 20, \ + .event_queue_size = 30, \ + .event_task_stack_size = 2048, \ + .event_task_priority = 5, \ + .line_buffer_size = 512 \ + } + +/** + * @brief Create and initialize Modem DTE object + * + * @param config configuration of ESP Modem DTE object + * @return modem_dte_t* + * - Modem DTE object + */ +modem_dte_t *esp_modem_dte_init(const esp_modem_dte_config_t *config); + +/** + * @brief Register event handler for ESP Modem event loop + * + * @param dte modem_dte_t type object + * @param handler event handler to register + * @param handler_args arguments for registered handler + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_NO_MEM on allocating memory for the handler failed + * - ESP_ERR_INVALID_ARG on invalid combination of event base and event id + */ +esp_err_t esp_modem_set_event_handler(modem_dte_t *dte, esp_event_handler_t handler, int32_t event_id, void *handler_args); + +/** + * @brief Unregister event handler for ESP Modem event loop + * + * @param dte modem_dte_t type object + * @param handler event handler to unregister + * @return esp_err_t + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG on invalid combination of event base and event id + */ +esp_err_t esp_modem_remove_event_handler(modem_dte_t *dte, esp_event_handler_t handler); + +/** + * @brief Setup PPP Session + * + * @param dte Modem DTE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_start_ppp(modem_dte_t *dte); + +/** + * @brief Exit PPP Session + * + * @param dte Modem DTE Object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_stop_ppp(modem_dte_t *dte); + +/** + * @brief Setup on reception callback + * + * @param dte ESP Modem DTE object + * @param receive_cb Function pointer to the reception callback + * @param receive_cb_ctx Contextual pointer to be passed to the reception callback + * + * @return ESP_OK on success + */ +esp_err_t esp_modem_set_rx_cb(modem_dte_t *dte, esp_modem_on_receive receive_cb, void *receive_cb_ctx); + +/** + * @brief Notify the modem, that ppp netif has closed + * + * @note This API should only be used internally by the modem-netif layer + * + * @param dte ESP Modem DTE object + * + * @return ESP_OK on success + */ +esp_err_t esp_modem_notify_ppp_netif_closed(modem_dte_t *dte); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem_compat.h b/esp_modem/include/esp_modem_compat.h new file mode 100644 index 000000000..592fd12e6 --- /dev/null +++ b/esp_modem/include/esp_modem_compat.h @@ -0,0 +1,62 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "lwip/ip.h" + +/** +* @brief ESP Modem Event backward compatible version +*/ +typedef enum { + MODEM_EVENT_PPP_START = 0x100, + MODEM_EVENT_PPP_CONNECT = 0x101, + MODEM_EVENT_PPP_DISCONNECT = 0x102, + MODEM_EVENT_PPP_STOP = 0x103, + MODEM_EVENT_UNKNOWN = 0x104, +} esp_modem_compat_event_t; + +/** + * @brief PPPoS Client IP Information backward compatible version + * + */ +typedef struct { + ip4_addr_t ip; /*!< IP Address */ + ip4_addr_t netmask; /*!< Net Mask */ + ip4_addr_t gw; /*!< Gateway */ + ip4_addr_t ns1; /*!< Name Server1 */ + ip4_addr_t ns2; /*!< Name Server2 */ + } ppp_client_ip_info_t; + +/** + * @brief Backward compatible version of esp_modem_set_event_handler() + */ +esp_err_t esp_modem_add_event_handler(modem_dte_t *dte, esp_event_handler_t handler, void *handler_args) __attribute__ ((deprecated)); + +/** + * @brief Backward compatible version of creating esp-netif(PPP) and attaching to esp_modem_start_ppp() + */ +esp_err_t esp_modem_setup_ppp(modem_dte_t *dte) __attribute__ ((deprecated)); + +/** + * @brief Backward compatible version of deleting esp-netif and esp_modem_stop_ppp() + */ +esp_err_t esp_modem_exit_ppp(modem_dte_t *dte) __attribute__ ((deprecated)); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem_dce.h b/esp_modem/include/esp_modem_dce.h new file mode 100644 index 000000000..7c180b421 --- /dev/null +++ b/esp_modem/include/esp_modem_dce.h @@ -0,0 +1,99 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_types.h" +#include "esp_err.h" +#include "esp_modem_dte.h" + +typedef struct modem_dce modem_dce_t; +typedef struct modem_dte modem_dte_t; + +/** + * @brief Result Code from DCE + * + */ +#define MODEM_RESULT_CODE_SUCCESS "OK" /*!< Acknowledges execution of a command */ +#define MODEM_RESULT_CODE_CONNECT "CONNECT" /*!< A connection has been established */ +#define MODEM_RESULT_CODE_RING "RING" /*!< Detect an incoming call signal from network */ +#define MODEM_RESULT_CODE_NO_CARRIER "NO CARRIER" /*!< Connection termincated or establish a connection failed */ +#define MODEM_RESULT_CODE_ERROR "ERROR" /*!< Command not recognized, command line maximum length exceeded, parameter value invalid */ +#define MODEM_RESULT_CODE_NO_DIALTONE "NO DIALTONE" /*!< No dial tone detected */ +#define MODEM_RESULT_CODE_BUSY "BUSY" /*!< Engaged signal detected */ +#define MODEM_RESULT_CODE_NO_ANSWER "NO ANSWER" /*!< Wait for quiet answer */ + +/** + * @brief Specific Length Constraint + * + */ +#define MODEM_MAX_NAME_LENGTH (32) /*!< Max Module Name Length */ +#define MODEM_MAX_OPERATOR_LENGTH (32) /*!< Max Operator Name Length */ +#define MODEM_IMEI_LENGTH (15) /*!< IMEI Number Length */ +#define MODEM_IMSI_LENGTH (15) /*!< IMSI Number Length */ + +/** + * @brief Specific Timeout Constraint, Unit: millisecond + * + */ +#define MODEM_COMMAND_TIMEOUT_DEFAULT (500) /*!< Default timeout value for most commands */ +#define MODEM_COMMAND_TIMEOUT_OPERATOR (75000) /*!< Timeout value for getting operator status */ +#define MODEM_COMMAND_TIMEOUT_MODE_CHANGE (5000) /*!< Timeout value for changing working mode */ +#define MODEM_COMMAND_TIMEOUT_HANG_UP (90000) /*!< Timeout value for hang up */ +#define MODEM_COMMAND_TIMEOUT_POWEROFF (1000) /*!< Timeout value for power down */ + +/** + * @brief Working state of DCE + * + */ +typedef enum { + MODEM_STATE_PROCESSING, /*!< In processing */ + MODEM_STATE_SUCCESS, /*!< Process successfully */ + MODEM_STATE_FAIL /*!< Process failed */ +} modem_state_t; + +/** + * @brief DCE(Data Communication Equipment) + * + */ +struct modem_dce { + char imei[MODEM_IMEI_LENGTH + 1]; /*!< IMEI number */ + char imsi[MODEM_IMSI_LENGTH + 1]; /*!< IMSI number */ + char name[MODEM_MAX_NAME_LENGTH]; /*!< Module name */ + char oper[MODEM_MAX_OPERATOR_LENGTH]; /*!< Operator name */ + modem_state_t state; /*!< Modem working state */ + modem_mode_t mode; /*!< Working mode */ + modem_dte_t *dte; /*!< DTE which connect to DCE */ + esp_err_t (*handle_line)(modem_dce_t *dce, const char *line); /*!< Handle line strategy */ + esp_err_t (*sync)(modem_dce_t *dce); /*!< Synchronization */ + esp_err_t (*echo_mode)(modem_dce_t *dce, bool on); /*!< Echo command on or off */ + esp_err_t (*store_profile)(modem_dce_t *dce); /*!< Store user settings */ + esp_err_t (*set_flow_ctrl)(modem_dce_t *dce, modem_flow_ctrl_t flow_ctrl); /*!< Flow control on or off */ + esp_err_t (*get_signal_quality)(modem_dce_t *dce, uint32_t *rssi, uint32_t *ber); /*!< Get signal quality */ + esp_err_t (*get_battery_status)(modem_dce_t *dce, uint32_t *bcs, + uint32_t *bcl, uint32_t *voltage); /*!< Get battery status */ + esp_err_t (*define_pdp_context)(modem_dce_t *dce, uint32_t cid, + const char *type, const char *apn); /*!< Set PDP Contex */ + esp_err_t (*set_working_mode)(modem_dce_t *dce, modem_mode_t mode); /*!< Set working mode */ + esp_err_t (*hang_up)(modem_dce_t *dce); /*!< Hang up */ + esp_err_t (*power_down)(modem_dce_t *dce); /*!< Normal power down */ + esp_err_t (*deinit)(modem_dce_t *dce); /*!< Deinitialize */ +}; + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem_dce_service.h b/esp_modem/include/esp_modem_dce_service.h new file mode 100644 index 000000000..9b9a3a6c4 --- /dev/null +++ b/esp_modem/include/esp_modem_dce_service.h @@ -0,0 +1,131 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_modem_dce.h" + +/** + * @brief Indicate that processing current command has done + * + * @param dce Modem DCE object + * @param state Modem state after processing + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static inline esp_err_t esp_modem_process_command_done(modem_dce_t *dce, modem_state_t state) +{ + dce->state = state; + return dce->dte->process_cmd_done(dce->dte); +} + +/** + * @brief Strip the tailed "\r\n" + * + * @param str string to strip + * @param len length of string + */ +static inline void strip_cr_lf_tail(char *str, uint32_t len) +{ + if (str[len - 2] == '\r') { + str[len - 2] = '\0'; + } else if (str[len - 1] == '\r') { + str[len - 1] = '\0'; + } +} + +/** + * @brief Default handler for response + * Some responses for command are simple, commonly will return OK when succeed of ERROR when failed + * + * @param dce Modem DCE object + * @param line line string + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_handle_response_default(modem_dce_t *dce, const char *line); + +/** + * @brief Syncronization + * + * @param dce Modem DCE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_sync(modem_dce_t *dce); + +/** + * @brief Enable or not echo mode of DCE + * + * @param dce Modem DCE object + * @param on true to enable echo mode, false to disable echo mode + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_echo(modem_dce_t *dce, bool on); + +/** + * @brief Store current parameter setting in the user profile + * + * @param dce Modem DCE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_store_profile(modem_dce_t *dce); + +/** + * @brief Set flow control mode of DCE in data mode + * + * @param dce Modem DCE object + * @param flow_ctrl flow control mode + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_set_flow_ctrl(modem_dce_t *dce, modem_flow_ctrl_t flow_ctrl); + +/** + * @brief Define PDP context + * + * @param dce Modem DCE object + * @param cid PDP context identifier + * @param type Protocol type + * @param apn Access point name + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_define_pdp_context(modem_dce_t *dce, uint32_t cid, const char *type, const char *apn); + +/** + * @brief Hang up + * + * @param dce Modem DCE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +esp_err_t esp_modem_dce_hang_up(modem_dce_t *dce); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem_dte.h b/esp_modem/include/esp_modem_dte.h new file mode 100644 index 000000000..ba7380c05 --- /dev/null +++ b/esp_modem/include/esp_modem_dte.h @@ -0,0 +1,66 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_types.h" +#include "esp_err.h" +#include "esp_event.h" + +typedef struct modem_dte modem_dte_t; +typedef struct modem_dce modem_dce_t; + +/** + * @brief Working mode of Modem + * + */ +typedef enum { + MODEM_COMMAND_MODE = 0, /*!< Command Mode */ + MODEM_PPP_MODE, /*!< PPP Mode */ + MODEM_TRANSITION_MODE /*!< Transition Mode betwen data and command mode indicating that + the modem is not yet ready for sending commands nor data */ +} modem_mode_t; + +/** + * @brief Modem flow control type + * + */ +typedef enum { + MODEM_FLOW_CONTROL_NONE = 0, + MODEM_FLOW_CONTROL_SW, + MODEM_FLOW_CONTROL_HW +} modem_flow_ctrl_t; + +/** + * @brief DTE(Data Terminal Equipment) + * + */ +struct modem_dte { + modem_flow_ctrl_t flow_ctrl; /*!< Flow control of DTE */ + modem_dce_t *dce; /*!< DCE which connected to the DTE */ + esp_err_t (*send_cmd)(modem_dte_t *dte, const char *command, uint32_t timeout); /*!< Send command to DCE */ + int (*send_data)(modem_dte_t *dte, const char *data, uint32_t length); /*!< Send data to DCE */ + esp_err_t (*send_wait)(modem_dte_t *dte, const char *data, uint32_t length, + const char *prompt, uint32_t timeout); /*!< Wait for specific prompt */ + esp_err_t (*change_mode)(modem_dte_t *dte, modem_mode_t new_mode); /*!< Changing working mode */ + esp_err_t (*process_cmd_done)(modem_dte_t *dte); /*!< Callback when DCE process command done */ + esp_err_t (*deinit)(modem_dte_t *dte); /*!< Deinitialize */ +}; + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/esp_modem_netif.h b/esp_modem/include/esp_modem_netif.h new file mode 100644 index 000000000..1eac1b497 --- /dev/null +++ b/esp_modem/include/esp_modem_netif.h @@ -0,0 +1,53 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Creates handle to esp_modem used as an esp-netif driver + * + * @param dte ESP Modem DTE object + * + * @return opaque pointer to esp-modem IO driver used to attach to esp-netif + */ +void *esp_modem_netif_setup(modem_dte_t *dte); + +/** + * @brief Destroys the esp-netif driver handle + * + * @param h pointer to the esp-netif adapter for esp-modem + */ +void esp_modem_netif_teardown(void *h); + +/** + * @brief Clears default handlers for esp-modem lifecycle + * + * @param h pointer to the esp-netif adapter for esp-modem + */ +esp_err_t esp_modem_netif_clear_default_handlers(void *h); + +/** + * @brief Setups default handlers for esp-modem lifecycle + * + * @param h pointer to the esp-netif adapter for esp-modem + * @param esp_netif pointer corresponding esp-netif instance + */ +esp_err_t esp_modem_netif_set_default_handlers(void *h, esp_netif_t * esp_netif); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/sim7600.h b/esp_modem/include/sim7600.h new file mode 100644 index 000000000..4fbb9a1b1 --- /dev/null +++ b/esp_modem/include/sim7600.h @@ -0,0 +1,33 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_modem_dce_service.h" +#include "esp_modem.h" + +/** + * @brief Create and initialize SIM7600 object + * + * @param dte Modem DTE object + * @return modem_dce_t* Modem DCE object + */ +modem_dce_t *sim7600_init(modem_dte_t *dte); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/include/sim800.h b/esp_modem/include/sim800.h new file mode 100644 index 000000000..f0455c7f8 --- /dev/null +++ b/esp_modem/include/sim800.h @@ -0,0 +1,33 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_modem_dce_service.h" +#include "esp_modem.h" + +/** + * @brief Create and initialize SIM800 object + * + * @param dte Modem DTE object + * @return modem_dce_t* Modem DCE object + */ +modem_dce_t *sim800_init(modem_dte_t *dte); + +#ifdef __cplusplus +} +#endif diff --git a/esp_modem/private_include/bg96_private.h b/esp_modem/private_include/bg96_private.h new file mode 100644 index 000000000..6b245fc8f --- /dev/null +++ b/esp_modem/private_include/bg96_private.h @@ -0,0 +1,37 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#pragma once + +/** + * @brief Macro defined for error checking + * + */ +#define DCE_CHECK(a, str, goto_tag, ...) \ + do \ + { \ + if (!(a)) \ + { \ + ESP_LOGE(DCE_TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + goto goto_tag; \ + } \ + } while (0) + +/** + * @brief BG96 Modem + * + */ +typedef struct { + void *priv_resource; /*!< Private resource */ + modem_dce_t parent; /*!< DCE parent class */ +} bg96_modem_dce_t; diff --git a/esp_modem/src/bg96.c b/esp_modem/src/bg96.c new file mode 100644 index 000000000..892d13c0a --- /dev/null +++ b/esp_modem/src/bg96.c @@ -0,0 +1,472 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_log.h" +#include "bg96.h" +#include "bg96_private.h" + +#define MODEM_RESULT_CODE_POWERDOWN "POWERED DOWN" + +static const char *DCE_TAG = "bg96"; + +/** + * @brief Handle response from AT+CSQ + */ +static esp_err_t bg96_handle_csq(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CSQ", strlen("+CSQ"))) { + /* store value of rssi and ber */ + uint32_t **csq = bg96_dce->priv_resource; + /* +CSQ: , */ + sscanf(line, "%*s%d,%d", csq[0], csq[1]); + err = ESP_OK; + } + return err; +} + +/** + * @brief Handle response from AT+CBC + */ +static esp_err_t bg96_handle_cbc(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CBC", strlen("+CBC"))) { + /* store value of bcs, bcl, voltage */ + uint32_t **cbc = bg96_dce->priv_resource; + /* +CBC: ,, */ + sscanf(line, "%*s%d,%d,%d", cbc[0], cbc[1], cbc[2]); + err = ESP_OK; + } + return err; +} + +/** + * @brief Handle response from +++ + */ +static esp_err_t bg96_handle_exit_data_mode(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_NO_CARRIER)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +/** + * @brief Handle response from ATD*99# + */ +static esp_err_t bg96_handle_atd_ppp(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_CONNECT)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +/** + * @brief Handle response from AT+CGMM + */ +static esp_err_t bg96_handle_cgmm(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->name, MODEM_MAX_NAME_LENGTH, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->name, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+CGSN + */ +static esp_err_t bg96_handle_cgsn(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->imei, MODEM_IMEI_LENGTH + 1, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->imei, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+CIMI + */ +static esp_err_t bg96_handle_cimi(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->imsi, MODEM_IMSI_LENGTH + 1, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->imsi, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+COPS? + */ +static esp_err_t bg96_handle_cops(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+COPS", strlen("+COPS"))) { + /* there might be some random spaces in operator's name, we can not use sscanf to parse the result */ + /* strtok will break the string, we need to create a copy */ + size_t len = strlen(line); + char *line_copy = malloc(len + 1); + strcpy(line_copy, line); + /* +COPS: [, [, ]] */ + char *str_ptr = NULL; + char *p[3]; + uint8_t i = 0; + /* strtok will broke string by replacing delimiter with '\0' */ + p[i] = strtok_r(line_copy, ",", &str_ptr); + while (p[i]) { + p[++i] = strtok_r(NULL, ",", &str_ptr); + } + if (i >= 3) { + int len = snprintf(dce->oper, MODEM_MAX_OPERATOR_LENGTH, "%s", p[2]); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->oper, len); + err = ESP_OK; + } + } + free(line_copy); + } + return err; +} + +/** + * @brief Handle response from AT+QPOWD=1 + */ +static esp_err_t bg96_handle_power_down(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = ESP_OK; + } else if (strstr(line, MODEM_RESULT_CODE_POWERDOWN)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } + return err; +} + +/** + * @brief Get signal quality + * + * @param dce Modem DCE object + * @param rssi received signal strength indication + * @param ber bit error ratio + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_signal_quality(modem_dce_t *dce, uint32_t *rssi, uint32_t *ber) +{ + modem_dte_t *dte = dce->dte; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + uint32_t *resource[2] = {rssi, ber}; + bg96_dce->priv_resource = resource; + dce->handle_line = bg96_handle_csq; + DCE_CHECK(dte->send_cmd(dte, "AT+CSQ\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "inquire signal quality failed", err); + ESP_LOGD(DCE_TAG, "inquire signal quality ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get battery status + * + * @param dce Modem DCE object + * @param bcs Battery charge status + * @param bcl Battery connection level + * @param voltage Battery voltage + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_battery_status(modem_dce_t *dce, uint32_t *bcs, uint32_t *bcl, uint32_t *voltage) +{ + modem_dte_t *dte = dce->dte; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + uint32_t *resource[3] = {bcs, bcl, voltage}; + bg96_dce->priv_resource = resource; + dce->handle_line = bg96_handle_cbc; + DCE_CHECK(dte->send_cmd(dte, "AT+CBC\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "inquire battery status failed", err); + ESP_LOGD(DCE_TAG, "inquire battery status ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Set Working Mode + * + * @param dce Modem DCE object + * @param mode woking mode + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_set_working_mode(modem_dce_t *dce, modem_mode_t mode) +{ + modem_dte_t *dte = dce->dte; + switch (mode) { + case MODEM_COMMAND_MODE: + vTaskDelay(pdMS_TO_TICKS(1000)); // spec: 1s delay for the modem to recognize the escape sequence + dce->handle_line = bg96_handle_exit_data_mode; + if (dte->send_cmd(dte, "+++", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) != ESP_OK) { + // "+++" Could fail if we are already in the command mode. + // in that case we ignore the timout and re-sync the modem + ESP_LOGI(DCE_TAG, "Sending \"+++\" command failed"); + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, "AT\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "sync failed", err); + } else { + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "enter command mode failed", err); + } + ESP_LOGD(DCE_TAG, "enter command mode ok"); + dce->mode = MODEM_COMMAND_MODE; + break; + case MODEM_PPP_MODE: + dce->handle_line = bg96_handle_atd_ppp; + DCE_CHECK(dte->send_cmd(dte, "ATD*99***1#\r", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) == ESP_OK, "send command failed", err); + if (dce->state != MODEM_STATE_SUCCESS) { + // Initiate PPP mode could fail, if we've already "dialed" the data call before. + // in that case we retry with "ATO" to just resume the data mode + ESP_LOGD(DCE_TAG, "enter ppp mode failed, retry with ATO"); + dce->handle_line = bg96_handle_atd_ppp; + DCE_CHECK(dte->send_cmd(dte, "ATO\r", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "enter ppp mode failed", err); + } + ESP_LOGD(DCE_TAG, "enter ppp mode ok"); + dce->mode = MODEM_PPP_MODE; + break; + default: + ESP_LOGW(DCE_TAG, "unsupported working mode: %d", mode); + goto err; + break; + } + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Power down + * + * @param bg96_dce bg96 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_power_down(modem_dce_t *dce) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = bg96_handle_power_down; + DCE_CHECK(dte->send_cmd(dte, "AT+QPOWD=1\r", MODEM_COMMAND_TIMEOUT_POWEROFF) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "power down failed", err); + ESP_LOGD(DCE_TAG, "power down ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module name + * + * @param bg96_dce bg96 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_module_name(bg96_modem_dce_t *bg96_dce) +{ + modem_dte_t *dte = bg96_dce->parent.dte; + bg96_dce->parent.handle_line = bg96_handle_cgmm; + DCE_CHECK(dte->send_cmd(dte, "AT+CGMM\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(bg96_dce->parent.state == MODEM_STATE_SUCCESS, "get module name failed", err); + ESP_LOGD(DCE_TAG, "get module name ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module IMEI number + * + * @param bg96_dce bg96 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_imei_number(bg96_modem_dce_t *bg96_dce) +{ + modem_dte_t *dte = bg96_dce->parent.dte; + bg96_dce->parent.handle_line = bg96_handle_cgsn; + DCE_CHECK(dte->send_cmd(dte, "AT+CGSN\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(bg96_dce->parent.state == MODEM_STATE_SUCCESS, "get imei number failed", err); + ESP_LOGD(DCE_TAG, "get imei number ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module IMSI number + * + * @param bg96_dce bg96 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_imsi_number(bg96_modem_dce_t *bg96_dce) +{ + modem_dte_t *dte = bg96_dce->parent.dte; + bg96_dce->parent.handle_line = bg96_handle_cimi; + DCE_CHECK(dte->send_cmd(dte, "AT+CIMI\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(bg96_dce->parent.state == MODEM_STATE_SUCCESS, "get imsi number failed", err); + ESP_LOGD(DCE_TAG, "get imsi number ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get Operator's name + * + * @param bg96_dce bg96 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t bg96_get_operator_name(bg96_modem_dce_t *bg96_dce) +{ + modem_dte_t *dte = bg96_dce->parent.dte; + bg96_dce->parent.handle_line = bg96_handle_cops; + DCE_CHECK(dte->send_cmd(dte, "AT+COPS?\r", MODEM_COMMAND_TIMEOUT_OPERATOR) == ESP_OK, "send command failed", err); + DCE_CHECK(bg96_dce->parent.state == MODEM_STATE_SUCCESS, "get network operator failed", err); + ESP_LOGD(DCE_TAG, "get network operator ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Deinitialize BG96 object + * + * @param dce Modem DCE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on fail + */ +static esp_err_t bg96_deinit(modem_dce_t *dce) +{ + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + if (dce->dte) { + dce->dte->dce = NULL; + } + free(bg96_dce); + return ESP_OK; +} + +modem_dce_t *bg96_init(modem_dte_t *dte) +{ + DCE_CHECK(dte, "DCE should bind with a DTE", err); + /* malloc memory for bg96_dce object */ + bg96_modem_dce_t *bg96_dce = calloc(1, sizeof(bg96_modem_dce_t)); + DCE_CHECK(bg96_dce, "calloc bg96_dce failed", err); + /* Bind DTE with DCE */ + bg96_dce->parent.dte = dte; + dte->dce = &(bg96_dce->parent); + /* Bind methods */ + bg96_dce->parent.handle_line = NULL; + bg96_dce->parent.sync = esp_modem_dce_sync; + bg96_dce->parent.echo_mode = esp_modem_dce_echo; + bg96_dce->parent.store_profile = esp_modem_dce_store_profile; + bg96_dce->parent.set_flow_ctrl = esp_modem_dce_set_flow_ctrl; + bg96_dce->parent.define_pdp_context = esp_modem_dce_define_pdp_context; + bg96_dce->parent.hang_up = esp_modem_dce_hang_up; + bg96_dce->parent.get_signal_quality = bg96_get_signal_quality; + bg96_dce->parent.get_battery_status = bg96_get_battery_status; + bg96_dce->parent.set_working_mode = bg96_set_working_mode; + bg96_dce->parent.power_down = bg96_power_down; + bg96_dce->parent.deinit = bg96_deinit; + /* Sync between DTE and DCE */ + DCE_CHECK(esp_modem_dce_sync(&(bg96_dce->parent)) == ESP_OK, "sync failed", err_io); + /* Close echo */ + DCE_CHECK(esp_modem_dce_echo(&(bg96_dce->parent), false) == ESP_OK, "close echo mode failed", err_io); + /* Get Module name */ + DCE_CHECK(bg96_get_module_name(bg96_dce) == ESP_OK, "get module name failed", err_io); + /* Get IMEI number */ + DCE_CHECK(bg96_get_imei_number(bg96_dce) == ESP_OK, "get imei failed", err_io); + /* Get IMSI number */ + DCE_CHECK(bg96_get_imsi_number(bg96_dce) == ESP_OK, "get imsi failed", err_io); + /* Get operator name */ + DCE_CHECK(bg96_get_operator_name(bg96_dce) == ESP_OK, "get operator name failed", err_io); + return &(bg96_dce->parent); +err_io: + free(bg96_dce); +err: + return NULL; +} diff --git a/esp_modem/src/esp_modem.c b/esp_modem/src/esp_modem.c new file mode 100644 index 000000000..f49f0015b --- /dev/null +++ b/esp_modem/src/esp_modem.c @@ -0,0 +1,566 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_modem.h" +#include "esp_log.h" +#include "sdkconfig.h" + +#define ESP_MODEM_EVENT_QUEUE_SIZE (16) + +#define MIN_PATTERN_INTERVAL (9) +#define MIN_POST_IDLE (0) +#define MIN_PRE_IDLE (0) + +/** + * @brief Macro defined for error checking + * + */ +static const char *MODEM_TAG = "esp-modem"; +#define MODEM_CHECK(a, str, goto_tag, ...) \ + do \ + { \ + if (!(a)) \ + { \ + ESP_LOGE(MODEM_TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + goto goto_tag; \ + } \ + } while (0) + +ESP_EVENT_DEFINE_BASE(ESP_MODEM_EVENT); + +/** + * @brief ESP32 Modem DTE + * + */ +typedef struct { + uart_port_t uart_port; /*!< UART port */ + uint8_t *buffer; /*!< Internal buffer to store response lines/data from DCE */ + QueueHandle_t event_queue; /*!< UART event queue handle */ + esp_event_loop_handle_t event_loop_hdl; /*!< Event loop handle */ + TaskHandle_t uart_event_task_hdl; /*!< UART event task handle */ + SemaphoreHandle_t process_sem; /*!< Semaphore used for indicating processing status */ + SemaphoreHandle_t exit_sem; /*!< Semaphore used for indicating PPP mode has stopped */ + modem_dte_t parent; /*!< DTE interface that should extend */ + esp_modem_on_receive receive_cb; /*!< ptr to data reception */ + void *receive_cb_ctx; /*!< ptr to rx fn context data */ + int line_buffer_size; /*!< line buffer size in commnad mode */ + int pattern_queue_size; /*!< UART pattern queue size */ +} esp_modem_dte_t; + +/** + * @brief Returns true if the supplied string contains only CR or LF + * + * @param str string to check + * @param len length of string + */ +static inline bool is_only_cr_lf(const char *str, uint32_t len) +{ + for (int i=0; ireceive_cb_ctx = receive_cb_ctx; + esp_dte->receive_cb = receive_cb; + return ESP_OK; +} + + +/** + * @brief Handle one line in DTE + * + * @param esp_dte ESP modem DTE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t esp_dte_handle_line(esp_modem_dte_t *esp_dte) +{ + modem_dce_t *dce = esp_dte->parent.dce; + MODEM_CHECK(dce, "DTE has not yet bind with DCE", err); + const char *line = (const char *)(esp_dte->buffer); + size_t len = strlen(line); + /* Skip pure "\r\n" lines */ + if (len > 2 && !is_only_cr_lf(line, len)) { + MODEM_CHECK(dce->handle_line, "no handler for line", err_handle); + MODEM_CHECK(dce->handle_line(dce, line) == ESP_OK, "handle line failed", err_handle); + } + return ESP_OK; +err_handle: + /* Send ESP_MODEM_EVENT_UNKNOWN signal to event loop */ + esp_event_post_to(esp_dte->event_loop_hdl, ESP_MODEM_EVENT, ESP_MODEM_EVENT_UNKNOWN, + (void *)line, strlen(line) + 1, pdMS_TO_TICKS(100)); +err: + return ESP_FAIL; +} + +/** + * @brief Handle when a pattern has been detected by UART + * + * @param esp_dte ESP32 Modem DTE object + */ +static void esp_handle_uart_pattern(esp_modem_dte_t *esp_dte) +{ + int pos = uart_pattern_pop_pos(esp_dte->uart_port); + int read_len = 0; + if (pos != -1) { + if (pos < esp_dte->line_buffer_size - 1) { + /* read one line(include '\n') */ + read_len = pos + 1; + } else { + ESP_LOGW(MODEM_TAG, "ESP Modem Line buffer too small"); + read_len = esp_dte->line_buffer_size - 1; + } + read_len = uart_read_bytes(esp_dte->uart_port, esp_dte->buffer, read_len, pdMS_TO_TICKS(100)); + if (read_len) { + /* make sure the line is a standard string */ + esp_dte->buffer[read_len] = '\0'; + /* Send new line to handle */ + esp_dte_handle_line(esp_dte); + } else { + ESP_LOGE(MODEM_TAG, "uart read bytes failed"); + } + } else { + size_t length = 0; + uart_get_buffered_data_len(esp_dte->uart_port, &length); + ESP_LOGW(MODEM_TAG, "Pattern not found in the pattern queue, uart data length = %d", length); + length = MIN(esp_dte->line_buffer_size-1, length); + length = uart_read_bytes(esp_dte->uart_port, esp_dte->buffer, length, portMAX_DELAY); + ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", esp_dte->buffer, length, ESP_LOG_DEBUG); + uart_flush(esp_dte->uart_port); + } +} + +/** + * @brief Handle when new data received by UART + * + * @param esp_dte ESP32 Modem DTE object + */ +static void esp_handle_uart_data(esp_modem_dte_t *esp_dte) +{ + size_t length = 0; + uart_get_buffered_data_len(esp_dte->uart_port, &length); + if (esp_dte->parent.dce->mode != MODEM_PPP_MODE) { + // Check if matches the pattern to process the data as pattern + int pos = uart_pattern_pop_pos(esp_dte->uart_port); + if (pos > -1) { + esp_handle_uart_pattern(esp_dte); + return; + } + // Read the data and process it using `handle_line` logic + length = MIN(esp_dte->line_buffer_size-1, length); + length = uart_read_bytes(esp_dte->uart_port, esp_dte->buffer, length, portMAX_DELAY); + ESP_LOG_BUFFER_HEXDUMP("esp-modem: debug_data", esp_dte->buffer, length, ESP_LOG_DEBUG); + esp_dte->buffer[length] = '\0'; + if (esp_dte->parent.dce->handle_line) { + /* Send new line to handle if handler registered */ + esp_dte_handle_line(esp_dte); + } + return; + } + + length = MIN(esp_dte->line_buffer_size, length); + length = uart_read_bytes(esp_dte->uart_port, esp_dte->buffer, length, portMAX_DELAY); + /* pass the input data to configured callback */ + if (length) { + esp_dte->receive_cb(esp_dte->buffer, length, esp_dte->receive_cb_ctx); + } +} + +/** + * @brief UART Event Task Entry + * + * @param param task parameter + */ +static void uart_event_task_entry(void *param) +{ + esp_modem_dte_t *esp_dte = (esp_modem_dte_t *)param; + uart_event_t event; + while (1) { + if (xQueueReceive(esp_dte->event_queue, &event, pdMS_TO_TICKS(100))) { + switch (event.type) { + case UART_DATA: + esp_handle_uart_data(esp_dte); + break; + case UART_FIFO_OVF: + ESP_LOGW(MODEM_TAG, "HW FIFO Overflow"); + uart_flush_input(esp_dte->uart_port); + xQueueReset(esp_dte->event_queue); + break; + case UART_BUFFER_FULL: + ESP_LOGW(MODEM_TAG, "Ring Buffer Full"); + uart_flush_input(esp_dte->uart_port); + xQueueReset(esp_dte->event_queue); + break; + case UART_BREAK: + ESP_LOGW(MODEM_TAG, "Rx Break"); + break; + case UART_PARITY_ERR: + ESP_LOGE(MODEM_TAG, "Parity Error"); + break; + case UART_FRAME_ERR: + ESP_LOGE(MODEM_TAG, "Frame Error"); + break; + case UART_PATTERN_DET: + esp_handle_uart_pattern(esp_dte); + break; + default: + ESP_LOGW(MODEM_TAG, "unknown uart event type: %d", event.type); + break; + } + } + /* Drive the event loop */ + esp_event_loop_run(esp_dte->event_loop_hdl, pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); +} + +/** + * @brief Send command to DCE + * + * @param dte Modem DTE object + * @param command command string + * @param timeout timeout value, unit: ms + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t esp_modem_dte_send_cmd(modem_dte_t *dte, const char *command, uint32_t timeout) +{ + esp_err_t ret = ESP_FAIL; + modem_dce_t *dce = dte->dce; + MODEM_CHECK(dce, "DTE has not yet bind with DCE", err); + MODEM_CHECK(command, "command is NULL", err); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + /* Calculate timeout clock tick */ + /* Reset runtime information */ + dce->state = MODEM_STATE_PROCESSING; + /* Send command via UART */ + uart_write_bytes(esp_dte->uart_port, command, strlen(command)); + /* Check timeout */ + MODEM_CHECK(xSemaphoreTake(esp_dte->process_sem, pdMS_TO_TICKS(timeout)) == pdTRUE, "process command timeout", err); + ret = ESP_OK; +err: + dce->handle_line = NULL; + return ret; +} + +/** + * @brief Send data to DCE + * + * @param dte Modem DTE object + * @param data data buffer + * @param length length of data to send + * @return int actual length of data that has been send out + */ +static int esp_modem_dte_send_data(modem_dte_t *dte, const char *data, uint32_t length) +{ + MODEM_CHECK(data, "data is NULL", err); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + if (esp_dte->parent.dce->mode == MODEM_TRANSITION_MODE) { + ESP_LOGD(MODEM_TAG, "Not sending data in transition mode"); + return -1; + } + return uart_write_bytes(esp_dte->uart_port, data, length); +err: + return -1; +} + + + +/** + * @brief Send data and wait for prompt from DCE + * + * @param dte Modem DTE object + * @param data data buffer + * @param length length of data to send + * @param prompt pointer of specific prompt + * @param timeout timeout value (unit: ms) + * @return esp_err_t + * ESP_OK on success + * ESP_FAIL on error + */ +static esp_err_t esp_modem_dte_send_wait(modem_dte_t *dte, const char *data, uint32_t length, + const char *prompt, uint32_t timeout) +{ + MODEM_CHECK(data, "data is NULL", err_param); + MODEM_CHECK(prompt, "prompt is NULL", err_param); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + // We'd better disable pattern detection here for a moment in case prompt string contains the pattern character + uart_disable_pattern_det_intr(esp_dte->uart_port); + // uart_disable_rx_intr(esp_dte->uart_port); + MODEM_CHECK(uart_write_bytes(esp_dte->uart_port, data, length) >= 0, "uart write bytes failed", err_write); + uint32_t len = strlen(prompt); + uint8_t *buffer = calloc(len + 1, sizeof(uint8_t)); + int res = uart_read_bytes(esp_dte->uart_port, buffer, len, pdMS_TO_TICKS(timeout)); + MODEM_CHECK(res >= len, "wait prompt [%s] timeout", err, prompt); + MODEM_CHECK(!strncmp(prompt, (const char *)buffer, len), "get wrong prompt: %s", err, buffer); + free(buffer); + uart_enable_pattern_det_baud_intr(esp_dte->uart_port, '\n', 1, MIN_PATTERN_INTERVAL, MIN_POST_IDLE, MIN_PRE_IDLE); + return ESP_OK; +err: + free(buffer); +err_write: + uart_enable_pattern_det_baud_intr(esp_dte->uart_port, '\n', 1, MIN_PATTERN_INTERVAL, MIN_POST_IDLE, MIN_PRE_IDLE); +err_param: + return ESP_FAIL; +} + +/** + * @brief Change Modem's working mode + * + * @param dte Modem DTE object + * @param new_mode new working mode + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t esp_modem_dte_change_mode(modem_dte_t *dte, modem_mode_t new_mode) +{ + modem_dce_t *dce = dte->dce; + MODEM_CHECK(dce, "DTE has not yet bind with DCE", err); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + modem_mode_t current_mode = dce->mode; + MODEM_CHECK(current_mode != new_mode, "already in mode: %d", err, new_mode); + dce->mode = MODEM_TRANSITION_MODE; // mode switching will be finished in set_working_mode() on success + // (or restored on failure) + switch (new_mode) { + case MODEM_PPP_MODE: + MODEM_CHECK(dce->set_working_mode(dce, new_mode) == ESP_OK, "set new working mode:%d failed", err_restore_mode, new_mode); + uart_disable_pattern_det_intr(esp_dte->uart_port); + uart_enable_rx_intr(esp_dte->uart_port); + break; + case MODEM_COMMAND_MODE: + MODEM_CHECK(dce->set_working_mode(dce, new_mode) == ESP_OK, "set new working mode:%d failed", err_restore_mode, new_mode); + uart_disable_rx_intr(esp_dte->uart_port); + uart_flush(esp_dte->uart_port); + uart_enable_pattern_det_baud_intr(esp_dte->uart_port, '\n', 1, MIN_PATTERN_INTERVAL, MIN_POST_IDLE, MIN_PRE_IDLE); + uart_pattern_queue_reset(esp_dte->uart_port, esp_dte->pattern_queue_size); + break; + default: + break; + } + return ESP_OK; +err_restore_mode: + dce->mode = current_mode; +err: + return ESP_FAIL; +} + +static esp_err_t esp_modem_dte_process_cmd_done(modem_dte_t *dte) +{ + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + return xSemaphoreGive(esp_dte->process_sem) == pdTRUE ? ESP_OK : ESP_FAIL; +} + +/** + * @brief Deinitialize a Modem DTE object + * + * @param dte Modem DTE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t esp_modem_dte_deinit(modem_dte_t *dte) +{ + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + /* Delete UART event task */ + vTaskDelete(esp_dte->uart_event_task_hdl); + /* Delete semaphores */ + vSemaphoreDelete(esp_dte->process_sem); + vSemaphoreDelete(esp_dte->exit_sem); + /* Delete event loop */ + esp_event_loop_delete(esp_dte->event_loop_hdl); + /* Uninstall UART Driver */ + uart_driver_delete(esp_dte->uart_port); + /* Free memory */ + free(esp_dte->buffer); + if (dte->dce) { + dte->dce->dte = NULL; + } + free(esp_dte); + return ESP_OK; +} + + + +modem_dte_t *esp_modem_dte_init(const esp_modem_dte_config_t *config) +{ + esp_err_t res; + /* malloc memory for esp_dte object */ + esp_modem_dte_t *esp_dte = calloc(1, sizeof(esp_modem_dte_t)); + MODEM_CHECK(esp_dte, "calloc esp_dte failed", err_dte_mem); + /* malloc memory to storing lines from modem dce */ + esp_dte->line_buffer_size = config->line_buffer_size; + esp_dte->buffer = calloc(1, config->line_buffer_size); + MODEM_CHECK(esp_dte->buffer, "calloc line memory failed", err_line_mem); + /* Set attributes */ + esp_dte->uart_port = config->port_num; + esp_dte->parent.flow_ctrl = config->flow_control; + /* Bind methods */ + esp_dte->parent.send_cmd = esp_modem_dte_send_cmd; + esp_dte->parent.send_data = esp_modem_dte_send_data; + esp_dte->parent.send_wait = esp_modem_dte_send_wait; + esp_dte->parent.change_mode = esp_modem_dte_change_mode; + esp_dte->parent.process_cmd_done = esp_modem_dte_process_cmd_done; + esp_dte->parent.deinit = esp_modem_dte_deinit; + + /* Config UART */ + uart_config_t uart_config = { + .baud_rate = config->baud_rate, + .data_bits = config->data_bits, + .parity = config->parity, + .stop_bits = config->stop_bits, + .source_clk = UART_SCLK_REF_TICK, + .flow_ctrl = (config->flow_control == MODEM_FLOW_CONTROL_HW) ? UART_HW_FLOWCTRL_CTS_RTS : UART_HW_FLOWCTRL_DISABLE + }; + MODEM_CHECK(uart_param_config(esp_dte->uart_port, &uart_config) == ESP_OK, "config uart parameter failed", err_uart_config); + if (config->flow_control == MODEM_FLOW_CONTROL_HW) { + res = uart_set_pin(esp_dte->uart_port, config->tx_io_num, config->rx_io_num, + config->rts_io_num, config->cts_io_num); + } else { + res = uart_set_pin(esp_dte->uart_port, config->tx_io_num, config->rx_io_num, + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + } + MODEM_CHECK(res == ESP_OK, "config uart gpio failed", err_uart_config); + /* Set flow control threshold */ + if (config->flow_control == MODEM_FLOW_CONTROL_HW) { + res = uart_set_hw_flow_ctrl(esp_dte->uart_port, UART_HW_FLOWCTRL_CTS_RTS, UART_FIFO_LEN - 8); + } else if (config->flow_control == MODEM_FLOW_CONTROL_SW) { + res = uart_set_sw_flow_ctrl(esp_dte->uart_port, true, 8, UART_FIFO_LEN - 8); + } + MODEM_CHECK(res == ESP_OK, "config uart flow control failed", err_uart_config); + /* Install UART driver and get event queue used inside driver */ + res = uart_driver_install(esp_dte->uart_port, config->rx_buffer_size, config->tx_buffer_size, + config->event_queue_size, &(esp_dte->event_queue), 0); + MODEM_CHECK(res == ESP_OK, "install uart driver failed", err_uart_config); + res = uart_set_rx_timeout(esp_dte->uart_port, 1); + MODEM_CHECK(res == ESP_OK, "set rx timeout failed", err_uart_config); + + /* Set pattern interrupt, used to detect the end of a line. */ + res = uart_enable_pattern_det_baud_intr(esp_dte->uart_port, '\n', 1, MIN_PATTERN_INTERVAL, MIN_POST_IDLE, MIN_PRE_IDLE); + /* Set pattern queue size */ + esp_dte->pattern_queue_size = config->pattern_queue_size; + res |= uart_pattern_queue_reset(esp_dte->uart_port, config->pattern_queue_size); + /* Starting in command mode -> explicitly disable RX interrupt */ + uart_disable_rx_intr(esp_dte->uart_port); + + MODEM_CHECK(res == ESP_OK, "config uart pattern failed", err_uart_pattern); + /* Create Event loop */ + esp_event_loop_args_t loop_args = { + .queue_size = ESP_MODEM_EVENT_QUEUE_SIZE, + .task_name = NULL + }; + MODEM_CHECK(esp_event_loop_create(&loop_args, &esp_dte->event_loop_hdl) == ESP_OK, "create event loop failed", err_eloop); + /* Create semaphore */ + esp_dte->process_sem = xSemaphoreCreateBinary(); + MODEM_CHECK(esp_dte->process_sem, "create process semaphore failed", err_sem1); + esp_dte->exit_sem = xSemaphoreCreateBinary(); + MODEM_CHECK(esp_dte->exit_sem, "create exit semaphore failed", err_sem); + + /* Create UART Event task */ + BaseType_t ret = xTaskCreate(uart_event_task_entry, //Task Entry + "uart_event", //Task Name + config->event_task_stack_size, //Task Stack Size(Bytes) + esp_dte, //Task Parameter + config->event_task_priority, //Task Priority + & (esp_dte->uart_event_task_hdl) //Task Handler + ); + MODEM_CHECK(ret == pdTRUE, "create uart event task failed", err_tsk_create); + return &(esp_dte->parent); + /* Error handling */ +err_tsk_create: + vSemaphoreDelete(esp_dte->exit_sem); +err_sem: + vSemaphoreDelete(esp_dte->process_sem); +err_sem1: + esp_event_loop_delete(esp_dte->event_loop_hdl); +err_eloop: + uart_disable_pattern_det_intr(esp_dte->uart_port); +err_uart_pattern: + uart_driver_delete(esp_dte->uart_port); +err_uart_config: + free(esp_dte->buffer); +err_line_mem: + free(esp_dte); +err_dte_mem: + return NULL; +} + +esp_err_t esp_modem_set_event_handler(modem_dte_t *dte, esp_event_handler_t handler, int32_t event_id, void *handler_args) +{ + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + return esp_event_handler_register_with(esp_dte->event_loop_hdl, ESP_MODEM_EVENT, event_id, handler, handler_args); +} + +esp_err_t esp_modem_remove_event_handler(modem_dte_t *dte, esp_event_handler_t handler) +{ + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + return esp_event_handler_unregister_with(esp_dte->event_loop_hdl, ESP_MODEM_EVENT, ESP_EVENT_ANY_ID, handler); +} + +esp_err_t esp_modem_start_ppp(modem_dte_t *dte) +{ + modem_dce_t *dce = dte->dce; + MODEM_CHECK(dce, "DTE has not yet bind with DCE", err); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + /* Set PDP Context */ + MODEM_CHECK(dce->define_pdp_context(dce, 1, "IP", CONFIG_EXAMPLE_COMPONENT_MODEM_APN) == ESP_OK, "set MODEM APN failed", err); + /* Enter PPP mode */ + MODEM_CHECK(dte->change_mode(dte, MODEM_PPP_MODE) == ESP_OK, "enter ppp mode failed", err); + + /* post PPP mode started event */ + esp_event_post_to(esp_dte->event_loop_hdl, ESP_MODEM_EVENT, ESP_MODEM_EVENT_PPP_START, NULL, 0, 0); + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_stop_ppp(modem_dte_t *dte) +{ + modem_dce_t *dce = dte->dce; + MODEM_CHECK(dce, "DTE has not yet bind with DCE", err); + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + + /* Enter command mode */ + MODEM_CHECK(dte->change_mode(dte, MODEM_COMMAND_MODE) == ESP_OK, "enter command mode failed", err); + /* post PPP mode stopped event */ + esp_event_post_to(esp_dte->event_loop_hdl, ESP_MODEM_EVENT, ESP_MODEM_EVENT_PPP_STOP, NULL, 0, 0); + /* Hang up */ + MODEM_CHECK(dce->hang_up(dce) == ESP_OK, "hang up failed", err); + /* wait for the PPP mode to exit gracefully */ + if (xSemaphoreTake(esp_dte->exit_sem, pdMS_TO_TICKS(20000)) != pdTRUE) { + ESP_LOGW(MODEM_TAG, "Failed to exit the PPP mode gracefully"); + } + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_notify_ppp_netif_closed(modem_dte_t *dte) +{ + esp_modem_dte_t *esp_dte = __containerof(dte, esp_modem_dte_t, parent); + return xSemaphoreGive(esp_dte->exit_sem) == pdTRUE ? ESP_OK : ESP_FAIL; +} \ No newline at end of file diff --git a/esp_modem/src/esp_modem_compat.c b/esp_modem/src/esp_modem_compat.c new file mode 100644 index 000000000..297acc248 --- /dev/null +++ b/esp_modem/src/esp_modem_compat.c @@ -0,0 +1,104 @@ +// Copyright 2019 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_netif.h" +#include "esp_netif_ppp.h" +#include "esp_modem.h" +#include "esp_modem_netif.h" +#include "esp_log.h" + +static const char *TAG = "esp-modem-compat"; + +static void on_modem_compat_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + int32_t compat_event_id = MODEM_EVENT_UNKNOWN; + switch (event_id) { + case ESP_MODEM_EVENT_PPP_START: + compat_event_id = MODEM_EVENT_PPP_START; + break; + case ESP_MODEM_EVENT_PPP_STOP: + compat_event_id = MODEM_EVENT_PPP_STOP; + break; + default: + break; + } + esp_event_post(ESP_MODEM_EVENT, compat_event_id, NULL, 0, 0); +} + +static void on_ip_event(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "IP event! %d", event_id); + if (event_id == IP_EVENT_PPP_GOT_IP) { + esp_netif_dns_info_t dns_info; + ppp_client_ip_info_t ipinfo = {0}; + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + esp_netif_t *netif = event->esp_netif; + ipinfo.ip.addr = event->ip_info.ip.addr; + ipinfo.gw.addr = event->ip_info.gw.addr; + ipinfo.netmask.addr = event->ip_info.netmask.addr; + esp_netif_get_dns_info(netif, 0, &dns_info); + ipinfo.ns1.addr = dns_info.ip.u_addr.ip4.addr; + ipinfo.ns2.addr = dns_info.ip.u_addr.ip4.addr; + esp_event_post(ESP_MODEM_EVENT, MODEM_EVENT_PPP_CONNECT, &ipinfo, sizeof(ipinfo), 0); + } else if (event_id == IP_EVENT_PPP_LOST_IP) { + ESP_LOGI(TAG, "Modem Disconnect from PPP Server"); + esp_event_post(ESP_MODEM_EVENT, MODEM_EVENT_PPP_DISCONNECT, NULL, 0, 0); + } +} + +esp_err_t esp_modem_add_event_handler(modem_dte_t *dte, esp_event_handler_t handler, void *handler_args) +{ + // event loop has to be created when using this API -- create and ignore failure if already created + esp_event_loop_create_default(); + ESP_ERROR_CHECK(esp_event_handler_register(ESP_MODEM_EVENT, MODEM_EVENT_PPP_START, handler, handler_args)); + ESP_ERROR_CHECK(esp_event_handler_register(ESP_MODEM_EVENT, MODEM_EVENT_PPP_CONNECT, handler, handler_args)); + ESP_ERROR_CHECK(esp_event_handler_register(ESP_MODEM_EVENT, MODEM_EVENT_PPP_DISCONNECT, handler, handler_args)); + ESP_ERROR_CHECK(esp_event_handler_register(ESP_MODEM_EVENT, MODEM_EVENT_PPP_STOP, handler, handler_args)); + return esp_modem_set_event_handler(dte, on_modem_compat_handler, ESP_EVENT_ANY_ID, handler_args); +} + +esp_err_t esp_modem_setup_ppp(modem_dte_t *dte) +{ +#if CONFIG_LWIP_PPP_PAP_SUPPORT && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME) && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD) + esp_netif_auth_type_t auth_type = NETIF_PPP_AUTHTYPE_PAP; +#elif CONFIG_LWIP_PPP_CHAP_SUPPORT && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME) && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD) + esp_netif_auth_type_t auth_type = NETIF_PPP_AUTHTYPE_CHAP; +#elif defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME) && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD) +#error "Unsupported AUTH Negotiation while AUTH_USERNAME and PASSWORD defined" +#endif + // Init netif object + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_PPP(); + esp_netif_t *esp_netif = esp_netif_new(&cfg); + assert(esp_netif); + + // event loop has to be created when using this API -- create and ignore failure if already created + esp_event_loop_create_default(); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &on_ip_event, NULL)); +#if defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME) && defined(CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD) + esp_netif_ppp_set_auth(esp_netif, auth_type, CONFIG_EXAMPLE_MODEM_PPP_AUTH_USERNAME, CONFIG_EXAMPLE_MODEM_PPP_AUTH_PASSWORD); +#endif + void *modem_netif_adapter = esp_modem_netif_setup(dte); + esp_modem_netif_set_default_handlers(modem_netif_adapter, esp_netif); + /* attach the modem to the network interface */ + return esp_netif_attach(esp_netif, modem_netif_adapter); +} + +esp_err_t esp_modem_exit_ppp(modem_dte_t *dte) +{ + // Note: A minor memory leak is expected when using esp-modem-compat + return esp_modem_stop_ppp(dte); +} diff --git a/esp_modem/src/esp_modem_dce_service.c b/esp_modem/src/esp_modem_dce_service.c new file mode 100644 index 000000000..4b73534cf --- /dev/null +++ b/esp_modem/src/esp_modem_dce_service.c @@ -0,0 +1,126 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_log.h" +#include "esp_modem_dce_service.h" + +/** + * @brief Macro defined for error checking + * + */ +static const char *DCE_TAG = "dce_service"; +#define DCE_CHECK(a, str, goto_tag, ...) \ + do \ + { \ + if (!(a)) \ + { \ + ESP_LOGE(DCE_TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + goto goto_tag; \ + } \ + } while (0) + +esp_err_t esp_modem_dce_handle_response_default(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +esp_err_t esp_modem_dce_sync(modem_dce_t *dce) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, "AT\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "sync failed", err); + ESP_LOGD(DCE_TAG, "sync ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_dce_echo(modem_dce_t *dce, bool on) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = esp_modem_dce_handle_response_default; + if (on) { + DCE_CHECK(dte->send_cmd(dte, "ATE1\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "enable echo failed", err); + ESP_LOGD(DCE_TAG, "enable echo ok"); + } else { + DCE_CHECK(dte->send_cmd(dte, "ATE0\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "disable echo failed", err); + ESP_LOGD(DCE_TAG, "disable echo ok"); + } + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_dce_store_profile(modem_dce_t *dce) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, "AT&W\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "save settings failed", err); + ESP_LOGD(DCE_TAG, "save settings ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_dce_set_flow_ctrl(modem_dce_t *dce, modem_flow_ctrl_t flow_ctrl) +{ + modem_dte_t *dte = dce->dte; + char command[16]; + int len = snprintf(command, sizeof(command), "AT+IFC=%d,%d\r", dte->flow_ctrl, flow_ctrl); + DCE_CHECK(len < sizeof(command), "command too long: %s", err, command); + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, command, MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "set flow control failed", err); + ESP_LOGD(DCE_TAG, "set flow control ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_dce_define_pdp_context(modem_dce_t *dce, uint32_t cid, const char *type, const char *apn) +{ + modem_dte_t *dte = dce->dte; + char command[64]; + int len = snprintf(command, sizeof(command), "AT+CGDCONT=%d,\"%s\",\"%s\"\r", cid, type, apn); + DCE_CHECK(len < sizeof(command), "command too long: %s", err, command); + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, command, MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "define pdp context failed", err); + ESP_LOGD(DCE_TAG, "define pdp context ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +esp_err_t esp_modem_dce_hang_up(modem_dce_t *dce) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, "ATH\r", MODEM_COMMAND_TIMEOUT_HANG_UP) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "hang up failed", err); + ESP_LOGD(DCE_TAG, "hang up ok"); + return ESP_OK; +err: + return ESP_FAIL; +} diff --git a/esp_modem/src/esp_modem_netif.c b/esp_modem/src/esp_modem_netif.c new file mode 100644 index 000000000..1c6f4bcf1 --- /dev/null +++ b/esp_modem/src/esp_modem_netif.c @@ -0,0 +1,181 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_netif.h" +#include "esp_netif_ppp.h" +#include "esp_modem.h" +#include "esp_log.h" + +static const char *TAG = "esp-modem-netif"; + +/** + * @brief ESP32 Modem handle to be used as netif IO object + */ +typedef struct esp_modem_netif_driver_s { + esp_netif_driver_base_t base; /*!< base structure reserved as esp-netif driver */ + modem_dte_t *dte; /*!< ptr to the esp_modem objects (DTE) */ +} esp_modem_netif_driver_t; + +static void on_ppp_changed(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + modem_dte_t *dte = arg; + if (event_id < NETIF_PP_PHASE_OFFSET) { + ESP_LOGI(TAG, "PPP state changed event %d", event_id); + // only notify the modem on state/error events, ignoring phase transitions + esp_modem_notify_ppp_netif_closed(dte); + } +} +/** + * @brief Transmit function called from esp_netif to output network stack data + * + * Note: This API has to conform to esp-netif transmit prototype + * + * @param h Opaque pointer representing esp-netif driver, esp_dte in this case of esp_modem + * @param data data buffer + * @param length length of data to send + * + * @return ESP_OK on success + */ +static esp_err_t esp_modem_dte_transmit(void *h, void *buffer, size_t len) +{ + modem_dte_t *dte = h; + if (dte->send_data(dte, (const char *)buffer, len) > 0) { + return ESP_OK; + } + return ESP_FAIL; +} + +/** + * @brief Post attach adapter for esp-modem + * + * Used to exchange internal callbacks, context between esp-netif nad modem-netif + * + * @param esp_netif handle to esp-netif object + * @param args pointer to modem-netif driver + * + * @return ESP_OK on success, modem-start error code if starting failed + */ +static esp_err_t esp_modem_post_attach_start(esp_netif_t * esp_netif, void * args) +{ + esp_modem_netif_driver_t *driver = args; + modem_dte_t *dte = driver->dte; + const esp_netif_driver_ifconfig_t driver_ifconfig = { + .driver_free_rx_buffer = NULL, + .transmit = esp_modem_dte_transmit, + .handle = dte + }; + driver->base.netif = esp_netif; + ESP_ERROR_CHECK(esp_netif_set_driver_config(esp_netif, &driver_ifconfig)); + + // enable both events, so we could notify the modem layer if an error occurred/state changed + esp_netif_ppp_config_t ppp_config = { + .ppp_error_event_enabled = true, + .ppp_phase_event_enabled = true + }; + esp_netif_ppp_set_params(esp_netif, &ppp_config); + + ESP_ERROR_CHECK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, dte)); + return esp_modem_start_ppp(dte); +} + +/** + * @brief Data path callback from esp-modem to pass data to esp-netif + * + * @param buffer data pointer + * @param len data length + * @param context context data used for esp-modem-netif handle + * + * @return ESP_OK on success + */ +static esp_err_t modem_netif_receive_cb(void *buffer, size_t len, void *context) +{ + esp_modem_netif_driver_t *driver = context; + esp_netif_receive(driver->base.netif, buffer, len, NULL); + return ESP_OK; +} + +void *esp_modem_netif_setup(modem_dte_t *dte) +{ + esp_modem_netif_driver_t *driver = calloc(1, sizeof(esp_modem_netif_driver_t)); + if (driver == NULL) { + ESP_LOGE(TAG, "Cannot allocate esp_modem_netif_driver_t"); + goto drv_create_failed; + } + esp_err_t err = esp_modem_set_rx_cb(dte, modem_netif_receive_cb, driver); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_modem_set_rx_cb failed with: %d", err); + goto drv_create_failed; + } + + driver->base.post_attach = esp_modem_post_attach_start; + driver->dte = dte; + return driver; + +drv_create_failed: + return NULL; +} + +void esp_modem_netif_teardown(void *h) +{ + esp_modem_netif_driver_t *driver = h; + free(driver); +} + +esp_err_t esp_modem_netif_clear_default_handlers(void *h) +{ + esp_modem_netif_driver_t *driver = h; + esp_err_t ret; + ret = esp_modem_remove_event_handler(driver->dte, esp_netif_action_start); + if (ret != ESP_OK) { + goto clear_event_failed; + } + ret = esp_modem_remove_event_handler(driver->dte, esp_netif_action_stop); + if (ret != ESP_OK) { + goto clear_event_failed; + } + return ESP_OK; + +clear_event_failed: + ESP_LOGE(TAG, "Failed to unregister event handlers"); + return ESP_FAIL; + +} + +esp_err_t esp_modem_netif_set_default_handlers(void *h, esp_netif_t * esp_netif) +{ + esp_modem_netif_driver_t *driver = h; + esp_err_t ret; + ret = esp_modem_set_event_handler(driver->dte, esp_netif_action_start, ESP_MODEM_EVENT_PPP_START, esp_netif); + if (ret != ESP_OK) { + goto set_event_failed; + } + ret = esp_modem_set_event_handler(driver->dte, esp_netif_action_stop, ESP_MODEM_EVENT_PPP_STOP, esp_netif); + if (ret != ESP_OK) { + goto set_event_failed; + } + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, esp_netif); + if (ret != ESP_OK) { + goto set_event_failed; + } + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected, esp_netif); + if (ret != ESP_OK) { + goto set_event_failed; + } + return ESP_OK; + +set_event_failed: + ESP_LOGE(TAG, "Failed to register event handlers"); + esp_modem_netif_clear_default_handlers(driver); + return ESP_FAIL; +} diff --git a/esp_modem/src/sim7600.c b/esp_modem/src/sim7600.c new file mode 100644 index 000000000..3cf8907de --- /dev/null +++ b/esp_modem/src/sim7600.c @@ -0,0 +1,90 @@ +// Copyright 2020 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_log.h" +#include "bg96.h" +#include "bg96_private.h" + +/** + * @brief This module supports SIM7600 module, which has a very similar interface + * to the BG96, so it just references most of the handlers from BG96 and implements + * only those that differ. + */ +static const char *DCE_TAG = "sim7600"; + +/** + * @brief Handle response from AT+CBC + */ +static esp_err_t sim7600_handle_cbc(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CBC", strlen("+CBC"))) { + /* store value of bcs, bcl, voltage */ + int32_t **cbc = bg96_dce->priv_resource; + int32_t volts = 0, fraction = 0; + /* +CBC: V*/ + sscanf(line, "+CBC: %d.%dV", &volts, &fraction); + /* Since the "read_battery_status()" API (besides voltage) returns also values for BCS, BCL (charge status), + * which are not applicable to this modem, we return -1 to indicate invalid value + */ + *cbc[0] = -1; // BCS + *cbc[1] = -1; // BCL + *cbc[2] = volts*1000 + fraction; + err = ESP_OK; + } + return err; +} + +/** + * @brief Get battery status + * + * @param dce Modem DCE object + * @param bcs Battery charge status + * @param bcl Battery connection level + * @param voltage Battery voltage + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim7600_get_battery_status(modem_dce_t *dce, uint32_t *bcs, uint32_t *bcl, uint32_t *voltage) +{ + modem_dte_t *dte = dce->dte; + bg96_modem_dce_t *bg96_dce = __containerof(dce, bg96_modem_dce_t, parent); + uint32_t *resource[3] = {bcs, bcl, voltage}; + bg96_dce->priv_resource = resource; + dce->handle_line = sim7600_handle_cbc; + DCE_CHECK(dte->send_cmd(dte, "AT+CBC\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "inquire battery status failed", err); + ESP_LOGD(DCE_TAG, "inquire battery status ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Create and initialize SIM7600 object + * + */ +modem_dce_t *sim7600_init(modem_dte_t *dte) +{ + modem_dce_t *dce = bg96_init(dte); + dte->dce->get_battery_status = sim7600_get_battery_status; + return dce; +} diff --git a/esp_modem/src/sim800.c b/esp_modem/src/sim800.c new file mode 100644 index 000000000..7bc1ece73 --- /dev/null +++ b/esp_modem/src/sim800.c @@ -0,0 +1,492 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_log.h" +#include "esp_modem_dce_service.h" +#include "sim800.h" + +#define MODEM_RESULT_CODE_POWERDOWN "POWER DOWN" + +/** + * @brief Macro defined for error checking + * + */ +static const char *DCE_TAG = "sim800"; +#define DCE_CHECK(a, str, goto_tag, ...) \ + do \ + { \ + if (!(a)) \ + { \ + ESP_LOGE(DCE_TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + goto goto_tag; \ + } \ + } while (0) + +/** + * @brief SIM800 Modem + * + */ +typedef struct { + void *priv_resource; /*!< Private resource */ + modem_dce_t parent; /*!< DCE parent class */ +} sim800_modem_dce_t; + +/** + * @brief Handle response from AT+CSQ + */ +static esp_err_t sim800_handle_csq(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + sim800_modem_dce_t *sim800_dce = __containerof(dce, sim800_modem_dce_t, parent); + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CSQ", strlen("+CSQ"))) { + /* store value of rssi and ber */ + uint32_t **csq = sim800_dce->priv_resource; + /* +CSQ: , */ + sscanf(line, "%*s%d,%d", csq[0], csq[1]); + err = ESP_OK; + } + return err; +} + +/** + * @brief Handle response from AT+CBC + */ +static esp_err_t sim800_handle_cbc(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + sim800_modem_dce_t *sim800_dce = __containerof(dce, sim800_modem_dce_t, parent); + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+CBC", strlen("+CBC"))) { + /* store value of bcs, bcl, voltage */ + uint32_t **cbc = sim800_dce->priv_resource; + /* +CBC: ,, */ + sscanf(line, "%*s%d,%d,%d", cbc[0], cbc[1], cbc[2]); + err = ESP_OK; + } + return err; +} + +/** + * @brief Handle response from +++ + */ +static esp_err_t sim800_handle_exit_data_mode(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_NO_CARRIER)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +/** + * @brief Handle response from ATD*99# + */ +static esp_err_t sim800_handle_atd_ppp(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_CONNECT)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } + return err; +} + +/** + * @brief Handle response from AT+CGMM + */ +static esp_err_t sim800_handle_cgmm(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->name, MODEM_MAX_NAME_LENGTH, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->name, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+CGSN + */ +static esp_err_t sim800_handle_cgsn(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->imei, MODEM_IMEI_LENGTH + 1, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->imei, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+CIMI + */ +static esp_err_t sim800_handle_cimi(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else { + int len = snprintf(dce->imsi, MODEM_IMSI_LENGTH + 1, "%s", line); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->imsi, len); + err = ESP_OK; + } + } + return err; +} + +/** + * @brief Handle response from AT+COPS? + */ +static esp_err_t sim800_handle_cops(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_SUCCESS)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } else if (strstr(line, MODEM_RESULT_CODE_ERROR)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_FAIL); + } else if (!strncmp(line, "+COPS", strlen("+COPS"))) { + /* there might be some random spaces in operator's name, we can not use sscanf to parse the result */ + /* strtok will break the string, we need to create a copy */ + size_t len = strlen(line); + char *line_copy = malloc(len + 1); + strcpy(line_copy, line); + /* +COPS: [, [, ]] */ + char *str_ptr = NULL; + char *p[3]; + uint8_t i = 0; + /* strtok will broke string by replacing delimiter with '\0' */ + p[i] = strtok_r(line_copy, ",", &str_ptr); + while (p[i]) { + p[++i] = strtok_r(NULL, ",", &str_ptr); + } + if (i >= 3) { + int len = snprintf(dce->oper, MODEM_MAX_OPERATOR_LENGTH, "%s", p[2]); + if (len > 2) { + /* Strip "\r\n" */ + strip_cr_lf_tail(dce->oper, len); + err = ESP_OK; + } + } + free(line_copy); + } + return err; +} + +/** + * @brief Handle response from AT+CPOWD=1 + */ +static esp_err_t sim800_handle_power_down(modem_dce_t *dce, const char *line) +{ + esp_err_t err = ESP_FAIL; + if (strstr(line, MODEM_RESULT_CODE_POWERDOWN)) { + err = esp_modem_process_command_done(dce, MODEM_STATE_SUCCESS); + } + return err; +} + +/** + * @brief Get signal quality + * + * @param dce Modem DCE object + * @param rssi received signal strength indication + * @param ber bit error ratio + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_signal_quality(modem_dce_t *dce, uint32_t *rssi, uint32_t *ber) +{ + modem_dte_t *dte = dce->dte; + sim800_modem_dce_t *sim800_dce = __containerof(dce, sim800_modem_dce_t, parent); + uint32_t *resource[2] = {rssi, ber}; + sim800_dce->priv_resource = resource; + dce->handle_line = sim800_handle_csq; + DCE_CHECK(dte->send_cmd(dte, "AT+CSQ\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "inquire signal quality failed", err); + ESP_LOGD(DCE_TAG, "inquire signal quality ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get battery status + * + * @param dce Modem DCE object + * @param bcs Battery charge status + * @param bcl Battery connection level + * @param voltage Battery voltage + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_battery_status(modem_dce_t *dce, uint32_t *bcs, uint32_t *bcl, uint32_t *voltage) +{ + modem_dte_t *dte = dce->dte; + sim800_modem_dce_t *sim800_dce = __containerof(dce, sim800_modem_dce_t, parent); + uint32_t *resource[3] = {bcs, bcl, voltage}; + sim800_dce->priv_resource = resource; + dce->handle_line = sim800_handle_cbc; + DCE_CHECK(dte->send_cmd(dte, "AT+CBC\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "inquire battery status failed", err); + ESP_LOGD(DCE_TAG, "inquire battery status ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Set Working Mode + * + * @param dce Modem DCE object + * @param mode woking mode + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_set_working_mode(modem_dce_t *dce, modem_mode_t mode) +{ + modem_dte_t *dte = dce->dte; + switch (mode) { + case MODEM_COMMAND_MODE: + dce->handle_line = sim800_handle_exit_data_mode; + vTaskDelay(pdMS_TO_TICKS(1000)); // spec: 1s delay for the modem to recognize the escape sequence + if (dte->send_cmd(dte, "+++", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) != ESP_OK) { + // "+++" Could fail if we are already in the command mode. + // in that case we ignore the timout and re-sync the modem + ESP_LOGI(DCE_TAG, "Sending \"+++\" command failed"); + dce->handle_line = esp_modem_dce_handle_response_default; + DCE_CHECK(dte->send_cmd(dte, "AT\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "sync failed", err); + } else { + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "enter command mode failed", err); + } + ESP_LOGD(DCE_TAG, "enter command mode ok"); + dce->mode = MODEM_COMMAND_MODE; + break; + case MODEM_PPP_MODE: + dce->handle_line = sim800_handle_atd_ppp; + DCE_CHECK(dte->send_cmd(dte, "ATD*99#\r", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) == ESP_OK, "send command failed", err); + if (dce->state != MODEM_STATE_SUCCESS) { + // Initiate PPP mode could fail, if we've already "dialed" the data call before. + // in that case we retry with "ATO" to just resume the data mode + ESP_LOGD(DCE_TAG, "enter ppp mode failed, retry with ATO"); + dce->handle_line = sim800_handle_atd_ppp; + DCE_CHECK(dte->send_cmd(dte, "ATO\r", MODEM_COMMAND_TIMEOUT_MODE_CHANGE) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "enter ppp mode failed", err); + } + ESP_LOGD(DCE_TAG, "enter ppp mode ok"); + dce->mode = MODEM_PPP_MODE; + break; + default: + ESP_LOGW(DCE_TAG, "unsupported working mode: %d", mode); + goto err; + break; + } + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Power down + * + * @param sim800_dce sim800 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_power_down(modem_dce_t *dce) +{ + modem_dte_t *dte = dce->dte; + dce->handle_line = sim800_handle_power_down; + DCE_CHECK(dte->send_cmd(dte, "AT+CPOWD=1\r", MODEM_COMMAND_TIMEOUT_POWEROFF) == ESP_OK, "send command failed", err); + DCE_CHECK(dce->state == MODEM_STATE_SUCCESS, "power down failed", err); + ESP_LOGD(DCE_TAG, "power down ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module name + * + * @param sim800_dce sim800 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_module_name(sim800_modem_dce_t *sim800_dce) +{ + modem_dte_t *dte = sim800_dce->parent.dte; + sim800_dce->parent.handle_line = sim800_handle_cgmm; + DCE_CHECK(dte->send_cmd(dte, "AT+CGMM\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(sim800_dce->parent.state == MODEM_STATE_SUCCESS, "get module name failed", err); + ESP_LOGD(DCE_TAG, "get module name ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module IMEI number + * + * @param sim800_dce sim800 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_imei_number(sim800_modem_dce_t *sim800_dce) +{ + modem_dte_t *dte = sim800_dce->parent.dte; + sim800_dce->parent.handle_line = sim800_handle_cgsn; + DCE_CHECK(dte->send_cmd(dte, "AT+CGSN\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(sim800_dce->parent.state == MODEM_STATE_SUCCESS, "get imei number failed", err); + ESP_LOGD(DCE_TAG, "get imei number ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get DCE module IMSI number + * + * @param sim800_dce sim800 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_imsi_number(sim800_modem_dce_t *sim800_dce) +{ + modem_dte_t *dte = sim800_dce->parent.dte; + sim800_dce->parent.handle_line = sim800_handle_cimi; + DCE_CHECK(dte->send_cmd(dte, "AT+CIMI\r", MODEM_COMMAND_TIMEOUT_DEFAULT) == ESP_OK, "send command failed", err); + DCE_CHECK(sim800_dce->parent.state == MODEM_STATE_SUCCESS, "get imsi number failed", err); + ESP_LOGD(DCE_TAG, "get imsi number ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Get Operator's name + * + * @param sim800_dce sim800 object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on error + */ +static esp_err_t sim800_get_operator_name(sim800_modem_dce_t *sim800_dce) +{ + modem_dte_t *dte = sim800_dce->parent.dte; + sim800_dce->parent.handle_line = sim800_handle_cops; + DCE_CHECK(dte->send_cmd(dte, "AT+COPS?\r", MODEM_COMMAND_TIMEOUT_OPERATOR) == ESP_OK, "send command failed", err); + DCE_CHECK(sim800_dce->parent.state == MODEM_STATE_SUCCESS, "get network operator failed", err); + ESP_LOGD(DCE_TAG, "get network operator ok"); + return ESP_OK; +err: + return ESP_FAIL; +} + +/** + * @brief Deinitialize SIM800 object + * + * @param dce Modem DCE object + * @return esp_err_t + * - ESP_OK on success + * - ESP_FAIL on fail + */ +static esp_err_t sim800_deinit(modem_dce_t *dce) +{ + sim800_modem_dce_t *sim800_dce = __containerof(dce, sim800_modem_dce_t, parent); + if (dce->dte) { + dce->dte->dce = NULL; + } + free(sim800_dce); + return ESP_OK; +} + +modem_dce_t *sim800_init(modem_dte_t *dte) +{ + DCE_CHECK(dte, "DCE should bind with a DTE", err); + /* malloc memory for sim800_dce object */ + sim800_modem_dce_t *sim800_dce = calloc(1, sizeof(sim800_modem_dce_t)); + DCE_CHECK(sim800_dce, "calloc sim800_dce failed", err); + /* Bind DTE with DCE */ + sim800_dce->parent.dte = dte; + dte->dce = &(sim800_dce->parent); + /* Bind methods */ + sim800_dce->parent.handle_line = NULL; + sim800_dce->parent.sync = esp_modem_dce_sync; + sim800_dce->parent.echo_mode = esp_modem_dce_echo; + sim800_dce->parent.store_profile = esp_modem_dce_store_profile; + sim800_dce->parent.set_flow_ctrl = esp_modem_dce_set_flow_ctrl; + sim800_dce->parent.define_pdp_context = esp_modem_dce_define_pdp_context; + sim800_dce->parent.hang_up = esp_modem_dce_hang_up; + sim800_dce->parent.get_signal_quality = sim800_get_signal_quality; + sim800_dce->parent.get_battery_status = sim800_get_battery_status; + sim800_dce->parent.set_working_mode = sim800_set_working_mode; + sim800_dce->parent.power_down = sim800_power_down; + sim800_dce->parent.deinit = sim800_deinit; + /* Sync between DTE and DCE */ + DCE_CHECK(esp_modem_dce_sync(&(sim800_dce->parent)) == ESP_OK, "sync failed", err_io); + /* Close echo */ + DCE_CHECK(esp_modem_dce_echo(&(sim800_dce->parent), false) == ESP_OK, "close echo mode failed", err_io); + /* Get Module name */ + DCE_CHECK(sim800_get_module_name(sim800_dce) == ESP_OK, "get module name failed", err_io); + /* Get IMEI number */ + DCE_CHECK(sim800_get_imei_number(sim800_dce) == ESP_OK, "get imei failed", err_io); + /* Get IMSI number */ + DCE_CHECK(sim800_get_imsi_number(sim800_dce) == ESP_OK, "get imsi failed", err_io); + /* Get operator name */ + DCE_CHECK(sim800_get_operator_name(sim800_dce) == ESP_OK, "get operator name failed", err_io); + return &(sim800_dce->parent); +err_io: + free(sim800_dce); +err: + return NULL; +}