Skip to content

Commit 1052d06

Browse files
jonathandreyerdavid-cermak
authored andcommitted
fix(modem): Initial adaptation to BG96
Plus rework a little to support implementation of multiple devices
1 parent f1c8287 commit 1052d06

File tree

7 files changed

+482
-223
lines changed

7 files changed

+482
-223
lines changed
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
if (CONFIG_EXAMPLE_MODEM_DEVICE_BG96)
2+
set(device_srcs sock_commands_bg96.cpp)
3+
elseif(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600)
4+
set(device_srcs sock_commands_sim7600.cpp)
5+
endif()
6+
17
idf_component_register(SRCS "modem_client.cpp"
28
"sock_dce.cpp"
3-
"sock_commands.cpp"
9+
"${device_srcs}"
410
INCLUDE_DIRS ".")
511
target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")

components/esp_modem/examples/modem_tcp_client/main/Kconfig.projbuild

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ menu "Example Configuration"
55
default EXAMPLE_MODEM_DEVICE_BG96
66
help
77
Select modem device connected to the ESP DTE.
8-
config EXAMPLE_MODEM_DEVICE_SIM800
9-
bool "SIM800"
10-
help
11-
SIMCom SIM800L is a GSM/GPRS module.
12-
It supports Quad-band 850/900/1800/1900MHz.
138
config EXAMPLE_MODEM_DEVICE_BG96
149
bool "BG96"
1510
help
@@ -26,53 +21,6 @@ menu "Example Configuration"
2621
help
2722
Set APN (Access Point Name), a logical name to choose data network
2823

29-
config EXAMPLE_MODEM_PPP_AUTH_USERNAME
30-
string "Set username for authentication"
31-
default "espressif"
32-
depends on !EXAMPLE_MODEM_PPP_AUTH_NONE
33-
help
34-
Set username for PPP Authentication.
35-
36-
config EXAMPLE_MODEM_PPP_AUTH_PASSWORD
37-
string "Set password for authentication"
38-
default "esp32"
39-
depends on !EXAMPLE_MODEM_PPP_AUTH_NONE
40-
help
41-
Set password for PPP Authentication.
42-
43-
config EXAMPLE_MODEM_PPP_AUTH_NONE
44-
bool "Skip PPP authentication"
45-
default n
46-
help
47-
Set to true for the PPP client to skip authentication
48-
49-
config EXAMPLE_SEND_MSG
50-
bool "Short message (SMS)"
51-
default n
52-
help
53-
Select this, the modem will send a short message before power off.
54-
55-
if EXAMPLE_SEND_MSG
56-
config EXAMPLE_SEND_MSG_PEER_PHONE_NUMBER
57-
string "Peer Phone Number (with area code)"
58-
default "+8610086"
59-
help
60-
Enter the peer phone number that you want to send message to.
61-
endif
62-
63-
config EXAMPLE_NEED_SIM_PIN
64-
bool "SIM PIN needed"
65-
default n
66-
help
67-
Enable to set SIM PIN before starting the example
68-
69-
config EXAMPLE_SIM_PIN
70-
string "Set SIM PIN"
71-
default "1234"
72-
depends on EXAMPLE_NEED_SIM_PIN
73-
help
74-
Pin to unlock the SIM
75-
7624
menu "UART Configuration"
7725
config EXAMPLE_MODEM_UART_TX_PIN
7826
int "TXD Pin Number"

components/esp_modem/examples/modem_tcp_client/main/modem_client.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ extern "C" void app_main(void)
9797
assert(dte);
9898

9999
/* Configure the DCE */
100-
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG(CONFIG_EXAMPLE_MODEM_APN);
100+
esp_modem_dce_config_t dce_config = ESP_MODEM_DCE_DEFAULT_CONFIG("lpwa.vodafone.com");
101101

