text: /*
  ;    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 = "netmanager";
  
  #include <string.h>
  #include <stdio.h>
  #include <lwip/ip_addr.h>
  #include <lwip/netif.h>
  #include <lwip/dns.h>
  #include <netinet/in.h>
  #include "metrics_standard.h"
  #include "ovms_peripherals.h"
  #include "ovms_netmanager.h"
  #include "ovms_command.h"
  #include "ovms_config.h"
  #include "ovms_module.h"
  
  #ifndef CONFIG_OVMS_NETMAN_TASK_PRIORITY
  #define CONFIG_OVMS_NETMAN_TASK_PRIORITY 5
  #endif
  
  OvmsNetManager MyNetManager __attribute__ ((init_priority (8999)));
  
  void network_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
    {
    struct netif *ni;
    for (ni = netif_list; ni; ni = ni->next)
      {
      if (ni->name[0]=='l' && ni->name[1]=='o')
        continue;
      writer->printf("Interface#%d: %c%c%d (ifup=%d linkup=%d)\n",
        ni->num,ni->name[0],ni->name[1],ni->num,
        ((ni->flags & NETIF_FLAG_UP) != 0),
        ((ni->flags & NETIF_FLAG_LINK_UP) != 0));
      writer->printf("  IPv4: " IPSTR "/" IPSTR " gateway " IPSTR "\n",
        IP2STR(&ni->ip_addr.u_addr.ip4), IP2STR(&ni->netmask.u_addr.ip4), IP2STR(&ni->gw.u_addr.ip4));
      writer->puts("");
      }
  
    writer->printf("DNS:");
    int dnsservers = 0;
    for (int k=0;k<DNS_MAX_SERVERS;k++)
      {
      const ip_addr_t* srv = dns_getserver(k);
      if (! ip_addr_isany(srv))
        {
        dnsservers++;
        if (srv->type == IPADDR_TYPE_V4)
          writer->printf(" " IPSTR, IP2STR(&srv->u_addr.ip4));
        else if (srv->type == IPADDR_TYPE_V6)
          writer->printf(" " IPSTR, IP2STR(&srv->u_addr.ip6));
        }
      }
    if (dnsservers == 0)
      writer->puts(" None");
    else
      writer->puts("");
  
    if (netif_default)
      {
      writer->printf("\nDefault Interface: %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")\n",
        netif_default->name[0], netif_default->name[1], netif_default->num,
        IP2STR(&netif_default->ip_addr.u_addr.ip4),
        IP2STR(&netif_default->netmask.u_addr.ip4),
        IP2STR(&netif_default->gw.u_addr.ip4));
      }
    else
      {
      writer->printf("\nDefault Interface: None\n");
      }
    }
  
  void network_restart(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
    {
    writer->puts("Restarting network...");
    vTaskDelay(pdMS_TO_TICKS(100));
    MyNetManager.RestartNetwork();
    }
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
  void network_connections(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
    {
    if (!MyNetManager.MongooseRunning())
      {
      writer->puts("ERROR: Mongoose task not running");
      return;
      }
  
    if (xTaskGetCurrentTaskHandle() == MyNetManager.GetMongooseTaskHandle())
      {
      // inline execution in mongoose context:
      if (strcmp(cmd->GetName(), "close") == 0)
        {
        uint32_t id = strtol(argv[0], NULL, 16);
        int cnt = MyNetManager.CloseConnection(id);
        if (cnt == 0)
          writer->printf("ERROR: connection ID %08x not found\n", id);
        else
          writer->printf("Close signal sent to %d connection(s)\n", cnt);
        }
      else if (strcmp(cmd->GetName(), "cleanup") == 0)
        {
        int cnt = MyNetManager.CleanupConnections();
        if (cnt == 0)
          writer->puts("All connections OK");
        else
          writer->printf("Close signal sent to %d connection(s)\n", cnt);
        }
      else
        {
        MyNetManager.ListConnections(verbosity, writer);
        }
      }
    else
      {
      // remote execution from non-mongoose context:
      static netman_job_t job;
      memset(&job, 0, sizeof(job));
      if (strcmp(cmd->GetName(), "close") == 0)
        {
        job.cmd = nmc_close;
        job.close.id = strtol(argv[0], NULL, 16);
        if (!MyNetManager.ExecuteJob(&job, pdMS_TO_TICKS(5000)))
          writer->puts("ERROR: job failed");
        else if (job.close.cnt == 0)
          writer->printf("ERROR: connection ID %08x not found\n", job.close.id);
        else
          writer->printf("Close signal sent to %d connection(s)\n", job.close.cnt);
        }
      else if (strcmp(cmd->GetName(), "cleanup") == 0)
        {
        job.cmd = nmc_cleanup;
        if (!MyNetManager.ExecuteJob(&job, pdMS_TO_TICKS(5000)))
          writer->puts("ERROR: job failed");
        else if (job.cleanup.cnt == 0)
          writer->puts("All connections OK");
        else
          writer->printf("Close signal sent to %d connection(s)\n", job.cleanup.cnt);
        }
      else
        {
        // Note: a timeout occurring after the job has begun processing will result in a crash
        //  here due to the StringWriter being deleted. This only applies to the list command.
        //  If this becomes a problem, we need to upgrade the job processing from send/notify
        //  to a full send/receive scheme.
        job.cmd = nmc_list;
        job.list.verbosity = verbosity;
        job.list.buf = new StringWriter();
        if (!MyNetManager.ExecuteJob(&job, pdMS_TO_TICKS(10000)))
          writer->puts("ERROR: job failed");
        else
          writer->write(job.list.buf->data(), job.list.buf->size());
        delete job.list.buf;
        job.list.buf = NULL;
        }
      }
    }
  
  #endif // CONFIG_OVMS_SC_GPL_MONGOOSE
  
  OvmsNetManager::OvmsNetManager()
    {
    ESP_LOGI(TAG, "Initialising NETMANAGER (8999)");
    m_connected_wifi = false;
    m_connected_modem = false;
    m_connected_any = false;
    m_wifi_sta = false;
    m_wifi_good = false;
    m_wifi_ap = false;
    m_network_any = false;
    m_cfg_wifi_sq_good = -87;
    m_cfg_wifi_sq_bad = -89;
  
    for (int i=0; i<DNS_MAX_SERVERS; i++)
      {
      m_dns_modem[i] = (ip_addr_t)ip_addr_any;
      m_dns_wifi[i] = (ip_addr_t)ip_addr_any;
      m_previous_dns[i] = (ip_addr_t)ip_addr_any;
      }
    m_previous_name[0] = m_previous_name[1] = 0;
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    m_mongoose_task = 0;
    m_mongoose_running = false;
    m_jobqueue = xQueueCreate(CONFIG_OVMS_HW_NETMANAGER_QUEUE_SIZE, sizeof(netman_job_t*));
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
    // Register our commands
    OvmsCommand* cmd_network = MyCommandApp.RegisterCommand("network","NETWORK framework",network_status, "", 0, 0, false);
    cmd_network->RegisterCommand("status","Show network status",network_status, "", 0, 0, false);
    cmd_network->RegisterCommand("restart","Restart network",network_restart, "", 0, 0, false);
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    cmd_network->RegisterCommand("list", "List network connections", network_connections);
    cmd_network->RegisterCommand("close", "Close network connection(s)", network_connections, "<id>\nUse ID from connection list / 0 to close all", 1, 1);
    cmd_network->RegisterCommand("cleanup", "Close orphaned network connections", network_connections);
  #endif // CONFIG_OVMS_SC_GPL_MONGOOSE
  
    // Register our events
    #undef bind  // Kludgy, but works
    using std::placeholders::_1;
    using std::placeholders::_2;
  
    MyEvents.RegisterEvent(TAG,"system.wifi.sta.gotip", std::bind(&OvmsNetManager::WifiStaGotIP, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.sta.lostip", std::bind(&OvmsNetManager::WifiStaLostIP, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"network.wifi.sta.good", std::bind(&OvmsNetManager::WifiStaGood, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"network.wifi.sta.bad", std::bind(&OvmsNetManager::WifiStaBad, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.sta.stop", std::bind(&OvmsNetManager::WifiStaStop, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.sta.connected", std::bind(&OvmsNetManager::WifiStaConnected, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.sta.disconnected", std::bind(&OvmsNetManager::WifiStaStop, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.down", std::bind(&OvmsNetManager::WifiStaStop, this, _1, _2));
  
    MyEvents.RegisterEvent(TAG,"system.wifi.ap.start", std::bind(&OvmsNetManager::WifiUpAP, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.ap.stop", std::bind(&OvmsNetManager::WifiDownAP, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.wifi.ap.sta.disconnected", std::bind(&OvmsNetManager::WifiApStaDisconnect, this, _1, _2));
  
    MyEvents.RegisterEvent(TAG,"system.modem.gotip", std::bind(&OvmsNetManager::ModemUp, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.modem.stop", std::bind(&OvmsNetManager::ModemDown, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.modem.down", std::bind(&OvmsNetManager::ModemDown, this, _1, _2));
  
    MyEvents.RegisterEvent(TAG,"config.mounted", std::bind(&OvmsNetManager::ConfigChanged, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"config.changed", std::bind(&OvmsNetManager::ConfigChanged, this, _1, _2));
    MyEvents.RegisterEvent(TAG,"system.shuttingdown", std::bind(&OvmsNetManager::EventSystemShuttingDown, this, _1, _2));
  
    MyConfig.RegisterParam("network", "Network Configuration", true, true);
    // Our instances:
    //   dns                Space-separated list of DNS servers
    //   wifi.sq.good       Threshold for usable wifi signal [dBm], default -87
    //   wifi.sq.bad        Threshold for unusable wifi signal [dBm], default -89
  
    MyMetrics.RegisterListener(TAG, MS_N_WIFI_SQ, std::bind(&OvmsNetManager::WifiStaCheckSQ, this, _1));
    }
  
  OvmsNetManager::~OvmsNetManager()
    {
    }
  
  void OvmsNetManager::RestartNetwork()
    {
  #ifdef CONFIG_OVMS_COMP_WIFI
    if (MyPeripherals && MyPeripherals->m_esp32wifi)
      MyPeripherals->m_esp32wifi->Restart();
  #endif // #ifdef CONFIG_OVMS_COMP_WIFI
  
  #ifdef CONFIG_OVMS_COMP_CELLULAR
    if (MyPeripherals && MyPeripherals->m_cellular_modem)
      MyPeripherals->m_cellular_modem->Restart();
  #endif // CONFIG_OVMS_COMP_CELLULAR
    }
  
  void OvmsNetManager::WifiConnect()
    {
    if (m_wifi_sta && !m_connected_wifi)
      {
      m_connected_wifi = true;
      PrioritiseAndIndicate();
  
      MyEvents.SignalEvent("network.wifi.up",NULL);
  
      if (m_connected_modem)
        {
        ESP_LOGI(TAG, "WIFI client up (with MODEM up): reconfigured for WIFI client priority");
        MyEvents.SignalEvent("network.reconfigured",NULL);
        }
      else
        {
        ESP_LOGI(TAG, "WIFI client up (with MODEM down): starting network with WIFI client");
        MyEvents.SignalEvent("network.up",NULL);
        }
  
      #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
        StartMongooseTask();
      #endif
  
      MyEvents.SignalEvent("network.interface.change",NULL);
      }
    }
  
  void OvmsNetManager::WifiDisconnect()
    {
    if (m_connected_wifi)
      {
      m_connected_wifi = false;
      PrioritiseAndIndicate();
  
      MyEvents.SignalEvent("network.wifi.down",NULL);
  
      if (m_connected_any)
        {
        ESP_LOGI(TAG, "WIFI client down (with MODEM up): reconfigured for MODEM priority");
        MyEvents.SignalEvent("network.reconfigured",NULL);
        #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
          ScheduleCleanup();
        #endif
        }
      else
        {
        ESP_LOGI(TAG, "WIFI client down (with MODEM down): network connectivity has been lost");
        MyEvents.SignalEvent("network.down",NULL);
  
        #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
          StopMongooseTask();
        #endif
        }
  
      MyEvents.SignalEvent("network.interface.change",NULL);
      }
    }
  
  void OvmsNetManager::WifiStaGotIP(std::string event, void* data)
    {
    m_wifi_sta = true;
    ESP_LOGI(TAG, "WIFI client got IP");
    SaveDNSServer(m_dns_wifi);
  
    // Re-prioritise, just in case, as Wifi stack seems to mess with this
    // (in particular if an AP interface is up, and STA goes down, Wifi
    // stack seems to switch default interface to AP)
    PrioritiseAndIndicate();
    #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
      ScheduleCleanup();
    #endif
  
    WifiStaCheckSQ(StdMetrics.ms_m_net_wifi_sq);
    }
  
  void OvmsNetManager::WifiStaLostIP(std::string event, void* data)
    {
    // Re-prioritise, just in case, as Wifi stack seems to mess with this
    // (in particular if an AP interface is up, and STA goes down, Wifi
    // stack seems to switch default interface to AP)
    PrioritiseAndIndicate();
    #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
      ScheduleCleanup();
    #endif
    }
  
  void OvmsNetManager::WifiStaConnected(std::string event, void* data)
    {
    // Re-prioritise, just in case, as Wifi stack seems to mess with this
    // (in particular if an AP interface is up, and STA goes down, Wifi
    // stack seems to switch default interface to AP)
    PrioritiseAndIndicate();
    #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
      ScheduleCleanup();
    #endif
    }
  
  void OvmsNetManager::WifiStaStop(std::string event, void* data)
    {
    WifiStaSetSQ(false);
    if (m_wifi_sta)
      {
      m_wifi_sta = false;
      ESP_LOGI(TAG, "WIFI client stop");
      if (m_connected_wifi)
        {
        WifiDisconnect();
        return;
        }
      }
  
    // Re-prioritise, just in case, as Wifi stack seems to mess with this
    // (in particular if an AP interface is up, and STA goes down, Wifi
    // stack seems to switch default interface to AP)
    PrioritiseAndIndicate();
    #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
      ScheduleCleanup();
    #endif
    }
  
  void OvmsNetManager::WifiStaGood(std::string event, void* data)
    {
    if (m_wifi_sta && !m_connected_wifi)
      {
      ESP_LOGI(TAG, "WIFI client has good signal quality (%.1f dBm); connect",
               StdMetrics.ms_m_net_wifi_sq->AsFloat());
      WifiConnect();
      }
    }
  
  void OvmsNetManager::WifiStaBad(std::string event, void* data)
    {
    if (m_wifi_sta && m_connected_wifi)
      {
      ESP_LOGI(TAG, "WIFI client has bad signal quality (%.1f dBm); disconnect",
               StdMetrics.ms_m_net_wifi_sq->AsFloat());
      // Stop using the Wifi network:
      WifiDisconnect();
      // If enabled, start reconnecting immediately (for fast transition to next best
      //  AP instead of trying to reassociate to current AP):
      if (MyPeripherals && MyPeripherals->m_esp32wifi &&
          MyConfig.GetParamValueBool("network", "wifi.bad.reconnect") == true)
        {
        MyPeripherals->m_esp32wifi->Reconnect(NULL);
        }
      }
    }
  
  void OvmsNetManager::WifiStaCheckSQ(OvmsMetric* metric)
    {
    if (m_wifi_sta)
      {
      float sq = StdMetrics.ms_m_net_wifi_sq->AsFloat();
      if ((!metric || !m_wifi_good) && sq >= m_cfg_wifi_sq_good)
        {
        m_wifi_good = true;
        MyEvents.SignalEvent("network.wifi.sta.good", NULL);
        }
      else if ((!metric || m_wifi_good) && sq <= m_cfg_wifi_sq_bad)
        {
        m_wifi_good = false;
        MyEvents.SignalEvent("network.wifi.sta.bad", NULL);
        }
      }
    }
  
  void OvmsNetManager::WifiStaSetSQ(bool good)
    {
    if (m_wifi_sta && m_wifi_good != good)
      {
      m_wifi_good = good;
      MyEvents.SignalEvent(good ? "network.wifi.sta.good" : "network.wifi.sta.bad", NULL);
      }
    }
  
  void OvmsNetManager::WifiUpAP(std::string event, void* data)
    {
    m_wifi_ap = true;
    PrioritiseAndIndicate();
  
    ESP_LOGI(TAG, "WIFI access point is up");
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    StartMongooseTask();
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
    MyEvents.SignalEvent("network.interface.change",NULL);
    }
  
  void OvmsNetManager::WifiDownAP(std::string event, void* data)
    {
    m_wifi_ap = false;
    PrioritiseAndIndicate();
  
    ESP_LOGI(TAG, "WIFI access point is down");
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    StopMongooseTask();
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
    MyEvents.SignalEvent("network.interface.change",NULL);
    }
  
  void OvmsNetManager::WifiApStaDisconnect(std::string event, void* data)
    {
    ESP_LOGI(TAG, "WIFI access point station disconnected");
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    ScheduleCleanup();
  #endif
    }
  
  void OvmsNetManager::ModemUp(std::string event, void* data)
    {
    m_connected_modem = true;
    SaveDNSServer(m_dns_modem);
    PrioritiseAndIndicate();
  
    MyEvents.SignalEvent("network.modem.up",NULL);
  
    if (!m_connected_wifi)
      {
      ESP_LOGI(TAG, "MODEM up (with WIFI client down): starting network with MODEM");
      MyEvents.SignalEvent("network.up",NULL);
      }
    else
      {
      ESP_LOGI(TAG, "MODEM up (with WIFI client up): staying with WIFI client priority");
      }
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    StartMongooseTask();
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
    MyEvents.SignalEvent("network.interface.change",NULL);
    }
  
  void OvmsNetManager::ModemDown(std::string event, void* data)
    {
    if (m_connected_modem)
      {
      m_connected_modem = false;
      PrioritiseAndIndicate();
  
      MyEvents.SignalEvent("network.modem.down",NULL);
  
      if (m_connected_any)
        {
        ESP_LOGI(TAG, "MODEM down (with WIFI client up): staying with WIFI client priority");
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
        ScheduleCleanup();
  #endif
        }
      else
        {
        ESP_LOGI(TAG, "MODEM down (with WIFI client down): network connectivity has been lost");
        MyEvents.SignalEvent("network.down",NULL);
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
        StopMongooseTask();
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
        }
  
      MyEvents.SignalEvent("network.interface.change",NULL);
      }
    else
      {
      // Re-prioritise, just in case, as Wifi stack seems to mess with this
      // (in particular if an AP interface is up, and STA goes down, Wifi
      // stack seems to switch default interface to AP)
      PrioritiseAndIndicate();
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
      ScheduleCleanup();
  #endif
      }
    }
  
  void OvmsNetManager::ConfigChanged(std::string event, void* data)
    {
    OvmsConfigParam* param = (OvmsConfigParam*)data;
    if (!param || param->GetName() == "network")
      {
      // Network config has been changed, apply:
      m_cfg_wifi_sq_good = MyConfig.GetParamValueFloat("network", "wifi.sq.good", -87);
      m_cfg_wifi_sq_bad  = MyConfig.GetParamValueFloat("network", "wifi.sq.bad",  -89);
      if (m_cfg_wifi_sq_good < m_cfg_wifi_sq_bad)
        {
        float x = m_cfg_wifi_sq_good;
        m_cfg_wifi_sq_good = m_cfg_wifi_sq_bad;
        m_cfg_wifi_sq_bad = x;
        }
      WifiStaCheckSQ(NULL);
      if (param && m_network_any)
        PrioritiseAndIndicate();
      }
    }
  
  void OvmsNetManager::EventSystemShuttingDown(std::string event, void* data)
    {
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    StopMongooseTask();
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
    }
  
  void OvmsNetManager::SaveDNSServer(ip_addr_t* dnsstore)
    {
    for (int i=0; i<DNS_MAX_SERVERS; i++)
      {
      dnsstore[i] = *dns_getserver(i);
      ESP_LOGD(TAG, "Saved DNS#%d %s", i, inet_ntoa(dnsstore[i]));
      }
    }
  
  void OvmsNetManager::SetDNSServer(ip_addr_t* dnsstore)
    {
    // Read DNS configuration:
    std::string servers = MyConfig.GetParamValue("network", "dns");
    if (!servers.empty())
      {
      int spos = 0;
      size_t sep = 0;
      while ((spos<DNS_MAX_SERVERS)&&(sep != string::npos))
        {
        // Set the specified DNS servers
        size_t next = servers.find(' ',sep);
        std::string cserver;
        ip_addr_t serverip;
        if (next == std::string::npos)
          {
          // The last one
          cserver = std::string(servers,sep,string::npos);
          sep = string::npos;
          }
        else
          {
          // One of many
          cserver = std::string(servers,sep,next-sep);
          sep = next+1;
          }
        ip4_addr_set_u32(ip_2_ip4(&serverip), ipaddr_addr(cserver.c_str()));
        serverip.type = IPADDR_TYPE_V4;
        if (!ip_addr_cmp(&serverip, &m_previous_dns[spos]))
          {
          m_previous_dns[spos] = serverip;
          ESP_LOGI(TAG, "Set DNS#%d %s",spos,cserver.c_str());
          }
        dns_setserver(spos++, &serverip);
        }
      for (;spos<DNS_MAX_SERVERS;spos++)
        {
        dns_setserver(spos, IP_ADDR_ANY);
        }
      }
    else if (dnsstore)
      {
      // Set stored DNS servers:
      for (int i=0; i<DNS_MAX_SERVERS; i++)
        {
        if (ip_addr_cmp(&dnsstore[i], &m_previous_dns[i]))
          {
          m_previous_dns[i] = dnsstore[i];
          ESP_LOGI(TAG, "Set DNS#%d %s", i, inet_ntoa(dnsstore[i]));
          }
        dns_setserver(i, &dnsstore[i]);
        }
      }
    }
  
  void OvmsNetManager::SetNetType(std::string type)
    {
    if (type == "wifi")
      {
      StdMetrics.ms_m_net_type->SetValue(type);
  #ifdef CONFIG_OVMS_COMP_WIFI
      if (MyPeripherals && MyPeripherals->m_esp32wifi)
        MyPeripherals->m_esp32wifi->UpdateNetMetrics();
  #endif // CONFIG_OVMS_COMP_WIFI
      }
    else if (type == "modem")
      {
      StdMetrics.ms_m_net_type->SetValue(type);
  #ifdef CONFIG_OVMS_COMP_CELLULAR
      if (MyPeripherals && MyPeripherals->m_cellular_modem)
        MyPeripherals->m_cellular_modem->UpdateNetMetrics();
  #endif // CONFIG_OVMS_COMP_CELLULAR
      }
    else
      {
        StdMetrics.ms_m_net_type->SetValue("none");
        StdMetrics.ms_m_net_provider->SetValue("");
        StdMetrics.ms_m_net_sq->SetValue(0, dbm);
      }
    }
  
  void SafePrioritiseAndIndicate(void* ctx)
    {
    MyNetManager.DoSafePrioritiseAndIndicate();
    }
  
  void OvmsNetManager::PrioritiseAndIndicate()
    {
    tcpip_callback_with_block(SafePrioritiseAndIndicate, NULL, 1);
    }
  
  void OvmsNetManager::DoSafePrioritiseAndIndicate()
    {
    const char *search = NULL;
    ip_addr_t* dns = NULL;
  
    // A convenient place to keep track of connectivity in general
    m_connected_any = m_connected_wifi || m_connected_modem;
    m_network_any = m_connected_wifi || m_connected_modem || m_wifi_ap;
  
    // Priority order...
    if (m_connected_wifi)
      {
      // Wifi is up
      SetNetType("wifi");
      search = "st";
      dns = m_dns_wifi;
      }
    else if (m_connected_modem)
      {
      // Modem is up
      SetNetType("modem");
      search = "pp";
      dns = m_dns_modem;
      }
  
    if (search == NULL)
      {
      SetNetType("none");
      return;
      }
  
    for (struct netif *pri = netif_list; pri != NULL; pri=pri->next)
      {
      if ((pri->name[0]==search[0])&&
          (pri->name[1]==search[1]))
        {
        if (search[0] != m_previous_name[0] || search[1] != m_previous_name[1])
          {
          ESP_LOGI(TAG, "Interface priority is %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")",
            pri->name[0], pri->name[1], pri->num,
            IP2STR(&pri->ip_addr.u_addr.ip4), IP2STR(&pri->netmask.u_addr.ip4), IP2STR(&pri->gw.u_addr.ip4));
          m_previous_name[0] = search[0];
          m_previous_name[1] = search[1];
          for (int i=0; i<DNS_MAX_SERVERS; i++)
            m_previous_dns[i] = (ip_addr_t)ip_addr_any;
          }
        netif_set_default(pri);
        SetDNSServer(dns);
        return;
        }
      }
    ESP_LOGE(TAG, "Inconsistent state: no interface of type '%s' found", search);
    }
  
  #ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  
  static void MongooseRawTask(void *pvParameters)
    {
    OvmsNetManager* me = (OvmsNetManager*)pvParameters;
    me->MongooseTask();
    }
  
  void OvmsNetManager::MongooseTask()
    {
    int pri, lastpri = CONFIG_OVMS_NETMAN_TASK_PRIORITY;
  
    // Initialise the mongoose manager
    ESP_LOGD(TAG, "MongooseTask starting");
    mg_mgr_init(&m_mongoose_mgr, NULL);
    MyEvents.SignalEvent("network.mgr.init",NULL);
  
    m_mongoose_running = true;
  
    // Main event loop
    while (m_mongoose_running)
      {
      // poll interfaces:
      if (mg_mgr_poll(&m_mongoose_mgr, 250) == 0)
        {
        ESP_LOGD(TAG, "MongooseTask: no interfaces available => exit");
        break;
        }
  
      // check for netmanager control jobs:
      ProcessJobs();
  
      // Detect broken mutex priority inheritance:
      // (may be removed if solved by esp-idf commit 22d636b7b0d6006e06b5b3cfddfbf6e2cf69b4b8)
      if ((pri = uxTaskPriorityGet(NULL)) != lastpri)
        {
        ESP_LOGD(TAG, "MongooseTask: priority changed from %d to %d", lastpri, pri);
        lastpri = pri;
        }
      }
  
    m_mongoose_running = false;
  
    // Shutdown cleanly
    ESP_LOGD(TAG, "MongooseTask stopping");
    MyEvents.SignalEvent("network.mgr.stop",NULL);
    mg_mgr_free(&m_mongoose_mgr);
    m_mongoose_task = NULL;
    vTaskDelete(NULL);
    }
  
  struct mg_mgr* OvmsNetManager::GetMongooseMgr()
    {
    return &m_mongoose_mgr;
    }
  
  bool OvmsNetManager::MongooseRunning()
    {
    return m_mongoose_running;
    }
  
  void OvmsNetManager::StartMongooseTask()
    {
    if (!m_mongoose_running)
      {
      if (m_network_any)
        {
        // wait for previous task to finish shutting down:
        while (m_mongoose_task)
          vTaskDelay(pdMS_TO_TICKS(50));
        // start new task:
        xTaskCreatePinnedToCore(MongooseRawTask, "OVMS NetMan",10*1024, (void*)this,
                                CONFIG_OVMS_NETMAN_TASK_PRIORITY, &m_mongoose_task, CORE(1));
        AddTaskToMap(m_mongoose_task);
        }
      }
    }
  
  void OvmsNetManager::StopMongooseTask()
    {
    if (!m_network_any)
      m_mongoose_running = false;
    }
  
  void OvmsNetManager::ProcessJobs()
    {
    netman_job_t* job;
    while (xQueueReceive(m_jobqueue, &job, 0) == pdTRUE)
      {
      ESP_LOGD(TAG, "MongooseTask: got cmd %d from %p", job->cmd, job->caller);
      switch (job->cmd)
        {
        case nmc_none:
          break;
        case nmc_list:
          job->list.cnt = ListConnections(job->list.verbosity, job->list.buf);
          break;
        case nmc_close:
          job->close.cnt = CloseConnection(job->close.id);
          break;
        case nmc_cleanup:
          job->cleanup.cnt = CleanupConnections();
          break;
        default:
          ESP_LOGW(TAG, "MongooseTask: got unknown cmd %d from %p", job->cmd, job->caller);
        }
      if (job->caller)
        xTaskNotifyGive(job->caller);
      ESP_LOGD(TAG, "MongooseTask: done cmd %d from %p", job->cmd, job->caller);
      }
    }
  
  bool OvmsNetManager::ExecuteJob(netman_job_t* job, TickType_t timeout /*=portMAX_DELAY*/)
    {
    if (!m_mongoose_running)
      return false;
    if (timeout)
      {
      job->caller = xTaskGetCurrentTaskHandle();
      xTaskNotifyWait(ULONG_MAX, ULONG_MAX, NULL, 0); // clear state
      }
    else
      job->caller = 0;
    ESP_LOGD(TAG, "send cmd %d from %p", job->cmd, job->caller);
    if (xQueueSend(m_jobqueue, &job, timeout) != pdTRUE)
      {
      ESP_LOGW(TAG, "ExecuteJob: cmd %d: queue overflow", job->cmd);
      return false;
      }
    if (timeout && ulTaskNotifyTake(pdTRUE, timeout) == 0)
      {
      // try to prevent delayed processing (cannot stop if already started):
      netman_cmd_t cmd = job->cmd;
      job->cmd = nmc_none;
      job->caller = 0;
      ESP_LOGW(TAG, "ExecuteJob: cmd %d: timeout", cmd);
      return false;
      }
    ESP_LOGD(TAG, "done cmd %d from %p", job->cmd, job->caller);
    return true;
    }
  
  void OvmsNetManager::ScheduleCleanup()
    {
    static netman_job_t job;
    memset(&job, 0, sizeof(job));
    job.cmd = nmc_cleanup;
    ExecuteJob(&job, 0);
    }
  
  int OvmsNetManager::ListConnections(int verbosity, OvmsWriter* writer)
    {
    if (!m_mongoose_running)
      return 0;
    mg_connection *c;
    int cnt = 0;
    char local[48], remote[48];
    writer->printf("ID        Flags     Handler   Local                  Remote\n");
    for (c = mg_next(&m_mongoose_mgr, NULL); c; c = mg_next(&m_mongoose_mgr, c))
      {
      if (c->flags & MG_F_LISTENING)
        continue;
      mg_conn_addr_to_str(c, local, sizeof(local), MG_SOCK_STRINGIFY_IP|MG_SOCK_STRINGIFY_PORT);
      mg_conn_addr_to_str(c, remote, sizeof(remote), MG_SOCK_STRINGIFY_IP|MG_SOCK_STRINGIFY_PORT|MG_SOCK_STRINGIFY_REMOTE);
      writer->printf("%08x  %08x  %08x  %-21s  %s\n", (uint32_t)c, (uint32_t)c->flags, (uint32_t)c->user_data, local, remote);
      cnt++;
      }
    return cnt;
    }
  
  int OvmsNetManager::CloseConnection(uint32_t id)
    {
    if (!m_mongoose_running)
      return 0;
    mg_connection *c;
    int cnt = 0;
    for (c = mg_next(&m_mongoose_mgr, NULL); c; c = mg_next(&m_mongoose_mgr, c))
      {
      if (c->flags & MG_F_LISTENING)
        continue;
      if (id == 0 || c == (mg_connection*)id)
        {
        c->flags |= MG_F_CLOSE_IMMEDIATELY;
        cnt++;
        }
      }
    return cnt;
    }
  
  int OvmsNetManager::CleanupConnections()
    {
    if (!m_mongoose_running)
      return 0;
  
    struct netif *ni;
    mg_connection *c;
    tcpip_adapter_sta_list_t ap_ip_list;
    union socket_address sa;
    socklen_t slen = sizeof(sa);
    int cnt = 0;
  
    // get IP addresses of stations connected to our wifi AP:
    ap_ip_list.num = 0;
    if (m_wifi_ap)
      {
      wifi_sta_list_t sta_list;
      if (esp_wifi_ap_get_sta_list(&sta_list) != ESP_OK)
        ESP_LOGW(TAG, "CleanupConnections: can't get AP station list");
      else if (tcpip_adapter_get_sta_list(&sta_list, &ap_ip_list) != ESP_OK)
        ESP_LOGW(TAG, "CleanupConnections: can't get AP station IP list");
      }
  
    for (c = mg_next(&m_mongoose_mgr, NULL); c; c = mg_next(&m_mongoose_mgr, c))
      {
      if (c->flags & MG_F_LISTENING)
        continue;
  
      // get local address:
      memset(&sa, 0, sizeof(sa));
      if (getsockname(c->sock, &sa.sa, &slen) != 0)
        {
        ESP_LOGW(TAG, "CleanupConnections: conn %08x: getsockname failed", (uint32_t)c);
        continue;
        }
  
      if (sa.sa.sa_family != AF_INET || sa.sin.sin_addr.s_addr == IPADDR_ANY || sa.sin.sin_addr.s_addr == IPADDR_NONE)
        continue;
  
      // find interface:
      for (ni = netif_list; ni; ni = ni->next)
        {
        if (sa.sin.sin_addr.s_addr == ip4_addr_get_u32(netif_ip4_addr(ni)))
          break;
        }
  
      if (ni)
        ESP_LOGD(TAG, "CleanupConnections: conn %08x -> iface %c%c%d", (uint32_t)c, ni->name[0], ni->name[1], ni->num);
      else
        ESP_LOGD(TAG, "CleanupConnections: conn %08x -> no iface", (uint32_t)c);
  
      if (!ni || !(ni->flags & NETIF_FLAG_UP) || !(ni->flags & NETIF_FLAG_LINK_UP))
        {
        ESP_LOGI(TAG, "CleanupConnections: closing conn %08x: interface/link down", (uint32_t)c);
        c->flags |= MG_F_CLOSE_IMMEDIATELY;
        cnt++;
        continue;
        }
  
      // check remote address on wifi AP:
      if (ni->name[0] == 'a' && ni->name[1] == 'p')
        {
        memset(&sa, 0, sizeof(sa));
        if (getpeername(c->sock, &sa.sa, &slen) != 0)
          {
          ESP_LOGW(TAG, "CleanupConnections: conn %08x: getpeername failed", (uint32_t)c);
          continue;
          }
        int i;
        for (i = 0; i < ap_ip_list.num; i++)
          {
          if (sa.sin.sin_addr.s_addr == ap_ip_list.sta[i].ip.addr)
            break;
          }
  
        if (i < ap_ip_list.num)
          {
          ESP_LOGD(TAG, "CleanupConnections: conn %08x -> AP IP " IPSTR, (uint32_t)c, IP2STR(&ap_ip_list.sta[i].ip));
          }
        else
          {
          ESP_LOGI(TAG, "CleanupConnections: closing conn %08x: AP peer disconnected", (uint32_t)c);
          c->flags |= MG_F_CLOSE_IMMEDIATELY;
          cnt++;
          }
        } // AP remotes
      } // connection loop
    return cnt;
    }
  
  bool OvmsNetManager::IsNetManagerTask()
    {
    // Return TRUE if the currently running task is the Net Manager task
    extern void *pxCurrentTCB[portNUM_PROCESSORS];
  
    return (m_mongoose_task == pxCurrentTCB[xPortGetCoreID()]);
    }
  
  #endif //#ifdef CONFIG_OVMS_SC_GPL_MONGOOSE
  