/* * @file MuxDaemon.cpp * @author Mateusz Piesta (mateusz.piesta@mudita.com) * @date 19.06.19 * @brief * @copyright Copyright (C) 2019 mudita.com * @details */ #include "MuxDaemon.hpp" #include #include "log/log.hpp" #include "FreeRTOS.h" #include "task.h" constexpr unsigned char MuxDaemon::closeChannelCmd[]; MuxDaemon::MuxDaemon() { cellular = bsp::Cellular::Create(); inSerialDataWorker = std::make_unique(this); inputBuffer = std::make_unique(virtualPortsCount,frameSize,cmuxMode); } MuxDaemon::~MuxDaemon() { } int MuxDaemon::Start() { // At first send AT command to check if modem is turned on if (SendAT("AT\r\n", 500) == -1) { LOG_INFO("Modem does not respond to AT commands, trying close mux mode"); //We don't know in what cmux_mode modem currently is so send both types of closing signals WriteMuxFrame(0, NULL, 0, static_cast(MuxDefines ::GSM0710_CONTROL_CLD) | static_cast(MuxDefines ::GSM0710_CR)); WriteMuxFrame(0, closeChannelCmd, sizeof(closeChannelCmd), static_cast(MuxDefines ::GSM0710_TYPE_UIH)); vTaskDelay(500); // give modem some time to establish mux // Try sending AT command once again if (SendAT("AT\r\n", 500) == -1){ LOG_INFO("Starting power up procedure..."); // If no response, power up modem and try again cellular->PowerUp(); uint32_t retries = 10; while (--retries) { if (SendAT("AT\r\n", 500) == 0) { break; } } if (retries == 0) { LOG_FATAL("No communication with GSM modem"); return -1; } } } // Set up modem configuration if(hardwareControlFlowEnable){ SendAT("AT+IFC=2,2\r\n", 500); // enable flow control function for module } else{ SendAT("AT+IFC=0,0\r\n", 500); // disable flow control function for module } // Set fixed baudrate SendAT(("AT+IPR=" + std::to_string(baudRate) + "\r").c_str(), 500); // Turn off local echo SendAT("ATE0\r", 500); // Route URCs to first MUX channel SendAT("AT+QCFG=\"cmux/urcport\",1\r\n", 500); // Turn off RI pin for incoming calls SendAT("AT+QCFG=\"urc/ri/ring\",\"off\"\r\n", 500); // Turn off RI pin for incoming sms SendAT("AT+QCFG=\"urc/ri/smsincoming\",\"off\"\r\n", 500); // Route URCs to UART1 SendAT("AT+QURCCFG=\"urcport\",\"uart1\"\r\n", 500); char gsm_command[128] = {}; if (cmuxMode) { snprintf(gsm_command, sizeof(gsm_command), "AT+CMUX=1\r\n"); } else { snprintf(gsm_command, sizeof(gsm_command), "AT+CMUX=%d,%d,%d,%d\r\n", cmuxMode, inputBuffer->cmux_subset, quectel_speeds[inputBuffer->cmux_port_speed],frameSize ); } // Start CMUX multiplexer SendAT(gsm_command, 500); state = States::MUX_STATE_MUXING; // Spawn input serial stream worker inSerialDataWorker->Init(); // Create virtual channels channels.push_back(MuxChannel(this,0,"ControlChannel")); channels.push_back(MuxChannel(this,1,"NotificationChannel")); for (uint32_t i = 2; i < virtualPortsCount; ++i) { channels.push_back(MuxChannel(this,i,("GenericChannel_" + std::to_string(i)).c_str())); } // Mux channels must be initialized in sequence for(auto &w : channels){ w.Open(); vTaskDelay(200); } } int MuxDaemon::Exit() { inSerialDataWorker->Deinit(); CloseMux(); } int MuxDaemon::SendAT(const char *cmd, uint32_t timeout) { char buff[256] = {0}; auto bytesWritten = cellular->Write(const_cast(cmd), strlen(cmd)); LOG_DEBUG("BytesWritten: %d",bytesWritten); if (cellular->Wait(500)) { vTaskDelay(50); auto bytesRead = cellular->Read(buff, sizeof buff); if (memstr((char *) buff, bytesRead, "OK")) { LOG_DEBUG("Received OK"); return 0; } else if (memstr((char *) buff, bytesRead, "ERROR")) { LOG_DEBUG("Received ERROR"); return -1; } else { LOG_DEBUG("Received unknown response"); return -1; } } else { return -1; } } ssize_t MuxDaemon::WriteMuxFrame(int channel, const unsigned char *input, int length, unsigned char type) { /* flag, GSM0710_EA=1 C channel, frame type, length 1-2 */ unsigned char prefix[5] = {static_cast(MuxDefines::GSM0710_FRAME_FLAG), static_cast(MuxDefines::GSM0710_EA) | static_cast(MuxDefines::GSM0710_CR), 0, 0, 0}; unsigned char postfix[2] = {0xFF, static_cast(MuxDefines::GSM0710_FRAME_FLAG )}; ssize_t prefix_length = 4; int c; unsigned char tmp[inputBuffer->cmux_FRAME]; LOG_DEBUG("Sending frame to channel %d", channel); /* GSM0710_EA=1, Command, let's add address */ prefix[1] = prefix[1] | ((63 & (unsigned char) channel) << 2); /* let's set control field */ prefix[2] = type; if ((type == static_cast(MuxDefines::GSM0710_TYPE_UIH) || type == static_cast(MuxDefines::GSM0710_TYPE_UI)) && uih_pf_bit_received == 1 && inputBuffer->GSM0710_COMMAND_IS(MuxDefines::GSM0710_CONTROL_MSC, input[0])) { prefix[2] = prefix[2] | static_cast(MuxDefines::GSM0710_PF); //Set the P/F bit in Response if Command from modem had it set uih_pf_bit_received = 0; //Reset the variable, so it is ready for next command } /* let's not use too big frames */ length = std::min(inputBuffer->cmux_N1, static_cast(length)); if (!inputBuffer->cmux_mode)//basic { /* Modified acording PATCH CRC checksum */ /* postfix[0] = frame_calc_crc (prefix + 1, prefix_length - 1); */ /* length */ if (length > 127) { prefix_length = 5; prefix[3] = (0x007F & length) << 1; prefix[4] = (0x7F80 & length) >> 7; } else { prefix[3] = 1 | (length << 1); } postfix[0] = GSM0710Buffer::frameCalcCRC(prefix + 1, prefix_length - 1); memcpy(tmp, prefix, prefix_length); if (length > 0) { memcpy(tmp + prefix_length, input, length); } memcpy(tmp + prefix_length + length, postfix, 2); c = prefix_length + length + 2; //Write newly created frame into serial output buffer WriteSerialCache(tmp, c); } return length; } ssize_t MuxDaemon::WriteSerialCache(unsigned char *input, size_t length) { //TODO: M.P implement actual caching cpp_freertos::LockGuard lock(serOutMutex); cellular->Write(input, length); } int MuxDaemon::CloseMux() { for (auto &w : channels) { if (w.GetState() == MuxChannel::State::Opened) { LOG_INFO("Logical channel %d closed", w.logicalNumber); w.Close(); } } channels.clear(); } int MuxDaemon::memstr(const char *haystack, int length, const char *needle) { int i; int j = 0; if (needle[0] == '\0') return 1; for (i = 0; i < length; i++) if (needle[j] == haystack[i]) { j++; if (needle[j] == '\0') // Entire needle was found return 1; } else j = 0; return 0; }