/*
;    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 = "events";

#include <string.h>
#include <stdio.h>
#include <esp_event_loop.h>
#include <esp_task_wdt.h>
#include "ovms_module.h"
#include "ovms_events.h"
#include "ovms_command.h"
#include "ovms_script.h"
#include "ovms_boot.h"

#ifdef CONFIG_OVMS_COMP_OTA
#include "ovms_ota.h"
#endif

OvmsEvents MyEvents __attribute__ ((init_priority (1200)));

typedef void (*event_signal_done_fn)(const char* event, void* data);

bool EventMap::GetCompletion(OvmsWriter* writer, const char* token) const
  {
  unsigned int index = 0;
  bool match = false;
  writer->SetCompletion(index, NULL);
  if (token)
    {
    size_t len = strlen(token);
    for (const_iterator it = begin(); it != end(); ++it)
      {
      if (it->first.compare("*") == 0)
        continue;
      if (it->first.compare(0, len, token) == 0)
        {
        writer->SetCompletion(index++, it->first.c_str());
        match = true;
        }
      }
    }
  return match;
  }

void EventStdFree(const char* event, void* data)
  {
  free(data);
  }

void EventLaunchTask(void *pvParameters)
  {
  OvmsEvents* me = (OvmsEvents*)pvParameters;

  me->EventTask();
  }

void event_trace(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  if (strcmp(cmd->GetName(),"on")==0)
    MyEvents.m_trace = true;
  else
    MyEvents.m_trace = false;

  writer->printf("Event tracing is now %s\n",cmd->GetName());
  }

void event_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  writer->printf("Event map has %d listeners, and queue has %d/%d entries\n",
    MyEvents.Map().size(),
    uxQueueMessagesWaiting(MyEvents.m_taskqueue),
    CONFIG_OVMS_HW_EVENT_QUEUE_SIZE);

  EventCallbackEntry* cbe = MyEvents.m_current_callback;
  if (cbe != NULL)
    {
    writer->printf("Currently dispatching:\n");
    writer->printf("  Event: %s\n",MyEvents.m_current_event.c_str());
    writer->printf("  To:    %s\n",cbe->m_caller.c_str());
    writer->printf("  For:   %u second(s)\n",monotonictime-MyEvents.m_current_started);
    }
  }

void event_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  std::string event;
  for (EventMap::const_iterator itm=MyEvents.Map().begin(); itm != MyEvents.Map().end(); ++itm)
    {
    if (argc > 0 && itm->first.find(argv[0]) == std::string::npos)
      continue;
    event.append(itm->first);
    event.append(":  ");
    EventCallbackList* el = itm->second;
    for (EventCallbackList::iterator itc=el->begin(); itc!=el->end(); )
      {
      EventCallbackEntry* ec = *itc;
      event.append(ec->m_caller);
      if (++itc != el->end())
        event.append(", ");
      }
    event.append("\n");
    }
  writer->printf("%s", event.c_str());
  }

int event_validate(OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv, bool complete)
  {
  int argpos = 0;
  for (int i=0; i < argc; i++)
    argpos += (argv[i][0] != '-') ? 1 : 0;
  if (argpos == 1 && MyEvents.Map().GetCompletion(writer, argv[argc-1]))
    return argc;
  return -1;
  }

void event_raise(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  std::string event;
  uint32_t delay_ms = 0;

  for (int i=0; i<argc; i++)
    {
    if (argv[i][0] == '-')
      {
      if (argv[i][1] != 'd')
        {
        cmd->PutUsage(writer);
        return;
        }
      delay_ms = atol(argv[i]+2);
      }
    else
      event = argv[i];
    }

  if (delay_ms)
    {
    writer->printf("Raising event in %u ms: %s\n", delay_ms, event.c_str());
    MyEvents.SignalEvent(event, NULL, (size_t)0, delay_ms);
    }
  else
    {
    writer->printf("Raising event: %s\n", event.c_str());
    MyEvents.SignalEvent(event, NULL);
    }
  }

#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE

static duk_ret_t DukOvmsRaiseEvent(duk_context *ctx)
  {
  const char *event = duk_to_string(ctx,0);
  uint32_t delay_ms = duk_is_number(ctx,1) ? duk_to_uint32(ctx,1) : 0;

  if (event != NULL)
    {
    MyEvents.SignalEvent(event, NULL, (size_t)0, delay_ms);
    }
  return 0;  /* no return value */
  }

