2019-10-05 07:54:26 -07:00
|
|
|
/*
|
|
|
|
* @file pid_from_msl.cpp
|
|
|
|
*
|
|
|
|
* @date Oct 5, 2019
|
|
|
|
* @author andreika, (c) 2019
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
#include <fstream>
|
|
|
|
#include <vector>
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
#include "pid_auto.h"
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
|
|
|
|
class MslData {
|
|
|
|
public:
|
|
|
|
bool readMsl(const char *fname, double startTime, double endTime, int inputIdx, int outputIdx) {
|
|
|
|
std::ifstream fp(fname);
|
|
|
|
|
|
|
|
if (!fp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
curIdx = -1;
|
2019-10-07 11:13:16 -07:00
|
|
|
settings.minValue = settings.maxValue = settings.maxPoint = 0;
|
|
|
|
settings.timeScale = 1,
|
|
|
|
settings.stepPoint = -1.0;
|
|
|
|
totalTime = 0;
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
std::string str;
|
|
|
|
for (int i = 0; std::getline(fp, str); i++) {
|
|
|
|
// data starts at 4th line
|
|
|
|
if (i < 4)
|
|
|
|
continue;
|
|
|
|
parseLine(str, startTime, endTime, inputIdx, outputIdx);
|
|
|
|
}
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
settings.maxPoint = getSaturationStartPoint();
|
2019-10-05 07:54:26 -07:00
|
|
|
assert(data.size() == curIdx);
|
|
|
|
|
2019-10-07 11:13:16 -07:00
|
|
|
settings.timeScale = settings.maxPoint / totalTime;
|
|
|
|
|
2019-10-05 07:54:26 -07:00
|
|
|
fp.close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool parseLine(const std::string & str, double startTime, double endTime, int inputIdx, int outputIdx) {
|
|
|
|
std::stringstream sstr(str);
|
|
|
|
std::string item;
|
|
|
|
for (int j = 0; getline(sstr, item, '\t'); j++) {
|
|
|
|
double v = atof(item.c_str());
|
|
|
|
// the first column is timestamp
|
|
|
|
if (j == 0) {
|
|
|
|
if (v < startTime || v > endTime)
|
|
|
|
return false;
|
2019-10-07 11:13:16 -07:00
|
|
|
if (curIdx < 0)
|
|
|
|
prevTime = v;
|
|
|
|
totalTime += v - prevTime;
|
|
|
|
prevTime = v;
|
2019-10-05 07:54:26 -07:00
|
|
|
} else if (j == inputIdx) {
|
|
|
|
// this is an input step, we should find it
|
|
|
|
if (curIdx < 0) {
|
2019-10-07 11:13:16 -07:00
|
|
|
settings.minValue = v;
|
2019-10-05 07:54:26 -07:00
|
|
|
curIdx = 0;
|
2019-10-07 11:13:16 -07:00
|
|
|
} else if (v != settings.minValue && settings.stepPoint < 0) {
|
|
|
|
settings.maxValue = v;
|
|
|
|
settings.stepPoint = curIdx;
|
2019-10-05 07:54:26 -07:00
|
|
|
}
|
|
|
|
curIdx++;
|
|
|
|
} else if (j == outputIdx) {
|
2019-10-05 12:56:49 -07:00
|
|
|
/*const float alpha = 0.5f;
|
|
|
|
float fv = alpha * prevV + (1.0f - alpha) * (float)v;
|
|
|
|
prevV = v;
|
|
|
|
data.push_back(fv);*/
|
2019-10-05 07:54:26 -07:00
|
|
|
data.push_back((float)v);
|
2019-10-09 10:11:18 -07:00
|
|
|
if (curIdx >= 0 && settings.stepPoint < 0) {
|
|
|
|
// calculate averaged level to determine the acceptable noise level
|
|
|
|
averagedMin = (averagedMin * (curIdx - 1) + v) / curIdx;
|
|
|
|
// this is not accurate because 'averagedMin' is continuously changing
|
|
|
|
acceptableNoiseLevel = std::max(acceptableNoiseLevel, abs(v - averagedMin));
|
|
|
|
}
|
2019-10-05 07:54:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
double getSaturationStartPoint() {
|
|
|
|
int i;
|
|
|
|
double j;
|
|
|
|
// max noise level is used to get the saturation limit of the signal
|
|
|
|
double curNoiseLevel = 0, averagedMax = 0;
|
|
|
|
// we step back some points from the last one and find the saturation start
|
|
|
|
for (i = curIdx - 1, j = 1.0; i > settings.stepPoint; i--, j += 1.0) {
|
|
|
|
double v = data[i];
|
|
|
|
averagedMax = (averagedMax * (j - 1) + v) / j;
|
|
|
|
// this is not accurate because 'averagedMax' is continuously changing
|
|
|
|
curNoiseLevel = std::max(curNoiseLevel, abs(v - averagedMax));
|
|
|
|
|
|
|
|
// we assume that the "upper" level noise is like the same as the "lower" noise, so we compare them,
|
|
|
|
// and if the noise level starts growing, then we're in the step transient zone, and we stop
|
|
|
|
if (curNoiseLevel > acceptableNoiseLevel) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// return the point in the middle, just to be safe (we don't want to be close to the transient zone)
|
|
|
|
return (curIdx - 1 + i) / 2;
|
|
|
|
}
|
|
|
|
|
2019-10-05 07:54:26 -07:00
|
|
|
public:
|
|
|
|
std::vector<float> data;
|
2019-10-07 11:13:16 -07:00
|
|
|
double totalTime = 0, prevTime = 0;
|
|
|
|
PidAutoTuneSettings settings;
|
2019-10-05 07:54:26 -07:00
|
|
|
int curIdx = -1;
|
2019-10-05 12:56:49 -07:00
|
|
|
float prevV = 0;
|
2019-10-09 10:11:18 -07:00
|
|
|
|
|
|
|
// we assume that the signal is quasi-stable (asymptotic) from the start point until the 'stepPoint';
|
|
|
|
// it's noise level is used to find the saturation limit of the rest of the data (see getSaturationStartPoint())
|
|
|
|
double acceptableNoiseLevel = 0;
|
|
|
|
double averagedMin = 0;
|
2019-10-05 07:54:26 -07:00
|
|
|
};
|
|
|
|
|
2019-10-05 12:56:49 -07:00
|
|
|
#if 1
|
2019-10-05 07:54:26 -07:00
|
|
|
int main(int argc, char **argv) {
|
|
|
|
if (argc < 6) {
|
|
|
|
printf("Usage: PID_FROM_MSL file.msl start_time end_time input_column output_column...\r\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
printf("PID_FROM_MSL - find PID controller coefficients based on a measured step response in a rusEFI log file.\r\n");
|
|
|
|
printf("Version 0.1 (c) andreika, 2019\r\n\r\n");
|
|
|
|
printf("Reading file %s...\r\n", argv[1]);
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
MslData data;
|
|
|
|
if (!data.readMsl(argv[1], atof(argv[2]), atof(argv[3]), atoi(argv[4]), atoi(argv[5]))) {
|
|
|
|
return -2;
|
|
|
|
}
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
printf("Measuring Settings: minValue=%g maxValue=%g stepPoint=%g maxPoint=%g numPoints=%d timeScale=%g\r\n",
|
|
|
|
data.settings.minValue, data.settings.maxValue, data.settings.stepPoint, data.settings.maxPoint, data.data.size(), data.settings.timeScale);
|
2019-10-05 07:54:26 -07:00
|
|
|
|
2019-10-11 11:14:56 -07:00
|
|
|
PidAutoTune chr1, chr2;
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
for (size_t i = 0; i < data.data.size(); i++) {
|
2019-10-09 10:11:18 -07:00
|
|
|
chr1.addData(data.data[i]);
|
|
|
|
chr2.addData(data.data[i]);
|
2019-10-05 07:54:26 -07:00
|
|
|
}
|
|
|
|
|
2019-10-09 10:11:18 -07:00
|
|
|
// todo: more flexible method chooser
|
2019-10-11 11:14:56 -07:00
|
|
|
PidTuneMethod method = PID_TUNE_AUTO1;
|
|
|
|
printf("\r\nTrying method Auto1:\r\n");
|
2019-10-09 10:11:18 -07:00
|
|
|
chr1.findPid(method, data.settings, nullptr);
|
|
|
|
|
2019-10-11 11:14:56 -07:00
|
|
|
method = PID_TUNE_AUTO2;
|
|
|
|
printf("\r\nTrying method Auto2:\r\n");
|
2019-10-09 10:11:18 -07:00
|
|
|
chr2.findPid(method, data.settings, nullptr);
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
printf("Done!\r\n");
|
|
|
|
|
2019-10-11 11:14:56 -07:00
|
|
|
// todo: is it correct?
|
|
|
|
double dTime = 1.0 / data.settings.timeScale;
|
|
|
|
const int numSimPoints = 1024;
|
|
|
|
|
|
|
|
PidAutoTune *chr[2] = { &chr1, &chr2 };
|
2019-10-09 10:11:18 -07:00
|
|
|
for (int k = 0; k < 2; k++) {
|
|
|
|
const double *p = chr[k]->getParams();
|
|
|
|
printf("Model-%d Params: K=%g T1=%g T2=%g L=%g\r\n", (k + 1), p[PARAM_K], p[PARAM_T], p[PARAM_T2], p[PARAM_L]);
|
2019-10-11 11:14:56 -07:00
|
|
|
const pid_s pid0 = chr[k]->getPid0();
|
|
|
|
const pid_s pid = chr[k]->getPid();
|
|
|
|
printf(" PID0: P=%.8f I=%.8f D=%.8f offset=%.8f\r\n", pid0.pFactor, pid0.iFactor, pid0.dFactor, pid0.offset);
|
|
|
|
|
|
|
|
PidSimulator<numSimPoints> sim(chr[k]->getMethodOrder(method), chr[k]->getAvgMeasuredMin(), chr[k]->getAvgMeasuredMax(), dTime, true);
|
|
|
|
PidAccuracyMetric metric0 = sim.simulate(numSimPoints, pid0, p);
|
|
|
|
printf(" Metric0 result: ITAE=%g ISE=%g Overshoot=%g%%\r\n", metric0.getItae(), metric0.getIse(), metric0.getMaxOvershoot() * 100.0);
|
|
|
|
|
|
|
|
printf(" PID: P=%.8f I=%.8f D=%.8f offset=%.8f\r\n", pid.pFactor, pid.iFactor, pid.dFactor, pid.offset);
|
|
|
|
PidAccuracyMetric metric = sim.simulate(numSimPoints, pid, p);
|
|
|
|
printf(" Metric result: ITAE=%g ISE=%g Overshoot=%g%%\r\n", metric.getItae(), metric.getIse(), metric.getMaxOvershoot() * 100.0);
|
2019-10-09 10:11:18 -07:00
|
|
|
}
|
2019-10-05 07:54:26 -07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|