1863 lines
51 KiB
C++
1863 lines
51 KiB
C++
/*
|
|
; Project: Open Vehicle Monitor System
|
|
; Date: 14th March 2017
|
|
;
|
|
; Changes:
|
|
; 1.0 Initial release
|
|
;
|
|
; (C) 2011 Michael Stegen / Stegen Electronics
|
|
; (C) 2011-2017 Mark Webb-Johnson
|
|
; (C) 2011 Sonny Chen @ EPRO/DX
|
|
;
|
|
; Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
; of this software and associated documentation files (the "Software"), to deal
|
|
; in the Software without restriction, including without limitation the rights
|
|
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
; copies of the Software, and to permit persons to whom the Software is
|
|
; furnished to do so, subject to the following conditions:
|
|
;
|
|
; The above copyright notice and this permission notice shall be included in
|
|
; all copies or substantial portions of the Software.
|
|
;
|
|
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
; THE SOFTWARE.
|
|
*/
|
|
|
|
#include "ovms_log.h"
|
|
static const char *TAG = "cellular";
|
|
|
|
#include <string.h>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include "ovms_cellular.h"
|
|
#include "ovms_peripherals.h"
|
|
#include "metrics_standard.h"
|
|
#include "ovms_config.h"
|
|
#include "ovms_events.h"
|
|
#include "ovms_notify.h"
|
|
#include "ovms_boot.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Global convenience variables
|
|
|
|
modem* MyModem = NULL;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// General utility functions
|
|
|
|
const char* ModemState1Name(modem::modem_state1_t state)
|
|
{
|
|
switch (state)
|
|
{
|
|
case modem::None: return "None";
|
|
case modem::CheckPowerOff: return "CheckPowerOff";
|
|
case modem::PoweringOn: return "PoweringOn";
|
|
case modem::Identify: return "Identify";
|
|
case modem::PoweredOn: return "PoweredOn";
|
|
case modem::MuxStart: return "MuxStart";
|
|
case modem::NetWait: return "NetWait";
|
|
case modem::NetStart: return "NetStart";
|
|
case modem::NetLoss: return "NetLoss";
|
|
case modem::NetHold: return "NetHold";
|
|
case modem::NetSleep: return "NetSleep";
|
|
case modem::NetMode: return "NetMode";
|
|
case modem::NetDeepSleep: return "NetDeepSleep";
|
|
case modem::PoweringOff: return "PoweringOff";
|
|
case modem::PoweredOff: return "PoweredOff";
|
|
case modem::PowerOffOn: return "PowerOffOn";
|
|
case modem::Development: return "Development";
|
|
default: return "Undefined";
|
|
};
|
|
}
|
|
|
|
const char* ModemNetRegName(modem::network_registration_t netreg)
|
|
{
|
|
switch (netreg)
|
|
{
|
|
case modem::NotRegistered: return "NotRegistered";
|
|
case modem::DeniedRegistration: return "DeniedRegistration";
|
|
case modem::Searching: return "Searching";
|
|
case modem::Registered: return "Registered";
|
|
case modem::RegisteredEmergencyServices: return "RegisteredEmergencyServices";
|
|
case modem::RegisteredRoamingSMS: return "RegisteredRoamingSMS";
|
|
case modem::RegisteredRoaming: return "RegisteredRoaming";
|
|
case modem::RegisteredHomeSMS: return "RegisteredHomeSMS";
|
|
case modem::RegisteredHome: return "RegisteredHome";
|
|
default: return "Unknown";
|
|
};
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// The modem task itself.
|
|
// This runs as a freertos task, controlling the modem via a state diagram.
|
|
|
|
static void MODEM_task(void *pvParameters)
|
|
{
|
|
modem *me = (modem*)pvParameters;
|
|
me->Task();
|
|
}
|
|
|
|
void modem::Task()
|
|
{
|
|
modem_or_uart_event_t event;
|
|
uint8_t data[128];
|
|
|
|
// Init UART:
|
|
uart_config_t uart_config =
|
|
{
|
|
.baud_rate = m_baud,
|
|
.data_bits = UART_DATA_8_BITS,
|
|
.parity = UART_PARITY_DISABLE,
|
|
.stop_bits = UART_STOP_BITS_1,
|
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
|
.rx_flow_ctrl_thresh = 122,
|
|
.use_ref_tick = 0,
|
|
};
|
|
uart_param_config(m_uartnum, &uart_config);
|
|
uart_set_pin(m_uartnum, m_txpin, m_rxpin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
|
uart_driver_install(m_uartnum,
|
|
CONFIG_OVMS_HW_CELLULAR_MODEM_UART_SIZE,
|
|
CONFIG_OVMS_HW_CELLULAR_MODEM_UART_SIZE,
|
|
CONFIG_OVMS_HW_CELLULAR_MODEM_QUEUE_SIZE,
|
|
(QueueHandle_t*)&m_queue,
|
|
ESP_INTR_FLAG_LEVEL2);
|
|
|
|
// Queue processing loop:
|
|
while (m_task)
|
|
{
|
|
if (xQueueReceive(m_queue, (void *)&event, (portTickType)portMAX_DELAY))
|
|
{
|
|
if (event.uart.type <= UART_EVENT_MAX)
|
|
{
|
|
// Must be a UART event
|
|
switch(event.uart.type)
|
|
{
|
|
case UART_DATA:
|
|
{
|
|
size_t buffered_size = event.uart.size;
|
|
while (buffered_size > 0)
|
|
{
|
|
if (buffered_size>sizeof(data)) buffered_size = sizeof(data);
|
|
int len = uart_read_bytes(m_uartnum, (uint8_t*)data, buffered_size, 100 / portTICK_RATE_MS);
|
|
|
|
if (!m_buffer.Push(data,len))
|
|
{ m_err_driver_buffer_full++; }
|
|
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("rx", (const char*)data, len); }
|
|
|
|
uart_get_buffered_data_len(m_uartnum, &buffered_size);
|
|
|
|
modem_state1_t newstate = State1Activity();
|
|
if ((newstate != m_state1)&&(newstate != None)) SetState1(newstate);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case UART_FIFO_OVF:
|
|
uart_flush(m_uartnum);
|
|
m_err_uart_fifo_ovf++;
|
|
ESP_LOGW(TAG, "UART hw fifo overflow");
|
|
break;
|
|
|
|
case UART_BUFFER_FULL:
|
|
uart_flush(m_uartnum);
|
|
m_err_uart_buffer_full++;
|
|
ESP_LOGW(TAG, "UART ring buffer full");
|
|
break;
|
|
|
|
case UART_BREAK:
|
|
ESP_LOGD(TAG, "UART rx break");
|
|
break;
|
|
|
|
case UART_PARITY_ERR:
|
|
ESP_LOGW(TAG, "UART parity check error");
|
|
m_err_uart_parity++;
|
|
break;
|
|
|
|
case UART_FRAME_ERR:
|
|
ESP_LOGW(TAG, "UART frame error");
|
|
m_err_uart_frame++;
|
|
break;
|
|
|
|
case UART_PATTERN_DET:
|
|
ESP_LOGD(TAG, "UART pattern detected");
|
|
break;
|
|
|
|
default:
|
|
ESP_LOGD(TAG, "UART unknown event type: %d", event.uart.type);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Must be a MODEM event
|
|
switch (event.event.type)
|
|
{
|
|
case SETSTATE:
|
|
{
|
|
// Handle queued state change request
|
|
SetState1(event.event.data.newstate);
|
|
}
|
|
break;
|
|
|
|
case TICKER1:
|
|
{
|
|
m_state1_ticker++;
|
|
modem_state1_t newstate = State1Ticker1();
|
|
if ((newstate != m_state1)&&(newstate != None))
|
|
{
|
|
// Handle direct state transitions
|
|
ESP_LOGD(TAG, "State transition %s => %s",
|
|
ModemState1Name(m_state1),
|
|
ModemState1Name(newstate));
|
|
SetState1(newstate);
|
|
}
|
|
else
|
|
{
|
|
// Handle auto-state transitions
|
|
if (m_state1_timeout_ticks > 0)
|
|
{
|
|
m_state1_timeout_ticks--;
|
|
if (m_state1_timeout_ticks <= 0)
|
|
{
|
|
ESP_LOGD(TAG, "State timeout %s => %s",
|
|
ModemState1Name(m_state1),
|
|
ModemState1Name(m_state1_timeout_goto));
|
|
SetState1(m_state1_timeout_goto);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SHUTDOWN:
|
|
m_task = 0;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_state1 == PoweredOff && MyBoot.IsShuttingDown())
|
|
{
|
|
m_task = 0;
|
|
}
|
|
}
|
|
|
|
// Shutdown:
|
|
ESP_LOGD(TAG, "UART shutdown");
|
|
m_queue = 0;
|
|
uart_wait_tx_done(m_uartnum, portMAX_DELAY);
|
|
uart_flush(m_uartnum);
|
|
uart_driver_delete(m_uartnum);
|
|
if (MyBoot.IsShuttingDown()) MyBoot.ShutdownReady(TAG);
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// The modem
|
|
// This class implement an OVMS PCP for modem control
|
|
|
|
modem::modem(const char* name, uart_port_t uartnum, int baud, int rxpin, int txpin, int pwregpio, int dtregpio)
|
|
: pcp(name), m_buffer(CONFIG_OVMS_HW_CELLULAR_MODEM_BUFFER_SIZE)
|
|
{
|
|
MyModem = this;
|
|
m_task = 0;
|
|
m_uartnum = uartnum;
|
|
m_baud = baud;
|
|
m_pwregpio = pwregpio;
|
|
m_dtregpio = dtregpio;
|
|
m_rxpin = rxpin;
|
|
m_txpin = txpin;
|
|
m_state1 = None;
|
|
m_state1_ticker = 0;
|
|
m_state1_timeout_goto = None;
|
|
m_state1_timeout_ticks = -1;
|
|
m_state1_userdata = 0;
|
|
m_line_unfinished = -1;
|
|
m_line_buffer.clear();
|
|
m_netreg = Unknown;
|
|
for (size_t k=0; k<CELLULAR_NETREG_COUNT; k++) { m_netreg_d[k] = Unknown; }
|
|
m_provider = "";
|
|
m_sq = 99; // Unknown
|
|
m_powermode = Off;
|
|
m_pincode_required = false;
|
|
m_err_uart_fifo_ovf = 0;
|
|
m_err_uart_buffer_full = 0;
|
|
m_err_uart_parity = 0;
|
|
m_err_uart_frame = 0;
|
|
m_err_driver_buffer_full = 0;
|
|
m_nmea = NULL;
|
|
m_mux = NULL;
|
|
m_ppp = NULL;
|
|
m_driver = NULL;
|
|
m_cmd_running = false;
|
|
m_cmd_output.clear();
|
|
|
|
ClearNetMetrics();
|
|
StartTask();
|
|
|
|
using std::placeholders::_1;
|
|
using std::placeholders::_2;
|
|
MyEvents.RegisterEvent(TAG,"ticker.1", std::bind(&modem::Ticker, this, _1, _2));
|
|
MyEvents.RegisterEvent(TAG, "system.shuttingdown", std::bind(&modem::EventListener, this, _1, _2));
|
|
}
|
|
|
|
modem::~modem()
|
|
{
|
|
StopTask();
|
|
if (m_driver != NULL)
|
|
{
|
|
delete m_driver;
|
|
m_driver = NULL;
|
|
}
|
|
MyModem = NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Modem power control and initialisation
|
|
|
|
void modem::SetPowerMode(PowerMode powermode)
|
|
{
|
|
PowerMode original = m_powermode;
|
|
pcp::SetPowerMode(powermode);
|
|
|
|
switch (powermode)
|
|
{
|
|
case On:
|
|
case Sleep:
|
|
case DeepSleep:
|
|
if ((original!=On)&&(original!=Sleep)&&(original!=DeepSleep))
|
|
SendSetState1(PoweringOn); // We are not in the task, so queue the state change
|
|
break;
|
|
|
|
case Off:
|
|
SendSetState1(PoweringOff); // We are not in the task, so queue the state change
|
|
break;
|
|
|
|
case Devel:
|
|
SendSetState1(Development); // We are not in the task, so queue the state change
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void modem::AutoInit()
|
|
{
|
|
if (MyConfig.GetParamValueBool("auto", "modem", false))
|
|
SetPowerMode(On);
|
|
}
|
|
|
|
void modem::Restart()
|
|
{
|
|
if (m_driver == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Cannot restart, as modem driver is not loaded");
|
|
}
|
|
else
|
|
{ m_driver->Restart(); }
|
|
}
|
|
|
|
void modem::PowerOff()
|
|
{
|
|
if (m_driver == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Cannot power off, as modem driver is not loaded");
|
|
}
|
|
else
|
|
{ m_driver->PowerOff(); }
|
|
}
|
|
|
|
void modem::PowerCycle()
|
|
{
|
|
if (m_driver == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Cannot power cycle, as modem driver is not loaded");
|
|
}
|
|
else
|
|
{ m_driver->PowerCycle(); }
|
|
}
|
|
|
|
void modem::PowerSleep(bool onoff)
|
|
{
|
|
if (m_driver == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Cannot power sleep, as modem driver is not loaded");
|
|
}
|
|
else
|
|
{ m_driver->PowerSleep(onoff); }
|
|
}
|
|
|
|
void modem::SupportSummary(OvmsWriter* writer, bool debug /*=FALSE*/)
|
|
{
|
|
writer->puts("MODEM Status");
|
|
|
|
if (m_pincode_required)
|
|
{
|
|
writer->printf(" PIN: SIM card PIN code required.\n");
|
|
if (MyConfig.GetParamValueBool("modem","wrongpincode"))
|
|
writer->printf(" Error: Wrong PIN (%s) entered\n", MyConfig.GetParamValue("modem","pincode").c_str());
|
|
}
|
|
|
|
writer->printf(" Model: %s\n",m_model.c_str());
|
|
|
|
if (m_powermode != Off)
|
|
{
|
|
writer->printf(" Network Registration: %s\n Provider: %s\n Signal: %d dBm\n Mode: %s\n",
|
|
ModemNetRegName(m_netreg),
|
|
m_provider.c_str(),
|
|
UnitConvert(sq, dbm, m_sq),
|
|
StandardMetrics.ms_m_net_mdm_mode->AsString().c_str());
|
|
// writer->printf(" GSM Registration: %s\n",ModemNetRegName(m_netreg_d[NRT_GSM]));
|
|
// writer->printf(" GPRS Registration: %s\n",ModemNetRegName(m_netreg_d[NRT_GPRS]));
|
|
// writer->printf(" EPS Registration: %s\n",ModemNetRegName(m_netreg_d[NRT_EPS]));
|
|
}
|
|
|
|
writer->printf(" State: %s\n", ModemState1Name(m_state1));
|
|
if (debug)
|
|
{
|
|
writer->printf(" Ticker: %d\n User Data: %d\n",
|
|
m_state1_ticker,
|
|
m_state1_userdata);
|
|
writer->printf(
|
|
" UART:\n"
|
|
" FIFO overflows: %d\n"
|
|
" Buffer overflows: %d\n"
|
|
" Parity errors: %d\n"
|
|
" Frame errors: %d\n"
|
|
" Driver Buffer overflows: %d\n"
|
|
, m_err_uart_fifo_ovf
|
|
, m_err_uart_buffer_full
|
|
, m_err_uart_parity
|
|
, m_err_uart_frame
|
|
, m_err_driver_buffer_full);
|
|
|
|
if (m_state1_timeout_goto != None)
|
|
{
|
|
writer->printf(" State Timeout Goto: %s (in %d seconds)\n",
|
|
ModemState1Name(m_state1_timeout_goto),
|
|
m_state1_timeout_ticks);
|
|
}
|
|
}
|
|
|
|
if (m_mux)
|
|
{
|
|
writer->printf(" Mux: Status %s\n", m_mux->IsMuxUp()?"up":"down");
|
|
if (debug)
|
|
{
|
|
writer->printf(" Open Channels: %d\n", m_mux->m_openchannels);
|
|
writer->printf(" Framing Errors: %d\n", m_mux->m_framingerrors);
|
|
writer->printf(" RX frames: %d\n", m_mux->m_rxframecount);
|
|
writer->printf(" TX frames: %d\n", m_mux->m_txframecount);
|
|
writer->printf(" Last RX frame: %d sec(s) ago\n", m_mux->GoodFrameAge());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
writer->puts(" Mux: Not running");
|
|
}
|
|
|
|
if (m_ppp == NULL)
|
|
{
|
|
writer->puts(" PPP: Not running");
|
|
}
|
|
else if (m_ppp->m_connected)
|
|
{
|
|
writer->printf(" PPP: Connected on channel: #%d\n", m_ppp->m_channel);
|
|
}
|
|
else
|
|
{
|
|
writer->puts(" PPP: Not connected");
|
|
}
|
|
if ((m_ppp != NULL)&&(m_ppp->m_lasterrcode > 0))
|
|
{
|
|
writer->printf(" Last Error: %s\n", m_ppp->ErrCodeName(m_ppp->m_lasterrcode));
|
|
}
|
|
|
|
if (m_nmea==NULL)
|
|
{
|
|
writer->puts(" GPS: Not running");
|
|
}
|
|
else
|
|
{
|
|
if (m_nmea->m_connected)
|
|
{
|
|
writer->printf(" GPS: Connected on channel: #%d\n", m_nmea->m_channel_nmea);
|
|
}
|
|
else
|
|
{
|
|
writer->puts(" GPS: Not connected");
|
|
}
|
|
if (debug)
|
|
{
|
|
writer->printf(" Status: %s\n",
|
|
MyConfig.GetParamValueBool("modem", "enable.gps", false) ? "enabled" : "disabled");
|
|
writer->printf(" Time: %s\n",
|
|
MyConfig.GetParamValueBool("modem", "enable.gpstime", false) ? "enabled" : "disabled");
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Modem state diagram implementation
|
|
|
|
void modem::SetState1(modem_state1_t newstate)
|
|
{
|
|
m_state1_timeout_ticks = -1;
|
|
m_state1_timeout_goto = None;
|
|
if (m_state1 != None) State1Leave(m_state1);
|
|
State1Enter(newstate);
|
|
}
|
|
|
|
modem::modem_state1_t modem::GetState1()
|
|
{
|
|
return m_state1;
|
|
}
|
|
|
|
void modem::State1Leave(modem_state1_t oldstate)
|
|
{
|
|
if (m_driver)
|
|
{
|
|
// See if the driver want's to override our default behaviour
|
|
if (m_driver->State1Leave(oldstate)) return;
|
|
}
|
|
|
|
switch (oldstate)
|
|
{
|
|
case CheckPowerOff:
|
|
break;
|
|
case PoweringOn:
|
|
break;
|
|
case Identify:
|
|
break;
|
|
case PoweredOn:
|
|
break;
|
|
case NetMode:
|
|
break;
|
|
case NetDeepSleep:
|
|
break;
|
|
case PoweringOff:
|
|
break;
|
|
case PoweredOff:
|
|
break;
|
|
case Development:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void modem::State1Enter(modem_state1_t newstate)
|
|
{
|
|
m_state1 = newstate;
|
|
m_state1_ticker = 0;
|
|
m_state1_userdata = 0;
|
|
|
|
if (m_driver == NULL)
|
|
{
|
|
// Register a default modem driver
|
|
std::string driver = MyConfig.GetParamValue("modem", "driver","auto");
|
|
SetCellularModemDriver(driver.c_str());
|
|
}
|
|
|
|
ESP_LOGI(TAG, "State: Enter %s state",ModemState1Name(newstate));
|
|
|
|
// See if the driver want's to override our default behaviour
|
|
if (m_driver->State1Enter(newstate)) return;
|
|
|
|
switch (m_state1)
|
|
{
|
|
case CheckPowerOff:
|
|
ClearNetMetrics();
|
|
PowerOff();
|
|
m_state1_timeout_ticks = 15;
|
|
m_state1_timeout_goto = PoweredOff;
|
|
break;
|
|
|
|
case PoweringOn:
|
|
if ( (strcmp(m_driver->GetModel(),"auto") != 0) &&
|
|
(MyConfig.GetParamValue("modem", "driver","auto").compare("auto")==0) )
|
|
{
|
|
SetCellularModemDriver("auto");
|
|
}
|
|
ClearNetMetrics();
|
|
MyEvents.SignalEvent("system.modem.poweringon", NULL);
|
|
PowerCycle();
|
|
m_state1_timeout_ticks = 30;
|
|
m_state1_timeout_goto = PoweringOn;
|
|
break;
|
|
|
|
case Identify:
|
|
m_state1_timeout_ticks = 30;
|
|
m_state1_timeout_goto = PowerOffOn;
|
|
break;
|
|
|
|
case PoweredOn:
|
|
ClearNetMetrics();
|
|
MyEvents.SignalEvent("system.modem.poweredon", NULL);
|
|
m_state1_timeout_ticks = 30;
|
|
m_state1_timeout_goto = PoweringOn;
|
|
break;
|
|
|
|
case MuxStart:
|
|
MyEvents.SignalEvent("system.modem.muxstart", NULL);
|
|
m_state1_timeout_ticks = 120;
|
|
m_state1_timeout_goto = PoweringOn;
|
|
StartMux();
|
|
break;
|
|
|
|
case NetWait:
|
|
MyEvents.SignalEvent("system.modem.netwait", NULL);
|
|
StartNMEA();
|
|
break;
|
|
|
|
case NetStart:
|
|
MyEvents.SignalEvent("system.modem.netstart", NULL);
|
|
m_state1_timeout_ticks = 30;
|
|
m_state1_timeout_goto = PowerOffOn;
|
|
break;
|
|
|
|
case NetLoss:
|
|
MyEvents.SignalEvent("system.modem.netloss", NULL);
|
|
m_state1_timeout_ticks = 10;
|
|
m_state1_timeout_goto = NetWait;
|
|
if (m_mux != NULL)
|
|
{ muxtx(m_mux_channel_POLL, "AT+CGATT=0\r\n"); }
|
|
StopPPP();
|
|
break;
|
|
|
|
case NetHold:
|
|
MyEvents.SignalEvent("system.modem.nethold", NULL);
|
|
break;
|
|
|
|
case NetSleep:
|
|
MyEvents.SignalEvent("system.modem.netsleep", NULL);
|
|
StopPPP();
|
|
StopNMEA();
|
|
break;
|
|
|
|
case NetMode:
|
|
MyEvents.SignalEvent("system.modem.netmode", NULL);
|
|
StartPPP();
|
|
break;
|
|
|
|
case NetDeepSleep:
|
|
ClearNetMetrics();
|
|
MyEvents.SignalEvent("system.modem.netdeepsleep", NULL);
|
|
StopPPP();
|
|
StopNMEA();
|
|
break;
|
|
|
|
case PoweringOff:
|
|
ClearNetMetrics();
|
|
StopPPP();
|
|
StopNMEA();
|
|
MyEvents.SignalEvent("system.modem.stop",NULL);
|
|
PowerCycle();
|
|
m_state1_timeout_ticks = 20;
|
|
m_state1_timeout_goto = CheckPowerOff;
|
|
break;
|
|
|
|
case PoweredOff:
|
|
ClearNetMetrics();
|
|
MyEvents.SignalEvent("system.modem.poweredoff", NULL);
|
|
StopMux();
|
|
if (m_driver)
|
|
{
|
|
delete m_driver;
|
|
m_driver = NULL;
|
|
m_model.clear();
|
|
}
|
|
break;
|
|
|
|
case PowerOffOn:
|
|
ClearNetMetrics();
|
|
StopPPP();
|
|
StopNMEA();
|
|
StopMux();
|
|
MyEvents.SignalEvent("system.modem.stop",NULL);
|
|
PowerCycle();
|
|
m_state1_timeout_ticks = 3;
|
|
m_state1_timeout_goto = PoweringOn;
|
|
break;
|
|
|
|
case Development:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
modem::modem_state1_t modem::State1Activity()
|
|
{
|
|
if (m_driver)
|
|
{
|
|
// See if the driver want's to override our default behaviour
|
|
modem::modem_state1_t driverstate = m_driver->State1Activity(m_state1);
|
|
if (driverstate == modem::None) return m_state1;
|
|
if (driverstate != m_state1) return driverstate;
|
|
}
|
|
|
|
switch (m_state1)
|
|
{
|
|
case None:
|
|
break;
|
|
|
|
case CheckPowerOff:
|
|
// We have activity, so modem is powered on
|
|
m_buffer.EmptyAll(); // Drain it
|
|
return PoweringOff;
|
|
break;
|
|
|
|
case PoweringOn:
|
|
// We have activity, so modem is powered on
|
|
m_buffer.EmptyAll(); // Drain it
|
|
if (MyConfig.GetParamValue("modem", "driver","auto").compare("auto")==0)
|
|
return Identify;
|
|
else
|
|
return PoweredOn;
|
|
break;
|
|
|
|
case Identify:
|
|
if (IdentifyModel()) return PoweredOn;
|
|
break;
|
|
|
|
case PoweredOn:
|
|
if (StandardIncomingHandler(m_mux_channel_CTRL, &m_buffer))
|
|
{
|
|
if (m_state1_ticker >= 20) return MuxStart;
|
|
}
|
|
break;
|
|
|
|
case MuxStart:
|
|
case NetWait:
|
|
case NetStart:
|
|
case NetHold:
|
|
case NetSleep:
|
|
case NetMode:
|
|
if (m_mux != NULL)
|
|
{ m_mux->Process(&m_buffer); }
|
|
break;
|
|
|
|
case NetLoss:
|
|
case NetDeepSleep:
|
|
case PoweringOff:
|
|
m_buffer.EmptyAll(); // Drain it
|
|
break;
|
|
|
|
case PoweredOff:
|
|
// We shouldn't have any activity while powered off, so try again...
|
|
return PoweringOff;
|
|
break;
|
|
|
|
case Development:
|
|
if ((m_mux != NULL)&&(m_mux->IsMuxUp()))
|
|
{
|
|
m_mux->Process(&m_buffer);
|
|
}
|
|
else
|
|
{
|
|
size_t needed = m_buffer.UsedSpace();
|
|
char* result = new char[needed+1];
|
|
m_buffer.Pop(needed, (uint8_t*)result);
|
|
result[needed] = 0;
|
|
MyCommandApp.HexDump(TAG, "rx", result, needed);
|
|
delete [] result;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
modem::modem_state1_t modem::State1Ticker1()
|
|
{
|
|
if ((m_mux != NULL)&&(m_mux->GoodFrameAge() > 180)&&(m_state1 != Development))
|
|
{
|
|
// Mux is up, but we haven't had a good MUX frame in 3 minutes.
|
|
// Let's assume the MUX has failed
|
|
ESP_LOGW(TAG, "3 minutes since last MUX rx frame - assume MUX has failed");
|
|
StopPPP();
|
|
StopNMEA();
|
|
StopMux();
|
|
MyEvents.SignalEvent("system.modem.stop",NULL);
|
|
PowerCycle();
|
|
return PoweringOn;
|
|
}
|
|
|
|
if (m_driver)
|
|
{
|
|
// See if the driver want's to override our default behaviour
|
|
modem::modem_state1_t driverstate = m_driver->State1Ticker1(m_state1);
|
|
if (driverstate == modem::None) return m_state1;
|
|
if (driverstate != m_state1) return driverstate;
|
|
}
|
|
|
|
switch (m_state1)
|
|
{
|
|
case None:
|
|
return CheckPowerOff;
|
|
break;
|
|
|
|
case CheckPowerOff:
|
|
if (m_state1_ticker > 10) tx("AT\r\n");
|
|
break;
|
|
|
|
case PoweringOn:
|
|
tx("AT\r\n");
|
|
break;
|
|
|
|
case Identify:
|
|
tx("AT+CGMM\r\n");
|
|
break;
|
|
|
|
case PoweredOn:
|
|
if (m_powermode == DeepSleep)
|
|
{
|
|
return NetDeepSleep; // Just hold, without starting the network
|
|
}
|
|
switch (m_state1_ticker)
|
|
{
|
|
case 10:
|
|
tx("AT+CPIN?;+CREG=1;+CTZU=1;+CTZR=1;+CLIP=1;+CMGF=1;+CNMI=1,2,0,0,0;+CSDH=1;+CMEE=2;+CSQ;+AUTOCSQ=1,1;E0;S0=0\r\n");
|
|
break;
|
|
case 12:
|
|
tx("AT+CGMR;+ICCID\r\n");
|
|
break;
|
|
case 20:
|
|
tx("AT+CMUX=0\r\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MuxStart:
|
|
if (m_mux != NULL)
|
|
{
|
|
if ((m_state1_ticker>5)&&((m_state1_ticker % 30) == 0))
|
|
{ m_driver->StatusPoller(); }
|
|
if (m_mux->IsMuxUp())
|
|
return NetWait;
|
|
}
|
|
break;
|
|
|
|
case NetWait:
|
|
if (m_powermode == Sleep)
|
|
{
|
|
return NetSleep; // Just hold, without starting the network
|
|
}
|
|
if (m_state1_ticker == 1)
|
|
{
|
|
// Check for exit out of this state...
|
|
std::string p = MyConfig.GetParamValue("modem", "apn");
|
|
if ((!MyConfig.GetParamValueBool("modem", "enable.net", true))||(p.empty()))
|
|
return NetHold; // Just hold, without starting PPP
|
|
}
|
|
else if ((m_state1_ticker > 3)&&((m_netreg >= Registered)))
|
|
return NetStart; // We have GSM, so start the network
|
|
if ((m_mux != NULL)&&(m_state1_ticker>3)&&((m_state1_ticker % 10) == 0))
|
|
{ m_driver->StatusPoller(); }
|
|
break;
|
|
|
|
case NetStart:
|
|
if (m_powermode == Sleep)
|
|
{
|
|
return NetSleep; // Just hold, without starting the network
|
|
}
|
|
if (m_state1_ticker == 1)
|
|
{
|
|
m_state1_userdata = 1;
|
|
if (m_mux != NULL)
|
|
{
|
|
std::string apncmd("AT+CGDCONT=1,\"IP\",\"");
|
|
apncmd.append(MyConfig.GetParamValue("modem", "apn"));
|
|
apncmd.append("\";+CGDATA=\"PPP\",1\r\n");
|
|
ESP_LOGD(TAG,"Netstart %s",apncmd.c_str());
|
|
muxtx(m_mux_channel_DATA,apncmd.c_str());
|
|
}
|
|
}
|
|
if (m_state1_userdata == 2)
|
|
return NetMode; // PPP Connection is ready to be started
|
|
else if (m_state1_userdata == 99)
|
|
return NetLoss;
|
|
else if (m_state1_userdata == 100)
|
|
{
|
|
ESP_LOGW(TAG, "NetStart: unresolvable error, performing modem power cycle");
|
|
return PowerOffOn;
|
|
}
|
|
break;
|
|
|
|
case NetLoss:
|
|
break;
|
|
|
|
case NetHold:
|
|
if ((m_mux != NULL)&&(m_state1_ticker>5)&&((m_state1_ticker % 30) == 0))
|
|
{ m_driver->StatusPoller(); }
|
|
break;
|
|
|
|
case NetSleep:
|
|
if (m_powermode == On) return NetWait;
|
|
if (m_powermode != Sleep) return PoweringOn;
|
|
if ((m_mux != NULL)&&(m_state1_ticker>5)&&((m_state1_ticker % 30) == 0))
|
|
{ m_driver->StatusPoller(); }
|
|
break;
|
|
|
|
case NetMode:
|
|
if (m_powermode == Sleep)
|
|
{
|
|
// Need to shutdown ppp, and get back to NetSleep mode
|
|
return NetSleep;
|
|
}
|
|
if (m_netreg < Registered)
|
|
{
|
|
// We've lost the network connection
|
|
ESP_LOGW(TAG, "Lost network connection (NetworkRegistration in NetMode)");
|
|
return NetLoss;
|
|
}
|
|
if (m_state1_userdata == 99)
|
|
{
|
|
// We've lost the network connection
|
|
ESP_LOGW(TAG, "Lost network connection (+PPP disconnect in NetMode)");
|
|
return NetLoss;
|
|
}
|
|
if ((m_mux != NULL)&&(m_state1_ticker>5)&&((m_state1_ticker % 30) == 0))
|
|
{ m_driver->StatusPoller(); }
|
|
break;
|
|
|
|
case NetDeepSleep:
|
|
if (m_powermode != DeepSleep) return PoweringOn;
|
|
break;
|
|
|
|
case PoweringOff:
|
|
break;
|
|
|
|
case PoweredOff:
|
|
break;
|
|
|
|
case Development:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
bool modem::StandardIncomingHandler(int channel, OvmsBuffer* buf)
|
|
{
|
|
bool result = false;
|
|
|
|
while(1)
|
|
{
|
|
if (buf->m_userdata != 0)
|
|
{
|
|
// Expecting N bytes of data mode
|
|
if (buf->UsedSpace() < (size_t)buf->m_userdata) return false;
|
|
StandardDataHandler(channel, buf);
|
|
result = true;
|
|
}
|
|
else
|
|
{
|
|
// Normal line mode
|
|
while (buf->HasLine() >= 0)
|
|
{
|
|
StandardLineHandler(channel, buf, buf->ReadLine());
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::StandardDataHandler(int channel, OvmsBuffer* buf)
|
|
{
|
|
// We have SMS data ready...
|
|
size_t needed = (size_t)buf->m_userdata;
|
|
|
|
char* result = new char[needed+1];
|
|
buf->Pop(needed, (uint8_t*)result);
|
|
result[needed] = 0;
|
|
|
|
// This may be a big performance hit, so disable for the moment
|
|
// MyCommandApp.HexDump(TAG, "data", result, needed);
|
|
|
|
delete [] result;
|
|
buf->m_userdata = 0;
|
|
}
|
|
|
|
void modem::StandardLineHandler(int channel, OvmsBuffer* buf, std::string line)
|
|
{
|
|
if (line.length() == 0)
|
|
return;
|
|
|
|
if ((m_cmd_running)&&(channel == m_mux_channel_CMD))
|
|
{
|
|
m_cmd_output.append(line);
|
|
m_cmd_output.append("\r\n");
|
|
}
|
|
|
|
// expecting continuation of previous line?
|
|
if (m_line_unfinished == channel)
|
|
{
|
|
m_line_buffer += line;
|
|
if (m_line_buffer.length() > 1000)
|
|
{
|
|
ESP_LOGE(TAG, "rx line buffer grown too long, discarding");
|
|
m_line_buffer.clear();
|
|
m_line_unfinished = -1;
|
|
return;
|
|
}
|
|
line = m_line_buffer;
|
|
}
|
|
|
|
if (line.compare(0, 2, "$G") == 0)
|
|
{
|
|
// GPS NMEA URC:
|
|
if (m_nmea) m_nmea->IncomingLine(line);
|
|
return;
|
|
}
|
|
|
|
// Log incoming data other than GPS NMEA
|
|
ESP_LOGD(TAG, "mux-rx-line #%d: %s", channel, line.c_str());
|
|
|
|
if ((line.compare(0, 8, "CONNECT ") == 0)&&(m_state1 == NetStart)&&(m_state1_userdata == 1))
|
|
{
|
|
ESP_LOGI(TAG, "PPP Connection is ready to start");
|
|
m_state1_userdata = 2;
|
|
}
|
|
else if ((line.compare(0, 5, "ERROR") == 0)&&(m_state1 == NetStart)&&(m_state1_userdata == 1))
|
|
{
|
|
ESP_LOGI(TAG, "PPP Connection init error");
|
|
m_state1_userdata = 100;
|
|
}
|
|
else if ((line.compare(0, 19, "+PPPD: DISCONNECTED") == 0)&&((m_state1 == NetStart)||(m_state1 == NetMode)))
|
|
{
|
|
ESP_LOGI(TAG, "PPP Connection disconnected");
|
|
m_state1_userdata = 99;
|
|
}
|
|
else if (line.compare(0, 8, "+ICCID: ") == 0)
|
|
{
|
|
StandardMetrics.ms_m_net_mdm_iccid->SetValue(line.substr(8));
|
|
}
|
|
else if (line.compare(0, 7, "+CGMR: ") == 0)
|
|
{
|
|
StandardMetrics.ms_m_net_mdm_model->SetValue(line.substr(7));
|
|
}
|
|
else if (line.compare(0, 6, "+CSQ: ") == 0)
|
|
{
|
|
SetSignalQuality(atoi(line.substr(6).c_str()));
|
|
}
|
|
else if (line.compare(0, 7, "+CREG: ") == 0)
|
|
{
|
|
size_t qp = line.find(',');
|
|
int creg;
|
|
network_registration_t nreg = Unknown;
|
|
if (qp != string::npos)
|
|
creg = atoi(line.substr(qp+1,1).c_str());
|
|
else
|
|
creg = atoi(line.substr(7,1).c_str());
|
|
switch (creg)
|
|
{
|
|
case 0: case 4: nreg = NotRegistered; break;
|
|
case 1: nreg = RegisteredHome; break;
|
|
case 2: nreg = Searching; break;
|
|
case 3: nreg = DeniedRegistration; break;
|
|
case 5: nreg = RegisteredRoaming; break;
|
|
default: break;
|
|
}
|
|
SetNetworkRegistration(NRT_GSM, nreg);
|
|
}
|
|
else if (line.compare(0, 8, "+CGREG: ") == 0)
|
|
{
|
|
size_t qp = line.find(',');
|
|
int creg;
|
|
network_registration_t nreg = Unknown;
|
|
if (qp != string::npos)
|
|
creg = atoi(line.substr(qp+1,1).c_str());
|
|
else
|
|
creg = atoi(line.substr(7,1).c_str());
|
|
switch (creg)
|
|
{
|
|
case 0: case 4: nreg = NotRegistered; break;
|
|
case 1: nreg = RegisteredHome; break;
|
|
case 2: nreg = Searching; break;
|
|
case 3: nreg = DeniedRegistration; break;
|
|
case 5: nreg = RegisteredRoaming; break;
|
|
default: break;
|
|
}
|
|
SetNetworkRegistration(NRT_GPRS, nreg);
|
|
}
|
|
else if (line.compare(0, 8, "+CEREG: ") == 0)
|
|
{
|
|
size_t qp = line.find(',');
|
|
int creg;
|
|
network_registration_t nreg = Unknown;
|
|
if (qp != string::npos)
|
|
creg = atoi(line.substr(qp+1,1).c_str());
|
|
else
|
|
creg = atoi(line.substr(7,1).c_str());
|
|
switch (creg)
|
|
{
|
|
case 0: case 4: nreg = NotRegistered; break;
|
|
case 1: nreg = RegisteredHome; break;
|
|
case 2: nreg = Searching; break;
|
|
case 3: nreg = DeniedRegistration; break;
|
|
case 5: nreg = RegisteredRoaming; break;
|
|
case 6: nreg = RegisteredHomeSMS; break;
|
|
case 7: nreg = RegisteredRoamingSMS; break;
|
|
case 8: nreg = RegisteredEmergencyServices; break;
|
|
default: break;
|
|
}
|
|
SetNetworkRegistration(NRT_EPS, nreg);
|
|
}
|
|
else if (line.compare(0, 7, "+CPSI: ") == 0)
|
|
{
|
|
size_t qp = line.find(',');
|
|
if (qp != string::npos)
|
|
{ qp = line.find(',',qp+1); }
|
|
if (qp != string::npos)
|
|
{ StandardMetrics.ms_m_net_mdm_mode->SetValue(line.substr(7,qp-7)); }
|
|
}
|
|
else if (line.compare(0, 7, "+COPS: ") == 0)
|
|
{
|
|
size_t qp = line.find('"');
|
|
if (qp != string::npos)
|
|
{
|
|
size_t qp2 = line.find('"',qp+1);
|
|
if (qp2 != string::npos)
|
|
{
|
|
SetProvider(line.substr(qp+1,qp2-qp-1));
|
|
}
|
|
}
|
|
}
|
|
// SMS received (URC):
|
|
else if (line.compare(0, 6, "+CMT: ") == 0)
|
|
{
|
|
size_t qp = line.find_last_of(',');
|
|
if (qp != string::npos)
|
|
{
|
|
buf->m_userdata = (void*)atoi(line.substr(qp+1).c_str());
|
|
ESP_LOGI(TAG,"SMS length is %d",(int)buf->m_userdata);
|
|
}
|
|
}
|
|
// SIM card PIN code required:
|
|
else if (line.compare(0, 14, "+CPIN: SIM PIN") == 0)
|
|
{
|
|
ESP_LOGI(TAG,"SIM card PIN code required");
|
|
m_pincode_required=true;
|
|
std::string pincode = MyConfig.GetParamValue("modem", "pincode");
|
|
if (!MyConfig.GetParamValueBool("modem","wrongpincode"))
|
|
{
|
|
if (pincode != "")
|
|
{
|
|
ESP_LOGI(TAG,"Using PIN code \"%s\"",pincode.c_str());
|
|
std::string at = "AT+CPIN=\"";
|
|
at.append(pincode);
|
|
at.append("\"\r\n");
|
|
tx(at.c_str());
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGE(TAG,"SIM card PIN code not set!");
|
|
MyEvents.SignalEvent("system.modem.pincode_not_set", NULL);
|
|
MyNotify.NotifyString("alert", "modem.no_pincode", "No PIN code for SIM card configured!");
|
|
}
|
|
} else
|
|
{
|
|
ESP_LOGW(TAG,"Wrong PIN code (%s) previously entered.. Will not re-enter until changed!", pincode.c_str());
|
|
MyNotify.NotifyStringf("alert", "modem.wrongpincode", "Wrong pin code (%s) previously entered! Did not re-enter it..",pincode.c_str());
|
|
}
|
|
}
|
|
else if (line.compare(0, 30, "+CME ERROR: incorrect password") == 0)
|
|
{
|
|
std::string pincode = MyConfig.GetParamValue("modem", "pincode");
|
|
ESP_LOGE(TAG,"Wrong PIN code entered!");
|
|
MyEvents.SignalEvent("system.modem.wrongpingcode", NULL);
|
|
MyNotify.NotifyStringf("alert", "modem.wrongpincode", "Wrong pin code (%s) entered!", pincode.c_str());
|
|
MyConfig.SetParamValueBool("modem","wrongpincode",true);
|
|
}
|
|
else if (line.compare(0, 28, "+CME ERROR: SIM not inserted") == 0)
|
|
{
|
|
ESP_LOGE(TAG,"SIM not inserted!");
|
|
MyEvents.SignalEvent("system.modem.simnotinserted", NULL);
|
|
StandardMetrics.ms_m_net_mdm_iccid->SetValue(line.substr(12));
|
|
}
|
|
|
|
// MMI/USSD response (URC):
|
|
// sent on all free channels or only on POLL, so we only process m_mux_channel_POLL
|
|
else if (channel == m_mux_channel_POLL && line.compare(0, 7, "+CUSD: ") == 0)
|
|
{
|
|
// Format: +CUSD: 0,"…msg…",15
|
|
// The message string may contain CR/LF so can come on multiple lines, with unknown length
|
|
size_t q1 = line.find('"');
|
|
size_t q2 = line.find_last_of('"');
|
|
if (q1 == string::npos || q2 == q1)
|
|
{
|
|
// check again after adding next line:
|
|
if (m_line_unfinished < 0)
|
|
{
|
|
m_line_unfinished = channel;
|
|
m_line_buffer = line;
|
|
m_line_buffer += "\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// complete, process:
|
|
m_line_buffer = line.substr(q1+1, q2-q1-1);
|
|
ESP_LOGI(TAG, "USSD received: %s", m_line_buffer.c_str());
|
|
MyEvents.SignalEvent("system.modem.received.ussd", (void*)m_line_buffer.c_str(),m_line_buffer.size()+1);
|
|
m_line_unfinished = -1;
|
|
m_line_buffer.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool modem::IdentifyModel()
|
|
{
|
|
while (m_buffer.HasLine() >= 0)
|
|
{
|
|
std::string line = m_buffer.ReadLine();
|
|
|
|
for (OvmsCellularModemFactory::map_modemdriver_t::iterator k=MyCellularModemFactory.m_drivermap.begin();
|
|
k!=MyCellularModemFactory.m_drivermap.end();
|
|
++k)
|
|
{
|
|
if (line.find(k->first) != string::npos)
|
|
{
|
|
ESP_LOGI(TAG, "Identified cellular modem: %s/%s", k->first,k->second.name );
|
|
SetCellularModemDriver(k->first);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Modem API functionality
|
|
|
|
void modem::tx(uint8_t* data, size_t size)
|
|
{
|
|
if (!m_task) return; // Quick exit if not task (we are stopped)
|
|
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("tx", (const char*)data, size); }
|
|
|
|
uart_write_bytes(m_uartnum, (const char*)data, size);
|
|
}
|
|
|
|
void modem::tx(const char* data, ssize_t size)
|
|
{
|
|
if (!m_task) return; // Quick exit if not task (we are stopped)
|
|
if (size == -1) size = strlen(data);
|
|
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("tx", data, size); }
|
|
|
|
if (size > 0)
|
|
ESP_LOGD(TAG, "tx-cmd: %s", data);
|
|
|
|
uart_write_bytes(m_uartnum, data, size);
|
|
}
|
|
|
|
void modem::muxtx(int channel, uint8_t* data, size_t size)
|
|
{
|
|
if (!m_task) return; // Quick exit if not task (we are stopped)
|
|
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("mux-tx", (const char*)data, size); }
|
|
|
|
if (m_mux != NULL)
|
|
{ m_mux->tx(channel, data, size); }
|
|
else
|
|
{ ESP_LOGE(TAG, "Attempt to transmit on a mux not running"); }
|
|
}
|
|
|
|
void modem::muxtx(int channel, const char* data, ssize_t size)
|
|
{
|
|
if (!m_task) return; // Quick exit if not task (we are stopped)
|
|
if (size == -1) size = strlen(data);
|
|
|
|
if (size > 0 && (channel == m_mux_channel_POLL || channel == m_mux_channel_CMD))
|
|
ESP_LOGD(TAG, "mux-tx #%d: %s", channel, data);
|
|
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("mux-tx", data, size); }
|
|
|
|
if (m_mux != NULL)
|
|
{ m_mux->tx(channel, (uint8_t*)data, size); }
|
|
else
|
|
{ ESP_LOGE(TAG, "Attempt to transmit on a mux not running"); }
|
|
}
|
|
|
|
bool modem::txcmd(const char* data, ssize_t size)
|
|
{
|
|
if (!m_task) return false; // Quick exit if not task (we are stopped)
|
|
if (size == -1) size = strlen(data);
|
|
if (size <= 0) return false;
|
|
|
|
if ((m_mux == NULL)||(m_mux->m_state == GsmMux::DlciClosed))
|
|
{
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("tx-cmd", (const char*)data, size); }
|
|
|
|
ESP_LOGD(TAG, "txcmd: %s", data);
|
|
|
|
tx((uint8_t*)data, size);
|
|
return true;
|
|
}
|
|
else if ((m_mux != NULL)&&(m_mux->IsChannelOpen(m_mux_channel_CMD)))
|
|
{
|
|
if (m_state1 == Development)
|
|
{ DevelopmentHexDump("mux-tx-cmd", (const char*)data, size); }
|
|
|
|
ESP_LOGD(TAG, "mux-tx-cmd #%d: %s", m_mux_channel_CMD, data);
|
|
|
|
m_mux->tx(m_mux_channel_CMD, (uint8_t*)data, size);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// High level API functions
|
|
|
|
void modem::StartTask()
|
|
{
|
|
if (!m_task)
|
|
{
|
|
ESP_LOGV(TAG, "Starting modem task");
|
|
xTaskCreatePinnedToCore(MODEM_task, "OVMS Cellular", CONFIG_OVMS_HW_CELLULAR_MODEM_STACK_SIZE, (void*)this, 20, (void**)&m_task, CORE(0));
|
|
}
|
|
}
|
|
|
|
void modem::StopTask()
|
|
{
|
|
if (m_task)
|
|
{
|
|
ESP_LOGV(TAG, "Stopping modem task (and waiting for completion)");
|
|
modem_or_uart_event_t ev;
|
|
ev.event.type = SHUTDOWN;
|
|
xQueueSend(m_queue, &ev, portMAX_DELAY);
|
|
while (m_queue)
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
ESP_LOGV(TAG, "Modem task has stopped");
|
|
}
|
|
}
|
|
|
|
bool modem::StartNMEA(bool force /*=false*/)
|
|
{
|
|
if ( (m_nmea == NULL) &&
|
|
(force || MyConfig.GetParamValueBool("modem", "enable.gps", false)) )
|
|
{
|
|
if (!m_mux || !m_driver)
|
|
{
|
|
ESP_LOGE(TAG, "StartNMEA failed: MUX or driver not available");
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGV(TAG, "Starting NMEA");
|
|
m_nmea = new GsmNMEA(m_mux, m_mux_channel_NMEA, m_mux_channel_CMD);
|
|
m_nmea->Startup();
|
|
m_driver->StartupNMEA();
|
|
}
|
|
}
|
|
return (m_nmea != NULL);
|
|
}
|
|
|
|
void modem::StopNMEA()
|
|
{
|
|
if (m_nmea != NULL)
|
|
{
|
|
if (!m_mux || !m_driver)
|
|
{
|
|
ESP_LOGE(TAG, "StopNMEA failed: MUX or driver not available");
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGV(TAG, "Stopping NMEA");
|
|
m_driver->ShutdownNMEA();
|
|
m_nmea->Shutdown();
|
|
delete m_nmea;
|
|
m_nmea = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::StartMux()
|
|
{
|
|
if (m_mux == NULL)
|
|
{
|
|
ESP_LOGV(TAG, "Starting MUX");
|
|
m_mux = new GsmMux(this, m_mux_channels);
|
|
m_mux->Startup();
|
|
}
|
|
}
|
|
|
|
void modem::StopMux()
|
|
{
|
|
if (m_mux != NULL)
|
|
{
|
|
ESP_LOGV(TAG, "Stopping MUX");
|
|
m_mux->Shutdown();
|
|
delete m_mux;
|
|
m_mux = NULL;
|
|
}
|
|
}
|
|
|
|
void modem::StartPPP()
|
|
{
|
|
if (m_ppp == NULL)
|
|
{
|
|
ESP_LOGV(TAG, "Launching PPP");
|
|
m_ppp = new GsmPPPOS(m_mux, m_mux_channel_DATA);
|
|
}
|
|
ESP_LOGV(TAG, "Starting PPP");
|
|
m_ppp->Initialise(m_mux, m_mux_channel_DATA);
|
|
m_ppp->Startup();
|
|
}
|
|
|
|
void modem::StopPPP()
|
|
{
|
|
if (m_ppp != NULL)
|
|
{
|
|
ESP_LOGV(TAG, "Stopping PPP");
|
|
m_ppp->Shutdown(true);
|
|
}
|
|
}
|
|
|
|
void modem::SetCellularModemDriver(const char* ModelType)
|
|
{
|
|
if (m_driver)
|
|
{
|
|
ESP_LOGD(TAG, "Remove old '%s' modem driver", m_driver->GetModel());
|
|
delete m_driver;
|
|
m_driver = NULL;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Set modem driver to '%s'", ModelType);
|
|
m_driver = MyCellularModemFactory.NewCellularModemDriver(ModelType);
|
|
m_model = std::string(ModelType);
|
|
m_mux_channels = m_driver->GetMuxChannels();
|
|
m_mux_channel_CTRL = m_driver->GetMuxChannelCTRL();
|
|
m_mux_channel_NMEA = m_driver->GetMuxChannelNMEA();
|
|
m_mux_channel_DATA = m_driver->GetMuxChannelDATA();
|
|
m_mux_channel_POLL = m_driver->GetMuxChannelPOLL();
|
|
m_mux_channel_CMD = m_driver->GetMuxChannelCMD();
|
|
|
|
if (m_model != "auto")
|
|
MyEvents.SignalEvent("system.modem.installed", NULL);
|
|
}
|
|
|
|
void modem::Ticker(std::string event, void* data)
|
|
{
|
|
modem_or_uart_event_t ev;
|
|
|
|
ev.event.type = TICKER1;
|
|
|
|
QueueHandle_t queue = m_queue;
|
|
if (queue) xQueueSend(queue,&ev,0);
|
|
}
|
|
|
|
void modem::EventListener(std::string event, void* data)
|
|
{
|
|
if (event == "system.shuttingdown")
|
|
{
|
|
if (m_state1 != PoweredOff)
|
|
{
|
|
MyBoot.ShutdownPending(TAG);
|
|
SendSetState1(PoweringOff); // We are not in the task, so queue the state change
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::IncomingMuxData(GsmMuxChannel* channel)
|
|
{
|
|
// The MUX has indicated there is data on the specified channel
|
|
|
|
// This maybe a big performance hit, so disable for the moment
|
|
// ESP_LOGD(TAG, "IncomingMuxData(CHAN=%d, buffer used=%d)",channel->m_channel,channel->m_buffer.UsedSpace());
|
|
|
|
if (channel->m_channel == m_mux_channel_CTRL)
|
|
{
|
|
channel->m_buffer.EmptyAll();
|
|
}
|
|
else if (channel->m_channel == m_mux_channel_NMEA)
|
|
{
|
|
StandardIncomingHandler(channel->m_channel, &channel->m_buffer);
|
|
}
|
|
else if (channel->m_channel == m_mux_channel_DATA)
|
|
{
|
|
if (m_state1 == NetMode)
|
|
{
|
|
uint8_t buf[32];
|
|
size_t n;
|
|
while ((m_ppp != NULL)&&(n = channel->m_buffer.Pop(sizeof(buf),buf)) > 0)
|
|
{
|
|
m_ppp->IncomingData(buf,n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StandardIncomingHandler(channel->m_channel, &channel->m_buffer);
|
|
}
|
|
}
|
|
else if (channel->m_channel == m_mux_channel_POLL)
|
|
{
|
|
StandardIncomingHandler(channel->m_channel, &channel->m_buffer);
|
|
}
|
|
else if (channel->m_channel == m_mux_channel_CMD)
|
|
{
|
|
StandardIncomingHandler(channel->m_channel, &channel->m_buffer);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(TAG, "Unrecognised mux channel #%d", channel->m_channel);
|
|
}
|
|
}
|
|
|
|
void modem::SendSetState1(modem_state1_t newstate)
|
|
{
|
|
modem_or_uart_event_t ev;
|
|
|
|
ev.event.type = SETSTATE;
|
|
ev.event.data.newstate = newstate;
|
|
|
|
QueueHandle_t queue = m_queue;
|
|
if (queue) xQueueSend(queue,&ev,0);
|
|
}
|
|
|
|
bool modem::IsStarted()
|
|
{
|
|
return (m_task != NULL);
|
|
}
|
|
|
|
void modem::SetNetworkRegistration(network_regtype_t regtype, network_registration_t netreg)
|
|
{
|
|
if (netreg != m_netreg_d[regtype])
|
|
{
|
|
m_netreg_d[regtype] = netreg;
|
|
|
|
// Need to re-calculate m_netreg as best of these
|
|
network_registration_t highest = Unknown;
|
|
for (size_t k=0; k<CELLULAR_NETREG_COUNT; k++)
|
|
{
|
|
if (highest < m_netreg_d[k]) highest = m_netreg_d[k];
|
|
}
|
|
if (highest != m_netreg)
|
|
{
|
|
m_netreg = highest;
|
|
const char *v = ModemNetRegName(m_netreg);
|
|
ESP_LOGI(TAG, "Network Registration status: %s", v);
|
|
StdMetrics.ms_m_net_mdm_netreg->SetValue(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::SetProvider(std::string provider)
|
|
{
|
|
// Trim provider, if necessary
|
|
provider.erase(provider.begin(),
|
|
std::find_if(provider.begin(), provider.end(),
|
|
std::not1(std::ptr_fun<int, int>(std::isspace))));
|
|
|
|
if (m_provider.compare(provider) != 0)
|
|
{
|
|
m_provider = provider;
|
|
ESP_LOGI(TAG, "Network Provider is: %s",m_provider.c_str());
|
|
StdMetrics.ms_m_net_mdm_network->SetValue(m_provider);
|
|
if (StdMetrics.ms_m_net_type->AsString() == "modem")
|
|
{
|
|
StdMetrics.ms_m_net_provider->SetValue(m_provider);
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::SetSignalQuality(int newsq)
|
|
{
|
|
if (m_sq != newsq)
|
|
{
|
|
m_sq = newsq;
|
|
ESP_LOGI(TAG, "Signal Quality is: %d (%d dBm)", m_sq, UnitConvert(sq, dbm, m_sq));
|
|
StdMetrics.ms_m_net_mdm_sq->SetValue(m_sq, sq);
|
|
if (StdMetrics.ms_m_net_type->AsString() == "modem")
|
|
{
|
|
StdMetrics.ms_m_net_sq->SetValue(m_sq, sq);
|
|
}
|
|
}
|
|
}
|
|
|
|
void modem::ClearNetMetrics()
|
|
{
|
|
m_netreg = Unknown;
|
|
for (size_t k=0; k<CELLULAR_NETREG_COUNT; k++) { m_netreg_d[k] = Unknown; }
|
|
StdMetrics.ms_m_net_mdm_netreg->Clear();
|
|
|
|
m_provider = "";
|
|
StdMetrics.ms_m_net_mdm_network->Clear();
|
|
|
|
m_sq = 99;
|
|
StdMetrics.ms_m_net_mdm_sq->Clear();
|
|
|
|
if (StdMetrics.ms_m_net_type->AsString() == "modem")
|
|
{
|
|
StdMetrics.ms_m_net_provider->Clear();
|
|
StdMetrics.ms_m_net_sq->Clear();
|
|
}
|
|
}
|
|
|
|
void modem::UpdateNetMetrics()
|
|
{
|
|
if (StdMetrics.ms_m_net_type->AsString() == "modem")
|
|
{
|
|
StdMetrics.ms_m_net_provider->SetValue(m_provider);
|
|
StdMetrics.ms_m_net_sq->SetValue(m_sq, sq);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Command interfaces
|
|
|
|
void cellular_tx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
std::string msg;
|
|
for (int k=0; k<argc; k++)
|
|
{
|
|
if (k>0)
|
|
{
|
|
msg.append(" ");
|
|
}
|
|
msg.append(argv[k]);
|
|
}
|
|
msg.append("\r\n");
|
|
MyModem->tx(msg.c_str(),msg.length());
|
|
}
|
|
|
|
void cellular_muxtx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
int channel = atoi(argv[0]);
|
|
|
|
std::string msg;
|
|
for (int k=1; k<argc; k++)
|
|
{
|
|
if (k>1)
|
|
{
|
|
msg.append(" ");
|
|
}
|
|
msg.append(argv[k]);
|
|
}
|
|
msg.append("\r\n");
|
|
MyModem->muxtx(channel,msg.c_str(),msg.length());
|
|
}
|
|
|
|
void cellular_cmd(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
std::string msg;
|
|
|
|
MyModem->m_cmd_output.clear();
|
|
MyModem->m_cmd_running = true;
|
|
|
|
for (int k=0; k<argc; k++)
|
|
{
|
|
if (k>0)
|
|
{
|
|
msg.append(" ");
|
|
}
|
|
msg.append(argv[k]);
|
|
}
|
|
msg.append("\r\n");
|
|
if (!MyModem->txcmd(msg.c_str(),msg.length()))
|
|
{
|
|
if (verbosity >= COMMAND_RESULT_MINIMAL)
|
|
{
|
|
writer->puts("ERROR: MODEM command channel not available!");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Wait for output to stabilise
|
|
size_t cmdsize = UINT_MAX;
|
|
size_t iter = 0;
|
|
while ((MyModem->m_cmd_output.size() != cmdsize) && (iter < 5))
|
|
{
|
|
iter++;
|
|
cmdsize = MyModem->m_cmd_output.size();
|
|
vTaskDelay(pdMS_TO_TICKS(500));
|
|
}
|
|
|
|
MyModem->m_cmd_running = false;
|
|
if (verbosity >= COMMAND_RESULT_MINIMAL)
|
|
{
|
|
writer->write(MyModem->m_cmd_output.c_str(), MyModem->m_cmd_output.size());
|
|
}
|
|
MyModem->m_cmd_output.clear();
|
|
}
|
|
|
|
void cellular_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
MyModem->SupportSummary(writer, (strcmp(cmd->GetName(), "debug") == 0));
|
|
}
|
|
|
|
void modem_setstate(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
const char* statename = cmd->GetName();
|
|
|
|
if (!MyModem->IsStarted())
|
|
{
|
|
writer->puts("Error: MODEM task is not running");
|
|
return;
|
|
}
|
|
|
|
for (int newstate = (int)modem::None;
|
|
(newstate <= (int)modem::Development);
|
|
newstate++)
|
|
{
|
|
if (strcmp(statename,ModemState1Name((modem::modem_state1_t)newstate)) == 0)
|
|
{
|
|
writer->printf("Set modem to state: %s\n",statename);
|
|
MyModem->SendSetState1((modem::modem_state1_t)newstate);
|
|
return;
|
|
}
|
|
}
|
|
|
|
writer->printf("Error: Unrecognised state %s\n",statename);
|
|
}
|
|
|
|
void modem_gps_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
writer->printf("GPS status: autostart %s, currently %s.\n",
|
|
MyConfig.GetParamValueBool("modem", "enable.gps", false) ? "enabled" : "disabled",
|
|
(MyModem && MyModem->m_nmea) ? "running" : "not running");
|
|
}
|
|
|
|
void modem_gps_start(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
if (!MyModem || !MyModem->m_mux || !MyModem->m_mux->IsMuxUp())
|
|
{
|
|
writer->puts("ERROR: Modem not ready");
|
|
return;
|
|
}
|
|
if (MyModem->m_nmea)
|
|
{
|
|
writer->puts("GPS already running.");
|
|
return;
|
|
}
|
|
|
|
if (MyModem->StartNMEA(true))
|
|
{
|
|
writer->puts("GPS started (may take a minute to find satellites).");
|
|
}
|
|
else
|
|
{
|
|
writer->puts("ERROR: GPS startup failed.");
|
|
}
|
|
}
|
|
|
|
void modem_gps_stop(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
if (!MyModem || !MyModem->m_mux || !MyModem->m_mux->IsMuxUp())
|
|
{
|
|
writer->puts("ERROR: Modem not ready");
|
|
return;
|
|
}
|
|
if (!MyModem->m_nmea)
|
|
{
|
|
writer->puts("GPS already stopped.");
|
|
return;
|
|
}
|
|
|
|
MyModem->StopNMEA();
|
|
writer->puts("GPS stopped.");
|
|
}
|
|
|
|
void cellular_drivers(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
writer->puts("Type Name");
|
|
for (OvmsCellularModemFactory::map_modemdriver_t::iterator k=MyCellularModemFactory.m_drivermap.begin();
|
|
k!=MyCellularModemFactory.m_drivermap.end();
|
|
++k)
|
|
{
|
|
writer->printf("%-10.10s %s\n",k->first,k->second.name);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Development assistance functions
|
|
|
|
void modem::DevelopmentHexDump(const char* prefix, const char* data, size_t length, size_t colsize /*=16*/)
|
|
{
|
|
char* buffer = NULL;
|
|
int rlength = (int)length;
|
|
|
|
while (rlength>0)
|
|
{
|
|
rlength = FormatHexDump(&buffer, data, rlength, colsize);
|
|
data += colsize;
|
|
ESP_LOGI(TAG, "%s: %s", prefix, buffer);
|
|
}
|
|
|
|
if (buffer) free(buffer);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// Modem Initialisation and Registrations
|
|
|
|
class CellularModemInit
|
|
{
|
|
public: CellularModemInit();
|
|
} MyModemInit __attribute__ ((init_priority (4600)));
|
|
|
|
CellularModemInit::CellularModemInit()
|
|
{
|
|
ESP_LOGI(TAG, "Initialising CELLULAR (4600)");
|
|
|
|
OvmsCommand* cmd_cellular = MyCommandApp.RegisterCommand("cellular","CELLULAR MODEM framework",cellular_status, "", 0, 0, false);
|
|
cmd_cellular->RegisterCommand("tx","Transmit data on CELLULAR MODEM",cellular_tx, "", 1, INT_MAX);
|
|
cmd_cellular->RegisterCommand("muxtx","Transmit data on CELLULAR MODEM MUX",cellular_muxtx, "<chan> <data>", 2, INT_MAX);
|
|
cmd_cellular->RegisterCommand("cmd","Send CELLULAR MODEM AT command",cellular_cmd, "<command>", 1, INT_MAX);
|
|
cmd_cellular->RegisterCommand("drivers","Show supported CELLULAR MODEM drivers",cellular_drivers, "", 0, 0);
|
|
OvmsCommand* cmd_status = cmd_cellular->RegisterCommand("status","Show CELLULAR MODEM status",cellular_status, "[debug]", 0, 0, false);
|
|
cmd_status->RegisterCommand("debug","Show extended CELLULAR MODEM status",cellular_status, "", 0, 0, false);
|
|
|
|
OvmsCommand* cmd_setstate = cmd_cellular->RegisterCommand("setstate","CELLULAR MODEM state change framework");
|
|
for (int x = modem::CheckPowerOff; x<=modem::PowerOffOn; x++)
|
|
{
|
|
cmd_setstate->RegisterCommand(ModemState1Name((modem::modem_state1_t)x),"Force CELLULAR MODEM state change",modem_setstate);
|
|
}
|
|
|
|
OvmsCommand* cmd_gps = cmd_cellular->RegisterCommand("gps", "GPS/GNSS state control", modem_gps_status);
|
|
cmd_gps->RegisterCommand("status", "GPS/GNSS status", modem_gps_status);
|
|
cmd_gps->RegisterCommand("start", "Start GPS/GNSS", modem_gps_start);
|
|
cmd_gps->RegisterCommand("stop", "Stop GPS/GNSS", modem_gps_stop);
|
|
|
|
MyConfig.RegisterParam("modem", "Modem Configuration", true, true);
|
|
// Our instances:
|
|
// 'driver': Driver to use (default: auto)
|
|
// 'gsmlock': GSM network to lock to (at COPS stage)
|
|
// 'apn': GSM APN
|
|
// 'apn.user': GSM username
|
|
// 'apn.password': GMS password
|
|
// 'enable.sms': Is SMS enabled? yes/no (default: yes)
|
|
// 'enable.net': Is NET enabled? yes/no (default: yes)
|
|
// 'enable.gps': Is GPS enabled? yes/no (default: no)
|
|
// 'enable.gpstime': use GPS time as system time? yes/no (default: no)
|
|
}
|