From 05ccd5f5f02512dcd56b1d376b99777a8b666b4c Mon Sep 17 00:00:00 2001 From: Andrey Tolstoy Date: Fri, 4 Oct 2024 07:36:34 +0700 Subject: [PATCH 1/2] at server + Tether API --- hal/network/lwip/ppp_client.cpp | 23 +- hal/network/lwip/pppservernetif.cpp | 211 +++++++- hal/network/lwip/pppservernetif.h | 3 + hal/network/ncp/at_parser/at_server.cpp | 484 ++++++++++++++++++ hal/network/ncp/at_parser/at_server.h | 353 +++++++++++++ hal/src/nRF52840/device_code.cpp | 3 +- .../nRF52840/hal_platform_nrf52840_config.h | 18 + hal/src/nRF52840/serial_stream.h | 11 + hal/src/rtl872x/serial_stream.h | 11 + wiring/inc/spark_wiring.h | 1 + wiring/inc/spark_wiring_ethernet.h | 22 - wiring/inc/spark_wiring_network.h | 25 + wiring/inc/spark_wiring_system.h | 10 +- wiring/inc/spark_wiring_tether.h | 130 +++++ wiring/inc/spark_wiring_usartserial.h | 34 ++ wiring/src/spark_wiring_network.cpp | 5 + wiring/src/spark_wiring_tether.cpp | 75 +++ 17 files changed, 1359 insertions(+), 60 deletions(-) create mode 100644 hal/network/ncp/at_parser/at_server.cpp create mode 100644 hal/network/ncp/at_parser/at_server.h create mode 100644 wiring/inc/spark_wiring_tether.h create mode 100644 wiring/src/spark_wiring_tether.cpp diff --git a/hal/network/lwip/ppp_client.cpp b/hal/network/lwip/ppp_client.cpp index f34bef9f76..1d81beb605 100644 --- a/hal/network/lwip/ppp_client.cpp +++ b/hal/network/lwip/ppp_client.cpp @@ -58,6 +58,7 @@ constexpr const char* Client::stateNames_[]; const auto NCP_CLIENT_LCP_ECHO_INTERVAL_SECONDS_DEFAULT = 5; const auto NCP_CLIENT_LCP_ECHO_INTERVAL_SECONDS_R510 = 240; // 4 minutes (4.25 minutes max) const auto NCP_CLIENT_LCP_ECHO_MAX_FAILS_DEFAULT = 10; +const auto NCP_CLIENT_LCP_ECHO_MAX_FAILS_DEFAULT_SERVER = 2; const auto NCP_CLIENT_LCP_ECHO_MAX_FAILS_R510 = 1; namespace { @@ -115,6 +116,10 @@ void Client::init() { SPARK_ASSERT(pcb_); if_.flags &= ~NETIF_FLAG_UP; + if (server_) { + if_.name[1] = 's'; // "ps" instead of "pp" + } + LOCK_TCPIP_CORE(); ipcp_ = std::make_unique(pcb_); SPARK_ASSERT(ipcp_); @@ -154,6 +159,11 @@ void Client::init() { pcb_->lcp_echos_pending = 0; // reset echo count } + if (server_) { + pcb_->settings.lcp_echo_interval = NCP_CLIENT_LCP_ECHO_INTERVAL_SECONDS_DEFAULT; + pcb_->settings.lcp_echo_fails = NCP_CLIENT_LCP_ECHO_MAX_FAILS_DEFAULT_SERVER; + } + pppapi_set_notify_phase_callback(pcb_, &Client::notifyPhaseCb); os_queue_create(&queue_, sizeof(QueueEvent), 5, nullptr); @@ -219,7 +229,7 @@ int Client::prepareConnect() { #if HAL_PLATFORM_PPP_SERVER LOCK_TCPIP_CORE(); if (server_) { - ppp_set_silent(pcb_, 1); + ppp_set_silent(pcb_, 0); ppp_set_auth(pcb_, PPPAUTHTYPE_ANY, "particle", "particle"); ppp_set_auth_required(pcb_, 0); } else { @@ -321,15 +331,10 @@ int Client::input(const uint8_t* data, size_t size) { const size_t header = 19; if (pppos->in_state == PDDATA && pppos->in_head->len >= header) { const size_t len = pppos->in_head->len - header; - const char breakAndAt[] = "+++AT"; - const char breakAndAtDial[] = "+++ATD"; - if (len >= strlen(breakAndAtDial) && !strncmp(((const char*)pppos->in_head->payload) + header, breakAndAtDial, strlen(breakAndAtDial))) { - const char okResponse[] = "\r\nCONNECT\r\n"; - output((const uint8_t*)okResponse, strlen(okResponse)); - } else if (len >= strlen(breakAndAt) && !strncmp(((const char*)pppos->in_head->payload) + header, breakAndAt, strlen(breakAndAt))) { + const char breakSeq[] = "+++"; + if (len >= strlen(breakSeq) && !strncmp(((const char*)pppos->in_head->payload) + header, breakSeq, strlen(breakSeq))) { pppos_drop_packet(pppos); - const char okResponse[] = "\r\nOK\r\n"; - output((const uint8_t*)okResponse, strlen(okResponse)); + disconnect(); } } } diff --git a/hal/network/lwip/pppservernetif.cpp b/hal/network/lwip/pppservernetif.cpp index 2b8a628e8c..f477d287ac 100644 --- a/hal/network/lwip/pppservernetif.cpp +++ b/hal/network/lwip/pppservernetif.cpp @@ -49,6 +49,14 @@ LOG_SOURCE_CATEGORY("net.pppserver"); #include "dnsproxy.h" #include "thread_runner.h" #include "nat.h" +#include "at_server.h" +#include "device_code.h" +#include "deviceid_hal.h" +#include "bytes2hexbuf.h" +#include "network/ncp/cellular/cellular_network_manager.h" +#include "network/ncp/cellular/cellular_ncp_client.h" +#include "network/ncp/cellular/ncp.h" + using namespace particle::net; @@ -171,6 +179,161 @@ int PppServerNetif::start() { g_natInstance = nat_.get(); + AtServerConfig conf; + auto server = std::make_unique(); + SPARK_ASSERT(server); + server_ = std::move(server); + conf.stream(serial_.get()); + conf.streamTimeout(1000); + server_->init(conf); + + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "H", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CGMI", "Particle")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CGMM", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + char buf[64] = {}; + get_device_usb_name(buf, sizeof(buf)); + request->sendResponse(buf); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "E0", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto conf = request->server()->config(); + conf.echoEnabled(false); + request->server()->init(conf); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "E", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto conf = request->server()->config(); + conf.echoEnabled(true); + request->server()->init(conf); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::ONE_INT_ARG, "X", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::ONE_INT_ARG, "V", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::ONE_INT_ARG, "&C", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+GCAP", "+GCAP: +CGSM,+CLTE")); + // +CSCS=? + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CGMR", PP_STR(SYSTEM_VERSION_STRING))); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CGSN", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + uint8_t id[HAL_DEVICE_ID_SIZE] = {}; + const auto n = hal_get_device_id(id, sizeof(id)); + if (n != HAL_DEVICE_ID_SIZE) { + return SYSTEM_ERROR_UNKNOWN; + } + char deviceId[HAL_DEVICE_ID_SIZE * 2 + 1] = {}; + bytes2hexbuf_lower_case(id, sizeof(id), deviceId); + request->sendResponse(deviceId); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CMEE", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "I", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + char buf[64] = {}; + get_device_usb_name(buf, sizeof(buf)); + request->sendResponse("%s %s", "Particle", buf); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::TEST, "+URAT", "+URAT: (0-9)")); // Pretend to support all + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+URAT", "+URAT: 3")); // Pretend to be LTE + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::TEST, "+CGDCONT", "+CGDCONT: (0-1),\"IP\",,,(0-2),(0-4),(0,1),(0,3),(0,1),(0,1),(0,1),(0,1),(0,1),(0,1)")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CGDCONT", "+CGDCONT: 1,\"IP\",\"particle\",0,0")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CFUN", "+CFUN: 1,0")); // Full functionality + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CMER", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CGACT", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CGACT", "+CGACT: 1,1")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::TEST, "+CMER", "+CMER: (0-3),(0),(0),(0-2),(0,1)")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CPIN", "+CPIN: READY")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CCID", "+CCID: 00000000000000000000")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CIMI", "000000000000000")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CNUM", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "PARTICLE1", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "PARTICLE2", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "Z", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "+CSQ", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + const auto mgr = cellularNetworkManager(); + CHECK_TRUE(mgr, SYSTEM_ERROR_UNKNOWN); + const auto client = mgr->ncpClient(); + CHECK_TRUE(client, SYSTEM_ERROR_UNKNOWN); + CellularSignalQuality s; + CHECK(client->getSignalQuality(&s)); + const auto strn = s.strength(); + const auto qual = s.quality(); + request->sendResponse("+CSQ: %d,%d", strn, qual); + return 0; + }, nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::TEST, "+IFC", "+IFC: (0-2)(0-2)")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+IFC", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto stream = (SerialStream*)data; + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + if (stream->config() & SERIAL_FLOW_CONTROL_RTS_CTS) { + request->sendResponse("+IFC: 2,2"); + } else { + request->sendResponse("+IFC: 0,0"); + } + return 0; + }, serial_.get())); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+IFC", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto stream = (SerialStream*)data; + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + int v[2] = {}; + CHECK_TRUE(request->scanf("%d,%d", &v[0], &v[1]) == 2, SYSTEM_ERROR_INVALID_ARGUMENT); + if (v[0] == 2 || v[1] == 2) { + auto c = stream->config() | SERIAL_FLOW_CONTROL_RTS_CTS; + return stream->setConfig(c); + } else if (v[0] == 0 || v[1] == 0) { + auto c = stream->config() & (~SERIAL_FLOW_CONTROL_RTS_CTS); + return stream->setConfig(c); + } + return SYSTEM_ERROR_INVALID_ARGUMENT; + }, serial_.get())); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CGEREP", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CREG", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CGREG", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CEREG", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CREG", "+CREG: 2,1")); // Dummy + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CGREG", "+CGREG: 2,1")); // Dummy + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+CEREG", "+CEREG: 2,1")); // Dummy + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CRC", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+CCWA", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+COPS", nullptr)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+COPS", "+COPS: 0,0,\"Particle\"")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::TEST, "+IPR", "+IPR: (0,9600,19200,38400,57600,115200,230400,460800,921600),()")); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::READ, "+IPR", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto stream = (SerialStream*)data; + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + request->sendResponse("+IPR: %u", stream->baudrate()); + return 0; + }, serial_.get())); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WRITE, "+IPR", [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto stream = (SerialStream*)data; + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + int v = 0; + CHECK_TRUE(request->scanf("%d", &v) == 1, SYSTEM_ERROR_INVALID_ARGUMENT); + CHECK_TRUE(v > 0, SYSTEM_ERROR_INVALID_ARGUMENT); + CHECK(stream->setBaudRate(v)); + return 0; + }, serial_.get())); + auto connectRequest = [](AtServerRequest* request, AtServerCommandType type, const char* command, void* data) -> int { + auto client = (net::ppp::Client*)data; + request->sendResponse("CONNECT %u", ((SerialStream*)request->server()->config().stream())->baudrate()); + request->setFinalResponse(AtServerRequest::CONNECT); + request->server()->suspend(); + client->setOutputCallback([](const uint8_t* data, size_t size, void* ctx) -> int { + // LOG(INFO, "output %u", size); + auto c = (Stream*)ctx; + int r = c->writeAll((const char*)data, size, 1000); + if (!r) { + return size; + } + return r; + }, request->server()->config().stream()); + client->connect(); + client->notifyEvent(ppp::Client::EVENT_LOWER_UP); + return 0; + }; + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::WILDCARD, "D*", connectRequest, &client_)); + server_->addCommandHandler(AtServerCommandHandler(AtServerCommandType::EXEC, "D", connectRequest, &client_)); + SPARK_ASSERT(os_thread_create(&thread_, "pppserver", OS_THREAD_PRIORITY_NETWORK, &PppServerNetif::loop, this, OS_THREAD_STACK_SIZE_DEFAULT_HIGH) == 0); return 0; } @@ -181,21 +344,28 @@ if_t PppServerNetif::interface() { void PppServerNetif::loop(void* arg) { PppServerNetif* self = static_cast(arg); - unsigned int timeout = 1000; while(!self->exit_) { - auto ev = self->serial_->waitEvent(SerialStream::READABLE | PPP_SERVER_NETIF_EVENT_EXIT, timeout); + auto ev = self->serial_->waitEvent(PPP_SERVER_NETIF_EVENT_EXIT, 0); if (ev & PPP_SERVER_NETIF_EVENT_EXIT) { - //break; + break; } + if (!self->server_->suspended()) { + self->server_->process(); + } else { + auto ev = self->serial_->waitEvent(SerialStream::READABLE | PPP_SERVER_NETIF_EVENT_EXIT, 1000); + if (ev & PPP_SERVER_NETIF_EVENT_EXIT) { + break; + } - if (ev & SerialStream::READABLE) { - char tmp[256] = {}; - while (self->serial_->availForRead() > 0) { - int sz = self->serial_->read(tmp, sizeof(tmp)); - // LOG(INFO, "input %d", sz); - auto r = self->client_.input((const uint8_t*)tmp, sz); - (void)r; - // LOG(INFO, "input result = %d", r); + if (ev & SerialStream::READABLE) { + char tmp[256] = {}; + while (self->serial_->availForRead() > 0) { + int sz = self->serial_->read(tmp, sizeof(tmp)); + // LOG(INFO, "input %d", sz); + auto r = self->client_.input((const uint8_t*)tmp, sz); + (void)r; + // LOG(INFO, "input result = %d", r); + } } } } @@ -206,24 +376,11 @@ void PppServerNetif::loop(void* arg) { } int PppServerNetif::up() { - LOG(INFO, "up"); CHECK(start()); - client_.setOutputCallback([](const uint8_t* data, size_t size, void* ctx) -> int { - // LOG(INFO, "output %u", size); - auto c = (PppServerNetif*)ctx; - int r = c->serial_->writeAll((const char*)data, size, 1000); - if (!r) { - return size; - } - return r; - }, this); - client_.connect(); - client_.notifyEvent(ppp::Client::EVENT_LOWER_UP); return SYSTEM_ERROR_NONE; } int PppServerNetif::down() { - LOG(INFO, "down"); if (dnsRunner_) { dnsRunner_->destroy(); } @@ -236,7 +393,6 @@ int PppServerNetif::down() { } int PppServerNetif::powerUp() { - LOG(INFO, "powerUp"); // FIXME: As long as the interface is initialized, // it must have been powered up as of right now. // The system network manager transit the interface to powering up, @@ -246,7 +402,6 @@ int PppServerNetif::powerUp() { } int PppServerNetif::powerDown() { - LOG(INFO, "powerDown"); int ret = down(); // FIXME: This don't really power off the module. // Notify the system network manager that the module is powered down @@ -302,7 +457,6 @@ void PppServerNetif::pppEventHandlerCb(particle::net::ppp::Client* c, uint64_t e } void PppServerNetif::pppEventHandler(uint64_t ev, int data) { - LOG(INFO, "ppp event %llx %d", ev, data); if (ev == particle::net::ppp::Client::EVENT_UP) { unsigned mtu = client_.getIf()->mtu; LOG(TRACE, "Negotiated MTU: %u", mtu); @@ -320,6 +474,9 @@ void PppServerNetif::pppEventHandler(uint64_t ev, int data) { dns_->destroy(); } nat_->disable(interface()); + // Drop back into AT mode + client_.notifyEvent(ppp::Client::EVENT_LOWER_DOWN); + server_->resume(); } else if (ev == particle::net::ppp::Client::EVENT_CONNECTING) { } else if (ev == particle::net::ppp::Client::EVENT_ERROR) { LOG(ERROR, "PPP error event data=%d", data); diff --git a/hal/network/lwip/pppservernetif.h b/hal/network/lwip/pppservernetif.h index a16c3e2809..98f59cc350 100644 --- a/hal/network/lwip/pppservernetif.h +++ b/hal/network/lwip/pppservernetif.h @@ -26,6 +26,7 @@ #include #include "ppp_client.h" #include "serial_stream.h" +#include "at_server.h" #ifdef __cplusplus @@ -102,6 +103,8 @@ class PppServerNetif : public BaseNetif { std::unique_ptr<::particle::ThreadRunner> dnsRunner_; std::unique_ptr nat_; if_req_ppp_server_uart_settings settings_; + std::unique_ptr server_; + }; } } // namespace particle::net diff --git a/hal/network/ncp/at_parser/at_server.cpp b/hal/network/ncp/at_parser/at_server.cpp new file mode 100644 index 0000000000..cbb3ee034f --- /dev/null +++ b/hal/network/ncp/at_parser/at_server.cpp @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#undef LOG_COMPILE_TIME_LEVEL +#define LOG_COMPILE_TIME_LEVEL LOG_LEVEL_ALL + +#include "logging.h" +#include "at_server.h" +#include "check.h" +#include "timer_hal.h" +#include +#include "stream.h" + +namespace particle { + +namespace { + +// Initial buffer size for the vprintf() method +const size_t PRINTF_INIT_BUF_SIZE = 128; + +} // unnamed + + +AtServer::AtServer() + : requestBuf_{}, + bufPos_(0), + newLineOffset_(0), + suspended_(false) { +} + +AtServer::~AtServer() { +} + +int AtServer::init(const AtServerConfig& config) { + conf_ = config; + return 0; +} + +void AtServer::destroy() { + reset(); +} + +int AtServer::reset() { + memset(requestBuf_, 0, sizeof(requestBuf_)); + bufPos_ = 0; + newLineOffset_ = 0; + return 0; +} + +int AtServer::addCommandHandler(const AtServerCommandHandler& handler) { + if (!handlers_.append(handler)) { + return SYSTEM_ERROR_NO_MEMORY; + } + std::sort(handlers_.begin(), handlers_.end(), [](const AtServerCommandHandler& h1, const AtServerCommandHandler& h2) { + return (strlen(h1.command()) > strlen(h2.command())); // In descending order + }); + return 0; +} + +int AtServer::removeCommandHandler(const char* command, AtServerCommandType type) { + bool removed = false; + for (int i = 0; i < handlers_.size();) { + auto& handler = handlers_[i]; + if (handler.type() != type && type != AtServerCommandType::ANY) { + i++; + continue; + } + if (!strcmp(handler.command(), command)) { + // Remove + handlers_.removeAt(i); + removed = true; + continue; + } + i++; + } + return removed ? 0 : SYSTEM_ERROR_NOT_FOUND; +} + +int AtServer::process() { + if (suspended_) { + return SYSTEM_ERROR_INVALID_STATE; + } + + while (hasNextLine()) { + CHECK(processRequest()); + if (suspended_) { + return 0; + } + } + + auto stream = conf_.stream(); + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + + auto start = HAL_Timer_Get_Milli_Seconds(); + auto end = start + conf_.streamTimeout(); + + for (;;) { + // Consume non-command bytes if any + CHECK(processRequest()); + + auto now = HAL_Timer_Get_Milli_Seconds(); + auto waitTimeout = now <= end ? end - now : 0; + CHECK(stream->waitEvent(Stream::READABLE, waitTimeout)); + + size_t read = CHECK(stream->read(requestBuf_ + bufPos_, DEFAULT_REQUEST_BUFFER_SIZE - bufPos_)); + if (read > 0 && conf_.echoEnabled()) { + // Echo + CHECK(write(requestBuf_ + bufPos_, read)); + } + bufPos_ += read; + + while (hasNextLine()) { + CHECK(processRequest()); + if (suspended_) { + return 0; + } + } + + if (waitTimeout == 0) { + break; + } + } + + return 0; +} + +int AtServer::suspend() { + suspended_ = true; + return 0; +} + +bool AtServer::suspended() const { + return suspended_; +} + +int AtServer::resume() { + suspended_ = false; + return 0; +} + +AtServerConfig AtServer::config() const { + return conf_; +} + +int AtServer::processRequest() { + size_t skip = 0; + bool found = false; + for (size_t i = 0; i < bufPos_; i++) { + if (std::toupper(requestBuf_[i]) == 'A') { + if ((i + 1) >= bufPos_) { + break; + } + if (std::toupper(requestBuf_[i + 1]) == 'T') { + // Found AT + found = true; + break; + } + } else { + skip++; + } + } + if (skip) { + memmove(requestBuf_, requestBuf_ + skip, bufPos_ - skip); + bufPos_ -= skip; + } + if (!found) { + return 0; + } + + // Supposedly a command + if (!hasNextLine()) { + return 0; + } + size_t lineLength = newLineOffset_; + AtServerCommandType type = AtServerCommandType::EXEC; // Default + size_t commandLength = lineLength; + size_t dataStart = lineLength; + for (size_t i = 0; i < lineLength; i++) { + if (requestBuf_[i] == '=') { + if ((i + 1) < lineLength && requestBuf_[i + 1] == '?') { + if ((i + 2) == lineLength) { + type = AtServerCommandType::TEST; + } else { + // This is kinda wrong, but ok, we'll try to handle it + type = AtServerCommandType::WRITE; + } + commandLength = i; + dataStart = i + 2; + break; + } else { + type = AtServerCommandType::WRITE; + commandLength = i; + dataStart = i + 1; + break; + } + } else if (requestBuf_[i] == '?') { + type = AtServerCommandType::READ; + commandLength = i; + dataStart = i + 1; + break; + } + } + + std::transform(requestBuf_, requestBuf_ + commandLength, requestBuf_, ::toupper); + found = false; + requestBuf_[lineLength] = '\0'; + AtServerRequest req(this, type, requestBuf_ + 2 /* AT */, lineLength - 2 /* AT */, dataStart - 2); + + if (commandLength > 2) { + logCmdLine(requestBuf_, lineLength); + for (auto& handler: handlers_) { + if (!strncmp(handler.command(), requestBuf_ + 2, std::max(commandLength - 2, strlen(handler.command()))) || + (handler.type() == AtServerCommandType::WILDCARD && !strncmp(handler.command(), requestBuf_ + 2, strlen(handler.command())))) { + // Matches + if (handler.type() == type || handler.type() == AtServerCommandType::ANY || handler.type() == AtServerCommandType::WILDCARD) { + found = true; + if (handler.handler(&req, type, (const char*)handler.command(), handler.data()) < 0) { + req.setFinalResponse(AtServerRequest::ERROR); + } + break; + } + } else if (handler.type() == AtServerCommandType::ONE_INT_ARG && type == AtServerCommandType::EXEC) { + if (!strncmp(handler.command(), requestBuf_ + 2, strlen(handler.command()))) { + int dummy = 0; + if (sscanf(requestBuf_ + 2 + strlen(handler.command()), "%d", &dummy) == 1) { + // Matches + found = true; + req.dataOffset_ = strlen(handler.command()); + if (handler.handler(&req, handler.type(), (const char*)handler.command(), handler.data()) < 0) { + req.setFinalResponse(AtServerRequest::ERROR); + } + break; + } + } + } + } + } + if (!found) { + if (commandLength > 2) { + req.setFinalResponse(AtServerRequest::ERROR); + } else { + req.setFinalResponse(AtServerRequest::OK); + } + } + // Consume with newline character + lineLength++; + memmove(requestBuf_, requestBuf_ + lineLength, bufPos_ - lineLength); + bufPos_ -= lineLength; + return 0; +} + +int AtServer::write(const char* str, size_t len) { + return write(str, len, conf_.streamTimeout()); +} + +int AtServer::write(const char* str, size_t len, system_tick_t timeout) { + auto stream = conf_.stream(); + CHECK_TRUE(stream, SYSTEM_ERROR_INVALID_STATE); + CHECK(stream->waitEvent(Stream::WRITABLE, timeout)); + return stream->write(str, len); +} + +int AtServer::writeNewLine() { + switch (conf_.commandTerminator()) { + case AtCommandTerminator::CR: { + return write("\r", 1); + } + case AtCommandTerminator::LF: { + return write("\n", 1); + } + case AtCommandTerminator::CRLF: { + return write("\r\n", 2); + } + } + return 0; +} + +bool AtServer::hasNextLine() { + for (size_t i = 0; i < bufPos_; i++) { + if (requestBuf_[i] == '\r' || requestBuf_[i] == '\n') { + newLineOffset_ = i; + return true; + } + } + return false; +} + +void AtServer::logCmdLine(const char* data, size_t size) const { + if (conf_.logEnabled() && size > 0) { + LOG_C(TRACE, conf_.logCategory(), "> %.*s", size, data); + } +} + +void AtServer::logRespLine(const char* data, size_t size) const { + if (conf_.logEnabled() && size > 0) { + LOG_C(TRACE, conf_.logCategory(), "< %.*s", size, data); + } +} + +AtServerCommandHandler::AtServerCommandHandler(AtServerCommandType type, const char* command, ServerCommandHandler handler, void* data) + : type_(type), + command_(command), + handler_(handler), + data_(data), + cResponse_(nullptr), + response_(nullptr) { + std::transform((const char*)command, (const char*)command + strlen(command), (char*)((const char*)command_), ::toupper); +} + +AtServerCommandHandler::AtServerCommandHandler(AtServerCommandType type, const char* command, const char* response) + : AtServerCommandHandler(type, command, nullptr, nullptr) { + cResponse_ = response; +} + +AtServerCommandHandler::AtServerCommandHandler(AtServerCommandType type, const char* command, CString response) + : AtServerCommandHandler(type, command, nullptr, nullptr) { + response_ = response; +} + +AtServerCommandType AtServerCommandHandler::type() const { + return type_; +} + +CString AtServerCommandHandler::command() const { + return command_; +} + +AtServerCommandHandler::ServerCommandHandler AtServerCommandHandler::handler() const { + return handler_; +} + +void* AtServerCommandHandler::data() const { + return data_; +} + +int AtServerCommandHandler::handler(AtServerRequest* request, AtServerCommandType type, const char* command, void* data) { + if (handler_) { + return handler_(request, type, command, data); + } else if ((const char*)response_) { + return request->sendResponse(response_); + } else if (cResponse_) { + return request->sendResponse(cResponse_); + } + return 0; +} + +AtServerRequest::AtServerRequest(AtServer* server, AtServerCommandType type, const char* data, size_t len, size_t dataOffset) + : server_(server), + finalResponse_(AtServerRequest::OK), + type_(type), + buf_(data), + len_(len), + dataOffset_(dataOffset) { + +} + +AtServer* AtServerRequest::server() const { + return server_; +} + +AtServerRequest::~AtServerRequest() { + // FIXME: duplication with AT parser + const char* finalResponses[] = { + "OK", + "ERROR", + "BUSY", + "NO_ANSWER", + "NO_CARRIER", + "NO_DIALTONE", + "+CME_ERROR", + "+CMS_ERROR", + "CONNECT" + }; + server_->writeNewLine(); + server_->logRespLine(finalResponses[finalResponse_], strlen(finalResponses[finalResponse_])); + server_->write(finalResponses[finalResponse_], strlen(finalResponses[finalResponse_])); + server_->writeNewLine(); +} + +CString AtServerRequest::readLine() { + return CString(buf_, len_); +} + +int AtServerRequest::readLine(char* data, size_t size) { + if (data) { + strncpy(data, buf_, size); + } + return std::min(size, len_); +} + +CString AtServerRequest::read() { + return CString(buf_ + dataOffset_, len_ - dataOffset_); +} + +int AtServerRequest::read(char* data, size_t size) { + if (data) { + strncpy(data, buf_ + dataOffset_, size); + } + return std::min(size, len_ - dataOffset_); +} + +int AtServerRequest::scanf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + const int ret = vscanf(fmt, args); + va_end(args); + return ret; +} + +int AtServerRequest::vscanf(const char* fmt, va_list args) { + int n = vsscanf(type_ == AtServerCommandType::ANY ? buf_ : buf_ + dataOffset_, fmt, args); + return n; +} + +int AtServerRequest::sendResponse(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + int r = sendResponseV(fmt, args); + va_end(args); + return r; +} + +int AtServerRequest::sendResponseV(const char* fmt, va_list args) { + if (!sentResponse_) { + server_->writeNewLine(); + } + sentResponse_ = true; + + char buf[PRINTF_INIT_BUF_SIZE]; + va_list args2; + va_copy(args2, args); + int err = 0; + int n = vsnprintf(buf, sizeof(buf), fmt, args); + if (n > 0) { + if ((size_t)n < sizeof(buf)) { + server_->write(buf, n); + server_->logRespLine(buf, n); + server_->writeNewLine(); + } else { + // Allocate a larger buffer on the heap + const std::unique_ptr buf2(new char[n + 1]); + if (buf2) { + n = vsnprintf(buf2.get(), n + 1, fmt, args2); + if (n > 0) { + server_->write(buf2.get(), n); + server_->logRespLine(buf2.get(), n); + server_->writeNewLine(); + } + } else { + err = SYSTEM_ERROR_NO_MEMORY; + } + } + } + if (n < 0) { + err = SYSTEM_ERROR_UNKNOWN; + } + va_end(args2); + return err; +} + +int AtServerRequest::sendNewLine() { + return server_->writeNewLine(); +} + +int AtServerRequest::setFinalResponse(Result result) { + finalResponse_ = result; + return 0; +} + +} // particle \ No newline at end of file diff --git a/hal/network/ncp/at_parser/at_server.h b/hal/network/ncp/at_parser/at_server.h new file mode 100644 index 0000000000..e2643a6159 --- /dev/null +++ b/hal/network/ncp/at_parser/at_server.h @@ -0,0 +1,353 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include +#include "c_string.h" +#include "at_parser.h" +#include "spark_wiring_vector.h" + +namespace particle { + +class AtServerConfig { +public: + /** + * Default command line terminator. + * For requests this is usally for responses the default is + * + * @see `commandTerminator()` + */ + static const auto DEFAULT_COMMAND_TERMINATOR = AtCommandTerminator::CRLF; + /** + * Default stream timeout in milliseconds. + * + * @see `streamTimeout()` + */ + static const auto DEFAULT_STREAM_TIMEOUT = 5000; + /** + * Default state of the echo handling. + * + * @see `echoEnabled()` + */ + static const auto DEFAULT_ECHO_ENABLED = true; + /** + * Default state of the logging. + * + * @see `logEnabled()` + */ + static const auto DEFAULT_LOG_ENABLED = true; + /** + * Default logging category + * + * @see `logCategory()` + */ + static constexpr auto DEFAULT_LOG_CATEGORY = "at.server"; + + /** + * Constructs a settings object with all parameters set to their default values. + */ + AtServerConfig(); + /** + * Sets the stream used for communication with the DCE. + * + * @param strm Stream instance. + * @return This settings object. + */ + AtServerConfig& stream(Stream* strm); + /** + * Returns the stream used for communication with the DCE. + * + * @return Stream instance. + */ + Stream* stream() const; + /** + * Sets the command line terminator. + * + * @param term Command line terminator. + * @return This settings object. + * + * @see `DEFAULT_COMMAND_TERMINATOR` + */ + AtServerConfig& commandTerminator(AtCommandTerminator term); + /** + * Returns the command line terminator. + * + * @return Command line terminator. + * + * @see `DEFAULT_COMMAND_TERMINATOR` + */ + AtCommandTerminator commandTerminator() const; + /** + * Sets the stream timeout. + * + * This method sets the inter-byte timeout for read and write operations. + * + * @param timeout Timeout in milliseconds. + * @return This settings object. + * + * @see `DEFAULT_STREAM_TIMEOUT` + */ + AtServerConfig& streamTimeout(unsigned timeout); + /** + * Returns the stream timeout. + * + * @return Timeout in milliseconds. + * + * @see `DEFAULT_STREAM_TIMEOUT` + */ + unsigned streamTimeout() const; + /** + * Enables or disables the echo handling. + * + * @return This settings object. + * + * @see `DEFAULT_ECHO_ENABLED` + */ + AtServerConfig& echoEnabled(bool enabled); + /** + * Returns `true` if the echo handling is enabled, or `false` otherwise. + * + * @see `DEFAULT_ECHO_ENABLED` + */ + bool echoEnabled() const; + /** + * Enables or disables the logging of AT commands. + * + * @return This settings object. + * + * @see `DEFAULT_LOG_ENABLED` + */ + AtServerConfig& logEnabled(bool enabled); + /** + * Returns `true` if the logging of AT commands is enabled, or `false` otherwise. + * + * @see `DEFAULT_LOG_ENABLED` + */ + bool logEnabled() const; + /** + * Sets the logging category. + * + * @return This settings object. + * + * @see `DEFAULT_LOG_CATEGORY` + */ + AtServerConfig& logCategory(const char* category); + /** + * Returns the logging category. + * + * @return Logging category. + * + * @see `DEFAULT_LOG_CATEGORY` + */ + const char* logCategory() const; + +private: + Stream* strm_; + AtCommandTerminator cmdTerm_; + unsigned strmTimeout_; + bool echoEnabled_; + bool logEnabled_; + CString logCategory_; +}; + +class AtServer; + +enum class AtServerCommandType { + ANY, + EXEC, + READ, + WRITE, + TEST, + ONE_INT_ARG, + WILDCARD +}; + +class AtServerRequest { +public: + AtServerRequest(AtServer* server, AtServerCommandType type, const char* data, size_t len, size_t dataOffset); + ~AtServerRequest(); + + enum Result { + OK = 0, + ERROR = 1, + BUSY = 2, + NO_ANSWER = 3, + NO_CARRIER = 4, + NO_DIALTONE = 5, + CME_ERROR = 6, + CMS_ERROR = 7, + CONNECT = 8 + }; + + CString readLine(); + int readLine(char* data, size_t size); + + // Argument data if type != ANY, otherwise full command line + int read(char* data, size_t size); + CString read(); + int scanf(const char* fmt, ...) __attribute__((format(scanf, 2, 3))); + int vscanf(const char* fmt, va_list args); + + int sendResponse(const char* fmt, ...); + int sendResponseV(const char* fmt, va_list args); + int sendNewLine(); + int setFinalResponse(Result result); + + AtServer* server() const; + +private: + friend class AtServer; + + bool sentResponse_ = false; + + AtServer* server_; + Result finalResponse_; + AtServerCommandType type_; + const char* buf_; + size_t len_; + size_t dataOffset_; +}; + +class AtServerCommandHandler { +public: + typedef int(*ServerCommandHandler)(AtServerRequest* request, AtServerCommandType type, const char* command, void* data); + AtServerCommandHandler() = delete; + AtServerCommandHandler(AtServerCommandType type, const char* command, ServerCommandHandler handler, void* data); + AtServerCommandHandler(AtServerCommandType type, const char* command, const char* response); + AtServerCommandHandler(AtServerCommandType type, const char* command, CString response); + + AtServerCommandType type() const; + CString command() const; + ServerCommandHandler handler() const; + void* data() const; + + virtual int handler(AtServerRequest* request, AtServerCommandType type, const char* command, void* data); + +private: + AtServerCommandType type_; + CString command_; + ServerCommandHandler handler_; + void* data_; + const char* cResponse_; + CString response_; +}; + +class AtServer { +public: + AtServer(); + ~AtServer(); + + int init(const AtServerConfig& config); + void destroy(); + int reset(); + + int addCommandHandler(const AtServerCommandHandler& handler); + int removeCommandHandler(const char* command, AtServerCommandType type = AtServerCommandType::ANY); + + int process(); + int processRequest(); + + int write(const char* str, size_t len); + int write(const char* str, size_t len, system_tick_t timeout); + int writeNewLine(); + bool hasNextLine(); + + int suspend(); + bool suspended() const; + int resume(); + + void logCmdLine(const char* data, size_t size) const; + + void logRespLine(const char* data, size_t size) const; + + AtServerConfig config() const; + +private: + static constexpr size_t DEFAULT_REQUEST_BUFFER_SIZE = 512; + Vector handlers_; + char requestBuf_[DEFAULT_REQUEST_BUFFER_SIZE]; + size_t bufPos_; + size_t newLineOffset_; + AtServerConfig conf_; + bool suspended_; +}; + + +inline AtServerConfig::AtServerConfig() : + strm_(nullptr), + cmdTerm_(DEFAULT_COMMAND_TERMINATOR), + strmTimeout_(DEFAULT_STREAM_TIMEOUT), + echoEnabled_(DEFAULT_ECHO_ENABLED), + logEnabled_(DEFAULT_LOG_ENABLED) { +} + +inline AtServerConfig& AtServerConfig::stream(Stream* strm) { + strm_ = strm; + return *this; +} + +inline Stream* AtServerConfig::stream() const { + return strm_; +} + +inline AtServerConfig& AtServerConfig::commandTerminator(AtCommandTerminator term) { + cmdTerm_ = term; + return *this; +} + +inline AtCommandTerminator AtServerConfig::commandTerminator() const { + return cmdTerm_; +} + +inline AtServerConfig& AtServerConfig::streamTimeout(unsigned timeout) { + strmTimeout_ = timeout; + return *this; +} + +inline unsigned AtServerConfig::streamTimeout() const { + return strmTimeout_; +} + +inline AtServerConfig& AtServerConfig::echoEnabled(bool enabled) { + echoEnabled_ = enabled; + return *this; +} + +inline bool AtServerConfig::echoEnabled() const { + return echoEnabled_; +} + +inline AtServerConfig& AtServerConfig::logEnabled(bool enabled) { + logEnabled_ = enabled; + return *this; +} + +inline bool AtServerConfig::logEnabled() const { + return logEnabled_; +} + +inline AtServerConfig& AtServerConfig::logCategory(const char* category) { + logCategory_ = category; + return *this; +} + +inline const char* AtServerConfig::logCategory() const { + return logCategory_ ? static_cast(logCategory_) : DEFAULT_LOG_CATEGORY; +} + +} // particle diff --git a/hal/src/nRF52840/device_code.cpp b/hal/src/nRF52840/device_code.cpp index 8a8f00ead3..8849669c4c 100644 --- a/hal/src/nRF52840/device_code.cpp +++ b/hal/src/nRF52840/device_code.cpp @@ -126,5 +126,6 @@ int get_device_name(char* buf, size_t size) { } int get_device_usb_name(char* buf, size_t size) { - return SYSTEM_ERROR_NOT_SUPPORTED; + strncpy(buf, HAL_PLATFORM_USB_PRODUCT_STRING, size); + return 0; } diff --git a/hal/src/nRF52840/hal_platform_nrf52840_config.h b/hal/src/nRF52840/hal_platform_nrf52840_config.h index 44f9080707..58c7c5ac4f 100644 --- a/hal/src/nRF52840/hal_platform_nrf52840_config.h +++ b/hal/src/nRF52840/hal_platform_nrf52840_config.h @@ -16,6 +16,8 @@ */ #pragma once +#include "platforms.h" + #define HAL_PLATFORM_CLOUD_UDP (1) #define HAL_PLATFORM_DCT (1) @@ -122,3 +124,19 @@ #define HAL_PLATFORM_INFLATE_USE_FILESYSTEM (1) #define HAL_PLATFORM_INCLUDE_LEGACY_MODULE_INFO (1) #endif + +#if PLATFORM_ID == PLATFORM_ARGON +# define HAL_PLATFORM_USB_PRODUCT_STRING "Argon" +#elif PLATFORM_ID == PLATFORM_BORON +# define HAL_PLATFORM_USB_PRODUCT_STRING "Boron" +#elif PLATFORM_ID == PLATFORM_ESOMX +# define HAL_PLATFORM_USB_PRODUCT_STRING "E-SoM-X" +#elif PLATFORM_ID == PLATFORM_ASOM +# define HAL_PLATFORM_USB_PRODUCT_STRING "A-SoM" +#elif PLATFORM_ID == PLATFORM_BSOM +# define HAL_PLATFORM_USB_PRODUCT_STRING "B-SoM" +#elif PLATFORM_ID == PLATFORM_B5SOM +# define HAL_PLATFORM_USB_PRODUCT_STRING "B5-SoM" +#elif PLATFORM_ID == PLATFORM_TRACKER +# define HAL_PLATFORM_USB_PRODUCT_STRING "Tracker" +#endif diff --git a/hal/src/nRF52840/serial_stream.h b/hal/src/nRF52840/serial_stream.h index 6726eb1874..e3d1e1e69d 100644 --- a/hal/src/nRF52840/serial_stream.h +++ b/hal/src/nRF52840/serial_stream.h @@ -43,6 +43,9 @@ class SerialStream: public EventGroupBasedStream { int setBaudRate(unsigned int baudrate); int setConfig(uint32_t config, unsigned int baudrate = 0); + uint32_t config() const; + unsigned int baudrate() const; + void enabled(bool enabled); bool enabled() const; @@ -74,6 +77,14 @@ inline bool SerialStream::on() const { return phyOn_; } +inline uint32_t SerialStream::config() const { + return config_; +} + +inline unsigned int SerialStream::baudrate() const { + return baudrate_; +} + static_assert((int)SerialStream::READABLE == (int)HAL_USART_PVT_EVENT_READABLE, "Serial::READABLE needs to match HAL_USART_PVT_EVENT_READABLE"); static_assert((int)SerialStream::WRITABLE == (int)HAL_USART_PVT_EVENT_WRITABLE, "Serial::WRITABLE needs to match HAL_USART_PVT_EVENT_WRITABLE"); diff --git a/hal/src/rtl872x/serial_stream.h b/hal/src/rtl872x/serial_stream.h index 6726eb1874..e3d1e1e69d 100644 --- a/hal/src/rtl872x/serial_stream.h +++ b/hal/src/rtl872x/serial_stream.h @@ -43,6 +43,9 @@ class SerialStream: public EventGroupBasedStream { int setBaudRate(unsigned int baudrate); int setConfig(uint32_t config, unsigned int baudrate = 0); + uint32_t config() const; + unsigned int baudrate() const; + void enabled(bool enabled); bool enabled() const; @@ -74,6 +77,14 @@ inline bool SerialStream::on() const { return phyOn_; } +inline uint32_t SerialStream::config() const { + return config_; +} + +inline unsigned int SerialStream::baudrate() const { + return baudrate_; +} + static_assert((int)SerialStream::READABLE == (int)HAL_USART_PVT_EVENT_READABLE, "Serial::READABLE needs to match HAL_USART_PVT_EVENT_READABLE"); static_assert((int)SerialStream::WRITABLE == (int)HAL_USART_PVT_EVENT_WRITABLE, "Serial::WRITABLE needs to match HAL_USART_PVT_EVENT_WRITABLE"); diff --git a/wiring/inc/spark_wiring.h b/wiring/inc/spark_wiring.h index ea211e6628..c014215652 100644 --- a/wiring/inc/spark_wiring.h +++ b/wiring/inc/spark_wiring.h @@ -44,6 +44,7 @@ #include "spark_wiring_wifi.h" #include "spark_wiring_cellular.h" #include "spark_wiring_ethernet.h" +#include "spark_wiring_tether.h" #include "spark_wiring_character.h" #include "spark_wiring_random.h" #include "spark_wiring_system.h" diff --git a/wiring/inc/spark_wiring_ethernet.h b/wiring/inc/spark_wiring_ethernet.h index dd6b128784..73ff6947c5 100644 --- a/wiring/inc/spark_wiring_ethernet.h +++ b/wiring/inc/spark_wiring_ethernet.h @@ -30,28 +30,6 @@ namespace spark { -#define GET_IF_ADDR(_ifindex, _addrType, _addr) \ - if_t iface = nullptr; \ - if (!if_get_by_index(_ifindex, &iface)) { \ - if_addrs* ifAddrList = nullptr; \ - if (!if_get_addrs(iface, &ifAddrList)) { \ - SCOPE_GUARD({ \ - if_free_if_addrs(ifAddrList); \ - }); \ - if_addr* ifAddr = nullptr; \ - for (if_addrs* i = ifAddrList; i; i = i->next) { \ - if (i->if_addr->addr->sa_family == AF_INET) { \ - ifAddr = i->if_addr; \ - break; \ - } \ - } \ - if (ifAddr) { \ - auto sockAddr = (const sockaddr_in*)ifAddr->_addrType; \ - _addr = (const uint8_t*)(&sockAddr->sin_addr.s_addr); \ - } \ - } \ - } - class EthernetClass : public NetworkClass { public: EthernetClass() : diff --git a/wiring/inc/spark_wiring_network.h b/wiring/inc/spark_wiring_network.h index e713002a99..147fe73446 100644 --- a/wiring/inc/spark_wiring_network.h +++ b/wiring/inc/spark_wiring_network.h @@ -36,6 +36,31 @@ class NetworkClass; // Defined as the primary network extern NetworkClass Network; +#if HAL_PLATFORM_IFAPI + +#define GET_IF_ADDR(_ifindex, _addrType, _addr) \ + if_t iface = nullptr; \ + if (!if_get_by_index(_ifindex, &iface)) { \ + if_addrs* ifAddrList = nullptr; \ + if (!if_get_addrs(iface, &ifAddrList)) { \ + SCOPE_GUARD({ \ + if_free_if_addrs(ifAddrList); \ + }); \ + if_addr* ifAddr = nullptr; \ + for (if_addrs* i = ifAddrList; i; i = i->next) { \ + if (i->if_addr->addr->sa_family == AF_INET) { \ + ifAddr = i->if_addr; \ + break; \ + } \ + } \ + if (ifAddr) { \ + auto sockAddr = (const sockaddr_in*)ifAddr->_addrType; \ + _addr = (const uint8_t*)(&sockAddr->sin_addr.s_addr); \ + } \ + } \ + } +#endif // HAL_PLATFORM_IFAPI + //Retained for compatibility and to flag compiler warnings as build errors class NetworkClass { diff --git a/wiring/inc/spark_wiring_system.h b/wiring/inc/spark_wiring_system.h index 466ec6d600..39678f0119 100644 --- a/wiring/inc/spark_wiring_system.h +++ b/wiring/inc/spark_wiring_system.h @@ -1165,11 +1165,19 @@ class SystemClass { } }; +namespace particle { +namespace detail { +[[deprecated("Starting with Device OS 6.2.0 threading is enabled by default. It can be explicitly disabled with SYSTEM_THREAD(DISABLED), however this macro and mode of operation is expected to be removed in future releases.")]] +inline void SYSTEM_THREAD(spark::feature::State feature, void* reserved) { + system_thread_set_state(feature, reserved); +} +} } // particle::detail + extern SystemClass System; #define SYSTEM_MODE(mode) SystemClass SystemMode(mode); -#define SYSTEM_THREAD(state) STARTUP(system_thread_set_state(spark::feature::state, nullptr)); +#define SYSTEM_THREAD(state) STARTUP(::particle::detail::SYSTEM_THREAD(spark::feature::state, nullptr)); #define waitFor(condition, timeout) System.waitCondition([]{ return (condition)(); }, (timeout)) #define waitUntil(condition) System.waitCondition([]{ return (condition)(); }) diff --git a/wiring/inc/spark_wiring_tether.h b/wiring/inc/spark_wiring_tether.h new file mode 100644 index 0000000000..50420c30cd --- /dev/null +++ b/wiring/inc/spark_wiring_tether.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include "spark_wiring_platform.h" + +#if HAL_PLATFORM_PPP_SERVER + +#include "spark_wiring_network.h" +#include "system_network.h" +#include "spark_wiring_usartserial.h" +#include "ifapi.h" +#include "scope_guard.h" +#include "check.h" + +namespace particle { + +struct TetherSerialConfig { + TetherSerialConfig(); + + TetherSerialConfig& serial(USARTSerial& s); + USARTSerial& serial() const; + + TetherSerialConfig& config(unsigned conf); + unsigned config() const; + + TetherSerialConfig& baudrate(unsigned baud); + unsigned baudrate() const; + +private: + USARTSerial& serial_; + unsigned config_; + unsigned baudrate_; +}; + +class TetherClass : public spark::NetworkClass { +public: + TetherClass() : + NetworkClass(NETWORK_INTERFACE_PPP_SERVER) { + } + + void on() { + network_on(*this, 0, 0, NULL); + } + + void off() { + network_off(*this, 0, 0, NULL); + } + + void connect(unsigned flags=0) { + network_connect(*this, flags, 0, NULL); + } + + bool connecting(void) { + return network_connecting(*this, 0, NULL); + } + + void disconnect() { + network_disconnect(*this, NETWORK_DISCONNECT_REASON_USER, NULL); + } + + void listen(bool begin=true) { + network_listen(*this, begin ? 0 : 1, NULL); + } + + void setListenTimeout(uint16_t timeout) { + network_set_listen_timeout(*this, timeout, NULL); + } + + uint16_t getListenTimeout(void) { + return network_get_listen_timeout(*this, 0, NULL); + } + + bool listening(void) { + return network_listening(*this, 0, NULL); + } + + bool ready() { + return network_ready(*this, 0, NULL); + } + + IPAddress localIP() { + IPAddress addr; + GET_IF_ADDR(NETWORK_INTERFACE_PPP_SERVER, addr, addr); + return addr; + } + + IPAddress subnetMask() { + IPAddress addr; + GET_IF_ADDR(NETWORK_INTERFACE_PPP_SERVER, netmask, addr); + return addr; + } + + IPAddress gatewayIP() { + IPAddress addr; + GET_IF_ADDR(NETWORK_INTERFACE_PPP_SERVER, gw, addr); + return addr; + } + + IPAddress dnsServerIP() { + return IPAddress(); + } + + IPAddress dhcpServerIP() { + return IPAddress(); + } + + int bind(const TetherSerialConfig& config); +}; + +extern TetherClass Tether; + +} /* namespace particle */ + +#endif /* HAL_PLATFORM_PPP_SERVER */ diff --git a/wiring/inc/spark_wiring_usartserial.h b/wiring/inc/spark_wiring_usartserial.h index 06c044b1bf..41d5b1a942 100644 --- a/wiring/inc/spark_wiring_usartserial.h +++ b/wiring/inc/spark_wiring_usartserial.h @@ -72,6 +72,8 @@ class USARTSerial : public Stream hal_usart_interface_t interface() const { return _serial; } + + static USARTSerial& from(hal_usart_interface_t iface); }; #if Wiring_Serial2 @@ -131,6 +133,38 @@ extern USARTSerial& __fetch_global_Serial5(); hal_usart_buffer_config_t __attribute__((weak)) acquireSerial5Buffer(); #endif +inline USARTSerial& USARTSerial::from(hal_usart_interface_t iface) { + switch (iface) { + case HAL_USART_SERIAL1: { + return Serial1; + } +#if Wiring_Serial2 + case HAL_USART_SERIAL2: { + return Serial2; + } +#endif +#if Wiring_Serial3 + case HAL_USART_SERIAL3: { + return Serial3; + } +#endif +#if Wiring_Serial4 + case HAL_USART_SERIAL4: { + return Serial4; + } +#endif +#if Wiring_Serial5 + case HAL_USART_SERIAL5: { + return Serial5; + } +#endif + + default: { + return Serial1; + } + } +} + #endif #endif diff --git a/wiring/src/spark_wiring_network.cpp b/wiring/src/spark_wiring_network.cpp index 60eef08767..507bfee33c 100644 --- a/wiring/src/spark_wiring_network.cpp +++ b/wiring/src/spark_wiring_network.cpp @@ -20,6 +20,7 @@ #include "spark_wiring_wifi.h" #include "spark_wiring_cellular.h" #include "spark_wiring_ethernet.h" +#include "spark_wiring_tether.h" #include "hal_platform.h" #if HAL_USE_INET_HAL_POSIX #include @@ -47,6 +48,10 @@ NetworkClass& NetworkClass::from(network_interface_t nif) { #if Wiring_Cellular case NETWORK_INTERFACE_CELLULAR: return Cellular; +#endif +#if HAL_PLATFORM_PPP_SERVER + case NETWORK_INTERFACE_PPP_SERVER: + return Tether; #endif default: return Network; diff --git a/wiring/src/spark_wiring_tether.cpp b/wiring/src/spark_wiring_tether.cpp new file mode 100644 index 0000000000..88267f8910 --- /dev/null +++ b/wiring/src/spark_wiring_tether.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "spark_wiring_tether.h" + +#if HAL_PLATFORM_PPP_SERVER + +namespace particle { + +TetherClass Tether; + +TetherSerialConfig::TetherSerialConfig() + : serial_(USARTSerial::from(HAL_PLATFORM_PPP_SERVER_USART)), + config_(HAL_PLATFORM_PPP_SERVER_USART_FLAGS), + baudrate_(HAL_PLATFORM_PPP_SERVER_USART_BAUDRATE) { +} + +TetherSerialConfig& TetherSerialConfig::serial(USARTSerial& s) { + serial_ = s; + return *this; +} + +USARTSerial& TetherSerialConfig::serial() const { + return serial_; +} + +TetherSerialConfig& TetherSerialConfig::config(unsigned conf) { + config_ = conf; + return *this; +} + +unsigned TetherSerialConfig::config() const { + return config_; +} + +TetherSerialConfig& TetherSerialConfig::baudrate(unsigned baud) { + baudrate_ = baud; + return *this; +} + +unsigned TetherSerialConfig::baudrate() const { + return baudrate_; +} + +int TetherClass::bind(const TetherSerialConfig& config) { + if_t iface = nullptr; + if_get_by_index(*this, &iface); + if (iface) { + if_req_ppp_server_uart_settings settings = {}; + settings.base.type = IF_REQ_DRIVER_SPECIFIC_PPP_SERVER_UART_SETTINGS; + settings.serial = config.serial().interface(); + settings.baud = config.baudrate(); + settings.config = config.config(); + return if_request(iface, IF_REQ_DRIVER_SPECIFIC, &settings, sizeof(settings), nullptr); + } + return SYSTEM_ERROR_NOT_FOUND; +} + +} // spark + +#endif // HAL_PLATFORM_PPP_SERVER From e46703ece489723d59f3aa1f8ab2202f52647061 Mon Sep 17 00:00:00 2001 From: Andrey Tolstoy Date: Fri, 4 Oct 2024 07:50:13 +0700 Subject: [PATCH 2/2] remove chatty log --- hal/network/lwip/nat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hal/network/lwip/nat.cpp b/hal/network/lwip/nat.cpp index dcec518f2c..3e06ebbc12 100644 --- a/hal/network/lwip/nat.cpp +++ b/hal/network/lwip/nat.cpp @@ -422,7 +422,7 @@ int Nat64::natInput(const ip_addr_t* src, const ip_addr_t* dst, L4Protocol proto uint16_t newMss = lwip_htons(outif->mtu - 40); uint16_t oldMss = lwip_ntohs(*(uint16_t*)(o + 2)); *(uint16_t*)(o + 2) = newMss; - LOG(ERROR, "Adjusted MSS from %u to %u", (unsigned)oldMss, outif->mtu - 40); + LOG_DEBUG(ERROR, "Adjusted MSS from %u to %u", (unsigned)oldMss, outif->mtu - 40); } o += 4; break;