#endif // #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE

OvmsEvents::OvmsEvents()
  {
  ESP_LOGI(TAG, "Initialising EVENTS (1200)");

  m_current_callback = NULL;

#ifdef CONFIG_OVMS_DEV_DEBUGEVENTS
  m_trace = true;
#else
  m_trace = false;
#endif // #ifdef CONFIG_OVMS_DEV_DEBUGEVENTS

  ESP_ERROR_CHECK(esp_event_loop_init(ReceiveSystemEvent, (void*)this));

  // Register our commands
  OvmsCommand* cmd_event = MyCommandApp.RegisterCommand("event","EVENT framework", event_status, "", 0, 0, false);
  cmd_event->RegisterCommand("status","Show status of event system",event_status);
  cmd_event->RegisterCommand("list","List registered events",event_list,"[<key>]", 0, 1);
  cmd_event->RegisterCommand("raise","Raise a textual event",event_raise,"[-d<delay_ms>] <event>", 1, 2, true, event_validate);
  OvmsCommand* cmd_eventtrace = cmd_event->RegisterCommand("trace","EVENT trace framework");
  cmd_eventtrace->RegisterCommand("on","Turn event tracing ON",event_trace);
  cmd_eventtrace->RegisterCommand("off","Turn event tracing OFF",event_trace);

  m_taskqueue = xQueueCreate(CONFIG_OVMS_HW_EVENT_QUEUE_SIZE,sizeof(event_queue_t));
  xTaskCreatePinnedToCore(EventLaunchTask, "OVMS Events", 8192, (void*)this, 8, &m_taskid, CORE(1));
  AddTaskToMap(m_taskid);

  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
  DuktapeObjectRegistration* dto = new DuktapeObjectRegistration("OvmsEvents");
  dto->RegisterDuktapeFunction(DukOvmsRaiseEvent, 2, "Raise");
  MyDuktape.RegisterDuktapeObject(dto);
  #endif // #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE
  }

OvmsEvents::~OvmsEvents()
  {
  }

void OvmsEvents::EventTask()
  {
  event_queue_t msg;

  esp_task_wdt_add(NULL); // WATCHDOG is active for this task
  while(1)
    {
    if (xQueueReceive(m_taskqueue, &msg, pdMS_TO_TICKS(5000)) == pdTRUE)
      {
      esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
      switch(msg.type)
        {
        case EVENT_none:
          break;
        case EVENT_signal:
          m_current_event = msg.body.signal.event;
          HandleQueueSignalEvent(&msg);
          esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
          m_current_event.clear();
          break;
        default:
          break;
        }
      }
    else
      {
#ifdef CONFIG_OVMS_COMP_OTA
      // Timeout on xQueueReceive: ignore during OTA flash job:
      OvmsMutexLock m_lock(&MyOTA.m_flashing, 0);
      if (!m_lock.IsLocked())
        continue;
#endif
      // …no OTA flashing in progress => abort:
      ESP_LOGE(TAG, "EventTask: [QueueTimeout] timer service / ticker timer has died => aborting");
      m_current_event = "[QueueTimeout]";
      m_current_started = monotonictime - 5;
      MyCommandApp.CloseLogfile();
      vTaskDelay(pdMS_TO_TICKS(100));
      abort();
      }
    }
  }

void OvmsEvents::HandleQueueSignalEvent(event_queue_t* msg)
  {
  // Log everything but the ticker & clock signals
  if (!startsWith(m_current_event, "ticker.") && !startsWith(m_current_event, "clock."))
    {
    if (m_trace)
      ESP_LOGI(TAG, "Signal(%s)",m_current_event.c_str());
    else
      ESP_LOGD(TAG, "Signal(%s)",m_current_event.c_str());
    }

  auto k = m_map.find(m_current_event);
  if (k != m_map.end())
    {
    EventCallbackList* el = k->second;
    if (el)
      {
      for (EventCallbackList::iterator itc=el->begin(); itc!=el->end(); ++itc)
        {
        m_current_started = monotonictime;
        m_current_callback = *itc;
        m_current_callback->m_callback(m_current_event, msg->body.signal.data);
        m_current_callback = NULL;
        }
      }
    }

  k = m_map.find("*");
  if (k != m_map.end())
    {
    EventCallbackList* el = k->second;
    if (el)
      {
      for (EventCallbackList::iterator itc=el->begin(); itc!=el->end(); ++itc)
        {
        m_current_started = monotonictime;
        m_current_callback = *itc;
        m_current_callback->m_callback(m_current_event, msg->body.signal.data);
        m_current_callback = NULL;
        }
      }
    }

  m_current_started = monotonictime;
  MyScripts.EventScript(m_current_event, msg->body.signal.data);

  FreeQueueSignalEvent(msg);
  }

