/* * @file ServiceCellular.cpp * @author Mateusz Piesta (mateusz.piesta@mudita.com) * @date 03.07.19 * @brief * @copyright Copyright (C) 2019 mudita.com * @details */ #include #include #include #include #include #include #include #include #include #include #include "Service/Message.hpp" #include "Service/Service.hpp" #include "ServiceCellular.hpp" #include "Service/Service.hpp" #include "Service/Message.hpp" #include "MessageType.hpp" #include "messages/CellularMessage.hpp" #include #include "log/log.hpp" #include "service-appmgr/ApplicationManager.hpp" #include "service-appmgr/messages/APMMessage.hpp" #include "service-cellular/SignalStrength.hpp" #include "service-evtmgr/messages/EVMessages.hpp" #include "ucs2/UCS2.hpp" #include "service-db/api/DBServiceAPI.hpp" #include "service-db/messages/DBNotificationMessage.hpp" #include "time/time_conversion.hpp" #include #include #include const char *ServiceCellular::serviceName = "ServiceCellular"; inline const auto cellularStack = 24000UL; using namespace cellular; const char *State::c_str(State::ST state) { switch (state) { case ST::Idle: return "Idle"; case ST::PowerUpProcedure: return "PowerUpProcedure"; case ST::AudioConfigurationProcedure: return "AudioConfigurationProcedure"; case ST::ModemOn: return "ModemOn"; case ST::SimSelect: return "SimSelect"; case ST::Failed: return "Failed"; case ST::SanityCheck: return "SanityCheck"; case ST::SimInit: return "SimInit"; case ST::ModemFatalFailure: return "ModemFatalFailure"; case ST::CellularConfProcedure: return "CellularStartConfProcedure"; } return ""; } void State::set(ServiceCellular *owner, ST state) { assert(owner); LOG_DEBUG("GSM state: (%s) -> (%s)", c_str(this->state), c_str(state)); this->state = state; auto msg = std::make_shared(state); sys::Bus::SendMulticast(msg, sys::BusChannels::ServiceCellularNotifications, owner); } State::ST State::get() const { return this->state; } ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack, sys::ServicePriority::Idle) { LOG_INFO("[ServiceCellular] Initializing"); busChannels.push_back(sys::BusChannels::ServiceCellularNotifications); busChannels.push_back(sys::BusChannels::ServiceDBNotifications); callStateTimerId = CreateTimer(Ticks::MsToTicks(1000), true); ongoingCall.setStartCallAction([=](const CalllogRecord &rec) { auto call = DBServiceAPI::CalllogAdd(this, rec); if (call.ID == DB_ID_NONE) { LOG_ERROR("CalllogAdd failed"); } return call; }); ongoingCall.setEndCallAction([=](const CalllogRecord &rec) { return DBServiceAPI::CalllogUpdate(this, rec); }); notificationCallback = [this](std::vector &data) { LOG_DEBUG("Notifications callback called with %u data bytes", static_cast(data.size())); TS0710_Frame frame(data); std::string message; auto msg = identifyNotification(frame.getFrame().data); if (msg->type == CellularNotificationMessage::Type::None) { LOG_INFO("Skipped uknown notification"); return; } sys::Bus::SendMulticast(msg, sys::BusChannels::ServiceCellularNotifications, this); }; } ServiceCellular::~ServiceCellular() { LOG_INFO("[ServiceCellular] Cleaning resources"); if (cmux != nullptr) { delete cmux; } } void ServiceCellular::CallStateTimerHandler() { std::shared_ptr msg = std::make_shared(MessageType::CellularListCurrentCalls); sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, this); } // Invoked when timer ticked void ServiceCellular::TickHandler(uint32_t id) { if (id == callStateTimerId) { CallStateTimerHandler(); } else { LOG_ERROR("Unrecognized timer ID = %" PRIu32, id); } } sys::ReturnCodes ServiceCellular::InitHandler() { state.set(this, State::ST::PowerUpProcedure); return sys::ReturnCodes::Success; } sys::ReturnCodes ServiceCellular::DeinitHandler() { return sys::ReturnCodes::Success; } sys::ReturnCodes ServiceCellular::SwitchPowerModeHandler(const sys::ServicePowerMode mode) { LOG_FATAL("[ServiceCellular] PowerModeHandler: %s", c_str(mode)); switch (mode) { case sys::ServicePowerMode ::Active: // muxdaemon->ExitSleepMode(); break; case sys::ServicePowerMode ::SuspendToRAM: case sys::ServicePowerMode ::SuspendToNVM: LOG_FATAL("TEMPORARY DISABLED!!!!! UNCOMMENT WHEN READY."); // muxdaemon->EnterSleepMode(); break; } return sys::ReturnCodes::Success; } void ServiceCellular::change_state(cellular::StateChange *msg) { assert(msg); switch(msg->request) { case State::ST::Idle: handle_idle(); break; case State::ST::PowerUpProcedure: handle_power_up_procedure(); break; case State::ST::AudioConfigurationProcedure: handle_audio_conf_procedure(); break; case State::ST::CellularConfProcedure: handle_start_conf_procedure(); break; case State::ST::SanityCheck: handle_sim_sanity_check(); break; case State::ST::SimInit: handle_sim_init(); break; case State::ST::SimSelect: handle_select_sim(); break; case State::ST::ModemOn: handle_modem_on(); break; case State::ST::ModemFatalFailure: handle_fatal_failure(); break; case State::ST::Failed: handle_failure(); break; }; } bool ServiceCellular::handle_idle() { LOG_DEBUG("Idle"); return true; } bool ServiceCellular::handle_power_up_procedure() { auto ret = cmux->PowerUpProcedure(); if (ret == TS0710::ConfState::Success) { state.set(this, State::ST::CellularConfProcedure); return true; } else if (ret == TS0710::ConfState::PowerUp) { state.set(this, State::ST::Idle); return true; } state.set(this, State::ST::Failed); return false; } bool ServiceCellular::handle_start_conf_procedure() { // Start configuration procedure, if it's first run modem will be restarted auto confRet = cmux->ConfProcedure(); if (confRet == TS0710::ConfState::Success) { state.set(this, State::ST::AudioConfigurationProcedure); return true; } state.set(this, State::ST::Failed); return false; } bool ServiceCellular::handle_audio_conf_procedure() { auto audioRet = cmux->AudioConfProcedure(); if (audioRet == TS0710::ConfState::Success) { auto cmd = at::factory(at::AT::IPR) + std::to_string(ATPortSpeeds_text[cmux->getStartParams().PortSpeed]) + "\r"; LOG_DEBUG("Setting baudrate %i baud", ATPortSpeeds_text[cmux->getStartParams().PortSpeed]); if (!cmux->getParser()->cmd(cmd)) { LOG_ERROR("Baudrate setup error"); state.set(this, State::ST::Failed); return false; } cmux->getCellular()->SetSpeed(ATPortSpeeds_text[cmux->getStartParams().PortSpeed]); vTaskDelay(1000); if (cmux->StartMultiplexer() == TS0710::ConfState::Success) { LOG_DEBUG("[ServiceCellular] Modem is fully operational"); // open channel - notifications DLC_channel *notificationsChannel = cmux->get(TS0710::Channel::Notifications); if (notificationsChannel != nullptr) { LOG_DEBUG("Setting up notifications callback"); notificationsChannel->setCallback(notificationCallback); } state.set(this, State::ST::SanityCheck); return true; } else { state.set(this, State::ST::Failed); return false; } } else if (audioRet == TS0710::ConfState::Failure) { /// restart state.set(this, State::ST::AudioConfigurationProcedure); return true; } // Reset procedure started, do nothing here state.set(this, State::ST::Idle); return true; } sys::Message_t ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) { std::shared_ptr responseMsg; switch (static_cast(msgl->messageType)) { case MessageType::CellularStateRequest: { change_state(dynamic_cast(msgl)); responseMsg = std::make_shared(true); } break; // Incoming notifications from Notification Virtual Channel case MessageType::CellularNotification: { CellularNotificationMessage *msg = dynamic_cast(msgl); if (msg != nullptr) { switch (msg->type) { case CellularNotificationMessage::Type::CallActive: { auto ret = ongoingCall.setActive(); responseMsg = std::make_shared(ret); break; } case CellularNotificationMessage::Type::IncomingCall: { auto ret = true; if (!ongoingCall.isValid()) { // CellularNotificationMessage::Type::IncomingCall is called periodically during not answered // incomming call create ongoing call only once ret = ongoingCall.startCall(msg->data, CallType::CT_INCOMING); } responseMsg = std::make_shared(ret); break; } case CellularNotificationMessage::Type::CallAborted: { stopTimer(callStateTimerId); auto ret = ongoingCall.endCall(); responseMsg = std::make_shared(ret); break; } case CellularNotificationMessage::Type::Ringing: { auto ret = ongoingCall.startCall(msg->data, CallType::CT_OUTGOING); responseMsg = std::make_shared(ret); break; } case CellularNotificationMessage::Type::PowerUpProcedureComplete: { state.set(this, State::ST::CellularConfProcedure); break; } case CellularNotificationMessage::Type::NewIncomingSMS: { LOG_INFO("New incoming sms received"); receiveSMS(msg->data); break; } case CellularNotificationMessage::Type::RawCommand: { auto m = dynamic_cast(msgl); auto channel = cmux->get(TS0710::Channel::Commands); if (!m || !channel) { LOG_ERROR("RawCommand error: %s %s", m == nullptr ? "" : "bad cmd", !channel ? "no channel" : ""); break; } auto respMsg = std::make_shared(true); auto ret = channel->cmd(m->command.c_str(), m->timeout); respMsg->response = ret.response; if (respMsg->response.size()) { for (auto const &el : respMsg->response) { LOG_DEBUG("> %s", el.c_str()); } } responseMsg = respMsg; break; } break; case CellularNotificationMessage::Type::SIM: if (Store::GSM::get()->tray == Store::GSM::Tray::IN) { state.set(this, cellular::State::ST::SimInit); } break; default: { LOG_INFO("Skipped CellularNotificationMessage::Type %d", static_cast(msg->type)); } } } } break; case MessageType::CellularSimProcedure: { state.set(this, State::ST::SimSelect); } break; case MessageType::CellularListCurrentCalls: { constexpr size_t numberOfExpectedTokens = 3; auto ret = cmux->get(TS0710::Channel::Commands)->cmd(at::AT::CLCC); if (ret && ret.response.size() == numberOfExpectedTokens) { // TODO: alek: add case when more status calls is returned // TODO: alek: add cellular call validation and check it with modemcall // TODO: alek - just handle parts of response properly // if CellularListCurrentCalls is recieved after the call is aborted it will return 2 tokens instead of 3 // this should be acceptable and hence warning instead of error is logged in such case bool retVal = true; auto callEntry = ret.response[1]; try { ModemCall::ModemCall call(callEntry); LOG_DEBUG("%s", utils::to_string(call).c_str()); // If call changed to "Active" state stop callStateTimer(used for polling for call state) if (call.state == ModemCall::CallState::Active) { auto msg = std::make_shared(CellularNotificationMessage::Type::CallActive); sys::Bus::SendMulticast(msg, sys::BusChannels::ServiceCellularNotifications, this); stopTimer(callStateTimerId); } } catch (const std::exception &e) { LOG_ERROR("exception \"%s\" was thrown", e.what()); assert(0); retVal = false; } responseMsg = std::make_shared(retVal); } else { responseMsg = std::make_shared(false); } } break; case MessageType::CellularHangupCall: { auto channel = cmux->get(TS0710::Channel::Commands); LOG_INFO("CellularHangupCall"); if (channel) { if (channel->cmd(at::AT::ATH)) { stopTimer(callStateTimerId); if (!ongoingCall.endCall(CellularCall::Forced::True)) { LOG_ERROR("Failed to end ongoing call"); } responseMsg = std::make_shared(true, msgl->messageType); } else { LOG_ERROR("Call not aborted"); responseMsg = std::make_shared(false, msgl->messageType); } break; } responseMsg = std::make_shared(false, msgl->messageType); } break; case MessageType::CellularAnswerIncomingCall: { auto channel = cmux->get(TS0710::Channel::Commands); auto ret = false; if (channel) { // TODO alek: check if your request isn't for 5 sec when you wait in command for 90000, it's exclusivelly // set to 5000ms in command requesting... auto response = channel->cmd(at::AT::ATA); if (response) { // Propagate "CallActive" notification into system sys::Bus::SendMulticast( std::make_shared(CellularNotificationMessage::Type::CallActive), sys::BusChannels::ServiceCellularNotifications, this); ret = true; } } responseMsg = std::make_shared(ret); } break; case MessageType::CellularDialNumber: { CellularRequestMessage *msg = reinterpret_cast(msgl); auto channel = cmux->get(TS0710::Channel::Commands); if (channel) { auto ret = channel->cmd(at::factory(at::AT::ATD) + msg->data + ";\r"); if (ret) { responseMsg = std::make_shared(true); // activate call state timer ReloadTimer(callStateTimerId); // Propagate "Ringing" notification into system sys::Bus::SendMulticast(std::make_shared( CellularNotificationMessage::Type::Ringing, msg->data), sys::BusChannels::ServiceCellularNotifications, this); break; } } responseMsg = std::make_shared(false); } break; case MessageType::DBServiceNotification: { DBNotificationMessage *msg = dynamic_cast(msgl); if (msg == nullptr) { responseMsg = std::make_shared(false); break; } LOG_DEBUG("Received multicast"); if ((msg->baseType == DB::BaseType::SmsDB) && ((msg->notificationType == DB::NotificationType::Updated) || (msg->notificationType == DB::NotificationType::Added))) { sendSMS(); return std::make_shared(); } return std::make_shared(sys::ReturnCodes::Failure); break; } case MessageType::CellularGetIMSI: { std::string temp; if (getIMSI(temp)) { responseMsg = std::make_shared(true, temp); } else { responseMsg = std::make_shared(false); } break; } case MessageType::CellularGetOwnNumber: { std::string temp; if (getOwnNumber(temp)) { responseMsg = std::make_shared(true, temp); } else { responseMsg = std::make_shared(false); } } break; case MessageType::CellularGetNetworkInfo: { LOG_INFO("CellularGetNetworkInfo handled"); responseMsg = std::make_shared(true); auto msg = std::make_shared(MessageType::CellularNetworkInfoResult); msg->data = getNetworkInfo(); sys::Bus::SendUnicast(msg, msgl->sender, this); } break; case MessageType::CellularStartOperatorsScan: { LOG_INFO("CellularStartOperatorsScan handled"); cellular::RawCommandResp response = (true); auto msg = std::make_shared(MessageType::CellularOperatorsScanResult); msg->data = scanOperators(); sys::Bus::SendUnicast(msg, msgl->sender, this); } break; case MessageType::CellularSelectAntenna: { uint8_t value = 0; auto msg = dynamic_cast(msgl); if (msg != nullptr) { try { value = std::stoi(msg->data); } catch (std::exception &e) { LOG_INFO("Service cellular CellularSelectAntenna exception occured: %s", e.what()); } cmux->SelectAntenna(value); vTaskDelay(50); // sleep for 50 ms... uint8_t actualAntenna = cmux->GetAntenna(); bool changedAntenna = (actualAntenna == value); responseMsg = std::make_shared(changedAntenna); } else { responseMsg = std::make_shared(false); } } break; default: break; } if (responseMsg == nullptr) { LOG_DEBUG("message not handled: %d, %d", static_cast(msgl->type), static_cast(msgl->messageType)); responseMsg = std::make_shared(false); } return responseMsg; } namespace { bool isAbortCallNotification(const std::string &str) { return ((str.find(at::Chanel::NO_CARRIER) != std::string::npos) || (str.find(at::Chanel::BUSY) != std::string::npos) || (str.find(at::Chanel::NO_ANSWER) != std::string::npos)); } } // namespace std::shared_ptr ServiceCellular::identifyNotification(const std::vector &data) { std::string str(data.begin(), data.end()); { std::string logStr = utils::removeNewLines(str); LOG_DEBUG("Notification:: %s", logStr.c_str()); } if (auto ret = str.find("+CPIN: ") != std::string::npos) { /// TODO handle different sim statuses - i.e. no sim, sim error, sim puk, sim pin etc. if (str.find("NOT READY", ret) == std::string::npos) { if (Store::GSM::get()->selected == Store::GSM::SIM::SIM1) { Store::GSM::get()->sim = Store::GSM::SIM::SIM1; } else { Store::GSM::get()->sim = Store::GSM::SIM::SIM2; } LOG_DEBUG("SIM OK!"); } else { LOG_ERROR("SIM ERROR"); Store::GSM::get()->sim = Store::GSM::SIM::SIM_FAIL; } auto message = std::make_shared(); sys::Bus::SendUnicast(message, "EventManager", this); return std::make_shared(CellularNotificationMessage::Type::SIM); } // Incoming call if (auto ret = str.find("+CLIP: ") != std::string::npos) { LOG_TRACE("incoming call..."); auto beg = str.find("\"", ret); auto end = str.find("\"", ret + beg + 1); auto message = str.substr(beg + 1, end - beg - 1); return std::make_shared(CellularNotificationMessage::Type::IncomingCall, message); } // Call aborted/failed if (isAbortCallNotification(str)) { LOG_TRACE("call aborted"); return std::make_shared(CellularNotificationMessage::Type::CallAborted); } // Received new SMS if (str.find("+CMTI: ") != std::string::npos) { LOG_TRACE("received new SMS notification"); // find message number auto tokens = utils::split(str, ','); if (tokens.size() == 2) { return std::make_shared(CellularNotificationMessage::Type::NewIncomingSMS, tokens[1]); } } // Received signal strength change auto qind = at::urc::QIND(str); if (qind.is() && qind.is_csq()) { if (!qind.validate(at::urc::QIND::RSSI)) { LOG_INFO("Invalid csq - ignore"); } else { SignalStrength signalStrength(std::stoi(qind.tokens[at::urc::QIND::RSSI])); if (signalStrength.isValid()) { Store::GSM::get()->setSignalStrength(signalStrength.data); return std::make_shared( CellularNotificationMessage::Type::SignalStrengthUpdate); } } } LOG_WARN("Unhandled notification"); return std::make_shared(CellularNotificationMessage::Type::None); } bool ServiceCellular::sendSMS(void) { auto record = DBServiceAPI::SMSGetLastRecord(this); // skip if it's not sms to send if (record.type != SMSType::QUEUED) { return false; } LOG_INFO("Trying to send SMS"); uint32_t textLen = record.body.length(); const uint32_t singleMessageLen = 30; bool result = false; // if text fit in single message send if (textLen < singleMessageLen) { if (cmux->CheckATCommandPrompt( cmux->get(TS0710::Channel::Commands) ->SendCommandPrompt( (std::string(at::factory(at::AT::CMGS)) + UCS2(record.number).modemStr() + "\"\r").c_str(), 1, 1000))) { if (cmux->get(TS0710::Channel::Commands)->cmd((UCS2(record.body).modemStr() + "\032").c_str())) { result = true; } else { LOG_INFO("SMS sending failed."); } } } // split text, and send concatenated messages else { const uint32_t maxConcatenatedCount = 7; uint32_t messagePartsCount = textLen / singleMessageLen; if ((textLen % singleMessageLen) != 0) { messagePartsCount++; } if (messagePartsCount > maxConcatenatedCount) { LOG_ERROR("Message to long"); return false; } for (uint32_t i = 0; i < messagePartsCount; i++) { uint32_t partLength = singleMessageLen; if (i * singleMessageLen + singleMessageLen > record.body.length()) { partLength = record.body.length() - i * singleMessageLen; } UTF8 messagePart = record.body.substr(i * singleMessageLen, partLength); std::string command(at::factory(at::AT::QCMGS) + UCS2(record.number).modemStr() + "\",120," + std::to_string(i + 1) + "," + std::to_string(messagePartsCount) + "\r"); if (cmux->CheckATCommandPrompt( cmux->get(TS0710::Channel::Commands)->SendCommandPrompt(command.c_str(), 1, 5000))) { // prompt sign received, send data ended by "Ctrl+Z" if (cmux->get(TS0710::Channel::Commands) ->cmd((UCS2(messagePart).modemStr() + "\032").c_str(), 2, 2000)) { result = true; } else { result = false; LOG_INFO("SMS sending failed."); } } } } if (result) { LOG_INFO("SMS sended."); record.type = SMSType::OUTBOX; DBServiceAPI::SMSUpdate(this, record); } return result; } bool ServiceCellular::receiveSMS(std::string messageNumber) { auto cmd = at::factory(at::AT::QCMGR); auto ret = cmux->get(TS0710::Channel::Commands)->cmd(cmd + messageNumber + "\r", cmd.timeout); bool messageParsed = false; std::string messageRawBody; UTF8 receivedNumber; if (ret) { for (uint32_t i = 0; i < ret.response.size(); i++) { if (ret.response[i].find("QCMGR") != std::string::npos) { std::istringstream ss(ret.response[i]); std::string token; std::vector tokens; while (std::getline(ss, token, ',')) { tokens.push_back(token); } tokens[1].erase(std::remove(tokens[1].begin(), tokens[1].end(), '\"'), tokens[1].end()); /* * tokens: * [0] - +QCMGR * [1] - sender number * [2] - none * [3] - date YY/MM/DD * [4] - hour HH/MM/SS/timezone * concatenaded messages * [5] - unique concatenated message id * [6] - current message number * [7] - total messages count * */ // parse sender number receivedNumber = UCS2(tokens[1]).toUTF8(); // parse date tokens[3].erase(std::remove(tokens[3].begin(), tokens[3].end(), '\"'), tokens[3].end()); utils::time::Timestamp time; time.set_time(tokens[3] + " " + tokens[4], "%y/%m/%d %H:%M:%S"); auto messageDate = time.getTime(); // if its single message process if (tokens.size() == 5) { messageRawBody = ret.response[i + 1]; messageParsed = true; } // if its concatenated message wait for last message else if (tokens.size() == 8) { uint32_t last = 0; uint32_t current = 0; try { last = std::stoi(tokens[7]); current = std::stoi(tokens[6]); } catch (const std::exception &e) { LOG_ERROR("ServiceCellular::receiveSMS error %s", e.what()); return false; } if (current == last) { messageParts.push_back(ret.response[i + 1]); for (uint32_t j = 0; j < messageParts.size(); j++) { messageRawBody += messageParts[j]; } messageParts.clear(); messageParsed = true; } else { messageParts.push_back(ret.response[i + 1]); } } if (messageParsed) { messageParsed = false; UTF8 decodedMessage = UCS2(messageRawBody).toUTF8(); SMSRecord record; record.body = decodedMessage; record.number = receivedNumber; record.type = SMSType::INBOX; record.date = messageDate; DBServiceAPI::SMSAdd(this, record); } } } } // delete message from modem memory cmux->get(TS0710::Channel::Commands)->cmd(at::factory(at::AT::CMGD) + messageNumber); return true; } bool ServiceCellular::getOwnNumber(std::string &destination) { auto ret = cmux->get(TS0710::Channel::Commands)->cmd(at::AT::CNUM); if (ret) { auto begin = ret.response[0].find(','); auto end = ret.response[0].rfind(','); if (begin != std::string::npos && end != std::string::npos) { std::string number; try { number = ret.response[0].substr(begin, end - begin); } catch (std::exception &e) { LOG_ERROR("ServiceCellular::getOwnNumber exception: %s", e.what()); return false; } number.erase(std::remove(number.begin(), number.end(), '"'), number.end()); number.erase(std::remove(number.begin(), number.end(), ','), number.end()); destination = number; return true; } } LOG_ERROR("ServiceCellular::getOwnNumber failed."); return false; } bool ServiceCellular::getIMSI(std::string &destination, bool fullNumber) { auto ret = cmux->get(TS0710::Channel::Commands)->cmd(at::AT::CIMI); if (ret) { if (fullNumber) { destination = ret.response[0]; } else { try { destination = ret.response[0].substr(0, 3); } catch (std::exception &e) { LOG_ERROR("ServiceCellular::getIMSI exception: %s", e.what()); return false; } } return true; } LOG_ERROR("ServiceCellular::getIMSI failed."); return false; } std::vector ServiceCellular::getNetworkInfo(void) { std::vector data; auto channel = cmux->get(TS0710::Channel::Commands); if (channel) { auto resp = channel->cmd(at::AT::CSQ); if (resp.code == at::Result::Code::OK) { // push back to response message std::string ret = resp.response[0]; std::string toErase = "+CSQ: "; auto pos = ret.find(toErase); if (pos != std::string::npos) { ret.erase(pos, toErase.length()); data.push_back(ret); } else { data.push_back(""); } } else { data.push_back(""); } resp = channel->cmd(at::AT::CREG); if (resp.code == at::Result::Code::OK) { std::map cregCodes; cregCodes.insert(std::pair(0, "Not registred")); cregCodes.insert(std::pair(1, "Registered, home network")); cregCodes.insert(std::pair(2, "Not registered, searching")); cregCodes.insert(std::pair(3, "Registration denied")); cregCodes.insert(std::pair(4, "Unknown")); cregCodes.insert(std::pair(5, "Registerd, roaming")); std::string rawResponse = resp.response[0]; auto pos = rawResponse.find(','); uint32_t cregValue; try { cregValue = std::stoi(rawResponse.substr(pos + 1, 1)); } catch (const std::exception &e) { LOG_ERROR("ServiceCellular::GetNetworkInfo exception %s", e.what()); cregValue = UINT32_MAX; } // push back to response message auto commandCode = cregCodes.find(cregValue); if (commandCode != cregCodes.end()) { data.push_back(commandCode->second); } else { data.push_back(""); } } else { data.push_back(""); } resp = channel->cmd(at::AT::QNWINFO); if (resp.code == at::Result::Code::OK) { auto rawResponse = resp.response[0]; std::string toErase("+QNWINFO: "); auto pos = rawResponse.find(toErase); if (pos != std::string::npos) { rawResponse.erase(pos, toErase.length()); } rawResponse.erase(std::remove(rawResponse.begin(), rawResponse.end(), '\"'), rawResponse.end()); data.push_back(rawResponse); } else { data.push_back(""); } } return data; } std::vector ServiceCellular::scanOperators(void) { auto channel = cmux->get(TS0710::Channel::Commands); std::vector result; if (channel) { auto resp = channel->cmd("AT+COPS=?\r", 180000, 2); if (resp.code == at::Result::Code::OK) { std::string rawResponse = resp.response[0]; std::string toErase("+COPS: "); auto pos = rawResponse.find(toErase); if (pos != std::string::npos) { rawResponse.erase(pos, toErase.length()); } std::string delimiter("),("); auto end = rawResponse.find(delimiter); auto begin = 0; while (end != std::string::npos) { auto operatorInfo = rawResponse.substr(begin, end - begin); operatorInfo.erase(std::remove(operatorInfo.begin(), operatorInfo.end(), '('), operatorInfo.end()); operatorInfo.erase(std::remove(operatorInfo.begin(), operatorInfo.end(), ')'), operatorInfo.end()); operatorInfo.erase(std::remove(operatorInfo.begin(), operatorInfo.end(), ','), operatorInfo.end()); result.push_back(operatorInfo); begin = end; end = rawResponse.find(delimiter, end + 1); } } } return result; } bool sim_check_hot_swap(DLC_channel *chanel) { assert(chanel); bool reboot_needed = false; auto ret = chanel->cmd(at::AT::SIM_DET); if (!ret) { LOG_FATAL("Cant check sim detection status!"); } else { if (ret.response[0].find("+QSIMDET: 1") != std::string::npos) { LOG_DEBUG("SIM detecition enabled!"); } else { LOG_FATAL("SIM detection failure - trying to enable! %s", ret.response[0].c_str()); reboot_needed = true; } } ret = chanel->cmd(at::AT::QSIMSTAT); if (!ret) { LOG_FATAL("Cant check sim stat status"); } else { if (ret.response[0].find("+QSIMSTAT: 1") != std::string::npos) { LOG_DEBUG("SIM swap enabled!"); } else { LOG_FATAL("SIM swap status failure! %s", ret.response[0].c_str()); reboot_needed = true; } } // try to force set sim detection and sim stat if (reboot_needed == true) { ret = chanel->cmd(at::AT::SIM_DET_ON); ret = chanel->cmd(at::AT::SIMSTAT_ON); LOG_FATAL("Please full reboot phone!"); } return !reboot_needed; } 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); } else { LOG_ERROR("Sanity check failure - user will be promped about full shutdown"); state.set(this, State::ST::ModemFatalFailure); } return ret; } bool ServiceCellular::handle_select_sim() { bsp::cellular::sim::sim_sel(); bsp::cellular::sim::hotswap_trigger(); #if defined(TARGET_Linux) auto ret = cmux->get(TS0710::Channel::Commands)->cmd(at::AT::QSIMSTAT); if (!ret) { LOG_FATAL("Cant check sim stat status"); } else { if (ret.response[0].find("+QSIMSTAT: 1,1") != std::string::npos) { // SIM IN - only sim1 mocup Store::GSM::get()->sim = Store::GSM::SIM::SIM1; } else { // NO SIM IN Store::GSM::get()->sim = Store::GSM::SIM::SIM_FAIL; } sys::Bus::SendUnicast(std::make_shared(), "EventManager", this); } #endif return true; } bool ServiceCellular::handle_modem_on() { state.set(this, State::ST::Idle); auto channel = cmux->get(TS0710::Channel::Commands); channel->cmd("AT+CCLK?\r"); return true; } bool ServiceCellular::handle_sim_init() { auto channel = cmux->get(TS0710::Channel::Commands); if (channel == nullptr) { LOG_ERROR("Cant configure sim! no Commands channel!"); state.set(this, State::ST::Failed); return false; } bool success = true; success = channel->cmd(at::AT::CALLER_NUMBER_PRESENTATION) && success; success = channel->cmd(at::AT::SMS_TEXT_FORMAT) && success; success = channel->cmd(at::AT::SMS_UCSC2) && success; success = channel->cmd(at::AT::SMS_STORAGE) && success; success = channel->cmd(at::AT::CRC_ON) && success; if (!success) { LOG_ERROR("SIM initialization failure!"); } state.set(this, State::ST::Idle); return success; } bool ServiceCellular::handle_failure() { state.set(this, State::ST::Idle); return true; } bool ServiceCellular::handle_fatal_failure() { LOG_FATAL("Await for death!"); while (true) { vTaskDelay(500); } }