From e532fd87346cb15bf2023ab0e0ac8dfaceb3c2f9 Mon Sep 17 00:00:00 2001 From: Maciej Janicki Date: Tue, 2 Feb 2021 16:08:30 +0100 Subject: [PATCH] [EGD-5748] Remake Cellular flow This commit changes TS0710 Worker operation. 1. It introduces MessageBuffers as main interprocess data passing mechanism. 2. DMA transaction status codes and CMUX errors are now propagated to Service. 3. Data processing has been refactored. --- enabled_unittests | 6 + .../board/linux/cellular/linux_cellular.cpp | 47 ++- .../board/linux/cellular/linux_cellular.hpp | 36 +- .../rt1051/bsp/cellular/rt1051_cellular.cpp | 321 ++++++++--------- .../rt1051/bsp/cellular/rt1051_cellular.hpp | 133 +++---- .../board/rt1051/common/irq/irq_gpio.cpp | 4 +- module-bsp/bsp/cellular/CellularResult.hpp | 156 +++++++++ module-bsp/bsp/cellular/bsp_cellular.cpp | 19 +- module-bsp/bsp/cellular/bsp_cellular.hpp | 112 +++--- module-cellular/Modem/ATCommon.cpp | 69 ++-- module-cellular/Modem/ATCommon.hpp | 55 ++- module-cellular/Modem/ATParser.cpp | 70 ++-- module-cellular/Modem/ATParser.hpp | 17 +- module-cellular/Modem/BaseChannel.hpp | 12 +- module-cellular/Modem/README.md | 98 ++++++ module-cellular/Modem/TS0710/DLC_channel.cpp | 231 ++++++------- module-cellular/Modem/TS0710/DLC_channel.h | 38 +- module-cellular/Modem/TS0710/TS0710.cpp | 327 ++++++++++-------- module-cellular/Modem/TS0710/TS0710.h | 79 +++-- module-cellular/Modem/TS0710/TS0710_DATA.cpp | 8 +- .../Modem/TS0710/TS0710_DLC_ESTABL.cpp | 15 +- module-cellular/Modem/TS0710/TS0710_Frame.h | 80 +++-- module-cellular/Modem/doc/Images/at_mode.svg | 72 ++++ module-cellular/Modem/doc/Images/at_mode.uml | 46 +++ .../Modem/doc/Images/cellular_mux_read.png | Bin 0 -> 31811 bytes .../doc/Images/cellular_result_struct.png | Bin 0 -> 4797 bytes .../doc/Images/cellular_result_struct.uml | 9 + module-cellular/Modem/doc/Images/cmx_mode.uml | 70 ++++ .../Modem/doc/Images/dma_result_struct.png | Bin 0 -> 4643 bytes .../Modem/doc/Images/dma_result_struct.uml | 7 + module-cellular/Modem/doc/Images/mux_mode.svg | 106 ++++++ .../Modem/doc/Images/single_cmd.uml | 24 ++ .../doc/Images/single_cmd_transmission.png | Bin 0 -> 31302 bytes module-cellular/at/Result.hpp | 23 +- module-cellular/test/CMakeLists.txt | 11 +- .../test/mock/AtCommon_channel.hpp | 8 +- .../test/unittest_CellularResult.cpp | 71 ++++ module-cellular/test/unittest_cmux.cpp | 5 +- .../service-cellular/ServiceCellular.cpp | 22 +- .../service-evtmgr/WorkerEvent.cpp | 2 +- 40 files changed, 1589 insertions(+), 820 deletions(-) create mode 100644 module-bsp/bsp/cellular/CellularResult.hpp create mode 100644 module-cellular/Modem/README.md create mode 100644 module-cellular/Modem/doc/Images/at_mode.svg create mode 100644 module-cellular/Modem/doc/Images/at_mode.uml create mode 100644 module-cellular/Modem/doc/Images/cellular_mux_read.png create mode 100644 module-cellular/Modem/doc/Images/cellular_result_struct.png create mode 100644 module-cellular/Modem/doc/Images/cellular_result_struct.uml create mode 100644 module-cellular/Modem/doc/Images/cmx_mode.uml create mode 100644 module-cellular/Modem/doc/Images/dma_result_struct.png create mode 100644 module-cellular/Modem/doc/Images/dma_result_struct.uml create mode 100644 module-cellular/Modem/doc/Images/mux_mode.svg create mode 100644 module-cellular/Modem/doc/Images/single_cmd.uml create mode 100644 module-cellular/Modem/doc/Images/single_cmd_transmission.png create mode 100644 module-cellular/test/unittest_CellularResult.cpp diff --git a/enabled_unittests b/enabled_unittests index f760840fdfce49521085183790f664aa5f468917..5c8f3b2df5b66804851c8832d30ba8fded57dde8 100644 --- a/enabled_unittests +++ b/enabled_unittests @@ -426,6 +426,12 @@ TESTS_LIST["catch2-unittest_ATURCStream"]=" URC AT Stream Parser; " #--------- +#--------- +TESTS_LIST["catch2-unittest_CellularResult"]=" + CellularResult: serializing/deserializing; + CellularResult: objects creation; +" +#--------- diff --git a/module-bsp/board/linux/cellular/linux_cellular.cpp b/module-bsp/board/linux/cellular/linux_cellular.cpp index 134a0a75c56f6b93ac943c79be5adeaec1c58f4b..955b35bdadaedacfa5cd6fcd58b8f61921de9028 100644 --- a/module-bsp/board/linux/cellular/linux_cellular.cpp +++ b/module-bsp/board/linux/cellular/linux_cellular.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "linux_cellular.hpp" @@ -12,10 +12,8 @@ #include #include #include -#include #include #include -#include #include #define _LINUX_UART_DEBUG 0 @@ -68,8 +66,6 @@ namespace bsp LOG_FATAL("Failed to create epoll file descriptor"); } - struct epoll_event event; - event.events = EPOLLIN; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event)) { @@ -91,23 +87,23 @@ namespace bsp } } - void LinuxCellular::PowerUp() + void LinuxCellular::powerUp() {} - void LinuxCellular::PowerDown() + void LinuxCellular::powerDown() {} - void LinuxCellular::Restart() + void LinuxCellular::restart() {} - ssize_t LinuxCellular::Read(void *buf, size_t nbytes) + ssize_t LinuxCellular::read(void *buf, size_t nbytes, std::chrono::milliseconds timeoutMs) { cpp_freertos::LockGuard lock(serOutMutex); int ret; for (;;) { - ret = read(fd, buf, nbytes); + ret = ::read(fd, buf, nbytes); if (ret != -1 || errno != EINTR) break; } @@ -124,7 +120,7 @@ namespace bsp return ret; } - ssize_t LinuxCellular::Write(void *buf, size_t nbytes) + ssize_t LinuxCellular::write(void *buf, size_t nbytes) { cpp_freertos::LockGuard lock(serOutMutex); #if _LINUX_UART_DEBUG @@ -136,30 +132,31 @@ namespace bsp #endif int ret; for (;;) { - ret = write(fd, buf, nbytes); + ret = ::write(fd, buf, nbytes); if (ret != -1 || errno != EINTR) break; } return ret; } - void LinuxCellular::InformModemHostAsleep(void) + void LinuxCellular::informModemHostAsleep(void) {} - void LinuxCellular::InformModemHostWakeup(void) + void LinuxCellular::informModemHostWakeup(void) {} - void LinuxCellular::EnterSleep() + void LinuxCellular::enterSleep() {} - void LinuxCellular::ExitSleep() + void LinuxCellular::exitSleep() {} - uint32_t LinuxCellular::Wait(uint32_t timeout) + uint32_t LinuxCellular::wait(std::chrono::milliseconds timeoutMs) { + auto timeoutTicks = pdMS_TO_TICKS(timeoutMs.count()); uint32_t currentTime = cpp_freertos::Ticks::GetTicks(); - uint32_t timeoutNeeded = timeout == UINT32_MAX ? UINT32_MAX : currentTime + timeout; + uint32_t timeoutNeeded = currentTime + timeoutTicks; uint32_t timeElapsed = currentTime; for (;;) { @@ -182,7 +179,7 @@ namespace bsp } } - void LinuxCellular::SetSpeed(uint32_t portSpeed) + void LinuxCellular::setSpeed(uint32_t portSpeed) { struct termios t; memset(&t, 0, sizeof(t)); @@ -201,10 +198,10 @@ namespace bsp ioctl(fd, TIOCMBIS, &status); } - void LinuxCellular::SelectAntenna(bsp::cellular::antenna antenna) + void LinuxCellular::selectAntenna(bsp::cellular::antenna antenna) {} - bsp::cellular::antenna LinuxCellular::GetAntenna() + bsp::cellular::antenna LinuxCellular::getAntenna() { return bsp::cellular::antenna::lowBand; } @@ -248,7 +245,7 @@ namespace bsp namespace sim { - auto trayIRQ_handler() -> BaseType_t + auto trayIRQHandler() -> BaseType_t { return BaseType_t(); } @@ -258,15 +255,15 @@ namespace bsp return Store::GSM::Tray::IN; } - void hotswap_trigger() + void hotSwapTrigger() {} - void sim_sel() + void simSelect() {} } // namespace sim namespace ringIndicator { - auto riIRQ_handler() -> BaseType_t + auto riIRQHandler() -> BaseType_t { return BaseType_t(); } diff --git a/module-bsp/board/linux/cellular/linux_cellular.hpp b/module-bsp/board/linux/cellular/linux_cellular.hpp index 06cc047ac61655e166bf12c936ed733d24bdbf54..6a62705e217926f0335b162db405a3952317f162 100644 --- a/module-bsp/board/linux/cellular/linux_cellular.hpp +++ b/module-bsp/board/linux/cellular/linux_cellular.hpp @@ -1,9 +1,10 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #ifndef PUREPHONE_LINUX_CELLULAR_HPP #define PUREPHONE_LINUX_CELLULAR_HPP +class milliseconds; #include "bsp/cellular/bsp_cellular.hpp" #include @@ -14,7 +15,6 @@ namespace bsp { - class LinuxCellular : public Cellular { private: @@ -24,40 +24,40 @@ namespace bsp LinuxCellular(const char *term, uint32_t portSpeed); ~LinuxCellular(); - void PowerUp() override final; + void powerUp() override final; - void PowerDown() override final; + void powerDown() override final; - void Restart() override final; + void restart() override final; - uint32_t Wait(uint32_t timeout) override final; + uint32_t wait(std::chrono::milliseconds timeoutMs) override final; - ssize_t Read(void *buf, size_t nbytes) override final; + ssize_t read(void *buf, size_t nbytes, std::chrono::milliseconds timeoutMs) override final; - ssize_t Write(void *buf, size_t nbytes) override final; + ssize_t write(void *buf, size_t nbytes) override final; - void InformModemHostAsleep() override final; + void informModemHostAsleep() override final; - void InformModemHostWakeup() override final; + void informModemHostWakeup() override final; - void EnterSleep() override final; + void enterSleep() override final; - void ExitSleep() override final; + void exitSleep() override final; - void SetSpeed(uint32_t portSpeed); + void setSpeed(uint32_t portSpeed) override final; - void SetSendingAllowed(bool state) override final + void setSendingAllowed(bool state) override final { pv_SendingAllowed = state; } - bool GetSendingAllowed() override final + bool getSendingAllowed() override final { return pv_SendingAllowed; } - void SelectAntenna(bsp::cellular::antenna antenna) override final; + void selectAntenna(bsp::cellular::antenna antenna) override final; - bsp::cellular::antenna GetAntenna() override final; + bsp::cellular::antenna getAntenna() override final; private: static constexpr speed_t baud_bits[] = {0, @@ -74,8 +74,6 @@ namespace bsp B3000000, B4000000}; - static const uint32_t portBaudRate = 115200; - static const uint32_t MAX_EVENTS = 1; int fd = -1; diff --git a/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.cpp b/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.cpp index 7d4f06390e3c5574acfd1ae6d9e8f14c794b3fb8..c1e8aa8172dd0fb5daf8ab04b7d5d52c6a5ffe59 100644 --- a/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.cpp +++ b/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.cpp @@ -2,17 +2,16 @@ // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "rt1051_cellular.hpp" -#include "FreeRTOS.h" #include "task.h" -#include "stream_buffer.h" -#include "dma_config.h" #include "fsl_cache.h" #include #include #include +static bsp::cellular::CellularDMAResultStruct RXfer; + #if _RT1051_UART_DEBUG == 1 #define logUARTdebug(...) LOG_DEBUG(__VA_ARGS__) #else @@ -35,40 +34,41 @@ extern "C" { void LPUART1_IRQHandler(void) { - uint32_t isrReg = LPUART_GetStatusFlags(CELLULAR_UART_BASE); + std::uint32_t isrReg = LPUART_GetStatusFlags(CELLULAR_UART_BASE); + + if (bsp::RT1051Cellular::uartRxBuffer != NULL) { + std::uint32_t count; - if (bsp::RT1051Cellular::uartRxStreamBuffer != NULL) { - auto RxDmaStatus = LPUART_TransferGetReceiveCountEDMA( - CELLULAR_UART_BASE, - &bsp::RT1051Cellular::uartDmaHandle, - reinterpret_cast(&bsp::RT1051Cellular::RXdmaReceivedCount)); + auto RxDmaStatus = + LPUART_TransferGetReceiveCountEDMA(CELLULAR_UART_BASE, &bsp::RT1051Cellular::uartDmaHandle, &count); + + if (RxDmaStatus == kStatus_Success) { + RXfer.dataSize = count; + } - if (isrReg & bsp::RT1051Cellular::startIRQMask) { + if ((isrReg & bsp::RT1051Cellular::startIRQMask) != 0u) { if (RxDmaStatus == kStatus_NoTransferInProgress) { LPUART_DisableInterrupts(CELLULAR_UART_BASE, bsp::RT1051Cellular::startIRQMaskEnable); - bsp::RT1051Cellular::RXdmaReceivedCount = -1; - if (not bsp::RT1051Cellular::StartReceive(bsp::RT1051Cellular::GetFreeStreamBufferSize())) { + if (not bsp::RT1051Cellular::startReceive(bsp::RT1051Cellular::getMaxBufferDataSize())) { bsp::RT1051Cellular::RestartReceivingManually = true; - bsp::RT1051Cellular::FinishReceive(); + bsp::RT1051Cellular::finishReceive(); } logUARTdebug("[RX] on Incoming data"); } } - if (isrReg & bsp::RT1051Cellular::finishIRQMask) { + if ((isrReg & bsp::RT1051Cellular::finishIRQMask) != 0u) { if (RxDmaStatus != kStatus_NoTransferInProgress) { LPUART_TransferAbortReceiveEDMA(CELLULAR_UART_BASE, &bsp::RT1051Cellular::uartDmaHandle); logUARTdebug("[RX idle] stopped engine"); } - logUARTdebug("[RX idle], received %d bytes", bsp::RT1051Cellular::RXdmaReceivedCount); + logUARTdebug("[RX idle], received %d bytes", RXfer.dataSize); // the main exit path on transmission done - if (bsp::RT1051Cellular::RXdmaReceivedCount >= 0) { - if (bsp::RT1051Cellular::RXdmaReceivedCount > 0) { - bsp::RT1051Cellular::MoveRxDMAtoStreamBuf(bsp::RT1051Cellular::RXdmaReceivedCount); - bsp::RT1051Cellular::RXdmaReceivedCount = -1; - } - if (not bsp::RT1051Cellular::RestartReceivingManually) { - bsp::RT1051Cellular::FinishReceive(); - } + + if (RXfer.dataSize > 0) { + bsp::RT1051Cellular::sendRxDmAresult(bsp::cellular::CellularResultCode::ReceivedAndIdle); + } + if (not bsp::RT1051Cellular::RestartReceivingManually) { + bsp::RT1051Cellular::finishReceive(); } } @@ -79,16 +79,14 @@ extern "C" namespace bsp { - uint8_t RT1051Cellular::RXdmaBuffer[RXdmaBufferSize] = {0}; - ssize_t RT1051Cellular::RXdmaReceivedCount = -1; - size_t RT1051Cellular::RXdmaMaxReceivedCount = -1; - bool RT1051Cellular::RestartReceivingManually = false; + std::size_t RT1051Cellular::RXdmaMaxReceivedCount = 0; + bool RT1051Cellular::RestartReceivingManually = false; using namespace drivers; - lpuart_edma_handle_t RT1051Cellular::uartDmaHandle = {}; - TaskHandle_t RT1051Cellular::untilReceivedNewHandle = nullptr; - StreamBufferHandle_t RT1051Cellular::uartRxStreamBuffer = nullptr; + lpuart_edma_handle_t RT1051Cellular::uartDmaHandle = {}; + TaskHandle_t RT1051Cellular::untilReceivedNewHandle = nullptr; + MessageBufferHandle_t RT1051Cellular::uartRxBuffer = nullptr; RT1051Cellular::RT1051Cellular() { @@ -98,9 +96,9 @@ namespace bsp GPIO_PinRead(GPIO2, BSP_CELLULAR_SIM_TRAY_INSERTED_PIN) == 0 ? Store::GSM::Tray::IN : Store::GSM::Tray::OUT; DMAInit(); - uartRxStreamBuffer = xStreamBufferCreate(rxStreamBufferLength, rxStreamBufferNotifyWatermark); - if (uartRxStreamBuffer == NULL) { - LOG_ERROR("Could not create the RX stream buffer!"); + uartRxBuffer = xMessageBufferCreate(rxMessageBufferLength); + if (uartRxBuffer == nullptr) { + LOG_ERROR("Could not create the RX message buffer!"); return; } @@ -133,21 +131,19 @@ namespace bsp return; } - static_assert(rxStreamBufferLength >= 6, "Minimum buffer size (i.e. sufficient to enable flow control)"); - static_assert(rxStreamBufferLength >= 1024, "To be able to fit entire response"); - - // CANNOT disable FIFO, dma *needs* it :o - // CELLULAR_UART_BASE->FIFO &= ~LPUART_FIFO_RXFE_MASK; + static_assert(rxMessageBufferLength >= RXfer.getMaxSize() + rxMessageBufferOverheadSize, + "Minimum buffer size to hold one full DMA rxfer"); LPUART_ClearStatusFlags(CELLULAR_UART_BASE, 0xFFFFFFFF); NVIC_ClearPendingIRQ(LPUART1_IRQn); NVIC_SetPriority(LPUART1_IRQn, configLIBRARY_LOWEST_INTERRUPT_PRIORITY); NVIC_EnableIRQ(LPUART1_IRQn); + enableRx(); isInitialized = true; } - void RT1051Cellular::SetSpeed(uint32_t portSpeed) + void RT1051Cellular::setSpeed(uint32_t portSpeed) { LOG_DEBUG("[RT1051] Setting %" PRIu32 " baudrate", portSpeed); LPUART_SetBaudRate(CELLULAR_UART_BASE, portSpeed, GetPerphSourceClock(PerphClock_LPUART)); @@ -155,14 +151,13 @@ namespace bsp RT1051Cellular::~RT1051Cellular() { - - if (uartRxStreamBuffer) { - vStreamBufferDelete(uartRxStreamBuffer); - uartRxStreamBuffer = nullptr; + if (uartRxBuffer) { + vMessageBufferDelete(uartRxBuffer); + uartRxBuffer = nullptr; } - DisableRx(); - DisableTx(); + disableRx(); + disableTx(); NVIC_DisableIRQ(LPUART1_IRQn); LPUART_DisableInterrupts(CELLULAR_UART_BASE, @@ -179,83 +174,66 @@ namespace bsp untilReceivedNewHandle = nullptr; } - void RT1051Cellular::PowerUp() + void RT1051Cellular::powerUp() { - const TickType_t POWER_UP_DELAY_MS = 500; + constexpr TickType_t POWER_UP_DELAY_MS = 500; - DisableRx(); + disableRx(); - ExitSleep(); + exitSleep(); TickType_t tick = xTaskGetTickCount(); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_POWER_PIN), 1); vTaskDelayUntil(&tick, POWER_UP_DELAY_MS); - // BSP_CellularSetPowerState(CellularPowerStateTurningOn); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_POWER_PIN), 0); - EnableRx(); + enableRx(); } - void RT1051Cellular::PowerDown() + void RT1051Cellular::powerDown() { - const uint16_t POWER_DOWN_DELAY_MS = 700; + constexpr std::uint16_t POWER_DOWN_DELAY_MS = 700; - ExitSleep(); + exitSleep(); TickType_t tick = xTaskGetTickCount(); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_POWER_PIN), 1); vTaskDelayUntil(&tick, POWER_DOWN_DELAY_MS); - // BSP_CellularSetPowerState(CellularPowerStateTurningOff); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_POWER_PIN), 0); - // vTaskDelay(pdMS_TO_TICKS(POWER_DOWN_IN_PROGRESS_DELAY_MS)); - - /* while (s_cellularPowerState != CellularPowerStatePoweredDown) - { - vTaskDelay(pdMS_TO_TICKS(200)); - }*/ - DisableRx(); + disableRx(); } - void RT1051Cellular::Restart() + void RT1051Cellular::restart() { - const uint16_t RESET_DELAY_MS = 460; + constexpr std::uint16_t RESET_DELAY_MS = 460; - ExitSleep(); + exitSleep(); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_RESET_PIN), 1); vTaskDelay(pdMS_TO_TICKS(RESET_DELAY_MS)); gpio_2->WritePin(static_cast(BoardDefinitions::CELLULAR_GPIO_2_RESET_PIN), 0); } - ssize_t RT1051Cellular::Write(void *buf, size_t nbytes) + ssize_t RT1051Cellular::write(void *buf, size_t nbytes) { lpuart_transfer_t sendXfer; -#if _RT1051_UART_DEBUG - LOG_PRINTF("[TX: %d]", nbytes); - uint8_t *ptr = (uint8_t *)buf; - LOG_PRINTF("\n{"); - for (size_t i = 0; i < nbytes; i++) - LOG_PRINTF("%02X ", (uint8_t)*ptr++); - LOG_PRINTF("}\n<"); - ptr = (uint8_t *)buf; - for (size_t i = 0; i < nbytes; i++) - LOG_PRINTF("%c", (uint8_t)*ptr++); - LOG_PRINTF(">"); - LOG_PRINTF("\n"); -#endif - sendXfer.data = static_cast(buf); + //#if _RT1051_UART_DEBUG + logData("TX", static_cast(buf), nbytes); + //#endif + sendXfer.data = static_cast(buf); sendXfer.dataSize = nbytes; - ExitSleep(); + exitSleep(); uartDmaHandle.userData = xTaskGetCurrentTaskHandle(); SCB_CleanInvalidateDCache(); - EnableTx(); + enableTx(); + if (LPUART_SendEDMA(CELLULAR_UART_BASE, &uartDmaHandle, &sendXfer) != kStatus_Success) { LOG_ERROR("Cellular: TX Failed!"); - DisableTx(); + disableTx(); return -1; } @@ -264,44 +242,53 @@ namespace bsp if (ulNotificationValue == 0) { LOG_ERROR("Cellular Uart error: TX Transmission timeout"); - DisableTx(); + disableTx(); return -1; } - DisableTx(); + disableTx(); return nbytes; } - bool RT1051Cellular::MoveRxDMAtoStreamBuf(size_t nbytes) + bool RT1051Cellular::sendRxDmAresult(bsp::cellular::CellularResultCode reason) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; - assert(nbytes > 0); - - if (nbytes > GetFreeStreamBufferSize()) { - LOG_ERROR("Cannot dump DMA buffer. Data is lost (%d>%d)", nbytes, GetFreeStreamBufferSize()); + if (RXfer.dataSize > getMaxBufferDataSize()) { + LOG_ERROR("Cannot dump DMA buffer (%d>%d)", RXfer.dataSize, getMaxBufferDataSize()); + RestartReceivingManually = true; return false; } + RXfer.resultCode = reason; + logUARTdebug("[RX reason] %d", reason); + #if _RT1051_UART_DEBUG auto ret = #endif - xStreamBufferSendFromISR(uartRxStreamBuffer, (void *)&RXdmaBuffer, nbytes, &xHigherPriorityTaskWoken); + xMessageBufferSendFromISR(uartRxBuffer, (void *)&RXfer, RXfer.getSize(), &xHigherPriorityTaskWoken); logUARTdebug("[RX] moved %d bytes to streambuf", ret); portEND_SWITCHING_ISR(xHigherPriorityTaskWoken); return true; } - size_t RT1051Cellular::GetFreeStreamBufferSize() + size_t RT1051Cellular::getMaxBufferDataSize() { - return xStreamBufferSpacesAvailable(bsp::RT1051Cellular::uartRxStreamBuffer); + const auto messageOverhead = + rxMessageBufferOverheadSize + bsp::cellular::CellularDMAResultStruct::getEmptySize(); + + if (const auto bytesFree = xMessageBufferSpaceAvailable(uartRxBuffer); bytesFree > messageOverhead) { + return bytesFree - messageOverhead; + } + + return 0; } - bool RT1051Cellular::StartReceive(size_t nbytes) + bool RT1051Cellular::startReceive(size_t nbytes) { - if (!(GetFreeStreamBufferSize() > 0)) { + if (getMaxBufferDataSize() <= 0) { logUARTdebug("Not starting RX DMA, stream buffer is full. Be aware"); return false; } @@ -311,11 +298,11 @@ namespace bsp RXdmaMaxReceivedCount = std::min(nbytes, static_cast(RXdmaBufferSize)); logUARTdebug("Starting DMA RX, max %d bytes", RXdmaMaxReceivedCount); } - assert(RXdmaMaxReceivedCount <= RXdmaBufferSize); + RXfer.dataSize = 0; // start RXfer if there is a byte incoming and no pending RXfer lpuart_transfer_t receiveXfer; - receiveXfer.data = RXdmaBuffer; + receiveXfer.data = RXfer.data; receiveXfer.dataSize = RXdmaMaxReceivedCount; // rx config @@ -333,50 +320,54 @@ namespace bsp return true; } - void RT1051Cellular::FinishReceive() + void RT1051Cellular::finishReceive() { logUARTdebug("[RX] finish"); bsp::cellular::notifyReceivedNew(); } - ssize_t RT1051Cellular::Read(void *buf, size_t nbytes) + ssize_t RT1051Cellular::read(void *buf, + size_t nbytes, + std::chrono::milliseconds timeoutMs = std::chrono::milliseconds{0}) { - ExitSleep(); - ssize_t ret = xStreamBufferReceive(uartRxStreamBuffer, buf, nbytes, 0); -#if _RT1051_UART_DEBUG - if (ret > 0) { - LOG_PRINTF("[RX: %d]", ret); - uint8_t *ptr = (uint8_t *)buf; - LOG_PRINTF("\n{"); - for (ssize_t i = 0; i < ret; i++) - LOG_PRINTF("%02X ", (uint8_t)*ptr++); - LOG_PRINTF("}\n<"); - ptr = (uint8_t *)buf; - for (ssize_t i = 0; i < ret; i++) - LOG_PRINTF("%c", (uint8_t)*ptr++); - LOG_PRINTF(">"); - } - else { - LOG_PRINTF("[RX] GOT NOTHING (requested %d)", nbytes); + logUARTdebug("[RX] Read"); + exitSleep(); + + auto timeoutTicks = pdMS_TO_TICKS(timeoutMs.count()); + if (timeoutTicks > portMAX_DELAY) { + timeoutTicks = portMAX_DELAY; } - LOG_PRINTF("\n"); -#endif - if (RestartReceivingManually) { - logUARTdebug("[RX] resume on Paused"); - // need to start manually, as RegBuf might be already full, therefore no Active Edge Interrupt - if (StartReceive(GetFreeStreamBufferSize())) { - RestartReceivingManually = false; - } - else { - // do not lose information as whether manual start needs to be initiated and schedule next poke on Read - LOG_WARN("Modem is waiting to send data. Stream buffer likely too small"); - FinishReceive(); + + size_t ret = xMessageBufferReceive(uartRxBuffer, buf, nbytes, timeoutTicks); + + if (ret > bsp::cellular::CellularDMAResultStruct::getEmptySize()) { + logData("RX", static_cast(buf), nbytes); + + if (RestartReceivingManually) { + if (RXfer.dataSize > 0) { + // residual data in DMA rx buffer + logUARTdebug("[RX] Dumping there is residual data in dma buf"); + if (sendRxDmAresult(bsp::cellular::CellularResultCode::ReceivedAfterFull)) { + return ret; + } + } + logUARTdebug("[RX] resume on Paused"); + // need to start manually, as RegBuf might be already full, therefore no Active Edge Interrupt + if (startReceive(getMaxBufferDataSize())) { + RestartReceivingManually = false; + } + else { + // do not lose information as whether manual start needs to be initiated and schedule next poke on + // Read + LOG_WARN("Modem is waiting to send data. Stream buffer likely too small"); + finishReceive(); + } } } return ret; } - uint32_t RT1051Cellular::Wait(uint32_t timeout) + uint32_t RT1051Cellular::wait(std::chrono::milliseconds timeoutMs) { logUARTdebug("[WAIT]"); if (untilReceivedNewHandle != nullptr) { @@ -385,32 +376,32 @@ namespace bsp } // no need to wait: buffer already contains something - if (xStreamBufferBytesAvailable(uartRxStreamBuffer)) { + if (!xMessageBufferIsEmpty(uartRxBuffer)) { return 1; } - EnableRx(); + enableRx(); // …start waiting for [timeout] until a new xfer from modem is received untilReceivedNewHandle = xTaskGetCurrentTaskHandle(); - auto ret = ulTaskNotifyTake(pdTRUE, timeout); + auto ret = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(timeoutMs.count())); untilReceivedNewHandle = nullptr; return ret; } - void RT1051Cellular::InformModemHostAsleep() + void RT1051Cellular::informModemHostAsleep() { gpio_2->WritePin(magic_enum::enum_integer(BoardDefinitions::CELLULAR_GPIO_2_APRDY_PIN), !CELLULAR_BSP_AP_READY_PIN_ACTIVE_STATE); } - void RT1051Cellular::InformModemHostWakeup() + void RT1051Cellular::informModemHostWakeup() { gpio_2->WritePin(magic_enum::enum_integer(BoardDefinitions::CELLULAR_GPIO_2_APRDY_PIN), CELLULAR_BSP_AP_READY_PIN_ACTIVE_STATE); } - void RT1051Cellular::EnterSleep() + void RT1051Cellular::enterSleep() { if (!isInSleepMode) { isInSleepMode = true; @@ -419,15 +410,14 @@ namespace bsp gpio_2->WritePin(magic_enum::enum_integer(BoardDefinitions::CELLULAR_GPIO_2_WAKEUP_PIN), 1); // Host sleep information must be before UART disable - InformModemHostAsleep(); - + informModemHostAsleep(); if (driverLPUART) { driverLPUART->Disable(); } } } - void RT1051Cellular::ExitSleep() + void RT1051Cellular::exitSleep() { // reset sleep timer countdown lastCommunicationTimestamp = cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()); @@ -444,7 +434,7 @@ namespace bsp vTaskDelay(pdMS_TO_TICKS(15)); // Host wake up information must be after UART enable - InformModemHostWakeup(); + informModemHostWakeup(); } } @@ -561,10 +551,6 @@ namespace bsp void RT1051Cellular::DMAInit() { - - // TODO:M.P add PLL support - // pll = DriverInterface::Create(static_cast(BoardDefinitions - // ::AUDIO_PLL),DriverPLLParams{}); dmamux = DriverDMAMux::Create(static_cast(BoardDefinitions::CELLULAR_DMAMUX), DriverDMAMuxParams{}); dma = DriverDMA::Create(static_cast(BoardDefinitions::CELLULAR_DMA), DriverDMAParams{}); @@ -608,29 +594,30 @@ namespace bsp } case kStatus_LPUART_RxIdle: logUARTdebug("[RX] Chunk done. Flow control must hold the gate from now on"); - if (MoveRxDMAtoStreamBuf(RXdmaMaxReceivedCount) and StartReceive(GetFreeStreamBufferSize())) { + RXfer.dataSize = RXdmaMaxReceivedCount; + if (sendRxDmAresult(cellular::CellularResultCode::ReceivedAndFull) and + startReceive(getMaxBufferDataSize())) { // usual mode: append a chunk and wait for line idle to finish receive } else { // the auxiliary exit path on stream buffer full RestartReceivingManually = true; - FinishReceive(); + finishReceive(); } - RXdmaReceivedCount = -1; break; } } - void RT1051Cellular::SetSendingAllowed(bool state) + void RT1051Cellular::setSendingAllowed(bool state) { pv_SendingAllowed = state; } - bool RT1051Cellular::GetSendingAllowed() + bool RT1051Cellular::getSendingAllowed() { return pv_SendingAllowed; } - void RT1051Cellular::SelectAntenna(bsp::cellular::antenna antenna) + void RT1051Cellular::selectAntenna(bsp::cellular::antenna antenna) { if (antenna == bsp::cellular::antenna::lowBand) { gpio_2->WritePin(magic_enum::enum_integer(BoardDefinitions::CELLULAR_GPIO_2_ANTSEL_PIN), @@ -644,7 +631,7 @@ namespace bsp } } - bsp::cellular::antenna RT1051Cellular::GetAntenna() + bsp::cellular::antenna RT1051Cellular::getAntenna() { // make sure ANTSEL pin has Software Input On Field set bool whichAntenna = gpio_2->ReadPin(magic_enum::enum_integer(BoardDefinitions::CELLULAR_GPIO_2_ANTSEL_PIN)); @@ -652,6 +639,23 @@ namespace bsp : bsp::cellular::antenna::highBand); } + void RT1051Cellular::logData(std::string title, uint8_t *buffer, size_t nbytes) + { +#if _RT1051_UART_DEBUG == 1 + LOG_PRINTF("[%s: %d]", title.c_str(), nbytes); + uint8_t *ptr = buffer; + LOG_PRINTF("\n{"); + for (size_t i = 0; i < nbytes; i++) + LOG_PRINTF("%02X ", *(ptr++)); + LOG_PRINTF("}\n<"); + ptr = buffer; + for (size_t i = 0; i < nbytes; i++) + LOG_PRINTF("%c", *(ptr++)); + LOG_PRINTF(">"); + LOG_PRINTF("\n"); +#endif + } + namespace cellular { static xQueueHandle qhandle = nullptr; @@ -716,7 +720,7 @@ namespace bsp { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (qhandle != NULL) { - uint8_t val = static_cast(IRQsource::statusPin); + std::uint8_t val = static_cast(IRQsource::statusPin); xQueueSendFromISR(qhandle, &val, &xHigherPriorityTaskWoken); } return xHigherPriorityTaskWoken; @@ -727,12 +731,12 @@ namespace bsp namespace sim { - auto trayIRQ_handler() -> BaseType_t + auto trayIRQHandler() -> BaseType_t { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (qhandle) { - uint8_t val = static_cast(IRQsource::trayPin); + std::uint8_t val = static_cast(IRQsource::trayPin); xQueueSendFromISR(qhandle, &val, &xHigherPriorityTaskWoken); } return xHigherPriorityTaskWoken; @@ -745,14 +749,14 @@ namespace bsp return state; } - void hotswap_trigger() + void hotSwapTrigger() { GPIO_PinWrite(BSP_CELLULAR_SIM_CARD_PRESENCE_PORT, BSP_CELLULAR_SIM_CARD_PRESENCE_PIN, 1); vTaskDelay(100); // sleep for 100 ms... GPIO_PinWrite(BSP_CELLULAR_SIM_CARD_PRESENCE_PORT, BSP_CELLULAR_SIM_CARD_PRESENCE_PIN, 0); } - void sim_sel() + void simSelect() { if (Store::GSM::get()->selected == Store::GSM::SIM::SIM2) { GPIO_PinWrite(BSP_CELLULAR_SIM_CARD_PRESENCE_PORT, BSP_CELLULAR_SIMSEL_PIN, 1); @@ -765,16 +769,15 @@ namespace bsp namespace ringIndicator { - auto riIRQ_handler() -> BaseType_t + auto riIRQHandler() -> BaseType_t { BaseType_t xHigherPriorityTaskWoken = pdFALSE; if (qhandle != NULL) { - uint8_t val = static_cast(IRQsource::ringIndicatorPin); + std::uint8_t val = static_cast(IRQsource::ringIndicatorPin); xQueueSendFromISR(qhandle, &val, &xHigherPriorityTaskWoken); } return xHigherPriorityTaskWoken; } } // namespace ringIndicator - - } // namespace cellular + } // namespace cellular } // namespace bsp diff --git a/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.hpp b/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.hpp index 1176122d9644f863315c20097ee4fcd4b50044e9..6e416369f656b9f2db6c88b068881b330973210d 100644 --- a/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.hpp +++ b/module-bsp/board/rt1051/bsp/cellular/rt1051_cellular.hpp @@ -1,18 +1,18 @@ // Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md -#ifndef PUREPHONE_RT1501_CELLULAR_HPP -#define PUREPHONE_RT1501_CELLULAR_HPP +#pragma once -#include "bsp/cellular/bsp_cellular.hpp" +#include +#include +#include -#include "FreeRTOS.h" -#include "stream_buffer.h" -#include "timers.h" #include "board.h" #include "fsl_lpuart.h" #include "fsl_lpuart_edma.h" +#include "bsp/cellular/bsp_cellular.hpp" +#include "bsp/cellular/CellularResult.hpp" #include "bsp/BoardDefinitions.hpp" #include "drivers/pll/DriverPLL.hpp" @@ -22,97 +22,85 @@ namespace bsp { - class RT1051Cellular : public Cellular { private: bool pv_SendingAllowed = true; + void logData(std::string title, uint8_t *buffer, size_t nbytes); + public: RT1051Cellular(); ~RT1051Cellular(); - void PowerUp() override final; - - void PowerDown() override final; - - void Restart() override final; - - uint32_t Wait(uint32_t timeout) override final; - - ssize_t Read(void *buf, size_t nbytes) override final; - - ssize_t Write(void *buf, size_t nbytes) override final; - - void InformModemHostAsleep() override final; - - void InformModemHostWakeup() override final; - - void EnterSleep() override final; - - void ExitSleep() override final; - - void SetSpeed(uint32_t portSpeed) override final; - - void SetSendingAllowed(bool state) override final; - bool GetSendingAllowed() override final; - - static StreamBufferHandle_t uartRxStreamBuffer; - static TimerHandle_t rxTimeoutTimer; - - void SelectAntenna(bsp::cellular::antenna antenna) override final; - bsp::cellular::antenna GetAntenna() override final; - + void powerUp() override final; + void powerDown() override final; + void restart() override final; + uint32_t wait(std::chrono::milliseconds timeoutMs) override final; + ssize_t read(void *buf, size_t nbytes, std::chrono::milliseconds timeoutMs) override final; + ssize_t write(void *buf, size_t nbytes) override final; + void informModemHostAsleep() override final; + void informModemHostWakeup() override final; + void enterSleep() override final; + void exitSleep() override final; + void setSpeed(uint32_t portSpeed) override final; + void setSendingAllowed(bool state) override final; + bool getSendingAllowed() override final; + void selectAntenna(bsp::cellular::antenna antenna) override final; + bsp::cellular::antenna getAntenna() override final; + + static MessageBufferHandle_t uartRxBuffer; static lpuart_edma_handle_t uartDmaHandle; + static bool RestartReceivingManually; + static constexpr std::size_t RXdmaBufferSize = 64; + static std::size_t RXdmaMaxReceivedCount; - static void FinishReceive(); + static constexpr std::uint32_t startIRQMaskEnable = + kLPUART_RxActiveEdgeInterruptEnable | kLPUART_RxDataRegFullInterruptEnable; + static constexpr std::uint32_t finishIRQMaskEnable = kLPUART_IdleLineInterruptEnable; + static constexpr std::uint32_t startIRQMask = kLPUART_RxActiveEdgeFlag | kLPUART_RxDataRegFullFlag; + static constexpr std::uint32_t finishIRQMask = kLPUART_IdleLineFlag; - static bool RestartReceivingManually; + static bool sendRxDmAresult(bsp::cellular::CellularResultCode reason); + static std::size_t getMaxBufferDataSize(); + static bool startReceive(std::size_t nbytes); + static void finishReceive(); + + static TaskHandle_t untilReceivedNewHandle; private: void MSPInit(); - void MSPDeinit(); - void DMAInit(); - void DMADeinit(); - public: - static constexpr uint32_t startIRQMaskEnable = - kLPUART_RxActiveEdgeInterruptEnable | kLPUART_RxDataRegFullInterruptEnable; - static constexpr uint32_t finishIRQMaskEnable = kLPUART_IdleLineInterruptEnable; - - static constexpr uint32_t startIRQMask = kLPUART_RxActiveEdgeFlag | kLPUART_RxDataRegFullFlag; - static constexpr uint32_t finishIRQMask = kLPUART_IdleLineFlag; + static void uartDMACallback(LPUART_Type *base, lpuart_edma_handle_t *handle, status_t status, void *userData); - private: - static inline void EnableRx(uint32_t irqMask = startIRQMaskEnable | finishIRQMaskEnable) + static inline void enableRx(uint32_t irqMask = startIRQMaskEnable | finishIRQMaskEnable) { LPUART_ClearStatusFlags(CELLULAR_UART_BASE, 0xFFFFFFFF); LPUART_EnableInterrupts(CELLULAR_UART_BASE, irqMask); LPUART_EnableRx(CELLULAR_UART_BASE, true); } - static inline void DisableRx(uint32_t irqMask = startIRQMaskEnable | finishIRQMaskEnable) + static inline void disableRx(uint32_t irqMask = startIRQMaskEnable | finishIRQMaskEnable) { LPUART_DisableInterrupts(CELLULAR_UART_BASE, irqMask); LPUART_ClearStatusFlags(CELLULAR_UART_BASE, irqMask); LPUART_EnableRx(CELLULAR_UART_BASE, false); } - inline void EnableTx() + inline void enableTx() { LPUART_EnableTx(CELLULAR_UART_BASE, true); } - inline void DisableTx() + inline void disableTx() { LPUART_EnableTx(CELLULAR_UART_BASE, false); } - // M.P: It is important to destroy these drivers in specific order std::shared_ptr pll; std::shared_ptr gpio_1; std::shared_ptr gpio_2; @@ -122,32 +110,11 @@ namespace bsp std::unique_ptr txDMAHandle; std::unique_ptr rxDMAHandle; - static void uartDMACallback(LPUART_Type *base, lpuart_edma_handle_t *handle, status_t status, void *userData); - - public: - static TaskHandle_t blockedTaskHandle; - - private: - // Constants - const static uint32_t baudrate = 115200; - const static uint32_t rxStreamBufferLength = 1024; - const static uint32_t rxStreamBufferNotifyWatermark = 1; - const static uint32_t CELLULAR_BSP_AP_READY_PIN_ACTIVE_STATE = 1; - const static uint32_t CELLULAR_BSP_ANTSEL_PIN_A_STATE = 0; - const static uint32_t CELLULAR_BSP_ANTSEL_PIN_B_STATE = 1; - - public: - static constexpr size_t RXdmaBufferSize = 16; - static uint8_t RXdmaBuffer[RXdmaBufferSize]; - static ssize_t RXdmaReceivedCount; - static size_t RXdmaMaxReceivedCount; - static bool MoveRxDMAtoStreamBuf(size_t nbytes); - static size_t GetFreeStreamBufferSize(); - static bool StartReceive(size_t nbytes); - - static TaskHandle_t untilReceivedNewHandle; + static constexpr std::uint32_t baudrate = 115200; + static constexpr std::uint32_t rxMessageBufferLength = 1024; + static constexpr std::uint16_t rxMessageBufferOverheadSize = 4; + static constexpr std::uint32_t CELLULAR_BSP_AP_READY_PIN_ACTIVE_STATE = 1; + static constexpr std::uint32_t CELLULAR_BSP_ANTSEL_PIN_A_STATE = 0; + static constexpr std::uint32_t CELLULAR_BSP_ANTSEL_PIN_B_STATE = 1; }; - } // namespace bsp - -#endif // PUREPHONE_RT1501_CELLULAR_HPP diff --git a/module-bsp/board/rt1051/common/irq/irq_gpio.cpp b/module-bsp/board/rt1051/common/irq/irq_gpio.cpp index 873744aee882379222f0ae46eff4a08ab84cbd05..eba69d4d3f9bd5fe97e45189bcd55f64b79f296f 100644 --- a/module-bsp/board/rt1051/common/irq/irq_gpio.cpp +++ b/module-bsp/board/rt1051/common/irq/irq_gpio.cpp @@ -121,7 +121,7 @@ namespace bsp } if (irq_mask & (1 << BSP_CELLULAR_SIM_TRAY_INSERTED_PIN)) { - xHigherPriorityTaskWoken |= bsp::cellular::sim::trayIRQ_handler(); + xHigherPriorityTaskWoken |= bsp::cellular::sim::trayIRQHandler(); } if (irq_mask & (1 << static_cast(BoardDefinitions::LIGHT_SENSOR_IRQ))) { @@ -152,7 +152,7 @@ namespace bsp } if (irq_mask & (1 << BSP_CELLULAR_RI_PIN)) { - bsp::cellular::ringIndicator::riIRQ_handler(); + bsp::cellular::ringIndicator::riIRQHandler(); } // Clear all IRQs diff --git a/module-bsp/bsp/cellular/CellularResult.hpp b/module-bsp/bsp/cellular/CellularResult.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a0e7eba1c0311aa7a6c27b1ea93047556a678048 --- /dev/null +++ b/module-bsp/bsp/cellular/CellularResult.hpp @@ -0,0 +1,156 @@ +#pragma once + +#include +#include +#include + +namespace bsp::cellular +{ + enum class CellularResultCode : std::uint8_t + { + Uninitialized, + ReceivedAndIdle, + ReceivedAndFull, + ReceivedAfterFull, + ReceivingNotStarted, + ReceivedNoData, + TransmittingNotStarted, + CMUXFrameError, + }; + + constexpr size_t CellularResultStructMaxDataSize = 128; + + struct __attribute__((__packed__)) CellularDMAResultStruct + { + CellularResultCode resultCode = CellularResultCode::ReceivedNoData; + size_t dataSize = 0; + std::uint8_t data[CellularResultStructMaxDataSize]; + + constexpr static size_t getEmptySize() + { + return sizeof(CellularDMAResultStruct) - CellularResultStructMaxDataSize; + } + + constexpr static size_t getMaxSize() + { + return getEmptySize() + sizeof(data); + } + + size_t getSize() + { + return getEmptySize() + dataSize; + } + }; + + struct CellularResultStruct + { + CellularResultCode resultCode = CellularResultCode::Uninitialized; + std::vector data; + + [[nodiscard]] auto serialize() const -> std::unique_ptr + { + auto serialized = std::unique_ptr(new uint8_t[data.size() + sizeof(resultCode)]); + serialized.get()[0] = static_cast(resultCode); + + if(data.size() > 0) { + memcpy(serialized.get() + 1, data.data(), data.size()); + } + + return serialized; + } + + auto deserialize(const uint8_t *serialized, const size_t size) -> void + { + if (size > 0) { + resultCode = static_cast(*serialized); + if (size > 1) { + data = {serialized + 1, serialized + size}; + } + } + } + + [[nodiscard]] auto getSerializedSize() const -> size_t + { + return (sizeof(resultCode) + data.size()); + } + }; + + class CellularResult + { + CellularResultStruct result; + + public: + CellularResult() : result{CellularResultCode::Uninitialized, {}} + {} + + explicit CellularResult(CellularDMAResultStruct &result) + : result{result.resultCode, {result.data, result.data + result.dataSize}} + {} + + explicit CellularResult(CellularResultStruct result) : result{std::move(result)} + {} + + explicit CellularResult(const uint8_t *serialized, const size_t size) + { + result.deserialize(serialized, size); + } + + auto getResultCode() const noexcept -> CellularResultCode + { + return result.resultCode; + } + + auto setResultCode(CellularResultCode code) -> void + { + result.resultCode = code; + } + + auto getData() const noexcept -> std::vector const & + { + return result.data; + } + + auto setData(const std::vector &data) -> void + { + result.data = data; + } + + auto getDataAsString() const -> std::string const + { + return std::string{result.data.begin(), result.data.end()}; + } + + auto getStruct() -> CellularResultStruct const * + { + return &result; + } + + auto getSerialized() const -> std::unique_ptr + { + return result.serialize(); + } + + auto getSerializedSize() const noexcept -> size_t + { + return result.getSerializedSize(); + } + }; +} // namespace bsp::cellular + +inline const char *c_str(bsp::cellular::CellularResultCode result) +{ + switch (result) { + case bsp::cellular::CellularResultCode::ReceivedAndIdle: + return "ReceivedAndIdle"; + case bsp::cellular::CellularResultCode::ReceivedAndFull: + return "ReceivedAndFull"; + case bsp::cellular::CellularResultCode::ReceivedAfterFull: + return "ReceivedAfterFull"; + case bsp::cellular::CellularResultCode::ReceivingNotStarted: + return "ReceivingNotStarted"; + case bsp::cellular::CellularResultCode::TransmittingNotStarted: + return "TransmittingNotStarted"; + default: + return "Unknown"; + } +} diff --git a/module-bsp/bsp/cellular/bsp_cellular.cpp b/module-bsp/bsp/cellular/bsp_cellular.cpp index 7e0a7c7f32d0c89dee48bf50ed09cd3e3a3dcea0..f20272a14e66a52257f180790092579d9f960f88 100644 --- a/module-bsp/bsp/cellular/bsp_cellular.cpp +++ b/module-bsp/bsp/cellular/bsp_cellular.cpp @@ -8,11 +8,10 @@ #error "Unsupported target" #endif - -namespace bsp{ - - std::optional> Cellular::Create( - [[maybe_unused]] const char* term, uint32_t portSpeed) { +namespace bsp +{ + std::optional> Cellular::create([[maybe_unused]] const char *term, uint32_t portSpeed) + { std::unique_ptr inst; @@ -24,26 +23,26 @@ namespace bsp{ #error "Unsupported target" #endif - if(inst->isInitialized){ + if (inst->isInitialized) { return inst; } return {}; } - [[nodiscard]] auto Cellular::GetCellularDevice() const noexcept -> std::shared_ptr + [[nodiscard]] auto Cellular::getCellularDevice() const noexcept -> std::shared_ptr { return driverLPUART; } - [[nodiscard]] auto Cellular::GetLastCommunicationTimestamp() const noexcept -> TickType_t + [[nodiscard]] auto Cellular::getLastCommunicationTimestamp() const noexcept -> TickType_t { return lastCommunicationTimestamp; } - [[nodiscard]] auto Cellular::IsCellularInSleepMode() const noexcept -> bool + [[nodiscard]] auto Cellular::isCellularInSleepMode() const noexcept -> bool { return isInSleepMode; } -} +} // namespace bsp diff --git a/module-bsp/bsp/cellular/bsp_cellular.hpp b/module-bsp/bsp/cellular/bsp_cellular.hpp index 8c9b8c1d474728516b226a4a784ae07c5ec1e3af..57d0cc98467d037ced708582fe51a98efc54736f 100644 --- a/module-bsp/bsp/cellular/bsp_cellular.hpp +++ b/module-bsp/bsp/cellular/bsp_cellular.hpp @@ -5,61 +5,65 @@ #include #include #include +#include #include #include #include "drivers/lpuart/DriverLPUART.hpp" #include -namespace bsp { -namespace cellular +namespace bsp +{ + namespace cellular { - enum class antenna{ - lowBand, - highBand - }; + enum class antenna + { + lowBand, + highBand + }; } - class Cellular { - public: - - static std::optional> Create(const char* term = "/dev/ttyUSB0", uint32_t portSpeed = 115200); + class Cellular + { + public: + static std::optional> create(const char *term = "/dev/ttyUSB0", + uint32_t portSpeed = 115200); - Cellular() {} - virtual ~Cellular() {} + Cellular() = default; + virtual ~Cellular() = default; + virtual void powerUp() = 0; - virtual void PowerUp() = 0; + virtual void powerDown() = 0; - virtual void PowerDown() = 0; + virtual void restart() = 0; - virtual void Restart() = 0; + virtual uint32_t wait(std::chrono::milliseconds timeoutMs) = 0; - virtual uint32_t Wait(uint32_t timeout) = 0; + virtual ssize_t read(void *buf, size_t nbytes, std::chrono::milliseconds timeoutMs) = 0; - virtual ssize_t Read(void *buf, size_t nbytes) = 0; + virtual ssize_t write(void *buf, size_t nbytes) = 0; - virtual ssize_t Write(void *buf, size_t nbytes) = 0; + virtual void informModemHostAsleep() = 0; - virtual void InformModemHostAsleep() = 0; + virtual void informModemHostWakeup() = 0; - virtual void InformModemHostWakeup() = 0; + virtual void enterSleep() = 0; - virtual void EnterSleep() = 0; + virtual void exitSleep() = 0; - virtual void ExitSleep() = 0; + virtual void setSpeed(uint32_t portSpeed) = 0; - virtual void SetSpeed(uint32_t portSpeed) = 0; + virtual void setSendingAllowed(bool state) = 0; - virtual void SetSendingAllowed(bool state) = 0; - virtual bool GetSendingAllowed() = 0; + virtual bool getSendingAllowed() = 0; - virtual void SelectAntenna(bsp::cellular::antenna antenna) = 0; - virtual bsp::cellular::antenna GetAntenna() = 0; + virtual void selectAntenna(bsp::cellular::antenna antenna) = 0; + virtual bsp::cellular::antenna getAntenna() = 0; - [[nodiscard]] auto GetCellularDevice() const noexcept -> std::shared_ptr; - [[nodiscard]] auto GetLastCommunicationTimestamp() const noexcept -> TickType_t; - [[nodiscard]] auto IsCellularInSleepMode() const noexcept -> bool; + [[nodiscard]] auto getCellularDevice() const noexcept -> std::shared_ptr; + [[nodiscard]] auto getLastCommunicationTimestamp() const noexcept -> TickType_t; + [[nodiscard]] auto isCellularInSleepMode() const noexcept -> bool; - protected: + protected: bool isInitialized = false; bool isInSleepMode{false}; TickType_t lastCommunicationTimestamp; @@ -67,36 +71,42 @@ namespace cellular std::shared_ptr driverLPUART; }; namespace cellular + { + enum IRQsource { - enum IRQsource{ statusPin, trayPin, - ringIndicatorPin, + ringIndicatorPin, }; /// initialize SIM queue directed to EventWorker int init(xQueueHandle qHandle); - namespace USB { - enum class PassthroughState { + namespace USB + { + enum class PassthroughState + { ENABLED, DISABLED, }; - enum class BootPinState { + enum class BootPinState + { FIRMWARE_UPGRADE, NORMAL_BOOT, }; - BootPinState getBootPin() ; - PassthroughState getPassthrough() ; + BootPinState getBootPin(); + PassthroughState getPassthrough(); void setBootPin(BootPinState bootPin); void setPassthrough(PassthroughState pass); - } + } // namespace USB - namespace status{ - enum class value { + namespace status + { + enum class value + { /// from the docs: When the module is turned on normally, the STATUS will present the low state. ACTIVE, INACTIVE, @@ -104,27 +114,27 @@ namespace cellular /// returns true if status OK. Open drain pulled up; ground is OK. bsp::cellular::status::value getStatus(); BaseType_t statusIRQhandler(); - } + } // namespace status namespace sim { /// handler for SIM tray which is connected to phone, not GSM - BaseType_t trayIRQ_handler(); + BaseType_t trayIRQHandler(); Store::GSM::Tray getTray(); /// trigger swap pin on gsm so that it would reload sim card in tray /// after that +QPIN urc should come - void hotswap_trigger(); - void sim_sel(); + void hotSwapTrigger(); + void simSelect(); } // namespace sim namespace ringIndicator - { - // handling incoming calls and sms - RI pin - BaseType_t riIRQ_handler(); - } // namespace RingIndicator + { + // handling incoming calls and sms - RI pin + BaseType_t riIRQHandler(); + } // namespace ringIndicator - } // namespace cellular -}; // namespace bsp + } // namespace cellular +}; // namespace bsp #endif // PUREPHONE_BSP_CELLULAR_HPP diff --git a/module-cellular/Modem/ATCommon.cpp b/module-cellular/Modem/ATCommon.cpp index 94b3f7ec8a428a7714010e64b6967826ed1708c6..22766dd2e6d2c2bd7dc240dfbf2b228d73ebf149 100644 --- a/module-cellular/Modem/ATCommon.cpp +++ b/module-cellular/Modem/ATCommon.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ATCommon.hpp" @@ -8,7 +8,6 @@ #include #include #include -#include #include // for PRIu32 #include #include "ATStream.hpp" @@ -27,22 +26,22 @@ const std::string Channel::CMS_ERROR = "+CMS ERROR:"; // const std::string Channel::RING = "RING"; // const std::string Channel::NO_DIALTONE = "NO DIALTONE"; -void Channel::cmd_log(std::string cmd, const Result &result, uint32_t timeout) +void Channel::cmdLog(std::string cmd, const Result &result, std::chrono::milliseconds timeout) { cmd.erase(std::remove(cmd.begin(), cmd.end(), '\r'), cmd.end()); cmd.erase(std::remove(cmd.begin(), cmd.end(), '\n'), cmd.end()); switch (result.code) { case Result::Code::TIMEOUT: { - LOG_INFO("[AT]: >%s<, timeout %" PRIu32 - " - please check the value with Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf", - cmd.c_str(), - timeout); + LOG_ERROR( + "[AT]: >%s<, timeout %llu - please check the value with Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf", + cmd.c_str(), + static_cast(timeout.count())); } break; case Result::Code::ERROR: { - LOG_INFO("[AT]: >%s<, >%s<", cmd.c_str(), result.response.size() ? result.response.back().c_str() : ""); + LOG_ERROR("[AT]: >%s<, >%s<", cmd.c_str(), result.response.size() ? result.response.back().c_str() : ""); } break; default: - LOG_INFO("[AT]: >%s<, >%s<", cmd.c_str(), result.response.size() ? result.response.back().c_str() : ""); + LOG_DEBUG("[AT]: >%s<, >%s<", cmd.c_str(), result.response.size() ? result.response.back().c_str() : ""); break; } #if DEBUG_MODEM_OUTPUT_RESPONSE @@ -64,30 +63,31 @@ std::string Channel::formatCommand(const std::string &cmd) const Result Channel::cmd(const std::string &cmd, std::chrono::milliseconds timeout, size_t rxCount) { Result result; - blockedTaskHandle = xTaskGetCurrentTaskHandle(); + ATStream atStream(rxCount); + + awaitingResponseFlag.set(); cmd_init(); std::string cmdFixed = formatCommand(cmd); - cmd_send(cmdFixed); - uint32_t currentTime = cpp_freertos::Ticks::GetTicks(); - uint32_t timeoutNeeded = ((timeout.count() == UINT32_MAX) ? UINT32_MAX : currentTime + timeout.count()); - uint32_t timeElapsed = currentTime; - - ATStream atStream(rxCount); + auto startTime = std::chrono::steady_clock::now(); + auto endTime = startTime + timeout; while (true) { - if (timeoutNeeded != UINT32_MAX && timeElapsed >= timeoutNeeded) { + if (std::chrono::steady_clock::now() > endTime) { result.code = Result::Code::TIMEOUT; break; } - auto taskUnlocked = ulTaskNotifyTake(pdTRUE, timeoutNeeded - timeElapsed); - timeElapsed = cpp_freertos::Ticks::GetTicks(); + if (size_t bytesRead = cmd_receive(receiveBuffer.get(), std::chrono::milliseconds{0}); bytesRead > 0) { + auto cellularResult = bsp::cellular::CellularResult{receiveBuffer.get(), bytesRead}; - if (taskUnlocked) { - atStream.write(cmd_receive()); + if (result = checkResult(cellularResult.getResultCode()); result.code != at::Result::Code::OK) { + break; + } + + atStream.write(cellularResult.getDataAsString()); if (atStream.isReady()) { result = atStream.getResult(); @@ -96,9 +96,10 @@ Result Channel::cmd(const std::string &cmd, std::chrono::milliseconds timeout, s } } - blockedTaskHandle = nullptr; + awaitingResponseFlag.clear(); + cmd_post(); - cmd_log(cmdFixed, result, timeout.count()); + cmdLog(cmdFixed, result, timeout); return result; } @@ -115,3 +116,25 @@ auto Channel::cmd(const at::AT &at) -> Result auto time = utils::time::Scoped("Time to run at command" + cmd.getCmd()); return this->cmd(cmd); } + +Result Channel::checkResult(bsp::cellular::CellularResultCode cellularResult) +{ + Result result; + + switch (cellularResult) { + case bsp::cellular::CellularResultCode::ReceivingNotStarted: + result.code = Result::Code::RECEIVING_NOT_STARTED; + break; + case bsp::cellular::CellularResultCode::TransmittingNotStarted: + result.code = Result::Code::TRANSMISSION_NOT_STARTED; + break; + case bsp::cellular::CellularResultCode::CMUXFrameError: + result.code = Result::Code::CMUX_FRAME_ERROR; + break; + default: + result.code = Result::Code::OK; + break; + } + + return result; +} diff --git a/module-cellular/Modem/ATCommon.hpp b/module-cellular/Modem/ATCommon.hpp index 3ca8a8f337da233f16f501be395ce673d20be204..3dbb9634bb23a38b581b4be5fc0c8aee90e8efd0 100644 --- a/module-cellular/Modem/ATCommon.hpp +++ b/module-cellular/Modem/ATCommon.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #pragma once @@ -7,6 +7,7 @@ #include #include #include +#include #include "BaseChannel.hpp" namespace sys @@ -16,7 +17,37 @@ namespace sys namespace at { - constexpr auto delimiter = "\r\n"; /// use std::strlen() + constexpr auto delimiter = "\r\n"; /// use std::strlen() + constexpr auto defaultBufferTimeoutMs = std::chrono::milliseconds{50}; + inline constexpr size_t defaultReceiveBufferSize = 128; // receiveBuffer size used for single message processing + inline constexpr size_t defaultMessageBufferSize = + defaultReceiveBufferSize * 2; // MessageBuffer size for passing data between tasks + + struct AwaitingResponseFlag + { + private: + cpp_freertos::MutexStandard mutex; + bool isWaiting = false; + + public: + void set() + { + cpp_freertos::LockGuard lock{mutex}; + isWaiting = true; + } + + void clear() + { + cpp_freertos::LockGuard lock{mutex}; + isWaiting = false; + } + + bool state() + { + cpp_freertos::LockGuard lock{mutex}; + return isWaiting; + } + }; class Channel : public BaseChannel { @@ -25,8 +56,10 @@ namespace at const std::array validTerm = {cmdSeparator, ',', '='}; [[nodiscard]] auto formatCommand(const std::string &cmd) const -> std::string; + MessageBufferHandle_t responseBuffer = nullptr; + std::unique_ptr receiveBuffer; + AwaitingResponseFlag awaitingResponseFlag; cpp_freertos::MutexStandard mutex; - TaskHandle_t blockedTaskHandle = nullptr; public: static const std::string OK; @@ -41,17 +74,19 @@ namespace at // const std::string Channel::RING = "RING"; // const std::string Channel::NO_DIALTONE = "NO DIALTONE"; + explicit Channel(uint8_t *receiveBuffer) + : receiveBuffer{receiveBuffer} { + + }; + /// waits till ok or timeout virtual auto cmd(const std::string &cmd, std::chrono::milliseconds timeout = at::default_timeout, size_t rxCount = 0) -> Result final; virtual auto cmd(const at::AT &at) -> Result final; virtual auto cmd(const at::Cmd &at) -> Result final; - virtual void cmd_log(std::string cmd, const Result &result, uint32_t timeout) final; - virtual auto ProcessNewData(sys::Service *service) -> int - { - return 0; - } - }; + virtual void cmdLog(std::string cmd, const Result &result, std::chrono::milliseconds timeout) final; -}; // namespace at + Result checkResult(bsp::cellular::CellularResultCode cellularResult); + }; +} // namespace at diff --git a/module-cellular/Modem/ATParser.cpp b/module-cellular/Modem/ATParser.cpp index a7fdc8ef64dfb34898abe995c8aaf4ec8639188d..c064e701af14a6c0389c999f24600e5b236fbf4e 100644 --- a/module-cellular/Modem/ATParser.cpp +++ b/module-cellular/Modem/ATParser.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ATParser.hpp" @@ -10,15 +10,14 @@ #include #include -ATParser::ATParser(bsp::Cellular *cellular) : cellular(cellular) +ATParser::ATParser(bsp::Cellular *cellular) : Channel{new uint8_t[at::defaultReceiveBufferSize]}, cellular(cellular) { - isInitialized = true; + responseBuffer = xMessageBufferCreate(at::defaultMessageBufferSize); } /// plz see 12.7 summary of urc in documentation std::vector ATParser::ParseURC() { - std::vector resp; size_t maxPos = 0, pos = 0; @@ -30,7 +29,7 @@ std::vector ATParser::ParseURC() }; for (const auto &el : vals) { - pos = responseBuffer.find(el.first); + pos = urcBuffer.find(el.first); if (pos != std::string::npos) { resp.push_back(el.second); maxPos = std::max(pos + el.first.length(), maxPos); @@ -38,50 +37,50 @@ std::vector ATParser::ParseURC() } } - if (responseBuffer.find("+QIND: \"FOTA\"") != std::string::npos) { - LOG_DEBUG("%s", responseBuffer.c_str()); + if (urcBuffer.find("+QIND: \"FOTA\"") != std::string::npos) { + LOG_DEBUG("%s", urcBuffer.c_str()); resp.push_back(ATParser::Urc::Fota); return resp; } // manage string buffer if (maxPos == 0) {} - else if (responseBuffer.size() >= maxPos) { - responseBuffer.erase(); + else if (urcBuffer.size() >= maxPos) { + urcBuffer.erase(); } else { - responseBuffer = responseBuffer.substr(maxPos); + urcBuffer = urcBuffer.substr(maxPos); } return resp; } -int ATParser::ProcessNewData(sys::Service *service) +at::Result ATParser::ProcessNewData(sys::Service *service, bsp::cellular::CellularResult *cellularResult) { - char rawBuffer[256] = {0}; - - LOG_DEBUG("Receiving data from ProcessNewData"); - auto length = cellular->Read(rawBuffer, sizeof(rawBuffer)); + at::Result result; { cpp_freertos::LockGuard lock(mutex); - responseBuffer.append(reinterpret_cast(rawBuffer), length); - LOG_DEBUG("Appending %d bytes to responseBuffer[%u]: %s", - static_cast(length), - static_cast(responseBuffer.size()), - utils::removeNewLines(responseBuffer).c_str()); + urcBuffer.append(cellularResult->getDataAsString()); } auto ret = ParseURC(); - if (blockedTaskHandle) { - xTaskNotifyGive(blockedTaskHandle); + + if (awaitingResponseFlag.state()) { + if (!xMessageBufferSend(responseBuffer, + (void *)cellularResult->getSerialized().get(), + cellularResult->getSerializedSize(), + pdMS_TO_TICKS(at::defaultBufferTimeoutMs.count()))) { + LOG_DEBUG("[AT] Message buffer full!"); + result.code = at::Result::Code::FULL_MSG_BUFFER; + } } - else if (ret.size()) { + else if (!ret.empty()) { if (ret.size() == 1 && ret[0] == ATParser::Urc::Fota) { - std::string fotaData(responseBuffer); + std::string fotaData(urcBuffer); LOG_DEBUG("parsing FOTA:\"%s\"", fotaData.c_str()); FotaService::API::sendRawProgress(service, fotaData); - responseBuffer.erase(); + urcBuffer.erase(); } else { urcs.insert(std::end(urcs), std::begin(ret), std::end(ret)); @@ -93,33 +92,36 @@ int ATParser::ProcessNewData(sys::Service *service) cpp_freertos::LockGuard lock(mutex); auto msg = std::make_shared(); service->bus.sendMulticast(msg, sys::BusChannel::ServiceCellularNotifications); - LOG_DEBUG("[!!!] Fucking away data"); - responseBuffer.erase(); + urcBuffer.erase(); urcs.clear(); } } - return 1; + else { + result.code = at::Result::Code::DATA_NOT_USED; + } + + return result; } void ATParser::cmd_init() { cpp_freertos::LockGuard lock(mutex); - responseBuffer.erase(); + urcBuffer.erase(); } void ATParser::cmd_send(std::string cmd) { - cellular->Write(const_cast(cmd.c_str()), cmd.size()); + cellular->write(const_cast(cmd.c_str()), cmd.size()); } -std::string ATParser::cmd_receive() +size_t ATParser::cmd_receive(std::uint8_t *buffer, std::chrono::milliseconds timeout = std::chrono::milliseconds{0}) { - cpp_freertos::LockGuard lock(mutex); - return responseBuffer; + return xMessageBufferReceive(responseBuffer, buffer, 256, pdMS_TO_TICKS(timeout.count())); } void ATParser::cmd_post() { cpp_freertos::LockGuard lock(mutex); - responseBuffer.erase(); // TODO:M.P is it okay to flush buffer here ? + urcBuffer.erase(); + xMessageBufferReset(responseBuffer); } diff --git a/module-cellular/Modem/ATParser.hpp b/module-cellular/Modem/ATParser.hpp index cff6a9b5fe676b986f3da7a92c2db5480de17d4b..12329d567309f2e37568f55db4ffbe63784077d7 100644 --- a/module-cellular/Modem/ATParser.hpp +++ b/module-cellular/Modem/ATParser.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #ifndef PUREPHONE_ATPARSER_HPP @@ -11,6 +11,9 @@ #include #include #include +#include + +#include namespace bsp { @@ -34,19 +37,19 @@ class ATParser : public at::Channel ATParser(bsp::Cellular *cellular); virtual ~ATParser() = default; - int ProcessNewData(sys::Service *service); + at::Result ProcessNewData(sys::Service *service, bsp::cellular::CellularResult *cellularResult); virtual void cmd_init() override final; virtual void cmd_send(std::string cmd) override final; - virtual std::string cmd_receive() override final; + virtual size_t cmd_receive(std::uint8_t *buffer, std::chrono::milliseconds timeout) override final; virtual void cmd_post() override final; private: std::vector ParseURC(); - bsp::Cellular *cellular = nullptr; - std::string responseBuffer; - std::vector urcs; - bool isInitialized = false; + bsp::Cellular *cellular = nullptr; + MessageBufferHandle_t responseBuffer = nullptr; + std::string urcBuffer = {}; + std::vector urcs = {}; }; #endif // PUREPHONE_ATPARSER_HPP diff --git a/module-cellular/Modem/BaseChannel.hpp b/module-cellular/Modem/BaseChannel.hpp index 21da4d4c61e8883c11884a56b6d858d027255e06..cc23afd427ec0fe0df138be7b423cc0d471e1283 100644 --- a/module-cellular/Modem/BaseChannel.hpp +++ b/module-cellular/Modem/BaseChannel.hpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include namespace at { @@ -29,12 +29,12 @@ namespace at /// @defgroup Channel Platform independent methods /// { - virtual void cmd_log(std::string cmd, const Result &result, uint32_t timeout) + virtual void cmdLog(std::string cmd, const Result &result, std::chrono::milliseconds timeout) {} - virtual void cmd_init() = 0; - virtual void cmd_send(std::string cmd) = 0; - virtual std::string cmd_receive() = 0; - virtual void cmd_post() = 0; + virtual void cmd_init() = 0; + virtual void cmd_send(std::string cmd) = 0; + virtual size_t cmd_receive(std::uint8_t *buffer, std::chrono::milliseconds timeoutMs) = 0; + virtual void cmd_post() = 0; /// } }; } // namespace at diff --git a/module-cellular/Modem/README.md b/module-cellular/Modem/README.md new file mode 100644 index 0000000000000000000000000000000000000000..231c1c9c4b1ec94458e13f37708dbf58088d654f --- /dev/null +++ b/module-cellular/Modem/README.md @@ -0,0 +1,98 @@ +![alt text](./doc/Images/mudita_logo.png "MUDITA") + +# Cellular Multiplexer + +## Table of contents +1. [History](#history) +2. [Modes](#modes) +3. [Single command data flow](#singlecmd) +5. [Cellular result structures](#result) +6. [Error codes](#errors) + +## History +| Authors | Change description | Status | Modification date | +| ----------------- | ------------------------- | ------ | ----------------- | +| Maciej Janicki | Initial version | Draft | 2021.03.24 | + +## Modes +Cellular operates in three modes: + - AT, + - CMUX setup, + - CMUX + +The AT mode is used for communication setup and modem configuration. + +![alt text](./doc/Images/at_mode.svg "Flow diagram for at mode") + +The CMUX setup is an intermidiate state starting a CMUX communication: + - It configures CMUX mode's transmission parameters. + - It creates channels for a frame multiplexing (Commands, Notifications, Data). +Data flow for both CMUX and CMUX setup in current implementation is identical. + +![alt text](./doc/Images/mux_mode.svg "Flow diagram for mux mode") + +## Single command data flow +![alt text](./doc/Images/single_cmd_transmission.png "Single command sequence") + +A single command sequence starts with a call from the Service and can end in one of three ways: + - Command response timeout (timeout depends on command issued). + - Service receives an error in result and handles it accordingly. + - Command response is successfully parsed before timeout and passed to service layer. + +## Result structs + +Currently, there are two types of structs used to pass data between contexts. + +DMA result struct is used to directly store data from DMA engine as well as pass these through +MessageBuffer to Worker. This struct has static data buffer maximum size. + +![alt text](./doc/Images/dma_result_struct.png "Dma result struct") + +Cellular Result is used to pass complete frames serialized in vector together with +result code. This struct has variable size. + +![alt text](./doc/Images/cellular_result_struct.png "Cellular result struct") + +## Error codes + + +![alt text](./Images/class_channel.png "Class diagram for channel") + +Currently there are different errors/results codes for different layers. + + 1. CellularResultCode + This code is used to pass information from bsp layer up to channel. + It can be either used to handle flow control messages from DMA or + to pass failures to upper layer (transmission/receiving not starter, + cmux frame error). + + ``` + Uninitialized, + ReceivedAndIdle, + ReceivedAndFull, + ReceivedAfterFull, + ReceivedNoData, + ReceivingNotStarted, + TransmittingNotStarted, + CMUXFrameError, + ``` + + 2. AtResult + This result code is passed to service layer. + + ``` + OK, /// at OK + ERROR, /// at ERROR For compatibility also for CME_ERROR and CMS_ERROR (details in errorCode) + CME_ERROR, /// In case CME error see errorCode + CMS_ERROR, /// In case CMS error see errorCode + TIMEOUT, /// at Timeout + TOKENS, /// at numbers of tokens needed met + NONE, /// no code + UNDEFINED, /// undefined result - usage of Undefined result, define and pin result to use it + PARSING_ERROR, /// parser error + FULL_MSG_BUFFER, /// at not enough space left in message buffer for new message + TRANSMISSION_NOT_STARTED, /// at dma not starting transmission + RECEIVING_NOT_STARTED, /// at dma not starting requested receiving + DATA_NOT_USED, /// at received data not being used + CMUX_FRAME_ERROR, /// at cmux deserialize error + ``` diff --git a/module-cellular/Modem/TS0710/DLC_channel.cpp b/module-cellular/Modem/TS0710/DLC_channel.cpp index 0296dd591b478785875e76ad486bdeaefb0049aa..cffb768f5c5b2da8065804234047a36ad925ac5a 100644 --- a/module-cellular/Modem/TS0710/DLC_channel.cpp +++ b/module-cellular/Modem/TS0710/DLC_channel.cpp @@ -1,50 +1,42 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md -/** - * Project Untitled - */ - #include "DLC_channel.h" -#include "TS0710.h" + #include "TS0710_DATA.h" -#include "TS0710_DLC_ESTABL.h" #include "TS0710_DLC_RELEASE.h" #include "TS0710_Frame.h" -#include "log/log.hpp" -#include "ticks.hpp" -#include -#include -/** - * DLC_channel implementation - */ +#include +#include +#include -DLC_channel::DLC_channel(DLCI_t DLCI, std::string name, bsp::Cellular *cellular, Callback_t callback) +DLC_channel::DLC_channel(DLCI_t DLCI, const std::string &name, bsp::Cellular *cellular, const Callback_t &callback) + : Channel{new uint8_t[at::defaultReceiveBufferSize]}, pv_name{name}, pv_DLCI{DLCI}, pv_cellular{cellular} { LOG_DEBUG("Creating DLCI %i channel \"%s\"", DLCI, name.c_str()); - pv_name = name; - pv_DLCI = DLCI; - pv_cellular = cellular; - if (callback != nullptr) + if (callback != nullptr) { pv_callback = callback; + } + + chanParams.TypeOfFrame = TypeOfFrame_e::SABM; + chanParams.ConvergenceLayer = 1; + chanParams.Priority = 1; + chanParams.AckTime = 100; // 100ms default + chanParams.MaxFrameSize = 128; + chanParams.MaxNumOfRetransmissions = 3; // default 3 + chanParams.ErrRecovWindowSize = 2; // default 2 - DLC_ESTABL_SystemParameters_t system_parameters; - system_parameters.TypeOfFrame = TypeOfFrame_e::SABM; - system_parameters.ConvergenceLayer = 1; - system_parameters.Priority = 1; - system_parameters.AckTime = 100; // 100ms default - system_parameters.MaxFrameSize = 128; - system_parameters.MaxNumOfRetransmissions = 3; // default 3 - system_parameters.ErrRecovWindowSize = 2; // default 2 - - TS0710_DLC_ESTABL establ = TS0710_DLC_ESTABL(DLCI, system_parameters, cellular); - pv_chanParams = establ.getParams(); - - // wait for return & set active - active = establ.getResponse(); - LOG_DEBUG("Create channel %s: %s", pv_name.c_str(), active ? "TRUE" : "FALSE"); + responseBuffer = xMessageBufferCreate(at::defaultMessageBufferSize); +} + +bool DLC_channel::init() +{ + active = establish(); + LOG_INFO("create channel %s: %s", pv_name.c_str(), active ? "TRUE" : "FALSE"); + + return active; } DLC_channel::~DLC_channel() @@ -54,41 +46,46 @@ DLC_channel::~DLC_channel() void DLC_channel::SendData(std::vector &data) { - TS0710_DATA _data = TS0710_DATA(pv_DLCI, pv_chanParams, data, pv_cellular); + TS0710_DATA _data = TS0710_DATA(pv_DLCI, chanParams, data, pv_cellular); } -#if 0 // left here for reference -ssize_t DLC_channel::ReceiveData(std::vector &data, uint32_t timeout) { - ssize_t ret = -1; - static uint8_t *buf = nullptr; - buf = reinterpret_cast(malloc(pv_chanParams.MaxFrameSize)); - bool complete = false; - - while((!complete) && (timeout--)) { //TODO: add timeout control - ret = pv_cellular->Read(reinterpret_cast(buf), pv_chanParams.MaxFrameSize); - if (ret > 0) { - LOG_DEBUG("Received %i bytes", ret); - for (int i = 0; i < ret; i++) - data.push_back(buf[i]); - complete = TS0710_Frame::isComplete(data); +bool DLC_channel::establish() +{ + LOG_DEBUG("Sending %s frame to DLCI %i", TypeOfFrame_text[chanParams.TypeOfFrame].c_str(), pv_DLCI); + + TS0710_Frame frame_c(TS0710_Frame::frame_t(static_cast(pv_DLCI << 2) | (1 << 1), + static_cast(chanParams.TypeOfFrame))); + + awaitingResponseFlag.set(); + + bool result = false; + + for (int retries = 0; retries < chanParams.MaxNumOfRetransmissions; ++retries) { + pv_cellular->write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); + + auto startTime = std::chrono::steady_clock::now(); + auto endTime = startTime + std::chrono::milliseconds{300}; + + // Wait for response: + while (true) { + if (std::chrono::steady_clock::now() > endTime) { + break; + } + + if (size_t bytesRead = cmd_receive(receiveBuffer.get(), std::chrono::milliseconds{0}); bytesRead > 0) { + auto cellularResult = bsp::cellular::CellularResult{receiveBuffer.get(), bytesRead}; + if (evaluateEstablishResponse(cellularResult)) { + result = true; + break; + } + } } - sleep_ms(1); - } - if (!complete) - LOG_ERROR("Incomplete frame received"); - if (timeout == 0) - LOG_ERROR("Timeout occured"); - - free(buf); - - if (!TS0710_Frame::isMyChannel(data, pv_DLCI)) { - data.clear(); - ret = -1; } - return ret; + awaitingResponseFlag.clear(); + + return result; } -#endif void DLC_channel::cmd_init() {} @@ -99,78 +96,48 @@ void DLC_channel::cmd_send(std::string cmd) SendData(data); } -std::string DLC_channel::cmd_receive() +size_t DLC_channel::cmd_receive(uint8_t *result, std::chrono::milliseconds timeout) { - cpp_freertos::LockGuard lock(mutex); - TS0710_Frame::frame_t frame; - std::vector v(responseBuffer.begin(), responseBuffer.end()); - - responseBuffer.clear(); - std::vector> mFrames; - std::vector rawBuffer; - - // get frames from buffer - for (size_t i = 0; i < v.size(); i++) { - rawBuffer.push_back(v[i]); - if (/*TS0710_Frame::isComplete(rawBuffer)*/ (rawBuffer.size() > 1) && (rawBuffer[0] == 0xF9) && - (rawBuffer[rawBuffer.size() - 1] == 0xF9)) { - // LOGrawBufferEBUG("Pushing back FRAME"); - mFrames.push_back(rawBuffer); - rawBuffer.clear(); - } - } - // deseriaise data from received frames - std::string deserialisedData; - for (std::vector vv : mFrames) { - frame.deserialize(vv); - std::string str(frame.data.begin(), frame.data.end()); - // append deserialised buffer - deserialisedData += str; - } - mFrames.clear(); - return deserialisedData; + return xMessageBufferReceive(responseBuffer, result, 2 * chanParams.MaxFrameSize, pdMS_TO_TICKS(timeout.count())); } void DLC_channel::cmd_post() {} -std::vector DLC_channel::SendCommandPrompt(const char *cmd, size_t rxCount, uint32_t timeout) +std::vector DLC_channel::SendCommandPrompt(const char *cmd, + size_t rxCount, + std::chrono::milliseconds timeout) { std::vector tokens; - blockedTaskHandle = xTaskGetCurrentTaskHandle(); + awaitingResponseFlag.set(); + at::Result result; cmd_init(); std::string cmdFixed = formatCommand(cmd); cmd_send(cmdFixed); - uint32_t currentTime = cpp_freertos::Ticks::GetTicks(); - uint32_t timeoutNeeded = timeout == UINT32_MAX ? UINT32_MAX : currentTime + timeout; - uint32_t timeElapsed = currentTime; - - // wait_for_data: - while (1) { + auto startTime = std::chrono::steady_clock::now(); + auto endTime = startTime + timeout; - if (timeElapsed >= timeoutNeeded) { + // Wait for response: + while (true) { + if (std::chrono::steady_clock::now() > endTime) { result.code = at::Result::Code::TIMEOUT; break; } - auto ret = ulTaskNotifyTake(pdTRUE, timeoutNeeded - timeElapsed); - timeElapsed = cpp_freertos::Ticks::GetTicks(); - if (ret) { + if (size_t bytesRead = cmd_receive(receiveBuffer.get(), std::chrono::milliseconds{0}); bytesRead > 0) { + auto cellularResult = bsp::cellular::CellularResult{receiveBuffer.get(), bytesRead}; + auto str = cellularResult.getDataAsString(); - std::vector strings; + if (result = checkResult(cellularResult.getResultCode()); result.code != at::Result::Code::OK) { + break; + } - cpp_freertos::LockGuard lock(mutex); - TS0710_Frame::frame_t frame; - std::vector v(responseBuffer.begin(), responseBuffer.end()); - responseBuffer.clear(); - frame.deserialize(v); - std::string str(frame.data.begin(), frame.data.end()); // tokenize responseBuffer - auto pos = str.find(">"); + auto pos = str.find('>'); if (pos != std::string::npos) { tokens.push_back(str.substr(pos, strlen(">"))); break; @@ -181,31 +148,41 @@ std::vector DLC_channel::SendCommandPrompt(const char *cmd, size_t } } - cmd_log(cmdFixed, result, timeout); + cmdLog(cmdFixed, result, timeout); cmd_post(); - blockedTaskHandle = nullptr; + + awaitingResponseFlag.clear(); return tokens; } -int DLC_channel::ParseInputData(std::vector &data) +at::Result DLC_channel::ParseInputData(bsp::cellular::CellularResult *cellularResult) { + at::Result result; - cpp_freertos::LockGuard lock(mutex); - - if (blockedTaskHandle) { - responseBuffer.append(reinterpret_cast(data.data()), data.size()); - xTaskNotifyGive(blockedTaskHandle); + if (awaitingResponseFlag.state()) { + if (!xMessageBufferSend(responseBuffer, + (void *)cellularResult->getSerialized().get(), + cellularResult->getSerializedSize(), + pdMS_TO_TICKS(at::defaultBufferTimeoutMs.count()))) { + LOG_DEBUG("[DLC] Message buffer full!"); + result.code = at::Result::Code::FULL_MSG_BUFFER; + } } else if (pv_callback != nullptr) { - - TS0710_Frame frame(data); - auto deserialised = frame.getFrame().data; - - std::string receivedData = std::string(deserialised.begin(), deserialised.end()); - + std::string receivedData = cellularResult->getDataAsString(); pv_callback(receivedData); } + else { + result.code = at::Result::Code::DATA_NOT_USED; + } - return 1; + return result; +} + +bool DLC_channel::evaluateEstablishResponse(bsp::cellular::CellularResult &response) const +{ + auto frame = TS0710_Frame{response.getData()}; + return (frame.getFrameDLCI() == pv_DLCI && + (frame.getFrame().Control == (static_cast(TypeOfFrame_e::UA) & ~(1 << 4)))); } diff --git a/module-cellular/Modem/TS0710/DLC_channel.h b/module-cellular/Modem/TS0710/DLC_channel.h index 5cf9a1e33607bd40648cce2f42236788a5f59a0e..be8a84b1ef1369bbc6bc88a59436ea081229b26c 100644 --- a/module-cellular/Modem/TS0710/DLC_channel.h +++ b/module-cellular/Modem/TS0710/DLC_channel.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /** @@ -12,10 +12,12 @@ #include #include -#include "../ATCommon.hpp" +#include "module-cellular/Modem/ATCommon.hpp" #include "TS0710_types.h" +#include #include #include +#include class DLC_channel : public at::Channel { @@ -26,24 +28,20 @@ class DLC_channel : public at::Channel std::string pv_name; DLCI_t pv_DLCI; bool active = false; - DLC_ESTABL_SystemParameters_t pv_chanParams; + DLC_ESTABL_SystemParameters_t chanParams{}; Callback_t pv_callback; - - std::string responseBuffer; - bsp::Cellular *pv_cellular; - + bsp::Cellular *pv_cellular{}; public: - // TS0710_DLC_ESTABL ctrlChanEstabl = TS0710_DLC_ESTABL(0); //use default values to create control channel DLCI0 + DLC_channel(DLCI_t DLCI, const std::string &name, bsp::Cellular *cellular, const Callback_t &callback = nullptr); + DLC_channel() : Channel{nullptr}, pv_name{"none"}, pv_DLCI{-1} + {} - DLC_channel(DLCI_t DLCI, std::string name, bsp::Cellular *cellular, Callback_t callback = nullptr); - DLC_channel() - { - pv_DLCI = -1; - pv_name = "none"; - } // default constructor creates empty channel virtual ~DLC_channel(); + bool init(); + bool establish(); + void SendData(std::vector &data); DLCI_t getDLCI() @@ -59,7 +57,6 @@ class DLC_channel : public at::Channel return active; } - // ssize_t ReceiveData(std::vector &data, uint32_t timeout); void setCallback(Callback_t callback) { LOG_DEBUG("[%s] Setting up callback for channel", pv_name.c_str()); @@ -68,12 +65,17 @@ class DLC_channel : public at::Channel virtual void cmd_init() override final; virtual void cmd_send(std::string cmd) override final; - virtual std::string cmd_receive() override final; + virtual size_t cmd_receive(std::uint8_t *result, + std::chrono::milliseconds timeout = std::chrono::milliseconds{300}) override final; virtual void cmd_post() override final; - std::vector SendCommandPrompt(const char *cmd, size_t rxCount, uint32_t timeout = 300); + std::vector SendCommandPrompt(const char *cmd, + size_t rxCount, + std::chrono::milliseconds timeout = std::chrono::milliseconds{300}); + + at::Result ParseInputData(bsp::cellular::CellularResult *cellularResult); - int ParseInputData(std::vector &data); + bool evaluateEstablishResponse(bsp::cellular::CellularResult &response) const; void callback(std::string &data) { diff --git a/module-cellular/Modem/TS0710/TS0710.cpp b/module-cellular/Modem/TS0710/TS0710.cpp index f487e859d4a06b1968cbb31153402b4b50d09149..251b4cf69e03998343d07c59f177d71a6fd66276 100644 --- a/module-cellular/Modem/TS0710/TS0710.cpp +++ b/module-cellular/Modem/TS0710/TS0710.cpp @@ -5,6 +5,7 @@ #include #include #include "bsp/cellular/bsp_cellular.hpp" +#include "bsp/cellular/CellularResult.hpp" #include "projdefs.h" #include #include @@ -44,39 +45,11 @@ std::map ATPortSpeeds_text = {{PortSpeed_e::PS9600, 9 #define SERIAL_PORT "/dev/null" #endif -#define USE_DAEFAULT_BAUDRATE 1 - -static const std::uint16_t threadSizeWords = 2048; +static constexpr std::uint16_t threadSizeWords = 2048; TS0710::TS0710(PortSpeed_e portSpeed, sys::Service *parent) { - LOG_INFO("Serial port: '%s'", SERIAL_PORT); - pv_portSpeed = portSpeed; - pv_cellular = bsp::Cellular::Create(SERIAL_PORT, 115200).value_or(nullptr); - parser = new ATParser(pv_cellular.get()); - pv_parent = parent; - - // start connection - startParams.PortSpeed = pv_portSpeed; - startParams.MaxFrameSize = 127; // maximum for Basic mode - startParams.AckTimer = 10; // 100ms default - startParams.MaxNumOfRetransmissions = 3; // default - startParams.MaxCtrlRespTime = 30; // 300ms default - startParams.WakeUpRespTime = 10; // 10s default - startParams.ErrRecovWindowSize = 2; // 2 default - - if (auto flushed = FlushReceiveData(); flushed > 0) { - LOG_INFO("Discarded initial %" PRIu32 " bytes sent by modem", - static_cast(flushed)); // not baud-accurate. Might be 460800Ă·115200 times more - } - - constexpr auto workerName = "TS0710SerialRxWorker"; - BaseType_t task_error = - xTaskCreate(workerTaskFunction, workerName, threadSizeWords, this, taskPriority, &taskHandle); - if (task_error != pdPASS) { - LOG_ERROR("Failed to start %s task", workerName); - return; - } + Init(portSpeed, parent); } TS0710::~TS0710() @@ -90,9 +63,47 @@ TS0710::~TS0710() if (taskHandle) { vTaskDelete(taskHandle); } + delete parser; } +void TS0710::Init(PortSpeed_e portSpeed, sys::Service *parent) +{ + pv_cellular = bsp::Cellular::create(SERIAL_PORT, 115200).value_or(nullptr); + parser = new ATParser(pv_cellular.get()); + pv_parent = parent; + + SetStartParams(portSpeed); + + if (auto flushed = flushReceiveData(); flushed > 0) { + LOG_INFO("Discarded initial %lu bytes sent by modem", + static_cast(flushed)); // not baud-accurate. Might be 460800Ă·115200 times more + } + else { + LOG_DEBUG("Nothing to discard"); + } + + constexpr auto workerName = "TS0710Worker"; + + BaseType_t task_error = + xTaskCreate(workerTaskFunction, workerName, threadSizeWords, this, taskPriority, &taskHandle); + if (task_error != pdPASS) { + LOG_ERROR("Failed to start %s task", workerName); + return; + } +} + +void TS0710::SetStartParams(PortSpeed_e portSpeed) +{ + startParams.PortSpeed = portSpeed; + startParams.MaxFrameSize = 127; // maximum for Basic mode + startParams.AckTimer = 10; // 100ms default + startParams.MaxNumOfRetransmissions = 3; // default + startParams.MaxCtrlRespTime = 30; // 300ms default + startParams.WakeUpRespTime = 10; // 10s default + startParams.ErrRecovWindowSize = 2; // 2 default +} + TS0710_Frame::frame_t createCMUXExitFrame() { TS0710_Frame::frame_t frame; @@ -145,7 +156,7 @@ void CloseCmux(std::unique_ptr &pv_cellular) { LOG_INFO("Closing mux mode"); TS0710_Frame::frame_t frame = createCMUXExitFrame(); - pv_cellular->Write((void *)frame.serialize().data(), frame.serialize().size()); + pv_cellular->write((void *)frame.serialize().data(), frame.serialize().size()); vTaskDelay(1000); // GSM module needs some time to close multiplexer } @@ -163,7 +174,7 @@ TS0710::ConfState TS0710::BaudDetectOnce() while (!result) { switch (step) { case BaudTestStep::baud460800_NoCmux: - pv_cellular->SetSpeed(ATPortSpeeds_text[PortSpeed_e::PS460800]); + pv_cellular->setSpeed(ATPortSpeeds_text[PortSpeed_e::PS460800]); lastStep = step; result = BaudDetectTestAT(parser, step, BaudTestStep::baud460800_Cmux); break; @@ -173,7 +184,7 @@ TS0710::ConfState TS0710::BaudDetectOnce() result = BaudDetectTestAT(parser, step, BaudTestStep::baud115200_NoCmux); break; case BaudTestStep::baud115200_NoCmux: - pv_cellular->SetSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); + pv_cellular->setSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); lastStep = step; result = BaudDetectTestAT(parser, step, BaudTestStep::baud115200_Cmux); break; @@ -183,8 +194,8 @@ TS0710::ConfState TS0710::BaudDetectOnce() result = BaudDetectTestAT(parser, step, BaudTestStep::baud_NotFound); break; case BaudTestStep::baud_NotFound: - pv_cellular->SetSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); // set port speed to default 115200 - LOG_DEBUG("No Baud found for modem."); + pv_cellular->setSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); // set port speed to default 115200 + LOG_ERROR("No Baud found for modem."); return ConfState::Failure; break; } @@ -206,12 +217,11 @@ TS0710::ConfState TS0710::BaudDetectProcedure(uint16_t timeout_s) return ConfState::Success; } } - pv_cellular->SetSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); // set port speed to default 115200 + pv_cellular->setSpeed(ATPortSpeeds_text[PortSpeed_e::PS115200]); // set port speed to default 115200 LOG_ERROR("No Baud found."); return ConfState::Failure; } -// TODO:M.P Fetch configuration from JSON/XML file TS0710::ConfState TS0710::ConfProcedure() { LOG_DEBUG("Configuring modem..."); @@ -292,14 +302,14 @@ TS0710::ConfState TS0710::AudioConfProcedure() if (!parser->cmd(at::AT::QMIC)) { return ConfState::Failure; } - return SetupEchoCanceller(EchoCancellerStrength::Tuned); + return setupEchoCanceller(EchoCancellerStrength::Tuned); } else { if (!parser->cmd(at::AT::QDAI_INIT)) { return ConfState::Failure; } else { - pv_cellular->Restart(); + pv_cellular->restart(); LOG_DEBUG("GSM module first run, performing reset..."); return ConfState::ModemNeedsReset; } @@ -359,13 +369,13 @@ TS0710::ConfState TS0710::StartMultiplexer() return ConfState::Failure; } - mode = Mode::CMUX_SETUP; - TS0710_START *pv_TS0710_Start = new TS0710_START(TS0710_START::Mode_e::Basic, startParams, pv_cellular.get()); + mode = Mode::CMUX_SETUP; + + TS0710_START TS0710_Start{TS0710_START::Mode_e::Basic, startParams, pv_cellular.get()}; // wait for confirmation - if (pv_TS0710_Start->ConnectionStatus()) { - channels.push_back(pv_TS0710_Start->getCtrlChannel()); // store control channel + if (TS0710_Start.ConnectionStatus()) { + channels.push_back(TS0710_Start.getCtrlChannel()); // store control channel } - delete pv_TS0710_Start; controlCallback = [this](std::string &data) { auto frameData = data; @@ -385,10 +395,10 @@ TS0710::ConfState TS0710::StartMultiplexer() LOG_PRINTF("RTR "); if (frameData[3] & 0x04) { LOG_PRINTF("RTC "); - this->getCellular()->SetSendingAllowed(true); + this->getCellular()->setSendingAllowed(true); } else - this->getCellular()->SetSendingAllowed(false); + this->getCellular()->setSendingAllowed(false); if (frameData[3] & 0x02) LOG_PRINTF("FC "); @@ -397,24 +407,23 @@ TS0710::ConfState TS0710::StartMultiplexer() } }; - // channels[0]->setCallback(controlCallback); - - // TODO: Open remaining channels OpenChannel(Channel::Commands); OpenChannel(Channel::Notifications); OpenChannel(Channel::Data); + LOG_DEBUG("[TS0710] Channels open"); - mode = Mode::CMUX; - DLC_channel *c = get(Channel::Commands); - if (c != nullptr) { + mode = Mode::CMUX; + DLC_channel *channel = get(Channel::Commands); + if (channel != nullptr) { // Route URCs to second (Notifications) MUX channel - c->cmd(at::AT::SET_URC_CHANNEL); + LOG_DEBUG("[TS0710] Setting URC Channel"); + channel->cmd(at::AT::SET_URC_CHANNEL); LOG_DEBUG("Sending test ATI"); - auto res = c->cmd(at::AT::SW_INFO); + auto res = channel->cmd(at::AT::SW_INFO); if (!res) { LOG_ERROR("Sending test ATI command failed"); } - res = c->cmd(at::AT::CSQ); + res = channel->cmd(at::AT::CSQ); if (res) { auto beg = res.response[0].find(" "); auto end = res.response[0].find(",", 1); @@ -443,123 +452,147 @@ TS0710::ConfState TS0710::StartMultiplexer() return ConfState::Failure; } + LOG_DEBUG("[TS0710] Mux started"); return ConfState::Success; } -void workerTaskFunction(void *ptr) +void TS0710::sendFrameToChannel(bsp::cellular::CellularResult &resultStruct) { - TS0710 *inst = reinterpret_cast(ptr); - - while (1) { - auto ret = inst->pv_cellular->Wait(UINT32_MAX); - if (ret == 0) { - continue; - } + auto frame = TS0710_Frame{resultStruct.getData()}; - // AT mode is used only during initialization phase - if (inst->mode == TS0710::Mode::AT) { - // inst->atParser->ProcessNewData(); - // TODO: add AT command processing - LOG_DEBUG("[Worker] Processing AT response"); - inst->parser->ProcessNewData(inst->pv_parent); - } - // CMUX mode is default operation mode - else if (inst->mode == TS0710::Mode::CMUX) { - // LOG_DEBUG("[Worker] Processing CMUX response"); - std::vector data; - inst->ReceiveData(data, static_cast(inst->startParams.MaxCtrlRespTime)); - // send data to fifo - for (uint8_t c : data) { - inst->RXFifo.push(c); + for (auto chan : getChannels()) { + if (frame.getFrameDLCI() == chan->getDLCI()) { + if (frame.getData().empty()) { + // Control frame contains no data + resultStruct.setData(frame.getSerData()); } - data.clear(); - // divide message to different frames as Quectel may send them one after another - std::vector> multipleFrames; - std::vector _d; - int fifoLen = inst->RXFifo.size(); - // LOG_DEBUG("[RXFifo] %i elements", fifoLen); - - for (int i = 0; i < fifoLen; i++) { - _d.push_back(inst->RXFifo.front()); - inst->RXFifo.pop(); - if (/*TS0710_Frame::isComplete(_d)*/ (_d.size() > 1) && (_d[0] == 0xF9) && - (_d[_d.size() - 1] == 0xF9)) { - // LOG_DEBUG("Pushing back FRAME"); - multipleFrames.push_back(_d); - _d.clear(); - } + else { + resultStruct.setData(frame.getData()); } - // if some data stored @_d then push it back to queue as incomplete packet - if (!_d.empty() && (_d[0] == 0xF9)) { - // LOG_DEBUG("Pushing back [%i] incomplete frame", _d.size()); - for (uint8_t c : _d) - inst->RXFifo.push(c); - } - _d.clear(); - // LOG_DEBUG("Received %i frames", multipleFrames.size()); - for (auto *chan : inst->channels) { - for (std::vector v : multipleFrames) { - if (TS0710_Frame::isMyChannel(v, chan->getDLCI())) - chan->ParseInputData(v); - } + if (frame.getFrameStatus() != TS0710_Frame::OK) { + resultStruct.setResultCode(bsp::cellular::CellularResultCode::CMUXFrameError); } - multipleFrames.clear(); + + chan->ParseInputData(&resultStruct); + return; } } } -size_t TS0710::FlushReceiveData() +void TS0710::parseCellularResultCMUX(bsp::cellular::CellularDMAResultStruct &result) { - using namespace std::chrono_literals; + static std::vector currentFrame; + static bool frameStartDetected = false; + + for (auto i = 0U; i < result.dataSize; ++i) { + uint8_t character = result.data[i]; + if (frameStartDetected || character == TS0710_FLAG) { + + currentFrame.push_back(character); + + // Check if frame is complete only in case of TS0710_FLAG + if (frameStartDetected && character == TS0710_FLAG) { + if (TS0710_Frame::isComplete(currentFrame)) { + frameStartDetected = false; + bsp::cellular::CellularResult cellularResult{{result.resultCode, currentFrame}}; + sendFrameToChannel(cellularResult); + currentFrame.clear(); + continue; + } + } - auto flushed = 0U; - constexpr auto flushInactivityTimeout = 20ms; - std::uint8_t dummyRead[50]; - while (pv_cellular->Wait(flushInactivityTimeout.count())) { - flushed += pv_cellular->Read(dummyRead, sizeof(dummyRead)); + frameStartDetected = true; + } } +} + +size_t TS0710::flushReceiveData() +{ + auto flushed = 0U; + constexpr auto flushTimeout = std::chrono::milliseconds{20}; + bsp::cellular::CellularDMAResultStruct dummyResult; + do { + flushed += pv_cellular->read(&dummyResult, dummyResult.getMaxSize(), flushTimeout); + } while (dummyResult.resultCode == bsp::cellular::CellularResultCode::ReceivedAndFull); return flushed; } -ssize_t TS0710::ReceiveData(std::vector &data, uint32_t timeout) +void TS0710::processError(bsp::cellular::CellularDMAResultStruct &result) { - ssize_t ret = -1; - std::unique_ptr buf(new uint8_t[startParams.MaxFrameSize]); - bool complete = false; - uint32_t _timeout = timeout; + bsp::cellular::CellularResult cellularResult{{result.resultCode, {}}}; - while ((!complete) && (--_timeout)) { - ret = pv_cellular->Read(reinterpret_cast(buf.get()), startParams.MaxFrameSize); - if (ret > 0) { - // LOG_DEBUG("Received %i bytes", ret); - for (int i = 0; i < ret; i++) { - data.push_back(buf[i]); - } - complete = TS0710_Frame::isComplete(data); - } - vTaskDelay(pdMS_TO_TICKS(1)); + if (mode == TS0710::Mode::AT) { + parser->ProcessNewData(pv_parent, &cellularResult); + } + else if (mode == TS0710::Mode::CMUX || mode == TS0710::Mode::CMUX_SETUP) { + sendFrameToChannel(cellularResult); + } +} + +void TS0710::processData(bsp::cellular::CellularDMAResultStruct &result) +{ + if (mode == TS0710::Mode::AT) { + bsp::cellular::CellularResult cellularResult{result}; + parser->ProcessNewData(pv_parent, &cellularResult); } - if ((!complete) && (_timeout)) { - LOG_ERROR("Incomplete frame received"); + else if (mode == TS0710::Mode::CMUX || mode == TS0710::Mode::CMUX_SETUP) { + parseCellularResultCMUX(result); } +} - return ret; +[[noreturn]] void workerTaskFunction(void *ptr) +{ + LOG_DEBUG("Worker start"); + + constexpr auto readTimeout = std::chrono::milliseconds{10000}; + TS0710 *inst = static_cast(ptr); + bsp::cellular::CellularDMAResultStruct result{}; + + while (true) { + result.resultCode = bsp::cellular::CellularResultCode::ReceivedNoData; + + inst->pv_cellular->read(&result, bsp::cellular::CellularDMAResultStruct::getMaxSize(), readTimeout); + + switch (result.resultCode) { + case bsp::cellular::CellularResultCode::ReceivedAndFull: + LOG_DEBUG("DMA buffer full"); + [[fallthrough]]; + case bsp::cellular::CellularResultCode::ReceivedAfterFull: + [[fallthrough]]; + case bsp::cellular::CellularResultCode::ReceivedAndIdle: + inst->processData(result); + break; + case bsp::cellular::CellularResultCode::Uninitialized: + LOG_DEBUG("DMA uninitialized"); + [[fallthrough]]; + case bsp::cellular::CellularResultCode::ReceivingNotStarted: + [[fallthrough]]; + case bsp::cellular::CellularResultCode::TransmittingNotStarted: + [[fallthrough]]; + case bsp::cellular::CellularResultCode::CMUXFrameError: + LOG_DEBUG("CellularResult Error: %s", c_str(result.resultCode)); + inst->processError(result); + break; + case bsp::cellular::CellularResultCode::ReceivedNoData: + break; + } + } } void TS0710::SelectAntenna(bsp::cellular::antenna antenna) { - pv_cellular->SelectAntenna(antenna); + pv_cellular->selectAntenna(antenna); } bsp::cellular::antenna TS0710::GetAntenna() { - return pv_cellular->GetAntenna(); + return pv_cellular->getAntenna(); } void TS0710::InformModemHostWakeup(void) { - return pv_cellular->InformModemHostWakeup(); + return pv_cellular->informModemHostWakeup(); } bool TS0710::IsModemActive(void) @@ -569,46 +602,46 @@ bool TS0710::IsModemActive(void) void TS0710::TurnOnModem(void) { - return pv_cellular->PowerUp(); + return pv_cellular->powerUp(); } void TS0710::ResetModem(void) { - return pv_cellular->Restart(); + return pv_cellular->restart(); } void TS0710::TurnOffModem(void) { - return pv_cellular->PowerDown(); + return pv_cellular->powerDown(); } void TS0710::EnterSleepMode(void) { - return pv_cellular->EnterSleep(); + return pv_cellular->enterSleep(); } void TS0710::ExitSleepMode(void) { - return pv_cellular->ExitSleep(); + return pv_cellular->exitSleep(); } void TS0710::RegisterCellularDevice(void) { - auto deviceRegistrationMsg = std::make_shared(pv_cellular->GetCellularDevice()); + auto deviceRegistrationMsg = std::make_shared(pv_cellular->getCellularDevice()); pv_parent->bus.sendUnicast(std::move(deviceRegistrationMsg), service::name::system_manager); } -[[nodiscard]] auto TS0710::GetLastCommunicationTimestamp() const noexcept -> TickType_t +[[nodiscard]] auto TS0710::getLastCommunicationTimestamp() const noexcept -> TickType_t { - return pv_cellular->GetLastCommunicationTimestamp(); + return pv_cellular->getLastCommunicationTimestamp(); } -[[nodiscard]] auto TS0710::IsCellularInSleepMode() const noexcept -> bool +[[nodiscard]] auto TS0710::isCellularInSleepMode() const noexcept -> bool { - return pv_cellular->IsCellularInSleepMode(); + return pv_cellular->isCellularInSleepMode(); } -TS0710::ConfState TS0710::SetupEchoCanceller(EchoCancellerStrength strength) +TS0710::ConfState TS0710::setupEchoCanceller(EchoCancellerStrength strength) { switch (strength) { diff --git a/module-cellular/Modem/TS0710/TS0710.h b/module-cellular/Modem/TS0710/TS0710.h index 940dc2ade95344da86c1d89964eae637d4d89b7f..ca748a78cc979b8577f73d58256176f3da257a3c 100644 --- a/module-cellular/Modem/TS0710/TS0710.h +++ b/module-cellular/Modem/TS0710/TS0710.h @@ -1,8 +1,7 @@ // Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md -#ifndef _TS0710_H -#define _TS0710_H +#pragma once /* 5.2.6 Basic Option @@ -190,6 +189,9 @@ repeated until a response is obtained or action is taken by a higher layer. */ #include +#include +#include +#include #include "TS0710_START.h" #include "TS0710_CLOSE.h" @@ -197,14 +199,13 @@ repeated until a response is obtained or action is taken by a higher layer. #include "TS0710_TEST.h" #include "TS0710_WAKEUP.h" #include "TS0710_types.h" -#include "DLC_channel.h" #include "TS0710_Frame.h" + +#include "DLC_channel.h" #include "Modem/ATParser.hpp" #include "Service/Service.hpp" -#include -#include -#include "module-bsp/bsp/cellular/bsp_cellular.hpp" +#include #if defined(__cplusplus) extern "C" @@ -221,7 +222,7 @@ namespace bsp class Cellular; } -void workerTaskFunction(void *ptr); +[[noreturn]] void workerTaskFunction(void *ptr); class TS0710 { @@ -233,6 +234,7 @@ class TS0710 Notifications = 2, Data = 3, }; + static std::string name(enum Channel name) { switch (name) { @@ -247,13 +249,13 @@ class TS0710 } return ""; } + enum class Mode { AT, /// AT raw text mode CMUX_SETUP, /// Modem is switching from AT to CMUX CMUX /// multiplexer mode enabled }; - void setMode(Mode mode); enum class ConfState { @@ -263,15 +265,16 @@ class TS0710 PowerUp }; + void setMode(Mode mode); + private: Mode mode = Mode::AT; std::vector channels; - friend void workerTaskFunction(void *ptr); - // worker's task handle - xTaskHandle taskHandle = nullptr; + const uint32_t taskPriority = 0; + xTaskHandle taskHandle = nullptr; + std::unique_ptr pv_cellular; - PortSpeed_e pv_portSpeed; ATParser *parser; int CloseMultiplexer(); @@ -290,7 +293,6 @@ class TS0710 sys::Service *pv_parent; DLC_channel::Callback_t controlCallback = nullptr; - std::queue RXFifo; enum class EchoCancellerStrength { @@ -299,7 +301,14 @@ class TS0710 Aggressive, Tuned }; - TS0710::ConfState SetupEchoCanceller(EchoCancellerStrength strength); + + friend void workerTaskFunction(void *ptr); + ConfState setupEchoCanceller(EchoCancellerStrength strength); + void parseCellularResultCMUX(bsp::cellular::CellularDMAResultStruct &result); + size_t flushReceiveData(); + void processData(bsp::cellular::CellularDMAResultStruct &result); + void processError(bsp::cellular::CellularDMAResultStruct &result); + void sendFrameToChannel(bsp::cellular::CellularResult &resultStruct); public: /// @brief get Channel by index @@ -316,31 +325,23 @@ class TS0710 return nullptr; } - DLC_channel *OpenChannel(Channel chanel_val) + std::vector &getChannels() { - DLC_channel *channel = new DLC_channel(static_cast(chanel_val), name(chanel_val), pv_cellular.get()); - channels.push_back(channel); - return channels.back(); + return channels; } - void CloseChannel(unsigned index) + DLC_channel *OpenChannel(Channel channelIndex) { - if (index >= channels.size()) { - LOG_ERROR("Wrong channel index"); - return; - } - delete channels[index]; - channels.erase(channels.begin() + index); - } + DLC_channel *channel = + new DLC_channel(static_cast(channelIndex), name(channelIndex), pv_cellular.get()); + channels.push_back(channel); - void CloseChannel(const std::string &name) - { - for (size_t i = 0; i < channels.size(); i++) { - if (channels[i]->getName() == name) { - delete channels[i]; - channels.erase(channels.begin() + 1); - } + if (!channel->init()) { + channels.pop_back(); + delete channel; } + + return channels.back(); } void CloseChannels() @@ -357,15 +358,13 @@ class TS0710 { return static_cast(channels.size() == 0 ? 0 : channels.size() - 1); } + ConfState BaudDetectOnce(); ConfState BaudDetectProcedure(uint16_t timeout_s = 30); ConfState ConfProcedure(); ConfState AudioConfProcedure(); ConfState StartMultiplexer(); - size_t FlushReceiveData(); - ssize_t ReceiveData(std::vector &data, uint32_t timeout); - bsp::Cellular *getCellular() { return pv_cellular.get(); @@ -427,6 +426,8 @@ class TS0710 TS0710(PortSpeed_e portSpeed, sys::Service *parent); TS0710() = delete; ~TS0710(); + void Init(PortSpeed_e portSpeed, sys::Service *parent); + void SetStartParams(PortSpeed_e portSpeed); void SelectAntenna(bsp::cellular::antenna antenna); bsp::cellular::antenna GetAntenna(); // Add error handling - only for Advanced mode. Leave for now @@ -441,8 +442,6 @@ class TS0710 void EnterSleepMode(void); void ExitSleepMode(void); void RegisterCellularDevice(void); - [[nodiscard]] auto GetLastCommunicationTimestamp() const noexcept -> TickType_t; - [[nodiscard]] auto IsCellularInSleepMode() const noexcept -> bool; + [[nodiscard]] auto getLastCommunicationTimestamp() const noexcept -> TickType_t; + [[nodiscard]] auto isCellularInSleepMode() const noexcept -> bool; }; - -#endif //_TS0710_H diff --git a/module-cellular/Modem/TS0710/TS0710_DATA.cpp b/module-cellular/Modem/TS0710/TS0710_DATA.cpp index 3be0d7d2a962ca32ba5b7f9db179fc5bfcc441bf..c2e0679f25cc602791db6e30a1abb2b4ced211ef 100644 --- a/module-cellular/Modem/TS0710/TS0710_DATA.cpp +++ b/module-cellular/Modem/TS0710/TS0710_DATA.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /** @@ -61,7 +61,7 @@ void TS0710_DATA::request(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t sysParams, frame.data = User_data; TS0710_Frame frame_c(frame); // UartSend(frame_c.getSerData().data(), frame_c.getSerData().size()); - pv_cellular->Write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); + pv_cellular->write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); } else { // if data size > max frame size int dataLeft = User_data.size(); @@ -76,7 +76,7 @@ void TS0710_DATA::request(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t sysParams, TS0710_Frame frame_c(frame); // UartSend(frame_c.getSerData().data(), frame_c.getSerData().size()); // while(!pv_cellular->GetSendingAllowed()); - pv_cellular->Write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); + pv_cellular->write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); // vTaskDelay(1); dataLeft -= (dataLeft <= maximumFrameLength ? dataLeft : maximumFrameLength); } @@ -88,4 +88,4 @@ void TS0710_DATA::request(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t sysParams, * @param User_data */ void TS0710_DATA::indication(DLCI_t DLCI, std::vector User_data) -{} \ No newline at end of file +{} diff --git a/module-cellular/Modem/TS0710/TS0710_DLC_ESTABL.cpp b/module-cellular/Modem/TS0710/TS0710_DLC_ESTABL.cpp index 53cc53cb8fc2e0f8c4d903544e46b30c72b25f14..b947a601b000d6ddc4056d5c18417b4e864333e9 100644 --- a/module-cellular/Modem/TS0710/TS0710_DLC_ESTABL.cpp +++ b/module-cellular/Modem/TS0710/TS0710_DLC_ESTABL.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md /** @@ -21,6 +21,8 @@ extern "C" #include "log/log.hpp" #include "FreeRTOS.h" +#include + /** * TS0710_DLC_ESTABL implementation */ @@ -81,12 +83,12 @@ bool TS0710_DLC_ESTABL::request(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t syste frame.Address = static_cast(DLCI << 2) | (1 << 1); // set C/R = 1 - command frame.Control = static_cast(system_parameters.TypeOfFrame); TS0710_Frame frame_c(frame); - pv_cellular->Write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); + pv_cellular->write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); // return true; int retries = system_parameters.MaxNumOfRetransmissions; while (retries--) { // UartSend(frame_c.getSerData().data(), frame_c.getSerData().size()); - pv_cellular->Write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); + pv_cellular->write(static_cast(frame_c.getSerData().data()), frame_c.getSerData().size()); vTaskDelay(system_parameters.AckTime); if (response(DLCI, system_parameters)) { LOG_DEBUG("Got response"); @@ -113,14 +115,13 @@ void TS0710_DLC_ESTABL::indication(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t sy bool TS0710_DLC_ESTABL::response(DLCI_t DLCI, DLC_ESTABL_SystemParameters_t system_parameters) { constexpr size_t size = 256; - std::unique_ptr data(new uint8_t[size]); + bsp::cellular::CellularDMAResultStruct result{}; - // uint32_t len = UartReceive(data); - ssize_t len = pv_cellular->Read(reinterpret_cast(data.get()), size); + ssize_t len = pv_cellular->read(&result, size, std::chrono::milliseconds{0}); LOG_DEBUG("RX length = %d", static_cast(len)); if (len > 0) { - std::vector v(data.get(), data.get() + len); + std::vector v(result.data, result.data + result.dataSize); TS0710_Frame frame_c(v); TS0710_Frame::frame_t frame = frame_c.getFrame(); diff --git a/module-cellular/Modem/TS0710/TS0710_Frame.h b/module-cellular/Modem/TS0710/TS0710_Frame.h index cd129f7caa5e460c108702154e005cb583a61e39..1e55b1974e14e9d95e5d8c6ccbc83a85d4f6a43f 100644 --- a/module-cellular/Modem/TS0710/TS0710_Frame.h +++ b/module-cellular/Modem/TS0710/TS0710_Frame.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved. +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #ifndef _TS0710_FRAME_H @@ -34,12 +34,26 @@ class TS0710_Frame 0xBD, 0x2C, 0x5E, 0xCF}; public: + enum TS0710FrameStatus : std::uint8_t + { + OK, + EmptyFrame, + IncorrectStartStopFlags, + CRCError, + }; + struct frame_t { + TS0710FrameStatus frameStatus = OK; uint8_t Address; uint8_t Control; std::vector data; + frame_t() = default; + + explicit frame_t(uint8_t address, uint8_t control) : frameStatus{OK}, Address{address}, Control{control} + {} + std::vector serialize() { std::vector ret; @@ -65,7 +79,7 @@ class TS0710_Frame unsigned char i = 1; if (Control == static_cast(TypeOfFrame_e::UIH)) { len = 3; // par. 5.3.6 of GSM0710 document states that for UIH frames, the FCS shall be calculated over - // only the address, control and length fields TODO: include 2-byte address + // only the address, control and length fields TODO: include 2-byte address // if (Length > 127) // len += 1; } @@ -85,8 +99,9 @@ class TS0710_Frame { if (serData.size() < 4) { LOG_ERROR("Trying to deserialize empty frame"); - Address = 0; - Control = 0; + Address = 0; + Control = 0; + frameStatus = EmptyFrame; return; } // iterate through frame to get correct trailing flag. In case multiple-frame stream provided @@ -104,8 +119,9 @@ class TS0710_Frame } if ((serData[0] != TS0710_FLAG) || (serData[myLen - 1] != TS0710_FLAG)) { - Address = 0; - Control = 0; + Address = 0; + Control = 0; + frameStatus = IncorrectStartStopFlags; LOG_ERROR("Received frame has incorrect leading/trailing flags. Dropping."); return; // return empty frame. Discard frame witout proper leading & trailing flag } @@ -135,7 +151,7 @@ class TS0710_Frame unsigned char i = 1; if (Control == static_cast(TypeOfFrame_e::UIH)) { len = 3; // par. 5.3.6 of GSM0710 document states that for UIH frames, the FCS shall be calculated over - // only the address, control and length fields: include 2-byte address + // only the address, control and length fields // if (Length > 127) // len += 1; } @@ -150,8 +166,9 @@ class TS0710_Frame (Control != static_cast( TypeOfFrame_e::UA))) { // error - but fuck FCS check if it's faulty Quectel UIH frame or UA frame - Address = 0; - Control = 0; + Address = 0; + Control = 0; + frameStatus = CRCError; data.clear(); LOG_ERROR("Received frame FCS [0x%02X] != 0xCF error. Dropping.", FCS); return; // return empty frame. Discard frame witout proper leading & trailing flag @@ -164,33 +181,42 @@ class TS0710_Frame std::vector pv_serData; public: - TS0710_Frame(frame_t frame) + explicit TS0710_Frame(frame_t frame) { // LOG_DEBUG("Serializing given frame"); pv_serData = frame.serialize(); pv_frame = frame; } - TS0710_Frame(std::vector &serData) + + explicit TS0710_Frame(const std::vector &serData) { // LOG_DEBUG("Deserializing serData"); pv_frame.deserialize(serData); pv_serData = serData; } + TS0710_Frame() { LOG_DEBUG("Deserializing pv_serData"); pv_frame.deserialize(pv_serData); } + ~TS0710_Frame() { pv_serData.clear(); pv_frame.data.clear(); } - frame_t getFrame() + frame_t &getFrame() { return pv_frame; } + + const std::vector &getData() const noexcept + { + return pv_frame.data; + } + std::vector getSerData() { return pv_serData; @@ -199,11 +225,13 @@ class TS0710_Frame /* F9 03 3F 01 1C F9 */ static bool isComplete(const std::vector &serData) { - if (serData.size() < 4) + if (serData.size() < 4) { return false; // check if buffer has enough data to get length + } - if ((serData[0] != TS0710_FLAG) || (serData[serData.size() - 1] != TS0710_FLAG)) + if ((serData[0] != TS0710_FLAG) || (serData[serData.size() - 1] != TS0710_FLAG)) { return false; + } int Length = 0; if (serData[3] & 0x01) { // short length @@ -212,22 +240,22 @@ class TS0710_Frame else if (serData.size() > 4) { // long length - another check if enough bytes in buffer Length = static_cast(serData[3] >> 1) + (static_cast(serData[4]) << 7); } - else + else { return false; + } if (serData.size() >= static_cast(TS0710_FRAME_HDR_LEN + Length + - (serData[3] & 0x01 ? 0 : 1))) // include extended address byte if present + (serData[3] & 0x01 ? 0 : 1))) { // include extended address byte if present return true; + } return false; } - static bool isMyChannel(const std::vector &serData, DLCI_t DLCI) + bool isMyChannel(DLCI_t DLCI) const { - if ((serData.size() > 1) && ((serData[1] >> 2) == DLCI)) - return true; - return false; + return (pv_serData.size() > 1) && ((pv_serData[1] >> 2) == DLCI); } static DLCI_t getFrameDLCI(const std::vector &serData) @@ -236,6 +264,18 @@ class TS0710_Frame return (serData[1] >> 2); return -1; } + + DLCI_t getFrameDLCI() const + { + if (pv_serData.size() > 1) + return (pv_serData[1] >> 2); + return -1; + } + + TS0710FrameStatus getFrameStatus() const + { + return pv_frame.frameStatus; + } }; #endif /*_TS0710_FRAME_H*/ diff --git a/module-cellular/Modem/doc/Images/at_mode.svg b/module-cellular/Modem/doc/Images/at_mode.svg new file mode 100644 index 0000000000000000000000000000000000000000..a139f762592902a10fecc4d53b6617381796e637 --- /dev/null +++ b/module-cellular/Modem/doc/Images/at_mode.svg @@ -0,0 +1,72 @@ +AT ModeCellularBSPCellularWorkerChannelUartIrqReads data with DMA to static bufferand packs them into struct with result code.SendToWorkerPuts structured data into workers MessageBuffer.outputDMAMessageBufferATParserParses URC packages.CmdAwaitingResponseoutputWorkerCheckIfFOTAStoreURCFotaServiceATBufferATStreamparses AT messagechannelOutputServiceCellularTrueFalseFalseTrue \ No newline at end of file diff --git a/module-cellular/Modem/doc/Images/at_mode.uml b/module-cellular/Modem/doc/Images/at_mode.uml new file mode 100644 index 0000000000000000000000000000000000000000..e57cff98c9b882cb3189bd55b1a9edaea5834111 --- /dev/null +++ b/module-cellular/Modem/doc/Images/at_mode.uml @@ -0,0 +1,46 @@ +@startuml +Title AT Mode + +state CellularBSP { + UartIrq: Reads data with DMA to static buffer + UartIrq: and packs them into struct with result code. + UartIrq --> SendToWorker + SendToWorker: Puts structured data into workers MessageBuffer. + SendToWorker --> output + + state output <> +} + +state CellularWorker{ + state DMAMessageBuffer <> + + DMAMessageBuffer --> ATParser + ATParser: Parses URC packages. + ATParser --> CmdAwaitingResponse + + state CmdAwaitingResponse <> + CmdAwaitingResponse ----> outputWorker : True + CmdAwaitingResponse --> CheckIfFOTA : False + + CheckIfFOTA --> StoreURC : False + CheckIfFOTA --> FotaService : True + + state outputWorker <> +} + +output --> DMAMessageBuffer + +state Channel { + state ATBuffer <> + + ATBuffer --> ATStream + ATStream: parses AT message + + ATStream --> channelOutput + + state channelOutput <> +} + +outputWorker --> ATBuffer +channelOutput --> ServiceCellular +@enduml diff --git a/module-cellular/Modem/doc/Images/cellular_mux_read.png b/module-cellular/Modem/doc/Images/cellular_mux_read.png new file mode 100644 index 0000000000000000000000000000000000000000..2f28f139530c31ff6ff40863f8c2dca4559148d6 GIT binary patch literal 31811 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 -r)2iaU7^#1sNK8oAy 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 literal 0 HcmV?d00001 diff --git a/module-cellular/Modem/doc/Images/cellular_result_struct.png b/module-cellular/Modem/doc/Images/cellular_result_struct.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e37ac7222460b7d0e18f4d59b008a2733bd1e8 GIT binary patch literal 4797 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 VQdt*Phxw6&1=cjsXi&3@{vT743F-g< literal 0 HcmV?d00001 diff --git a/module-cellular/Modem/doc/Images/cellular_result_struct.uml b/module-cellular/Modem/doc/Images/cellular_result_struct.uml new file mode 100644 index 0000000000000000000000000000000000000000..f9eb76be7e4a357bd2897a10391facea3421b6bc --- /dev/null +++ b/module-cellular/Modem/doc/Images/cellular_result_struct.uml @@ -0,0 +1,9 @@ +@startuml +object CellularResultStruct { + CellularResultCode resultCode + vector data + + +std::shared_ptr serialize() + +void deserialize(serialized, size) +} +@enduml diff --git a/module-cellular/Modem/doc/Images/cmx_mode.uml b/module-cellular/Modem/doc/Images/cmx_mode.uml new file mode 100644 index 0000000000000000000000000000000000000000..dec462c45b13a5b145c3f8dda3983457d9cb7767 --- /dev/null +++ b/module-cellular/Modem/doc/Images/cmx_mode.uml @@ -0,0 +1,70 @@ +@startuml +Title CMUX Mode + +state CellularBSP { + UartIrq: Reads data with DMA to static buffer + UartIrq: and packs them into struct with result code. + UartIrq --> SendToWorker + SendToWorker: Puts structured data into workers MessageBuffer. + SendToWorker --> output + + state output <> +} + +state CellularWorker{ + state DMAMessageBuffer <> + + DMAMessageBuffer --> ATParser + ATParser: Parses URC packages. + ATParser --> CmdAwaitingResponse + + state CmdAwaitingResponse <> + CmdAwaitingResponse ----> workerOutput : True + CmdAwaitingResponse --> callback : False + + state workerOutput <> +} + +state Notifications { + state input1 <> + + input1 --> ATStream1 + + ATStream1 --> output1 + + state output1 <> +} + +state Commands { + state input2 <> + + input2 --> ATStream2 + + ATStream2 --> output2 + + state output2 <> +} + +state Data { + state input3 <> + + input3 --> ATStream3 + + ATStream3 --> output3 + + state output3 <> +} + +state channelsFork <> +state channelsJoin <> + +output --> DMAMessageBuffer : Result{data,resultCode} +workerOutput --> channelsFork +channelsFork --> input1 +channelsFork --> input2 +channelsFork --> input3 +output1 --> channelsJoin +output2 --> channelsJoin +output3 --> channelsJoin +channelsJoin --> ServiceCellular : Result{parsedData,resultCode} +@enduml diff --git a/module-cellular/Modem/doc/Images/dma_result_struct.png b/module-cellular/Modem/doc/Images/dma_result_struct.png new file mode 100644 index 0000000000000000000000000000000000000000..fdd5a051db4f3fa5fa73c0acd4b7faccbe26d543 GIT binary patch literal 4643 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 literal 0 HcmV?d00001 diff --git a/module-cellular/Modem/doc/Images/dma_result_struct.uml b/module-cellular/Modem/doc/Images/dma_result_struct.uml new file mode 100644 index 0000000000000000000000000000000000000000..fcc46a8ce67181b954ba0bfcfb0d74b63d0e1cb8 --- /dev/null +++ b/module-cellular/Modem/doc/Images/dma_result_struct.uml @@ -0,0 +1,7 @@ +@startuml +object CellularDMAResultStruct { + CellularResultCode resultCode + size_t dataSize + uint8_t data[CellularResultStructMaxDataSize] +} +@enduml diff --git a/module-cellular/Modem/doc/Images/mux_mode.svg b/module-cellular/Modem/doc/Images/mux_mode.svg new file mode 100644 index 0000000000000000000000000000000000000000..3216cf97edf7d75aeaeb570fe953dba341e549a9 --- /dev/null +++ b/module-cellular/Modem/doc/Images/mux_mode.svg @@ -0,0 +1,106 @@ +CMUX ModeCellularBSPCellularWorkerNotificationsCommandsDataUartIrqReads data with DMA to static bufferand packs them into struct with result code.SendToWorkerPuts structured data into workers MessageBuffer.outputDMAMessageBufferATParserParses URC packages.CmdAwaitingResponseworkerOutputcallbackinput1ATStream1output1input2ATStream2output2input3ATStream3output3ServiceCellularTrueFalseResult{data,resultCode}Result{parsedData,resultCode} \ No newline at end of file diff --git a/module-cellular/Modem/doc/Images/single_cmd.uml b/module-cellular/Modem/doc/Images/single_cmd.uml new file mode 100644 index 0000000000000000000000000000000000000000..976af2c889939ac35e2072ed2b157f474ee01977 --- /dev/null +++ b/module-cellular/Modem/doc/Images/single_cmd.uml @@ -0,0 +1,24 @@ +@startuml +title Single cmd processing + +participant IRQ order 1 +participant BSP order 2 +participant CellularWorker order 3 +participant Channel order 4 +participant Service order 5 + +CellularWorker -> BSP: Read() +rnote over CellularWorker: Wait for data in MessageBuffer +Service -> Channel: cmd() +activate CellularWorker +Channel -> BSP : Write() +rnote over Channel: Wait for cmd response in MessageBuffer +activate Channel +rnote over IRQ: DMA received data +IRQ -> CellularWorker: MessageBufferSendFromISR() +rnote over CellularWorker: processData() +CellularWorker -> Channel: MessageBufferSend() +rnote over Channel: Parse response +deactivate Channel +Channel -> Service: Result +@enduml diff --git a/module-cellular/Modem/doc/Images/single_cmd_transmission.png b/module-cellular/Modem/doc/Images/single_cmd_transmission.png new file mode 100644 index 0000000000000000000000000000000000000000..05d94cb85b8558425901ad86985a229f73a06abf GIT binary patch literal 31302 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