void OvmsEvents::FreeQueueSignalEvent(event_queue_t* msg)
  {
  if (msg->body.signal.donefn != NULL)
    {
    msg->body.signal.donefn(msg->body.signal.event, msg->body.signal.data);
    }
  free(msg->body.signal.event);
  }

void OvmsEvents::RegisterEvent(std::string caller, std::string event, EventCallback callback)
  {
  auto k = m_map.find(event);
  if (k == m_map.end())
    {
    m_map[event] = new EventCallbackList();
    k = m_map.find(event);
    }
  if (k == m_map.end())
    {
    ESP_LOGE(TAG, "Problem registering event %s for caller %s",event.c_str(),caller.c_str());
    return;
    }

  EventCallbackList *el = k->second;
  el->push_back(new EventCallbackEntry(caller,callback));
  }

void OvmsEvents::DeregisterEvent(std::string caller)
  {
  EventMap::iterator itm=m_map.begin();
  while (itm!=m_map.end())
    {
    EventCallbackList* el = itm->second;
    EventCallbackList::iterator itc=el->begin();
    while (itc!=el->end())
      {
      EventCallbackEntry* ec = *itc;
      if (ec->m_caller == caller)
        {
        itc = el->erase(itc);
        delete ec;
        }
      else
        {
        ++itc;
        }
      }
    if (el->empty())
      {
      itm = m_map.erase(itm);
      delete el;
      }
    else
      {
      ++itm;
      }
    }
  }

static void CheckQueueOverflow(const char* from, char* event)
  {
  EventCallbackEntry* cbe = MyEvents.m_current_callback;
  if (cbe != NULL)
    {
    ESP_LOGE(TAG, "%s: queue overflow (running %s->%s for %u sec), event '%s' dropped",
      from,
      MyEvents.m_current_event.c_str(),
      cbe->m_caller.c_str(),
      monotonictime-MyEvents.m_current_started,
      event);
    }
  else
    {
    ESP_LOGE(TAG, "%s: queue overflow, event '%s' dropped", from, event);
    }
  if (strncmp(event, "ticker.", 7) != 0)
    {
    // We've dropped a potentially important event, system is instable now.
    // As the event queue is full, a normal reboot is no option, so…
#ifdef CONFIG_OVMS_COMP_OTA
    // …wait for a running OTA job to finish…
    OvmsMutexLock m_lock(&MyOTA.m_flashing);
#endif
    // …then abort:
    ESP_LOGE(TAG, "%s: lost important event => aborting", from);
    MyCommandApp.CloseLogfile();
    vTaskDelay(pdMS_TO_TICKS(100));
    abort();
    }
  }

void OvmsEvents::SignalScheduledEvent(TimerHandle_t timer)
  {
  OvmsMutexLock lock(&MyEvents.m_timers_mutex);

  // retrieve timer payload message (event):
  event_queue_t* msg = (event_queue_t*) pvTimerGetTimerID(timer);
  vTimerSetTimerID(timer, NULL);
  if (!msg)
    {
    // This should no longer happen, but keeping the test doesn't hurt.
    // See: https://github.com/espressif/esp-idf/issues/8234
    ESP_LOGW(TAG, "SignalScheduledEvent: duplicate callback invocation detected");
    return;
    }

  // … and pass on to event task:
  if (xQueueSend(MyEvents.m_taskqueue, msg, 0) != pdTRUE)
    {
    CheckQueueOverflow("SignalScheduledEvent", msg->body.signal.event);
    MyEvents.FreeQueueSignalEvent(msg);
    }

  delete msg;
  MyEvents.m_timer_active[timer] = false;
  }

