420 lines
13 KiB
C++
420 lines
13 KiB
C++
/**
|
|
* Project: Open Vehicle Monitor System
|
|
* Module: Renault Twizy SEVCON Gen4 access: monitoring
|
|
*
|
|
* (c) 2018 Michael Balzer <dexter@dexters-web.de>
|
|
*
|
|
* 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 = "v-twizy";
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <iomanip>
|
|
|
|
#include "crypt_base64.h"
|
|
#include "ovms_notify.h"
|
|
|
|
#include "vehicle_renaulttwizy.h"
|
|
#include "rt_sevcon.h"
|
|
|
|
|
|
/**
|
|
* Shell command:
|
|
* - xrt mon start <filename>
|
|
*/
|
|
void SevconClient::shell_mon_start(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
SevconClient* me = GetInstance();
|
|
FILE* fp = (FILE*)me->m_mon_file;
|
|
|
|
if (fp) {
|
|
OvmsMutexLock lock(&me->m_mon_mutex);
|
|
me->m_mon_enable = false;
|
|
me->m_mon_file = NULL;
|
|
fclose(fp);
|
|
writer->puts("Recording stopped.");
|
|
}
|
|
|
|
if (argc == 1) {
|
|
if (MyConfig.ProtectedPath(argv[0])) {
|
|
writer->printf("Error: protected path '%s'!\n", argv[0]);
|
|
return;
|
|
}
|
|
if ((me->m_mon_file = fopen(argv[0], "a")) == NULL) {
|
|
writer->printf("Error: can't open '%s'!\n", argv[0]);
|
|
return;
|
|
} else {
|
|
// write CSV header:
|
|
fputs(
|
|
"timestamp,kph,rpm,throttle,kickdown,brake,"
|
|
"mot_torque_limit,mot_torque_demand,mot_torque,"
|
|
"bat_voltage,bat_current,bat_power,cap_voltage,"
|
|
"mot_voltage,mot_current,mot_power,mot_voltmod,mot_slipfreq,mot_outputfreq\n",
|
|
(FILE*)me->m_mon_file);
|
|
me->m_mon_enable = true;
|
|
writer->printf("Recording started to '%s'.\n", argv[0]);
|
|
}
|
|
} else {
|
|
if (me->m_mon_enable) {
|
|
writer->puts("Monitoring already running.");
|
|
} else {
|
|
me->m_mon_enable = true;
|
|
writer->puts("Monitoring started.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Shell command:
|
|
* - xrt mon stop
|
|
*/
|
|
void SevconClient::shell_mon_stop(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
SevconClient* me = GetInstance();
|
|
|
|
if (!me->m_mon_enable) {
|
|
writer->puts("Monitoring not started.");
|
|
return;
|
|
}
|
|
|
|
FILE* fp = (FILE*)me->m_mon_file;
|
|
me->m_mon_enable = false;
|
|
|
|
if (fp) {
|
|
OvmsMutexLock lock(&me->m_mon_mutex);
|
|
me->m_mon_file = NULL;
|
|
fclose(fp);
|
|
writer->puts("Recording stopped.");
|
|
}
|
|
|
|
me->SendMonitoringData();
|
|
writer->puts("Monitoring stopped.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Shell command:
|
|
* - xrt mon reset
|
|
*/
|
|
void SevconClient::shell_mon_reset(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
|
|
{
|
|
SevconClient* me = GetInstance();
|
|
if (me->m_mon_enable) {
|
|
me->SendMonitoringData();
|
|
}
|
|
me->m_mon.reset_speedmaps();
|
|
writer->puts("Reset done.");
|
|
}
|
|
|
|
|
|
/**
|
|
* Reset speed maps
|
|
*/
|
|
void sc_mondata::reset_speedmaps()
|
|
{
|
|
for (int i=0; i<SCMON_MAX_KPH; i++) {
|
|
bat_power_drv[i] = 0;
|
|
bat_power_rec[i] = 0;
|
|
mot_torque_drv[i] = 0;
|
|
mot_torque_rec[i] = 0;
|
|
}
|
|
m_bat_power_drv->ClearValue();
|
|
m_bat_power_rec->ClearValue();
|
|
m_mot_torque_drv->ClearValue();
|
|
m_mot_torque_rec->ClearValue();
|
|
}
|
|
|
|
|
|
/**
|
|
* SendMonitoringData:
|
|
*/
|
|
void SevconClient::SendMonitoringData()
|
|
{
|
|
std::string msg;
|
|
|
|
msg = "RT-ENG-BatPwrDrv,0,86400,";
|
|
msg.append(m_mon.m_bat_power_drv->AsString());
|
|
MyNotify.NotifyString("data", "xrt.power.dyno", msg.c_str());
|
|
|
|
msg = "RT-ENG-BatPwrRec,0,86400,";
|
|
msg.append(m_mon.m_bat_power_rec->AsString());
|
|
MyNotify.NotifyString("data", "xrt.power.dyno", msg.c_str());
|
|
|
|
msg = "RT-ENG-MotTrqDrv,0,86400,";
|
|
msg.append(m_mon.m_mot_torque_drv->AsString());
|
|
MyNotify.NotifyString("data", "xrt.power.dyno", msg.c_str());
|
|
|
|
msg = "RT-ENG-MotTrqRec,0,86400,";
|
|
msg.append(m_mon.m_mot_torque_rec->AsString());
|
|
MyNotify.NotifyString("data", "xrt.power.dyno", msg.c_str());
|
|
}
|
|
|
|
|
|
/**
|
|
* InitMonitoringMetrics:
|
|
*/
|
|
void SevconClient::InitMonitoring()
|
|
{
|
|
// .i. = Inverter:
|
|
|
|
m_mon.mot_current = MyMetrics.InitFloat("xrt.i.cur.act", SM_STALE_MIN, 0, Amps);
|
|
m_mon.mot_voltage = MyMetrics.InitFloat("xrt.i.vlt.act", SM_STALE_MIN, 0, Volts);
|
|
m_mon.mot_power = MyMetrics.InitFloat("xrt.i.pwr.act", SM_STALE_MIN, 0, kW);
|
|
m_mon.mot_voltmod = MyMetrics.InitFloat("xrt.i.vlt.mod", SM_STALE_MIN, 0, Percentage);
|
|
|
|
m_mon.mot_slipfreq = MyMetrics.InitFloat("xrt.i.frq.slip", SM_STALE_MIN, 0, Other);
|
|
m_mon.mot_outputfreq = MyMetrics.InitFloat("xrt.i.frq.output", SM_STALE_MIN, 0, Other);
|
|
|
|
m_mon.mot_torque_demand = MyMetrics.InitFloat("xrt.i.trq.demand", SM_STALE_MIN, 0, Other);
|
|
m_mon.mot_torque = MyMetrics.InitFloat("xrt.i.trq.act", SM_STALE_MIN, 0, Other);
|
|
m_mon.mot_torque_limit = MyMetrics.InitFloat("xrt.i.trq.limit", SM_STALE_MIN, 0, Other);
|
|
|
|
m_mon.bat_voltage = MyMetrics.InitFloat("xrt.i.vlt.bat", SM_STALE_MIN, 0, Other);
|
|
m_mon.cap_voltage = MyMetrics.InitFloat("xrt.i.vlt.cap", SM_STALE_MIN, 0, Amps);
|
|
|
|
// .s. = Statistics:
|
|
|
|
m_mon.m_bat_power_drv = new OvmsMetricVector<float>("xrt.s.b.pwr.drv", SM_STALE_HIGH, kW);
|
|
m_mon.m_bat_power_rec = new OvmsMetricVector<float>("xrt.s.b.pwr.rec", SM_STALE_HIGH, kW);
|
|
|
|
m_mon.m_mot_torque_drv = new OvmsMetricVector<float>("xrt.s.m.trq.drv", SM_STALE_HIGH, Nm);
|
|
m_mon.m_mot_torque_rec = new OvmsMetricVector<float>("xrt.s.m.trq.rec", SM_STALE_HIGH, Nm);
|
|
}
|
|
|
|
|
|
/**
|
|
* ShutdownMonitoring:
|
|
*/
|
|
void SevconClient::ShutdownMonitoring()
|
|
{
|
|
// close file:
|
|
if (m_mon_enable) {
|
|
FILE* fp = (FILE*)m_mon_file;
|
|
m_mon_enable = false;
|
|
if (fp) {
|
|
OvmsMutexLock lock(&m_mon_mutex);
|
|
m_mon_file = NULL;
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
// delete metrics:
|
|
MyMetrics.DeregisterMetric(m_mon.mot_current);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_voltage);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_power);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_voltmod);
|
|
|
|
MyMetrics.DeregisterMetric(m_mon.mot_slipfreq);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_outputfreq);
|
|
|
|
MyMetrics.DeregisterMetric(m_mon.mot_torque_demand);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_torque);
|
|
MyMetrics.DeregisterMetric(m_mon.mot_torque_limit);
|
|
|
|
MyMetrics.DeregisterMetric(m_mon.bat_voltage);
|
|
MyMetrics.DeregisterMetric(m_mon.cap_voltage);
|
|
|
|
MyMetrics.DeregisterMetric(m_mon.m_bat_power_drv);
|
|
MyMetrics.DeregisterMetric(m_mon.m_bat_power_rec);
|
|
|
|
MyMetrics.DeregisterMetric(m_mon.m_mot_torque_drv);
|
|
MyMetrics.DeregisterMetric(m_mon.m_mot_torque_rec);
|
|
}
|
|
|
|
|
|
/**
|
|
* QueryMonitoringData: request monitored SDOs from SEVCON
|
|
* - called by IncomingFrameCan1 (triggered by PDO 0x629, 100ms interval)
|
|
* - running in vehicle task context
|
|
* Results are processed by ProcessMonitoringData()
|
|
*/
|
|
void SevconClient::QueryMonitoringData()
|
|
{
|
|
if (!m_mon_enable || m_cfgmode_request || CtrlCfgMode() || !CtrlLoggedIn() || StdMetrics.ms_v_env_gear->AsInt()==0)
|
|
return;
|
|
|
|
// 4600.0c Actual AC Motor Current [A]
|
|
SendRead(0x4600, 0x0c, &m_mon.mot_current_raw);
|
|
// 4600.0d Actual AC Motor Voltage [1/16 V]
|
|
SendRead(0x4600, 0x0d, &m_mon.mot_voltage_raw);
|
|
// 4600.0b Voltage modulation [100/255 %]
|
|
SendRead(0x4600, 0x0b, &m_mon.mot_voltmod_raw);
|
|
|
|
// 4600.01 Slip Frequency [1/256 rad/s]
|
|
SendRead(0x4600, 0x01, &m_mon.mot_slipfreq_raw);
|
|
// 4600.0f Electrical output frequency [1/16 rad/s]
|
|
SendRead(0x4600, 0x0f, &m_mon.mot_outputfreq_raw);
|
|
|
|
// 4602.0b Torque demand value (U.T_d) [1/16 Nm]
|
|
SendRead(0x4602, 0x0b, &m_mon.mot_torque_demand_raw);
|
|
// 4602.0c Torque actual value (DWork.Td) [1/16 Nm]
|
|
SendRead(0x4602, 0x0c, &m_mon.mot_torque_raw);
|
|
// 4602.0e Maximum power limit torque (Y.T_power_limit) [1/16 Nm]
|
|
SendRead(0x4602, 0x0e, &m_mon.mot_torque_limit_raw);
|
|
|
|
// 4602.11 Battery Voltage [1/16 V]
|
|
SendRead(0x4602, 0x11, &m_mon.bat_voltage_raw);
|
|
// 4602.12 Capacitor Voltage [1/16 V]
|
|
SendRead(0x4602, 0x12, &m_mon.cap_voltage_raw);
|
|
}
|
|
|
|
|
|
/**
|
|
* ProcessMonitoringData: process async read results
|
|
* - called by SevconAsyncTask
|
|
* - running in m_asynctask context
|
|
*/
|
|
void SevconClient::ProcessMonitoringData(CANopenJob &job)
|
|
{
|
|
if (job.type != COJT_ReadSDO || job.result != COR_OK)
|
|
return;
|
|
|
|
uint32_t addr = (uint32_t)job.sdo.index << 8 | job.sdo.subindex;
|
|
bool complete = false;
|
|
switch (addr) {
|
|
// 4600.0c Actual AC Motor Current [A]
|
|
case 0x46000c:
|
|
m_mon.mot_current->SetValue(m_mon.mot_current_raw);
|
|
break;
|
|
// 4600.0d Actual AC Motor Voltage [1/16 V]
|
|
case 0x46000d:
|
|
m_mon.mot_voltage->SetValue(m_mon.mot_voltage_raw / 16.0f);
|
|
m_mon.mot_power->SetValue(m_mon.mot_voltage_raw / 16.0f * m_mon.mot_current->AsFloat() / 1000.0f);
|
|
break;
|
|
// 4600.0b Voltage modulation [100/255 %]
|
|
case 0x46000b:
|
|
m_mon.mot_voltmod->SetValue(m_mon.mot_voltmod_raw * 100.0f / 255.0f);
|
|
break;
|
|
|
|
// 4600.01 Slip Frequency [1/256 rad/s]
|
|
case 0x460001:
|
|
m_mon.mot_slipfreq->SetValue(m_mon.mot_slipfreq_raw / 256.0f);
|
|
break;
|
|
// 4600.0f Electrical output frequency [1/16 rad/s]
|
|
case 0x46000f:
|
|
m_mon.mot_outputfreq->SetValue(m_mon.mot_outputfreq_raw / 16.0f);
|
|
break;
|
|
|
|
// 4602.0b Torque demand value (U.T_d) [1/16 Nm]
|
|
case 0x46020b:
|
|
m_mon.mot_torque_demand->SetValue(m_mon.mot_torque_demand_raw / 16.0f);
|
|
break;
|
|
// 4602.0c Torque actual value (DWork.Td) [1/16 Nm]
|
|
case 0x46020c:
|
|
m_mon.mot_torque->SetValue(m_mon.mot_torque_raw / 16.0f);
|
|
break;
|
|
// 4602.0e Maximum power limit torque (Y.T_power_limit) [1/16 Nm]
|
|
case 0x46020e:
|
|
m_mon.mot_torque_limit->SetValue(m_mon.mot_torque_limit_raw / 16.0f);
|
|
break;
|
|
|
|
// 4602.11 Battery Voltage [1/16 V]
|
|
case 0x460211:
|
|
m_mon.bat_voltage->SetValue(m_mon.bat_voltage_raw / 16.0f);
|
|
break;
|
|
// 4602.12 Capacitor Voltage [1/16 V]
|
|
case 0x460212:
|
|
m_mon.cap_voltage->SetValue(m_mon.cap_voltage_raw / 16.0f);
|
|
complete = true; // last in series
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (complete) {
|
|
|
|
// update speed maps:
|
|
|
|
float spd = StdMetrics.ms_v_pos_speed->AsFloat();
|
|
float batpwr = m_mon.bat_voltage->AsFloat() * StdMetrics.ms_v_bat_current->AsFloat();
|
|
float mottrq = m_mon.mot_torque->AsFloat();
|
|
|
|
int si = (int) spd;
|
|
if (si < SCMON_MAX_KPH) {
|
|
if (batpwr > 0) {
|
|
// drive:
|
|
if (m_mon.bat_power_drv[si] < (int16_t)batpwr) {
|
|
m_mon.bat_power_drv[si] = (int16_t)batpwr;
|
|
m_mon.m_bat_power_drv->SetElemValue(si, ((int16_t)batpwr) / 1000.0f);
|
|
}
|
|
if (m_mon.mot_torque_drv[si] < mottrq) {
|
|
m_mon.mot_torque_drv[si] = mottrq;
|
|
m_mon.m_mot_torque_drv->SetElemValue(si, mottrq);
|
|
}
|
|
} else {
|
|
// recup:
|
|
if (m_mon.bat_power_rec[si] < -(int16_t)batpwr) {
|
|
m_mon.bat_power_rec[si] = -(int16_t)batpwr;
|
|
m_mon.m_bat_power_rec->SetElemValue(si, (-(int16_t)batpwr) / 1000.0f);
|
|
}
|
|
if (m_mon.mot_torque_rec[si] < -mottrq) {
|
|
m_mon.mot_torque_rec[si] = -mottrq;
|
|
m_mon.m_mot_torque_rec->SetElemValue(si, -mottrq);
|
|
}
|
|
}
|
|
}
|
|
|
|
// write log:
|
|
if (m_mon_file) {
|
|
OvmsMutexLock lock(&m_mon_mutex);
|
|
if (m_mon_file) {
|
|
fprintf((FILE*)m_mon_file,
|
|
// timestamp,kph,rpm,throttle,kickdown,brake,
|
|
"%u,%.1f,%d,%.0f,%u,%.0f,"
|
|
// mot_torque_limit,mot_torque_demand,mot_torque,
|
|
"%.1f,%.1f,%.1f,"
|
|
// bat_voltage,bat_current,bat_power,cap_voltage,
|
|
"%.1f,%.2f,%.1f,%.1f,"
|
|
// mot_voltage,mot_current,mot_power,mot_voltmod,mot_slipfreq,mot_outputfreq
|
|
"%.1f,%.0f,%.1f,%.1f,%.1f,%.1f\n",
|
|
|
|
esp_log_timestamp(),
|
|
spd,
|
|
StdMetrics.ms_v_mot_rpm->AsInt(),
|
|
StdMetrics.ms_v_env_throttle->AsFloat(),
|
|
m_twizy->twizy_kickdown_hold,
|
|
StdMetrics.ms_v_env_footbrake->AsFloat(),
|
|
|
|
m_mon.mot_torque_limit->AsFloat(),
|
|
m_mon.mot_torque_demand->AsFloat(),
|
|
mottrq,
|
|
|
|
m_mon.bat_voltage->AsFloat(),
|
|
StdMetrics.ms_v_bat_current->AsFloat(),
|
|
batpwr,
|
|
m_mon.cap_voltage->AsFloat(),
|
|
|
|
m_mon.mot_voltage->AsFloat(),
|
|
m_mon.mot_current->AsFloat(),
|
|
m_mon.mot_power->AsFloat(),
|
|
m_mon.mot_voltmod->AsFloat(),
|
|
m_mon.mot_slipfreq->AsFloat(),
|
|
m_mon.mot_outputfreq->AsFloat());
|
|
}
|
|
}
|
|
}
|
|
}
|