/*
;    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.
;
; Portions of this are based on the work of Thomas Barth, and licensed
; under MIT license.
; Copyright (c) 2017, Thomas Barth, barth-dev.de
; https://github.com/ThomasBarth/ESP32-CAN-Driver
*/

#include "ovms_log.h"
static const char *TAG = "can";

#include "can.h"
#include "canlog.h"
#include "canplay.h"
#include "dbc.h"
#include "dbc_app.h"
#include <algorithm>
#include <ctype.h>
#include <string.h>
#include <iomanip>
#include "ovms_config.h"
#include "ovms_command.h"
#include "metrics_standard.h"

#if defined(CONFIG_OVMS_COMP_ESP32CAN) || \
    defined(CONFIG_OVMS_COMP_MCP2515) || \
    defined(CONFIG_OVMS_COMP_EXTERNAL_SWCAN)
static const bool includeCAN = true;
#else
static const bool includeCAN = false;
#endif

can MyCan __attribute__ ((init_priority (4510)));

////////////////////////////////////////////////////////////////////////
// CAN command processing
////////////////////////////////////////////////////////////////////////

void can_start(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  const char* mode = cmd->GetName();
  int baud = atoi(argv[0]);
  esp_err_t res;

  CAN_mode_t smode = CAN_MODE_LISTEN;
  if (strcmp(mode, "active")==0) smode = CAN_MODE_ACTIVE;

  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  dbcfile *dbcfile = NULL;
  if (argc>1)
    {
    dbcfile = MyDBC.Find(argv[1]);
    if (dbcfile == NULL)
      {
      writer->printf("Error: Could not find dbc file %s\n",argv[1]);
      return;
      }
    if (baud == 0)
      {
      baud = dbcfile->m_bittiming.GetBaudRate();
      }
    }

  switch (baud)
    {
    case 33333:
      res = sbus->Start(smode,CAN_SPEED_33KBPS,dbcfile);
      break;
    case 50000:
      res = sbus->Start(smode,CAN_SPEED_50KBPS,dbcfile);
      break;
    case 83333:
      res = sbus->Start(smode,CAN_SPEED_83KBPS,dbcfile);
      break;
    case 100000:
      res = sbus->Start(smode,CAN_SPEED_100KBPS,dbcfile);
      break;
    case 125000:
      res = sbus->Start(smode,CAN_SPEED_125KBPS,dbcfile);
      break;
    case 250000:
      res = sbus->Start(smode,CAN_SPEED_250KBPS,dbcfile);
      break;
    case 500000:
      res = sbus->Start(smode,CAN_SPEED_500KBPS,dbcfile);
      break;
    case 1000000:
      res = sbus->Start(smode,CAN_SPEED_1000KBPS,dbcfile);
      break;
    default:
      writer->puts("Error: Unrecognised speed (33333, 50000, 83333, 100000, 125000, 250000, 500000, 1000000 are accepted)");
      return;
    }
  if (res == ESP_OK)
    writer->printf("Can bus %s started in mode %s at speed %dbps\n",
                 bus, mode, baud);
  else
    writer->printf("Failed to start can bus %s\n",bus);
  }

void can_stop(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }
  sbus->Stop();
  writer->printf("Can bus %s stopped\n",bus);
  }

void can_dbc_attach(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  dbcfile *dbcfile = MyDBC.Find(argv[0]);
  if (dbcfile == NULL)
    {
    writer->printf("Error: Could not find dbc file %s\n",argv[0]);
    return;
    }

  sbus->AttachDBC(dbcfile);
  writer->printf("DBC %s attached to %s\n",argv[0],bus);
  }

void can_dbc_detach(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  sbus->DetachDBC();
  writer->printf("DBC detached from %s\n",bus);
  }

void can_tx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  const char* mode = cmd->GetName();
  CAN_frame_format_t smode = CAN_frame_std;
  uint32_t idmax = (1 << 11) - 1;
  if (strcmp(mode, "extended")==0)
    {
    smode = CAN_frame_ext;
    idmax = (1 << 29) - 1;
    }

  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }
  if (sbus->GetPowerMode() != On)
    {
    writer->puts("Error: Can bus is not powered on");
    return;
    }

  CAN_frame_t frame = {};
  frame.origin = sbus;
  frame.FIR.U = 0;
  frame.FIR.B.DLC = argc-1;
  frame.FIR.B.FF = smode;
  char* ep;
  uint32_t uv = strtoul(argv[0], &ep, 16);
  if (*ep != '\0' || uv > idmax)
    {
    writer->printf("Error: Invalid CAN ID \"%s\" (0x%lx max)\n", argv[0], idmax);
    return;
    }
  frame.MsgID = uv;
  frame.callback = NULL;
  for(int k=0;k<(argc-1);k++)
    {
    uv = strtoul(argv[k+1], &ep, 16);
    if (*ep != '\0' || uv > 0xff)
      {
      writer->printf("Error: Invalid CAN octet \"%s\"\n", argv[k+1]);
      return;
      }
    frame.data.u8[k] = uv;
    }
  sbus->Write(&frame, pdMS_TO_TICKS(500));
  }