bool OvmsEvents::ScheduleEvent(event_queue_t* msg, uint32_t delay_ms)
  {
  OvmsMutexLock lock(&m_timers_mutex);
  TimerHandle_t timer;
  TimerList::iterator it;
  event_queue_t *msgdup = new event_queue_t(*msg);
  int timerticks = pdMS_TO_TICKS(delay_ms); if (timerticks<1) timerticks=1;

  if (!msgdup)
    {
    ESP_LOGE(TAG, "ScheduleEvent: message duplication failed, event dropped");
    return false;
    }

  // find available timer:
  for (it = m_timers.begin(); it != m_timers.end(); it++)
    {
    timer = *it;
    // Note: xTimerIsTimerActive() must not be used here, it has a
    //  multicore race condition with FreeRTOS V8.2.0 (esp-idf 3.3):
    //  an expired timer is removed from the active list before its
    //  callback is executed, so while the callback is running, the
    //  timer already appears to be free. Workaround is to use our
    //  own timer status map:
    if (!m_timer_active[timer])
      break;
    }
  if (it == m_timers.end())
    {
    // create & register a new timer:
    ESP_LOGI(TAG, "ScheduleEvent: creating new timer");
    timer = xTimerCreate("ScheduleEvent", timerticks, pdFALSE, NULL, SignalScheduledEvent);
    if (!timer)
      {
      ESP_LOGE(TAG, "ScheduleEvent: xTimerCreate failed, event dropped");
      delete msgdup;
      return false;
      }
    m_timers.push_back(timer);
    m_timer_active[timer] = false;
    }

  // set payload, update & start the timer:
  vTimerSetTimerID(timer, msgdup);
  if (xTimerChangePeriod(timer, timerticks, 0) != pdPASS) // also starts the timer
    {
    ESP_LOGE(TAG, "ScheduleEvent: xTimerChangePeriod failed, event dropped");
    vTimerSetTimerID(timer, NULL);
    delete msgdup;
    return false;
    }

  m_timer_active[timer] = true;
  return true;
  }

void OvmsEvents::SignalEvent(std::string event, void* data, event_signal_done_fn callback /*=NULL*/,
                             uint32_t delay_ms /*=0*/)
  {
  event_queue_t msg;
  memset(&msg, 0, sizeof(msg));

  msg.type = EVENT_signal;
  msg.body.signal.event = (char*)ExternalRamMalloc(event.size()+1);
  strcpy(msg.body.signal.event, event.c_str());
  msg.body.signal.data = data;
  msg.body.signal.donefn = callback;

  if (delay_ms == 0)
    {
    if (xQueueSend(m_taskqueue, &msg, 0) != pdTRUE)
      {
      CheckQueueOverflow("SignalEvent", msg.body.signal.event);
      FreeQueueSignalEvent(&msg);
      }
    }
  else
    {
    if (ScheduleEvent(&msg, delay_ms) != true)
      {
      ESP_LOGE(TAG, "SignalEvent: no timer available, event '%s' dropped", msg.body.signal.event);
      FreeQueueSignalEvent(&msg);
      }
    }
  }

void OvmsEvents::SignalEvent(std::string event, void* data, size_t length,
                             uint32_t delay_ms /*=0*/)
  {
  event_queue_t msg;
  memset(&msg, 0, sizeof(msg));

  msg.type = EVENT_signal;
  msg.body.signal.event = (char*)ExternalRamMalloc(event.size()+1);
  strcpy(msg.body.signal.event, event.c_str());
  if (data != NULL)
    {
    msg.body.signal.data = ExternalRamMalloc(length);
    memcpy(msg.body.signal.data, data, length);
    msg.body.signal.donefn = EventStdFree;
    }
  else
    {
    msg.body.signal.data = NULL;
    msg.body.signal.donefn = NULL;
    }

  if (delay_ms == 0)
    {
    if (xQueueSend(m_taskqueue, &msg, 0) != pdTRUE)
      {
      CheckQueueOverflow("SignalEvent", msg.body.signal.event);
      FreeQueueSignalEvent(&msg);
      }
    }
  else
    {
    if (ScheduleEvent(&msg, delay_ms) != true)
      {
      ESP_LOGE(TAG, "SignalEvent: no timer available, event '%s' dropped", msg.body.signal.event);
      FreeQueueSignalEvent(&msg);
      }
    }
  }

