OVMS3/OVMS.V3/components/can/src/can.cpp

1253 lines
33 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.
;
; 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%x 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%x 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",
"Metric"
};
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;
}