void can_rx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  const char* mode = cmd->GetName();
  CAN_frame_format_t smode = CAN_frame_std;
  if (strcmp(mode, "extended")==0) smode = CAN_frame_ext;

  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }
  if (sbus->GetPowerMode() != On)
    {
    writer->puts("Error: Can bus is not powered on");
    return;
    }

  CAN_frame_t frame = {};
  frame.origin = sbus;
  frame.FIR.U = 0;
  frame.FIR.B.DLC = argc-1;
  frame.FIR.B.FF = smode;
  frame.MsgID = (int)strtol(argv[0],NULL,16);
  frame.callback = NULL;
  for(int k=0;k<(argc-1);k++)
    {
    frame.data.u8[k] = strtol(argv[k+1],NULL,16);
    }
  MyCan.IncomingFrame(&frame);
  }

void can_testtx(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetParent()->GetName();
  const char* mode = cmd->GetName();
  CAN_frame_format_t smode = CAN_frame_std;
  uint32_t idmax = (1 << 11) - 1;
  if (strcmp(mode, "extended")==0)
    {
    smode = CAN_frame_ext;
    idmax = (1 << 29) - 1;
    }

  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }
  if (sbus->GetPowerMode() != On)
    {
    writer->puts("Error: Can bus is not powered on");
    return;
    }

  CAN_frame_t frame = {};
  memset(&frame, 0, sizeof(frame));
  frame.origin = sbus;
  frame.FIR.U = 0;
  frame.FIR.B.DLC = 8;
  frame.FIR.B.FF = smode;
  char* ep;
  uint32_t uv = strtoul(argv[0], &ep, 16);
  if (*ep != '\0' || uv > idmax)
    {
    writer->printf("Error: Invalid CAN ID \"%s\" (0x%lx max)\n", argv[0], idmax);
    return;
    }
  frame.MsgID = uv;
  frame.callback = NULL;

  uint32_t count = strtoul(argv[1], &ep, 10);
  uint32_t delayms = strtoul(argv[2], &ep, 10);

  for (uint32_t k=0;k<count;k++)
    {
    frame.data.u8[0] = (uint8_t)k;
    sbus->Write(&frame, pdMS_TO_TICKS(500));
    if (delayms != 0) vTaskDelay(pdMS_TO_TICKS(delayms));
    }
  }

void can_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  sbus->LogStatus(CAN_LogStatus_Statistics);

  writer->printf("CAN:       %s\n",sbus->GetName());
  writer->printf("Mode:      %s\n",(sbus->m_mode==CAN_MODE_OFF)?"Off":
                                   ((sbus->m_mode==CAN_MODE_LISTEN)?"Listen":"Active"));
  writer->printf("Speed:     %d\n",MAP_CAN_SPEED(sbus->m_speed));
  writer->printf("DBC:       %s\n",(sbus->GetDBC())?sbus->GetDBC()->GetName().c_str():"none");

  writer->printf("\nInterrupts:%20d\n",sbus->m_status.interrupts);
  writer->printf("Rx pkt:    %20d\n",sbus->m_status.packets_rx);
  writer->printf("Rx ovrflw: %20d\n",sbus->m_status.rxbuf_overflow);
  writer->printf("Tx pkt:    %20d\n",sbus->m_status.packets_tx);
  writer->printf("Tx delays: %20d\n",sbus->m_status.txbuf_delay);
  writer->printf("Tx ovrflw: %20d\n",sbus->m_status.txbuf_overflow);
  writer->printf("Tx fails:  %20d\n",sbus->m_status.tx_fails);

  writer->printf("\nErr flags: 0x%08x\n",sbus->m_status.error_flags);
  writer->printf("Rx err:    %20d\n",sbus->m_status.errors_rx);
  writer->printf("Tx err:    %20d\n",sbus->m_status.errors_tx);
  writer->printf("Rx invalid:%20d\n",sbus->m_status.invalid_rx);
  writer->printf("Wdg Resets:%20d\n",sbus->m_status.watchdog_resets);
  if (sbus->m_watchdog_timer>0)
    {
    writer->printf("Wdg Timer: %20d sec(s)\n",monotonictime-sbus->m_watchdog_timer);
    }
  writer->printf("Err Resets:%20d\n",sbus->m_status.error_resets);
  }