yExY* zT?wyHdTR5NkC24Y`T0%7oJ=R-Dc6dX-f1d6ashXSlg9~K9U4RoR&BIB;y6}8Xu?4w zb<@73HMY-poZ%K~YaP2nbY;}yrhAGS(~Ayt)XsF*ejdS_tei_z@IvnxpdkG_x%GaUd@7=v4q4{4g{oHGobK y*R0XuKuVgrE@-ofYXmC&pfjiOn?JRUFVi;F^riUy0r*Adedo@q%4eZXef|gZr3KOe literal 0 HcmV?d00001 diff --git a/module-cellular/at/Result.hpp b/module-cellular/at/Result.hpp index 74b50442ae73faa36a319c7a4243aea31c8f9077..ff82b0026b3dfa30f7ece37f95166cde0190d19f 100644 --- a/module-cellular/at/Result.hpp +++ b/module-cellular/at/Result.hpp @@ -16,15 +16,20 @@ namespace at /// result class for AT send -> receive command, could return promise :p enum class Code { - OK, /// at OK - ERROR, /// at ERROR For compatibility also for CME_ERROR and CMS_ERROR (details in errorCode) - CME_ERROR, /// In case CME error see errorCode - CMS_ERROR, /// In case CMS error see errorCode - TIMEOUT, /// at Timeout - TOKENS, /// at numbers of tokens needed met - NONE, /// no code - UNDEFINED, /// undefined result - usage of Undefined result, define and pin result to use it - PARSING_ERROR, /// parser error + OK, /// at OK + ERROR, /// at ERROR For compatibility also for CME_ERROR and CMS_ERROR (details in errorCode) + CME_ERROR, /// In case CME error see errorCode + CMS_ERROR, /// In case CMS error see errorCode + TIMEOUT, /// at Timeout + TOKENS, /// at numbers of tokens needed met + NONE, /// no code + UNDEFINED, /// undefined result - usage of Undefined result, define and pin result to use it + PARSING_ERROR, /// parser error + FULL_MSG_BUFFER, /// at not enough space left in message buffer for new message + TRANSMISSION_NOT_STARTED, /// at dma not starting transmission + RECEIVING_NOT_STARTED, /// at dma not starting requested receiving + DATA_NOT_USED, /// at received data not being used + CMUX_FRAME_ERROR, /// at cmux deserialize error } code = Code::UNDEFINED; Result() = default; diff --git a/module-cellular/test/CMakeLists.txt b/module-cellular/test/CMakeLists.txt index c42d76da143fcf4d45c32bb3c135a0c6eca447de..62a8a1f56180fde8fab28b420f99af181a362537 100644 --- a/module-cellular/test/CMakeLists.txt +++ b/module-cellular/test/CMakeLists.txt @@ -46,7 +46,6 @@ add_catch2_executable( module-cellular ) - add_catch2_executable( NAME unittest_ATURCStream @@ -55,3 +54,13 @@ add_catch2_executable( LIBS module-cellular ) + +add_catch2_executable( + NAME + unittest_CellularResult + SRCS + unittest_CellularResult.cpp + LIBS + module-cellular + module-bsp +) diff --git a/module-cellular/test/mock/AtCommon_channel.hpp b/module-cellular/test/mock/AtCommon_channel.hpp index 91cc9dac07ab62f2cb3354e3889bc9d95893cded..95ddcc643c70306b0de80158ac7a0b0ed22885c3 100644 --- a/module-cellular/test/mock/AtCommon_channel.hpp +++ b/module-cellular/test/mock/AtCommon_channel.hpp @@ -41,7 +41,7 @@ namespace at void cmd_post() override {} - std::string cmd_receive() override + size_t cmd_receive(std::uint8_t *buffer, std::chrono::milliseconds timeoutMs) override { return {}; } @@ -49,7 +49,7 @@ namespace at class FailingChannel : public ChannelMock { - virtual Result ResultMock() + auto ResultMock() -> Result override { auto r = Result(); r.code = Result::Code::ERROR; @@ -75,7 +75,7 @@ namespace at /// provides CSCS bad response class CSCS_badChannel : public ChannelMock { - virtual Result ResultMock() + auto ResultMock() -> Result override { auto r = Result(); r.code = Result::Code::ERROR; @@ -87,7 +87,7 @@ namespace at /// standard bad CSCA values I get from modem (with result OK) class CSCA_emptyData : public ChannelMock { - virtual Result ResultMock() + auto ResultMock() -> Result override { auto r = Result(); r.code = Result::Code::OK; diff --git a/module-cellular/test/unittest_CellularResult.cpp b/module-cellular/test/unittest_CellularResult.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8bf685ed69e01eb24e2bc73e471d802f17d3c1c --- /dev/null +++ b/module-cellular/test/unittest_CellularResult.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + +#define CATCH_CONFIG_MAIN + +#include +#include + +TEST_CASE("CellularResult") +{ + SECTION("CellularDMAResultStruct") + { + bsp::cellular::CellularDMAResultStruct cellularDmaResultStruct; + + auto maxSize = bsp::cellular::CellularResultStructMaxDataSize + sizeof(cellularDmaResultStruct.resultCode) + + sizeof(cellularDmaResultStruct.dataSize); + + REQUIRE(cellularDmaResultStruct.getMaxSize() == maxSize); + REQUIRE(cellularDmaResultStruct.resultCode == bsp::cellular::CellularResultCode::ReceivedNoData); + REQUIRE(cellularDmaResultStruct.getEmptySize() == maxSize - bsp::cellular::CellularResultStructMaxDataSize); + REQUIRE(cellularDmaResultStruct.getSize() == cellularDmaResultStruct.getEmptySize()); + } + + SECTION("Create CellularResultStruct") + { + bsp::cellular::CellularResultStruct cellularResultStruct; + + REQUIRE(cellularResultStruct.resultCode == bsp::cellular::CellularResultCode::Uninitialized); + REQUIRE(cellularResultStruct.data.empty()); + } + + SECTION("Serialize empty CellularResultStruct") + { + bsp::cellular::CellularResultStruct cellularResultStruct; + + auto serialized = cellularResultStruct.serialize(); + auto serializedResult = serialized.get()[0]; + serialized.release(); + + REQUIRE(cellularResultStruct.getSerializedSize() == sizeof(cellularResultStruct.resultCode)); + REQUIRE(serializedResult == static_cast(bsp::cellular::CellularResultCode::Uninitialized)); + } + + SECTION("Deserialize empty buffer") + { + bsp::cellular::CellularResultStruct cellularResultStruct; + + uint8_t serialized[0] = {}; + cellularResultStruct.deserialize(serialized, 0); + + REQUIRE(cellularResultStruct.resultCode == bsp::cellular::CellularResultCode::Uninitialized); + REQUIRE(cellularResultStruct.data.empty()); + } + + SECTION("Serialize and deserialize CellularResultStruct") + { + bsp::cellular::CellularResultStruct cellularResultStruct; + cellularResultStruct.resultCode = bsp::cellular::CellularResultCode::ReceivedAndFull; + cellularResultStruct.data = {1, 2, 3, 4, 5, 10}; + + auto serialized = cellularResultStruct.serialize(); + + bsp::cellular::CellularResultStruct cellularResultStructDeserialized; + cellularResultStructDeserialized.deserialize(serialized.get(), cellularResultStruct.getSerializedSize()); + + serialized.release(); + + REQUIRE(cellularResultStructDeserialized.resultCode == cellularResultStruct.resultCode); + REQUIRE(cellularResultStructDeserialized.data == cellularResultStruct.data); + } +} diff --git a/module-cellular/test/unittest_cmux.cpp b/module-cellular/test/unittest_cmux.cpp index 3a4ddf8966b3c652584c1653b12e3b33aec34ab3..1087cae650910e64ef9d78357ce2313a35f19b01 100644 --- a/module-cellular/test/unittest_cmux.cpp +++ b/module-cellular/test/unittest_cmux.cpp @@ -1,3 +1,6 @@ +// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. +// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md + #define CATCH_CONFIG_MAIN #include @@ -24,7 +27,7 @@ TEST_CASE("TS0170 frame") TS0710_Frame frame(tempFrame); REQUIRE(frame.isComplete(frame.getSerData()) == true); - REQUIRE(frame.isMyChannel(frame.getSerData(), DLCI) == true); + REQUIRE(frame.isMyChannel(DLCI) == true); REQUIRE(frame.getFrameDLCI(frame.getSerData()) == DLCI); REQUIRE(frame.getSerData().size() == cmuxMinimumFrameLength + command.length()); } diff --git a/module-services/service-cellular/ServiceCellular.cpp b/module-services/service-cellular/ServiceCellular.cpp index 0a2db75e5a02716a9e32a129367839fc9e890f02..9017646a89213c841d7fca779de7c6bc44e483e9 100644 --- a/module-services/service-cellular/ServiceCellular.cpp +++ b/module-services/service-cellular/ServiceCellular.cpp @@ -246,7 +246,7 @@ static bool isSettingsAutomaticTimeSyncEnabled() void ServiceCellular::SleepTimerHandler() { auto currentTime = cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()); - auto lastCommunicationTimestamp = cmux->GetLastCommunicationTimestamp(); + auto lastCommunicationTimestamp = cmux->getLastCommunicationTimestamp(); auto timeOfInactivity = currentTime >= lastCommunicationTimestamp ? currentTime - lastCommunicationTimestamp : std::numeric_limits::max() - lastCommunicationTimestamp + currentTime; @@ -347,8 +347,8 @@ void ServiceCellular::registerMessageHandlers() connect(typeid(CellularChangeSimDataMessage), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); Store::GSM::get()->selected = msg->getSim(); - bsp::cellular::sim::sim_sel(); - bsp::cellular::sim::hotswap_trigger(); + bsp::cellular::sim::simSelect(); + bsp::cellular::sim::hotSwapTrigger(); return std::make_shared(true); }); @@ -445,7 +445,7 @@ void ServiceCellular::registerMessageHandlers() } if (typeid(*msg->event.get()) == typeid(sdesktop::developerMode::CellularSleepModeInfoRequestEvent)) { auto event = std::make_unique( - cmux->IsCellularInSleepMode()); + cmux->isCellularInSleepMode()); auto message = std::make_shared(std::move(event)); bus.sendUnicast(std::move(message), service::name::service_desktop); } @@ -891,7 +891,7 @@ bool ServiceCellular::handle_audio_conf_procedure() state.set(this, State::ST::Failed); return false; } - cmux->getCellular()->SetSpeed(ATPortSpeeds_text[cmux->getStartParams().PortSpeed]); + cmux->getCellular()->setSpeed(ATPortSpeeds_text[cmux->getStartParams().PortSpeed]); vTaskDelay(1000); if (cmux->StartMultiplexer() == TS0710::ConfState::Success) { @@ -1242,8 +1242,7 @@ auto ServiceCellular::sendSMS(SMSRecord record) -> bool std::string body = UCS2(UTF8(receiver)).str(); std::string suffix = "\""; std::string command_data = command + body + suffix; - if (cmux->CheckATCommandPrompt( - channel->SendCommandPrompt(command_data.c_str(), 1, commandTimeout.count()))) { + if (cmux->CheckATCommandPrompt(channel->SendCommandPrompt(command_data.c_str(), 1, commandTimeout))) { if (channel->cmd((UCS2(record.body).str() + "\032").c_str(), commandTimeout)) { result = true; @@ -1281,8 +1280,7 @@ auto ServiceCellular::sendSMS(SMSRecord record) -> bool std::string command(at::factory(at::AT::QCMGS) + UCS2(UTF8(receiver)).str() + "\",120," + std::to_string(i + 1) + "," + std::to_string(messagePartsCount)); - if (cmux->CheckATCommandPrompt( - channel->SendCommandPrompt(command.c_str(), 1, commandTimeout.count()))) { + if (cmux->CheckATCommandPrompt(channel->SendCommandPrompt(command.c_str(), 1, commandTimeout))) { // prompt sign received, send data ended by "Ctrl+Z" if (channel->cmd(UCS2(messagePart).str() + "\032", commandTimeout, 2)) { result = true; @@ -1632,7 +1630,7 @@ bool ServiceCellular::handle_sim_sanity_check() auto ret = sim_check_hot_swap(cmux->get(TS0710::Channel::Commands)); if (ret) { state.set(this, State::ST::ModemOn); - bsp::cellular::sim::sim_sel(); + bsp::cellular::sim::simSelect(); } else { LOG_ERROR("Sanity check failure - user will be promped about full shutdown"); @@ -1644,8 +1642,8 @@ bool ServiceCellular::handle_sim_sanity_check() bool ServiceCellular::handle_select_sim() { - bsp::cellular::sim::sim_sel(); - bsp::cellular::sim::hotswap_trigger(); + bsp::cellular::sim::simSelect(); + bsp::cellular::sim::hotSwapTrigger(); #if defined(TARGET_Linux) DLC_channel *channel = cmux->get(TS0710::Channel::Commands); auto ret = channel->cmd(at::AT::QSIMSTAT); diff --git a/module-services/service-evtmgr/WorkerEvent.cpp b/module-services/service-evtmgr/WorkerEvent.cpp index c21fa28c5f66012046d3c5d93e94f1cb02d3ac49..b78b5e5c51e111f0b2fc3f063e44ac8b31ad8a1b 100644 --- a/module-services/service-evtmgr/WorkerEvent.cpp +++ b/module-services/service-evtmgr/WorkerEvent.cpp @@ -163,7 +163,7 @@ bool WorkerEvent::handleMessage(uint32_t queueID) if (notification == bsp::cellular::trayPin) { Store::GSM::Tray pinstate = bsp::cellular::sim::getTray(); LOG_DEBUG("SIM state change: %d", static_cast(pinstate)); - bsp::cellular::sim::hotswap_trigger(); + bsp::cellular::sim::hotSwapTrigger(); } if (notification == bsp::cellular::ringIndicatorPin) {