esp_err_t OvmsEvents::ReceiveSystemEvent(void *ctx, system_event_t *event)
  {
  OvmsEvents* e = (OvmsEvents*)ctx;
  e->SignalEvent("system.event",(void*)event,sizeof(system_event_t));
  e->SignalSystemEvent(event);
  return ESP_OK;
  }

void OvmsEvents::SignalSystemEvent(system_event_t *event)
  {
  switch (event->event_id)
    {
    case SYSTEM_EVENT_WIFI_READY:      // ESP32 WiFi ready
      SignalEvent("system.wifi.ready",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_SCAN_DONE:       // ESP32 finish scanning AP
      SignalEvent("system.wifi.scan.done",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_START:       // ESP32 station start
      SignalEvent("system.wifi.sta.start",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_STOP:        // ESP32 station stop
      SignalEvent("system.wifi.sta.stop",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_CONNECTED:   // ESP32 station connected to AP
      SignalEvent("system.wifi.sta.connected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:  // ESP32 station disconnected from AP
      SignalEvent("system.wifi.sta.disconnected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:  // the auth mode of AP connected by ESP32 station changed
      SignalEvent("system.wifi.sta.authmodechange",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_GOT_IP:           // ESP32 station got IP from connected AP
      SignalEvent("network.interface.up", NULL);
      SignalEvent("system.wifi.sta.gotip",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_LOST_IP:         // ESP32 station lost IP and the IP is reset to 0
      SignalEvent("system.wifi.sta.lostip",(void*)&event->event_info);
      break;
    case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:  // ESP32 station wps succeeds in enrollee mode
      SignalEvent("system.wifi.sta.wpser.success",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_WPS_ER_FAILED:   // ESP32 station wps fails in enrollee mode
      SignalEvent("system.wifi.sta.wpser.failed",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:  // ESP32 station wps timeout in enrollee mode
      SignalEvent("system.wifi.sta.wpser.timeout",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_STA_WPS_ER_PIN:      // ESP32 station wps pin code in enrollee mode
      SignalEvent("system.wifi.sta.wpser.pin",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_START:            // ESP32 soft-AP start
      SignalEvent("system.wifi.ap.start",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_STOP:             // ESP32 soft-AP stop
      SignalEvent("system.wifi.ap.stop",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_STACONNECTED:     // a station connected to ESP32 soft-AP
      SignalEvent("system.wifi.ap.sta.connected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_STADISCONNECTED:  // a station disconnected from ESP32 soft-AP
      SignalEvent("system.wifi.ap.sta.disconnected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_STAIPASSIGNED:    // ESP32 soft-AP assigned an IP to a connected station
      SignalEvent("system.wifi.ap.sta.ipassigned",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_PROBEREQRECVED:   // Receive probe request packet in soft-AP interface
      SignalEvent("system.wifi.ap.proberx",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_AP_STA_GOT_IP6:      // ESP32 station or ap interface v6IP addr is preferred
      SignalEvent("system.wifi.ap.sta.gotip6",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_ETH_START:           // ESP32 ethernet start
      SignalEvent("system.eth.start",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_ETH_STOP:            // ESP32 ethernet stop
      SignalEvent("system.eth.stop",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_ETH_CONNECTED:       // ESP32 ethernet phy link up
      SignalEvent("system.eth.connected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_ETH_DISCONNECTED:    // ESP32 ethernet phy link down
      SignalEvent("system.eth.disconnected",(void*)&event->event_info,sizeof(event->event_info));
      break;
    case SYSTEM_EVENT_ETH_GOT_IP:          // ESP32 ethernet got IP from connected AP
      SignalEvent("system.eth.gotip",(void*)&event->event_info,sizeof(event->event_info));
      break;
    default:
     break;
    }
  }

EventCallbackEntry::EventCallbackEntry(std::string caller, EventCallback callback)
  {
  m_caller = caller;
  m_callback = callback;
  }

EventCallbackEntry::~EventCallbackEntry()
  {
  }