void can_list(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  for (int k=1;k<5;k++)
    {
    static const char* name[4] = {"can1", "can2", "can3", "can4"};
    canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(name[k-1]);
    if (sbus != NULL)
      {
      writer->printf("%s: %s/%d dbc %s\n",
        sbus->GetName(),
        (sbus->m_mode==CAN_MODE_OFF)?"Off":
          ((sbus->m_mode==CAN_MODE_LISTEN)?"Listen":"Active"),
        MAP_CAN_SPEED(sbus->m_speed),
        (sbus->GetDBC())?sbus->GetDBC()->GetName().c_str():"none");
      }
    }
  }

void can_clearstatus(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  sbus->ClearStatus();
  writer->puts("Status cleared");
  }

void can_view_registers(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }

  if (sbus->ViewRegisters() == ESP_ERR_NOT_SUPPORTED)
    writer->puts("Error: Not implemented");
  else
    writer->puts("See logs for details");
  }

void can_set_register(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  const char* bus = cmd->GetParent()->GetName();
  canbus* sbus = (canbus*)MyPcpApp.FindDeviceByName(bus);
  if (sbus == NULL)
    {
    writer->puts("Error: Cannot find named CAN bus");
    return;
    }
  if (argc<2)
    {
    writer->puts("Error: register address and value must be given");
    return;
    }
  uint32_t addr = (int)strtol(argv[0],NULL,16);
  uint32_t value = (int)strtol(argv[1],NULL,16);
  if (addr > 255)
    {
    writer->puts("Error: Register address out of range");
    return;
    }
  if (value > 255)
    {
    writer->puts("Error: Register value out of range");
    return;
    }

  sbus->WriteReg(addr,value);
  }

////////////////////////////////////////////////////////////////////////
// CAN Filtering (software based filter)
// The canfilter object encapsulates the filtering of CAN frames
////////////////////////////////////////////////////////////////////////

canfilter::canfilter()
  {
  }

canfilter::~canfilter()
  {
  ClearFilters();
  }

void canfilter::ClearFilters()
  {
  for (CAN_filter_t* filter : m_filters)
    {
    delete filter;
    }
  m_filters.clear();
  }

void canfilter::AddFilter(uint8_t bus, uint32_t id_from, uint32_t id_to)
  {
  CAN_filter_t* f = new CAN_filter_t;
  f->bus = bus;
  f->id_from = id_from;
  f->id_to = id_to;
  m_filters.push_back(f);
  }

void canfilter::AddFilter(const char* filterstring)
  {
  char* fs = (char*)filterstring;
  if (fs[1] == 0)
    {
    AddFilter((uint8_t)fs[0]);
    }
  else
    {
    uint8_t bus = 0;
    uint32_t id_from = 0;
    uint32_t id_to = UINT32_MAX;
    if (fs[1] == ':')
      {
      bus = fs[0];
      fs += 2;
      }
    id_from = strtol(fs, &fs, 16);
    if (*fs)
      id_to = strtol(fs+1, NULL, 16); // id range
    else
      id_to = id_from; // single id
    AddFilter(bus,id_from,id_to);
    }
  }

bool canfilter::RemoveFilter(uint8_t bus, uint32_t id_from, uint32_t id_to)
  {
  for (CAN_filter_t* filter : m_filters)
    {
    if ((filter->bus == bus)&&
        (filter->id_from == id_from)&&
        (filter->id_to == id_to))
      {
      delete filter;
      return true;
      }
    }
  return false;
  }

bool canfilter::IsFiltered(const CAN_frame_t* p_frame)
  {
  if (m_filters.size() == 0) return true;
  if (! p_frame) return false;

  char buskey = '0';
  if (p_frame->origin) buskey = p_frame->origin->m_busnumber + '1';

  for (CAN_filter_t* filter : m_filters)
    {
    if ((filter->bus)&&(filter->bus != buskey)) continue;
    if ((p_frame->MsgID >= filter->id_from) && (p_frame->MsgID <= filter->id_to))
      return true;
    }

  return false;
  }

bool canfilter::IsFiltered(canbus* bus)
  {
  if (m_filters.size() == 0) return true;
  if (bus == NULL) return true;

  char buskey = bus->GetName()[3];

  for (CAN_filter_t* filter : m_filters)
    {
    if ((filter->bus)&&(filter->bus == buskey)) return true;
    }

  return false;
  }