102102
/* create the DCE and initialize network manually (using AT commands) */
103103
auto dce = sock_dce::create(&dce_config, std::move(dte));
@@ -106,10 +106,10 @@ extern "C" void app_main(void)
106106
return;
107107
}
108108

109-
dce->init(8883);
109+
dce->init(1883);
110110
esp_mqtt_client_config_t mqtt_config = {};
111111
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
112-
mqtt_config.broker.address.uri = "mqtts://127.0.0.1";
112+
mqtt_config.broker.address.uri = "mqtt://127.0.0.1";
113113
mqtt_config.session.message_retransmit_timeout = 10000;
114114
#else
115115
mqtt_config.uri = "mqtt://127.0.0.1";
@@ -118,7 +118,7 @@ extern "C" void app_main(void)
118118
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
119119
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, NULL);
120120
esp_mqtt_client_start(mqtt_client);
121-
if (!dce->start(BROKER_URL, 8883)) {
121+
if (!dce->start(BROKER_URL, 1883)) {
122122
ESP_LOGE(TAG, "Failed to start DCE");
123123
return;
124124
}
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <charconv>
8+
#include <cstring>
9+
#include <sys/socket.h>
10+
#include "sock_commands.hpp"
11+
#include "cxx_include/esp_modem_command_library_utils.hpp"
12+
#include "sock_dce.hpp"
13+
14+
static const char *TAG = "sock_commands";
15+
16+
namespace sock_commands {
17+
18+
using namespace esp_modem;
19+
20+
command_result net_open(CommandableIf *t)
21+
{
22+
ESP_LOGV(TAG, "%s", __func__ );
23+
std::string out;
24+
auto ret = dce_commands::generic_get_string(t, "AT+QISTATE?\r", out, 1000);
25+
if (ret != command_result::OK) {
26+
return ret;
27+
}
28+
if (out.find("+QISTATE: 0") != std::string::npos) {
29+
ESP_LOGV(TAG, "%s", out.data() );
30+
ESP_LOGD(TAG, "Already there");
31+
return command_result::OK;
32+
} else if (out.empty()) {
33+
return dce_commands::generic_command(t, "AT+QIACT=1\r", "OK", "ERROR", 150000);
34+
}
35+
return command_result::FAIL;
36+
}
37+
38+
command_result net_close(CommandableIf *t)
39+
{
40+
ESP_LOGV(TAG, "%s", __func__ );
41+
return dce_commands::generic_command(t, "AT+QIDEACT=1\r", "OK", "ERROR", 40000);
42+
}
43+
44+
command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout)
45+
{
46+
ESP_LOGV(TAG, "%s", __func__ );
47+
std::string ip_open = R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r";
48+
auto ret = dce_commands::generic_command(t, ip_open, "+QIOPEN: 0,0", "ERROR", timeout);
49+
if (ret != command_result::OK) {
50+
ESP_LOGE(TAG, "%s Failed", __func__ );
51+
return ret;
52+
}
53+
return command_result::OK;
54+
}
55+
56+
command_result tcp_close(CommandableIf *t)
57+
{
58+
ESP_LOGV(TAG, "%s", __func__ );
59+
return dce_commands::generic_command(t, "AT+QICLOSE=0\r", "OK", "ERROR", 10000);
60+
}
61+
62+
command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len)
63+
{
64+
ESP_LOGV(TAG, "%s", __func__ );
65+
assert(0); // Remove when fix done
66+
return command_result::FAIL;
67+
}
68+
69+
command_result tcp_recv(CommandableIf *t, uint8_t *data, size_t len, size_t &out_len)
70+
{
71+
ESP_LOGV(TAG, "%s", __func__ );
72+
assert(0); // Remove when fix done
73+
return command_result::FAIL;
74+
}
75+
76+
command_result get_ip(CommandableIf *t, std::string &ip)
77+
{
78+
ESP_LOGV(TAG, "%s", __func__ );
79+
std::string out;
80+
auto ret = dce_commands::generic_get_string(t, "AT+QIACT?\r", out, 5000);
81+
if (ret != command_result::OK) {
82+
return ret;
83+
}
84+
auto pos = out.find("+QIACT: 1");
85+
auto property = 0;
86+
while (pos != std::string::npos) {
87+
// Looking for: +QIACT: <contextID>,<context_state>,<context_type>,<IP_address>
88+
if (property++ == 3) { // ip is after 3rd comma (as a 4rd property of QIACT string)
89+
ip = out.substr(++pos);
90+
// strip quotes if present
91+
auto quote1 = ip.find('"');
92+
auto quote2 = ip.rfind('"');
93+
if (quote1 != std::string::npos && quote2 != std::string::npos) {
94+
ip = ip.substr(quote1 + 1, quote2 - 1);
95+
}
96+
return command_result::OK;
97+
}
98+
pos = out.find(',', ++pos);
99+
}
100+
return command_result::FAIL;
101+
}
102+
103+
} // sock_commands
104+
105+
namespace sock_dce {
106+
107+
void Listener::start_sending(size_t len)
108+
{
109+
data_to_send = len;
110+
send_stat = 0;
111+
send_cmd("AT+QISEND=0," + std::to_string(len) + "\r");
112+
}
113+
114+
void Listener::start_receiving(size_t len)
115+
{
116+
send_cmd("AT+QIRD=0," + std::to_string(size) + "\r");
117+
}
118+
119+
bool Listener::start_connecting(std::string host, int port)
120+
{
121+
send_cmd(R"(AT+QIOPEN=1,0,"TCP",")" + host + "\"," + std::to_string(port) + "\r");
122+
return true;
123+
}
124+
125+
Listener::state Listener::recv(uint8_t *data, size_t len)
126+
{
127+
const size_t MIN_MESSAGE = 6;
128+
const std::string_view head = "+QIRD: ";
129+
auto head_pos = (char *)std::search(data, data + len, head.begin(), head.end());
130+
if (head_pos == nullptr) {
131+
return state::FAIL;
132+
}
133+
134+
auto next_nl = (char *)memchr(head_pos + head.size(), '\n', MIN_MESSAGE);
135+
if (next_nl == nullptr) {
136+
return state::FAIL;
137+
}
138+
139+
size_t actual_len;
140+
if (std::from_chars(head_pos + head.size(), next_nl, actual_len).ec == std::errc::invalid_argument) {
141+
ESP_LOGE(TAG, "cannot convert");
142+
return state::FAIL;
143+
}
144+
145+
ESP_LOGD(TAG, "Received: actual len=%d", actual_len);
146+
if (actual_len == 0) {
147+
ESP_LOGD(TAG, "no data received");
148+
return state::FAIL;
149+
}
150+
151+
// TODO improve : compare *actual_len* & data size (to be sure that received data is equal to *actual_len*)
152+
if (actual_len > size) {
153+
ESP_LOGE(TAG, "TOO BIG");
154+
return state::FAIL;
155+
}
156+
::send(sock, next_nl + 1, actual_len, 0);
157+
158+
// "OK" after the data
159+
auto last_pos = (char *)memchr(next_nl + 1 + actual_len, 'O', MIN_MESSAGE);
160+
if (last_pos == nullptr || last_pos[1] != 'K') {
161+
return state::FAIL;
162+
}
163+
if ((char *)data + len - last_pos > MIN_MESSAGE) {
164+
// check for async replies after the Recv header
165+
std::string_view response((char *)last_pos + 2 /* OK */, (char *)data + len - last_pos);
166+
check_async_replies(response);
167+
}
168+
return state::OK;
169+
}
170+
171+
172+
Listener::state Listener::send(uint8_t *data, size_t len)
173+
{
174+
if (send_stat == 0) {
175+
if (memchr(data, '>', len) == NULL) {
176+
ESP_LOGE(TAG, "Missed >");
177+
return state::FAIL;
178+
}
179+
auto written = dte->write(&buffer[0], data_to_send);
180+
if (written != data_to_send) {
181+
ESP_LOGE(TAG, "written %d (%d)...", written, len);
182+
return state::FAIL;
183+
}
184+
data_to_send = 0;
185+
send_stat++;
186+
}
187+
return Listener::state::IN_PROGRESS;
188+
}
189+
190+
Listener::state Listener::send(std::string_view response)
191+
{
192+
if (send_stat == 1) {
193+
if (response.find("SEND OK") != std::string::npos) {
194+
send_cmd("AT+QISEND=0,0\r");
195+
send_stat++;
196+
} else if (response.find("SEND FAIL") != std::string::npos) {
197+
ESP_LOGE(TAG, "Sending buffer full");
198+
return state::FAIL;
199+
} else if (response.find("ERROR") != std::string::npos) {
200+
ESP_LOGE(TAG, "Failed to sent");
201+
return state::FAIL;
202+
}
203+
} else if (send_stat == 2) {
204+
constexpr std::string_view head = "+QISEND: ";
205+
if (response.find(head) != std::string::npos) {
206+
// Parsing +QISEND: <total_send_length>,<ackedbytes>,<unackedbytes>
207+
size_t head_pos = response.find(head);
208+
response = response.substr(head_pos + head.size());
209+
int pos, property = 0;
210+
int total = 0, ack = 0, unack = 0;
211+
while ((pos = response.find(',')) != std::string::npos) {
212+
auto next_comma = (char *)memchr(response.data(), ',', response.size());
213+
214+
// extract value
215+
size_t value;
216+
if (std::from_chars(response.data(), next_comma, value).ec == std::errc::invalid_argument) {
217+
ESP_LOGE(TAG, "cannot convert");
218+
return state::FAIL;
219+
}
220+
221+
switch (property++) {
222+
case 0: total = value;
223+
break;
224+
case 1: ack = value;
225+
break;
226+
default:
227+
return state::FAIL;
228+
}
229+
response = response.substr(pos + 1);
230+
}
231+
if (std::from_chars(response.data(), response.data() + pos, unack).ec == std::errc::invalid_argument) {
232+
return state::FAIL;
233+
}
234+
235+
// TODO improve : need check *total* & *ack* values, or loop (every 5 sec) with 90s or 120s timeout
236+
if (ack < total) {
237+
ESP_LOGE(TAG, "all sending data are not ack (missing %d bytes acked)", (total - ack));
238+
}
239+
return state::OK;
240+
} else if (response.find("ERROR") != std::string::npos) {
241+
ESP_LOGE(TAG, "Failed to check sending");
242+
return state::FAIL;
243+
}
244+
245+
}
246+
return Listener::state::IN_PROGRESS;
247+
}
248+
249+
Listener::state Listener::connect(std::string_view response)
250+
{
251+
if (response.find("+QIOPEN: 0,0") != std::string::npos) {
252+
ESP_LOGI(TAG, "Connected!");
253+
return state::OK;
254+
}
255+
if (response.find("ERROR") != std::string::npos) {
256+
ESP_LOGE(TAG, "Failed to open");
257+
return state::FAIL;
258+
}
259+
return Listener::state::IN_PROGRESS;
260+
}
261+
262+
void Listener::check_async_replies(std::string_view &response) const
263+
{
264+
ESP_LOGD(TAG, "response %.*s", static_cast<int>(response.size()), response.data());
265+
if (response.find("+QIURC: \"recv\",0") != std::string::npos) {
266+
uint64_t data_ready = 1;
267+
write(data_ready_fd, &data_ready, sizeof(data_ready));
268+
ESP_LOGD(TAG, "Got data on modem!");
269+
}
270+
}
271+
272+
273+
} // sock_dce

0 commit comments

Comments
 (0)