std::string canfilter::Info()
  {
  std::ostringstream buf;

  for (CAN_filter_t* filter : m_filters)
    {
    if (filter->bus > 0) buf << std::setfill(' ') << std::dec << filter->bus << ':';
    buf << std::setfill('0') << std::setw(3) << std::hex;
    if (filter->id_from == filter->id_to)
      { buf << filter->id_from << ' '; }
    else
      { buf << filter->id_from << '-' << filter->id_to << ' '; }
    }

  return buf.str();
  }

////////////////////////////////////////////////////////////////////////
// CAN logging and tracing
// These structures are involved in formatting, logging and tracing of
// CAN messages (both frames and status/control messages)
////////////////////////////////////////////////////////////////////////

static const char* const CAN_log_type_names[] = {
  "-",
  "RX",
  "TX",
  "TX_Queue",
  "TX_Fail",
  "Error",
  "Status",
  "Comment",
  "Info",
  "Event"
  };

const char* GetCanLogTypeName(CAN_log_type_t type)
  {
  return CAN_log_type_names[type];
  }

void can::LogFrame(canbus* bus, CAN_log_type_t type, const CAN_frame_t* frame)
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  for (canlog_map_t::iterator it=m_loggermap.begin(); it!=m_loggermap.end(); ++it)
    {
    it->second->LogFrame(bus, type, frame);
    }
  }

void can::LogStatus(canbus* bus, CAN_log_type_t type, const CAN_status_t* status)
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  for (canlog_map_t::iterator it=m_loggermap.begin(); it!=m_loggermap.end(); ++it)
    {
    it->second->LogStatus(bus, type, status);
    }
  }

void can::LogInfo(canbus* bus, CAN_log_type_t type, const char* text)
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  for (canlog_map_t::iterator it=m_loggermap.begin(); it!=m_loggermap.end(); ++it)
    {
    it->second->LogInfo(bus, type, text);
    }
  }

void canbus::LogFrame(CAN_log_type_t type, const CAN_frame_t* frame)
  {
  MyCan.LogFrame(this, type, frame);
  }

void canbus::LogStatus(CAN_log_type_t type)
  {
  if (type==CAN_LogStatus_Error)
    {
    if (!StatusChanged())
      return;
    ESP_LOGE(TAG,
      "%s: intr=%d rxpkt=%d txpkt=%d errflags=%#x rxerr=%d txerr=%d rxinval=%d"
      " rxovr=%d txovr=%d txdelay=%d txfail=%d wdgreset=%d errreset=%d",
      m_name, m_status.interrupts, m_status.packets_rx, m_status.packets_tx,
      m_status.error_flags, m_status.errors_rx, m_status.errors_tx,
      m_status.invalid_rx, m_status.rxbuf_overflow, m_status.txbuf_overflow,
      m_status.txbuf_delay, m_status.tx_fails, m_status.watchdog_resets,
      m_status.error_resets);
    }
  if (MyCan.HasLogger())
    MyCan.LogStatus(this, type, &m_status);
  }

void canbus::LogInfo(CAN_log_type_t type, const char* text)
  {
  MyCan.LogInfo(this, type, text);
  }

bool canbus::StatusChanged()
  {
  // simple checksum to prevent log flooding:
  uint32_t chksum = m_status.errors_rx + m_status.errors_tx
    + m_status.invalid_rx + m_status.rxbuf_overflow + m_status.txbuf_overflow
    + m_status.error_flags + m_status.txbuf_delay + m_status.tx_fails
    + m_status.watchdog_resets + m_status.error_resets;
  if (chksum != m_status_chksum)
    {
    m_status_chksum = chksum;
    return true;
    }
  return false;
  }

static const char* const CAN_errorstate_names[] = {
  "none",
  "active",
  "warning",
  "passive",
  "busoff"
  };

const char* GetCanErrorStateName(CAN_errorstate_t error_state)
  {
  return CAN_errorstate_names[error_state];
  }

CAN_errorstate_t canbus::GetErrorState()
  {
  if (m_status.errors_tx == 0 && m_status.errors_rx == 0)
    return CAN_errorstate_none;
  else if (m_status.errors_tx < 96 && m_status.errors_rx < 96)
    return CAN_errorstate_active;
  else if (m_status.errors_tx < 128 && m_status.errors_rx < 128)
    return CAN_errorstate_warning;
  else if (m_status.errors_tx < 256 && m_status.errors_rx < 256)
    return CAN_errorstate_passive;
  else
    return CAN_errorstate_busoff;
  }

const char* canbus::GetErrorStateName()
  {
  return GetCanErrorStateName(GetErrorState());
  }


uint32_t can::AddLogger(canlog* logger, int filterc, const char* const* filterv)
  {
  if (filterc>0)
    {
    canfilter *filter = new canfilter();
    for (int k=0;k<filterc;k++)
      {
      filter->AddFilter(filterv[k]);
      }
    logger->SetFilter(filter);
    }

  OvmsRecMutexLock lock(&m_loggermap_mutex);
  uint32_t id = m_logger_id++;
  m_loggermap[id] = logger;

  return id;
  }

bool can::HasLogger()
  {
  return (m_loggermap.size() != 0);
  }

canlog* can::GetLogger(uint32_t id)
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  auto k = m_loggermap.find(id);
  if (k != m_loggermap.end())
    return k->second;
  else
    return NULL;
  }

bool can::RemoveLogger(uint32_t id)
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  auto k = m_loggermap.find(id);
  if (k != m_loggermap.end())
    {
    k->second->Close();
    vTaskDelay(pdMS_TO_TICKS(100)); // give logger task time to finish
    delete k->second;
    m_loggermap.erase(k);
    return true;
    }
  return false;
  }

void can::RemoveLoggers()
  {
  OvmsRecMutexLock lock(&m_loggermap_mutex);

  for (canlog_map_t::iterator it=m_loggermap.begin(); it!=m_loggermap.end();)
    {
    it->second->Close();
    vTaskDelay(pdMS_TO_TICKS(100)); // give logger task time to finish
    delete it->second;
    it = m_loggermap.erase(it);
    }
  }

uint32_t can::AddPlayer(canplay* player, int filterc, const char* const* filterv)
  {
  if (filterc>0)
    {
    canfilter *filter = new canfilter();
    for (int k=0;k<filterc;k++)
      {
      filter->AddFilter(filterv[k]);
      }
    player->SetFilter(filter);
    }

  OvmsMutexLock lock(&m_playermap_mutex);
  uint32_t id = m_player_id++;
  m_playermap[id] = player;

  return id;
  }

bool can::HasPlayer()
  {
  OvmsMutexLock lock(&m_playermap_mutex);

  return (m_playermap.size() > 0);
  }

canplay* can::GetPlayer(uint32_t id)
  {
  OvmsMutexLock lock(&m_playermap_mutex);

  auto k = m_playermap.find(id);
  if (k != m_playermap.end())
    return k->second;
  else
    return NULL;
  }

bool can::RemovePlayer(uint32_t id)
  {
  OvmsMutexLock lock(&m_playermap_mutex);

  auto k = m_playermap.find(id);
  if (k != m_playermap.end())
    {
    k->second->Close();
    vTaskDelay(pdMS_TO_TICKS(100)); // give logger task time to finish
    delete k->second;
    m_playermap.erase(k);
    return true;
    }
  return false;
  }

void can::RemovePlayers()
  {
  OvmsMutexLock lock(&m_playermap_mutex);

  for (canplay_map_t::iterator it=m_playermap.begin(); it!=m_playermap.end();)
    {
    it->second->Close();
    vTaskDelay(pdMS_TO_TICKS(100)); // give logger task time to finish
    delete it->second;
    it = m_playermap.erase(it);
    }
  }

////////////////////////////////////////////////////////////////////////
// CAN controller task
////////////////////////////////////////////////////////////////////////

void can::CAN_rxtask(void *pvParameters)
  {
  can *me = (can*)pvParameters;
  CAN_queue_msg_t msg;

  while(1)
    {
    if (xQueueReceive(me->m_rxqueue,&msg, (portTickType)portMAX_DELAY)==pdTRUE)
      {
      switch(msg.type)
        {
        case CAN_frame:
          me->IncomingFrame(&msg.body.frame);
          break;
        case CAN_asyncinterrupthandler:
          {
          bool loop;
          // Loop until all interrupts are handled
          do {
            uint32_t receivedFrames;
            loop = msg.body.bus->AsynchronousInterruptHandler(&msg.body.frame, &receivedFrames);
            } while (loop);
          break;
          }
        case CAN_txcallback:
          msg.body.bus->TxCallback(&msg.body.frame, true);
          break;
        case CAN_txfailedcallback:
          msg.body.bus->TxCallback(&msg.body.frame, false);
          break;
        case CAN_logerror:
          msg.body.bus->LogStatus(CAN_LogStatus_Error);
          break;
        case CAN_logstatus:
          msg.body.bus->LogStatus(CAN_LogStatus_Statistics);
          break;
        default:
          break;
        }
      }
    }
  }

////////////////////////////////////////////////////////////////////////
// can - the CAN system controller
////////////////////////////////////////////////////////////////////////

can::can()
  {
  if (!includeCAN) return;

  ESP_LOGI(TAG, "Initialising CAN (4510)");

  m_logger_id = 1;
  m_player_id = 1;

  MyConfig.RegisterParam("can", "CAN Configuration", true, true);

  OvmsCommand* cmd_can = MyCommandApp.RegisterCommand("can","CAN framework");

  for (int k=0;k<CAN_MAXBUSES;k++) m_buslist[k] = NULL;

  for (int k=1;k<5;k++)
    {
    static const char* name[4] = {"can1", "can2", "can3", "can4"};
    OvmsCommand* cmd_canx = cmd_can->RegisterCommand(name[k-1],"CANx framework");
    OvmsCommand* cmd_canstart = cmd_canx->RegisterCommand("start","CAN start framework");
    cmd_canstart->RegisterCommand("listen","Start CAN bus in listen mode",can_start,"<baud> [<dbc>]", 1, 2);
    cmd_canstart->RegisterCommand("active","Start CAN bus in active mode",can_start,"<baud> [<dbc>]", 1, 2);
    cmd_canx->RegisterCommand("stop","Stop CAN bus",can_stop);
    OvmsCommand* cmd_candbc = cmd_canx->RegisterCommand("dbc","CAN dbc framework");
    cmd_candbc->RegisterCommand("attach","Attach a DBC file to a CAN bus",can_dbc_attach,"<dbc>", 1, 1);
    cmd_candbc->RegisterCommand("detach","Detach the DBC file from a CAN bus",can_dbc_detach);
    OvmsCommand* cmd_cantx = cmd_canx->RegisterCommand("tx","CAN tx framework");
    cmd_cantx->RegisterCommand("standard","Transmit standard CAN frame",can_tx,"<id> <data...>", 1, 9);
    cmd_cantx->RegisterCommand("extended","Transmit extended CAN frame",can_tx,"<id> <data...>", 1, 9);
    OvmsCommand* cmd_canrx = cmd_canx->RegisterCommand("rx","CAN rx framework");
    cmd_canrx->RegisterCommand("standard","Simulate reception of standard CAN frame",can_rx,"<id> <data...>", 1, 9);
    cmd_canrx->RegisterCommand("extended","Simulate reception of extended CAN frame",can_rx,"<id> <data...>", 1, 9);
    OvmsCommand* cmd_cantesttx = cmd_canx->RegisterCommand("testtx","CAN test tx framework");
    cmd_cantesttx->RegisterCommand("standard","Transmit test standard CAN frames",can_testtx,"<id> <count> <delayms>", 3, 3);
    cmd_cantesttx->RegisterCommand("extended","Transmit test extended CAN frames",can_testtx,"<id> <count> <delayms>", 3, 3);
    cmd_canx->RegisterCommand("status","Show CAN status",can_status);
    cmd_canx->RegisterCommand("clear","Clear CAN status",can_clearstatus);
    cmd_canx->RegisterCommand("viewregisters","view can controller registers",can_view_registers);
    cmd_canx->RegisterCommand("setregister","set can controller register",can_set_register,"<reg> <value>",2,2);
    }

  cmd_can->RegisterCommand("list", "List CAN buses", can_list);

  m_rxqueue = xQueueCreate(CONFIG_OVMS_HW_CAN_RX_QUEUE_SIZE,sizeof(CAN_queue_msg_t));
  xTaskCreatePinnedToCore(CAN_rxtask, "OVMS CanRx", 2*2048, (void*)this, 23, &m_rxtask, CORE(0));
  }

can::~can()
  {
  }

canbus* can::GetBus(int busnumber)
  {
  if ((busnumber<0)||(busnumber>=CAN_MAXBUSES)) return NULL;

  canbus* found = m_buslist[busnumber];
  if (found != NULL) return found;

  char cbus[5];
  strcpy(cbus,"can");
  cbus[3] = busnumber+'1';
  cbus[4] = 0;
  found = (canbus*)MyPcpApp.FindDeviceByName(cbus);
  if (found)
    {
    m_buslist[busnumber] = found;
    }
  return found;
  }

void can::IncomingFrame(CAN_frame_t* p_frame)
  {
  p_frame->origin->m_status.packets_rx++;
  p_frame->origin->m_watchdog_timer = monotonictime;

  ExecuteCallbacks(p_frame, false, true /*ignored*/);
  p_frame->origin->LogFrame(CAN_LogFrame_RX, p_frame);
  NotifyListeners(p_frame, false);
  }

void can::RegisterListener(QueueHandle_t queue, bool txfeedback)
  {
  m_listeners[queue] = txfeedback;
  }

void can::DeregisterListener(QueueHandle_t queue)
  {
  auto it = m_listeners.find(queue);
  if (it != m_listeners.end())
    m_listeners.erase(it);
  }

void can::NotifyListeners(const CAN_frame_t* frame, bool tx)
  {
  for (CanListenerMap_t::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
    {
    if (!tx || (tx && it->second))
      xQueueSend(it->first,frame,0);
    }
  }

void can::RegisterCallback(const char* caller, CanFrameCallback callback, bool txfeedback)
  {
  if (txfeedback)
    m_txcallbacks.push_back(new CanFrameCallbackEntry(caller, callback));
  else
    m_rxcallbacks.push_back(new CanFrameCallbackEntry(caller, callback));
  }

void can::DeregisterCallback(const char* caller)
  {
  m_rxcallbacks.remove_if([caller](CanFrameCallbackEntry* entry){ return strcmp(entry->m_caller, caller)==0; });
  m_txcallbacks.remove_if([caller](CanFrameCallbackEntry* entry){ return strcmp(entry->m_caller, caller)==0; });
  }

int can::ExecuteCallbacks(const CAN_frame_t* frame, bool tx, bool success)
  {
  int cnt = 0;
  if (tx)
    {
    if (frame->callback)
      {
      // invoke frame-specific callback function
      (*(frame->callback))(frame, success);
      cnt++;
      }
    for (auto entry : m_txcallbacks)
      {
      // invoke generic tx callbacks
      entry->m_callback(frame, success);
      cnt++;
      }
    }
  else
    {
    for (auto entry : m_rxcallbacks)
      {
      entry->m_callback(frame, success);
      cnt++;
      }
    }
  return cnt;
  }

////////////////////////////////////////////////////////////////////////
// canbus - the definition of a CAN bus
////////////////////////////////////////////////////////////////////////

canbus::canbus(const char* name)
  : pcp(name)
  {
  m_busnumber = name[strlen(name)-1] - '1';
  m_txqueue = xQueueCreate(CONFIG_OVMS_HW_CAN_TX_QUEUE_SIZE, sizeof(CAN_frame_t));
  m_mode = CAN_MODE_OFF;
  m_speed = CAN_SPEED_1000KBPS;
  m_dbcfile = NULL;
  m_tx_frame = {};
  ClearStatus();

  using std::placeholders::_1;
  using std::placeholders::_2;
  MyEvents.RegisterEvent(TAG, "ticker.10", std::bind(&canbus::BusTicker10, this, _1, _2));
  }

canbus::~canbus()
  {
  vQueueDelete(m_txqueue);
  }

esp_err_t canbus::Start(CAN_mode_t mode, CAN_speed_t speed)
  {
  ClearStatus();
  return ESP_FAIL;
  }

esp_err_t canbus::Start(CAN_mode_t mode, CAN_speed_t speed, dbcfile *dbcfile)
  {
  if (m_dbcfile) DetachDBC();
  if (dbcfile) AttachDBC(dbcfile);
  return Start(mode, speed);
  }

esp_err_t canbus::Stop()
  {
  if (m_dbcfile) DetachDBC();

  return ESP_FAIL;
  }

esp_err_t canbus::ViewRegisters()
  {
  return ESP_ERR_NOT_SUPPORTED;
  }

esp_err_t canbus::WriteReg( uint8_t reg, uint8_t value )
  {
  return ESP_FAIL;
  }

void canbus::ClearStatus()
  {
  memset(&m_status, 0, sizeof(m_status));
  m_status_chksum = 0;
  m_watchdog_timer = monotonictime;
  }

void canbus::AttachDBC(dbcfile *dbcfile)
  {
  if (m_dbcfile) DetachDBC();
  m_dbcfile = dbcfile;
  m_dbcfile->LockFile();
  }

bool canbus::AttachDBC(const char *name)
  {
  if (m_dbcfile) DetachDBC();

  dbcfile *dbcfile = MyDBC.Find(name);
  if (dbcfile == NULL) return false;

  m_dbcfile = dbcfile;
  m_dbcfile->LockFile();
  return true;
  }

void canbus::DetachDBC()
  {
  if (m_dbcfile)
    {
    m_dbcfile->UnlockFile();
    m_dbcfile = NULL;
    }
  }

dbcfile* canbus::GetDBC()
  {
  return m_dbcfile;
  }

void canbus::BusTicker10(std::string event, void* data)
  {
  if ((m_powermode==On)&&(StandardMetrics.ms_v_env_on->AsBool()))
    {
    // Bus is powered on, and vehicle is ON
    if ((monotonictime-m_watchdog_timer) > 60)
      {
      // We have a watchdog timeout
      // We need to reset the CAN bus...
      m_status.watchdog_resets++;
      CAN_mode_t m = m_mode;
      CAN_speed_t s = m_speed;
      CAN_status_t t; memcpy(&t,&m_status,sizeof(CAN_status_t));
      ESP_LOGE(TAG, "%s watchdog inactivity timeout - resetting bus",m_name);
      Stop();
      Start(m, s);
      memcpy(&m_status,&t,sizeof(CAN_status_t));
      m_watchdog_timer = monotonictime;
      }
    }
  else
    {
    // Vehicle is OFF, so just tickle the watchdog timer
    m_watchdog_timer = monotonictime;
    }
  }

bool canbus::AsynchronousInterruptHandler(CAN_frame_t* frame, uint32_t* framesReceived)
  {
  return false;
  }

void canbus::TxCallback(CAN_frame_t* p_frame, bool success)
  {
  if (success)
    {
    m_status.packets_tx++;
    MyCan.ExecuteCallbacks(p_frame, true, success);
    MyCan.NotifyListeners(p_frame, true);
    LogFrame(CAN_LogFrame_TX, p_frame);
    }
  else
    {
    m_status.tx_fails++;
    int cnt = MyCan.ExecuteCallbacks(p_frame, true, success);
    LogFrame(CAN_LogFrame_TX_Fail, p_frame);
    // log error status if no application callbacks were called for this frame:
    if (cnt == 0)
      {
      LogStatus(CAN_LogStatus_Error);
      }
    }
  }

/**
 * canbus::Write -- main TX API
 *    - returns ESP_OK, ESP_QUEUED or ESP_FAIL
 *      … ESP_OK = frame delivered to CAN transceiver (not necessarily sent!)
 *      … ESP_FAIL = TX queue is full (TX overflow)
 *    - actual TX implementation in driver override
 */
esp_err_t canbus::Write(const CAN_frame_t* p_frame, TickType_t maxqueuewait /*=0*/)
  {
  m_tx_frame = *p_frame; // save a local copy of this frame to be used later in txcallback
  m_tx_frame.origin = this;
  return ESP_OK;
  }

/**
 * canbus::QueueWrite -- add a frame to the TX queue for later delivery
 *    - internal method, called by driver if no TX buffer is available
 */
esp_err_t canbus::QueueWrite(const CAN_frame_t* p_frame, TickType_t maxqueuewait /*=0*/)
  {
  if (xQueueSend(m_txqueue, p_frame, maxqueuewait) == pdTRUE)
    {
    m_status.txbuf_delay++;
    LogFrame(CAN_LogFrame_TX_Queue, p_frame);
    return ESP_QUEUED;
    }
  else
    {
    m_status.txbuf_overflow++;
    LogFrame(CAN_LogFrame_TX_Fail, p_frame);
    return ESP_FAIL;
    }
  }

/**
 * canbus::WriteExtended -- application TX utility
 */
esp_err_t canbus::WriteExtended(uint32_t id, uint8_t length, uint8_t *data, TickType_t maxqueuewait /*=0*/)
  {
  if (length > 8)
    {
    abort();
    }

  CAN_frame_t frame;
  memset(&frame, 0, sizeof(frame));

  frame.origin = this;
  frame.FIR.U = 0;
  frame.FIR.B.DLC = length;
  frame.FIR.B.FF = CAN_frame_ext;
  frame.MsgID = id;
  memcpy(frame.data.u8, data, length);
  return this->Write(&frame, maxqueuewait);
  }

/**
 * canbus::WriteStandard -- application TX utility
 */
esp_err_t canbus::WriteStandard(uint16_t id, uint8_t length, uint8_t *data, TickType_t maxqueuewait /*=0*/)
  {
  if (length > 8)
    {
    abort();
    }

  CAN_frame_t frame;
  memset(&frame, 0, sizeof(frame));

  frame.origin = this;
  frame.FIR.U = 0;
  frame.FIR.B.DLC = length;
  frame.FIR.B.FF = CAN_frame_std;
  frame.MsgID = id;
  memcpy(frame.data.u8, data, length);
  return this->Write(&frame, maxqueuewait);
  }

/**
 * CAN_frame_t::Write -- main TX API
 *    - returns ESP_OK, ESP_QUEUED or ESP_FAIL
 *      … ESP_OK = frame delivered to CAN transceiver (not necessarily sent!)
 *      … ESP_FAIL = TX queue is full (TX overflow)
 */
esp_err_t CAN_frame_t::Write(canbus* bus /*=NULL*/, TickType_t maxqueuewait /*=0*/)
  {
  if (!bus)
    bus = origin;
  return bus ? bus->Write(this, maxqueuewait) : ESP_FAIL;
  }
