497 lines
18 KiB
C++
497 lines
18 KiB
C++
#include "akbmonitorpage.h"
|
|
#include "ui_akbmonitorpage.h"
|
|
|
|
#include <QIntValidator>
|
|
#include <QDoubleValidator>
|
|
#include <QDateTime>
|
|
#include <math.h>
|
|
|
|
#include "configparamsgetter.h"
|
|
|
|
AkbMonitorPage::AkbMonitorPage(QWidget *parent) :
|
|
QFrame(parent),
|
|
ui(new Ui::AkbMonitorPage)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
ui->pbLoadDataFromBmsMemory->hide();
|
|
|
|
// L
|
|
ui->lbCellNumberLeft->hide();
|
|
ui->lbCellNumberLeftDots->hide();
|
|
ui->editCellNumberLeft->hide();
|
|
// R
|
|
ui->lbCellNumberRight->hide();
|
|
ui->lbCellNumberRightDots->hide();
|
|
ui->editCellNumberRight->hide();
|
|
|
|
// 1 // L
|
|
ui->editBatteryChargeLevel ->setValidator( new QIntValidator(-1000, 1000, this) ); // mValues.soC
|
|
ui->editBatteryVoltage ->setValidator( new QIntValidator(-1000, 1000, this) ); // mValues.packVoltage
|
|
ui->editNominalCapacity ->setValidator( new QIntValidator(-1000, 1000, this) ); //, mDieBieMS->bmsConfig(), "batteryCapacity");
|
|
ui->editBatteryTemperature ->setValidator( new QDoubleValidator(-100, 100, 2, this) ); // mValues.tempBattHigh
|
|
// 1 // R
|
|
ui->editModulesNumber ->setValidator( new QIntValidator(-1000, 1000, this) ); //, mDieBieMS->bmsConfig(), "cellMonitorICCount");
|
|
ui->editCellsNumber ->setValidator( new QIntValidator(-1000, 1000, this) ); //, mDieBieMS->bmsConfig(), "noOfCellsSeries");
|
|
ui->editActualCapacity ->setValidator( new QIntValidator(-1000, 1000, this) ); // NOT IN SETTINGS
|
|
ui->editBmsTemperature ->setValidator( new QDoubleValidator(-100, 100, 2, this) ); // mValues.tempBMSHigh
|
|
//
|
|
// 2 // L ->clear();
|
|
ui->editMaxVoltageOnCell ->setValidator( new QDoubleValidator(-100, 100, 2, this) ); // mValues.cVHigh
|
|
ui->editCellNumberLeft ->setValidator( new QIntValidator(-1000, 1000, this) ); // NOT IN SETTINGS
|
|
ui->editCircuit ->setValidator( new QIntValidator(-1000, 1000, this) ); // mValues.packCurrent
|
|
// 2 // R ->clear();
|
|
ui->editMinVoltageOnCell ->setValidator( new QDoubleValidator(-100, 100, 2, this) ); // mValues.cVLow
|
|
ui->editCellNumberRight ->setValidator( new QIntValidator(-100, 1000, this) ); // NOT IN SETTINGS
|
|
|
|
|
|
mTimer = new QTimer(this);
|
|
mTimer->start(1000);
|
|
|
|
connect(mTimer, &QTimer::timeout,this, &AkbMonitorPage::timerSlot);
|
|
|
|
clearControlls();
|
|
}
|
|
|
|
AkbMonitorPage::~AkbMonitorPage()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
|
|
|
|
|
|
BMSInterface *AkbMonitorPage::bms() const
|
|
{
|
|
return mDieBieMS;
|
|
}
|
|
|
|
void AkbMonitorPage::setDieBieMS(BMSInterface *dieBieMS)
|
|
{
|
|
mDieBieMS = dieBieMS;
|
|
|
|
if (mDieBieMS) {
|
|
connect(mDieBieMS->commands(), &Commands::valuesReceived,this, &AkbMonitorPage::valuesReceived);
|
|
//connect(mDieBieMS->commands(), &Commands::cellsReceived,this, &VisualizationPage::cellsReceived);
|
|
connect(mDieBieMS->bmsConfig(), &ConfigParams::updated, this, &AkbMonitorPage::onLoadParams);
|
|
}
|
|
}
|
|
|
|
|
|
void AkbMonitorPage::timerSlot()
|
|
{
|
|
//
|
|
// We will start revieve Data WHEN
|
|
// SEE - void MainWindowNew::onTimerSlot()
|
|
// one of those left-tab-bar buttons are selected (ui->pbMainMenuBarVisualization->isChecked() || ui->pbMainMenuBarVisualization->isChecked())
|
|
//
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 1 // L
|
|
if(!mSoc.empty())
|
|
ui->editBatteryChargeLevel->setText(QString::number(mSoc.last())); // mValues.soC
|
|
|
|
if(!mPackVoltage.empty()) {
|
|
//ui->editBatteryVoltage->setText(QString::number(mPackVoltage.last())); // mValues.packVoltage
|
|
ui->editBatteryVoltage->setText(QString::number(mPackVoltage.last(), 'f', 2)); // mValues.packVoltage
|
|
}
|
|
|
|
//addParamRow(ui->editNominalCapacity, mDieBieMS->bmsConfig(), "batteryCapacity");
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
if(!mTempBattHigh.empty())
|
|
ui->editBatteryTemperature->setText(QString::number((int)mTempBattHigh.last())); // mValues.tempBattHigh
|
|
//ui->editBatteryTemperature->setText(QString::number(mTempBattHigh.last(), 'f', 2)); // mValues.tempBattHigh
|
|
|
|
// 1 // R
|
|
//addParamRow(ui->editModulesNumber, mDieBieMS->bmsConfig(), "cellMonitorICCount");
|
|
//addParamRow(ui->editCellsNumber, mDieBieMS->bmsConfig(), "noOfCellsSeries");
|
|
//// ui->editActualCapacity // NOT IN SETTINGS // AND NOT IN BMS_VALUES - valuesReceived(BMS_VALUES values)
|
|
if(!mTempBMSHigh.empty())
|
|
ui->editBmsTemperature->setText(QString::number((int)mTempBMSHigh.last())); // mValues.tempBMSHigh
|
|
//ui->editBmsTemperature->setText(QString::number(mTempBMSHigh.last(), 'f', 2)); // mValues.tempBMSHigh
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// 2 // L
|
|
if(!mCellVHigh.empty())
|
|
ui->editMaxVoltageOnCell->setText(QString::number(mCellVHigh.last(), 'f', 2)); // mValues.cVHigh
|
|
|
|
//// ui->editCellNumberLeft // NOT IN SETTINGS // AND NOT IN BMS_VALUES - valuesReceived(BMS_VALUES values)
|
|
|
|
if(!mPackCurrent.empty())
|
|
ui->editCircuit->setText(QString::number(mPackCurrent.last())); // mValues.packCurrent
|
|
// 2 // R
|
|
|
|
if(!mCellVLow.empty())
|
|
ui->editMinVoltageOnCell->setText(QString::number(mCellVLow.last(), 'f', 2)); // mValues.cVLow
|
|
|
|
//// ui->editCellNumberRight // NOT IN SETTINGS // AND NOT IN BMS_VALUES - valuesReceived(BMS_VALUES values)
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//if (mUpdateValPlot)
|
|
//{
|
|
/*
|
|
int dataSize = mPackVoltage.size();
|
|
|
|
QVector<double> xAxis(dataSize);
|
|
for (int i = 0;i < mSeconds.size();i++) {
|
|
xAxis[i] = mSeconds[i];
|
|
}
|
|
*/
|
|
|
|
/*
|
|
// Current and duty-plot
|
|
///int graphIndex = 0;
|
|
///ui->ivLCGraph->graph(graphIndex++)->setData(xAxis, mPackVoltage);
|
|
///ui->ivLCGraph->graph(graphIndex++)->setData(xAxis, mLCLoadVoltage);
|
|
///ui->ivLCGraph->graph(graphIndex++)->setData(xAxis, mLCLoadCurrent);
|
|
|
|
///graphIndex = 0;
|
|
///ui->ivHCGraph->graph(graphIndex++)->setData(xAxis, mHCLoadVoltage);
|
|
///ui->ivHCGraph->graph(graphIndex++)->setData(xAxis, mHCLoadCurrent);
|
|
|
|
///graphIndex = 0;
|
|
///ui->cellGraph->graph(graphIndex++)->setData(xAxis, mCellVHigh);
|
|
///ui->cellGraph->graph(graphIndex++)->setData(xAxis, mCellVAverage);
|
|
///ui->cellGraph->graph(graphIndex++)->setData(xAxis, mCellVLow);
|
|
|
|
///graphIndex = 0;
|
|
///mTempBMSLow.fill(0,1);
|
|
///ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBMSHigh);
|
|
///ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBMSAverage);
|
|
///ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBMSLow);
|
|
*/
|
|
|
|
// V
|
|
/*
|
|
double valVMax = mHCLoadVoltage.last();
|
|
double valVMid = mPackVoltage.last();
|
|
double valVMin = mLCLoadVoltage.last();
|
|
*/
|
|
/*// increas X
|
|
chartV->appendNextX();
|
|
// then set all new Ys
|
|
chartV->appendYToSeries(VoltageMax, valVMax);
|
|
chartV->appendYToSeries(VoltageMid, valVMid);
|
|
chartV->appendYToSeries(VoltageMin, valVMin);
|
|
|
|
ui->lbVMaxLegendVal->setText(QString("%1").arg(valVMax));
|
|
ui->lbVMidLegendVal->setText(QString("%1").arg(valVMid));
|
|
ui->lbVMinLegendVal->setText(QString("%1").arg(valVMin));
|
|
|
|
chartV->update();
|
|
ui->chartViewVoltage->repaint();
|
|
*/
|
|
|
|
// A
|
|
/*
|
|
double valAMax = mHCLoadCurrent.last();
|
|
double valAMid = mMCLoadCurrent_.last();
|
|
double valAMin = mLCLoadCurrent.last();
|
|
*/
|
|
/*// increas X
|
|
chartA->appendNextX();
|
|
// then set all new Ys
|
|
chartA->appendYToSeries(AmperageMax, valAMax);
|
|
chartA->appendYToSeries(AmperageMid, valAMid);
|
|
chartA->appendYToSeries(AmperageMin, valAMin);
|
|
|
|
ui->lbAMaxLegendVal->setText(QString("%1").arg(valAMax));
|
|
ui->lbAMidLegendVal->setText(QString("%1").arg(valAMid));
|
|
ui->lbAMinLegendVal->setText(QString("%1").arg(valAMin));
|
|
|
|
chartA->update();
|
|
ui->chartViewAmperage->repaint();
|
|
*/
|
|
|
|
// T
|
|
/*
|
|
double valTMax = mTempBattHigh.last();
|
|
double valTMid = mTempBattAverage.last();
|
|
double valTMin = mTempBattLow_.last();
|
|
*/
|
|
/*mTempBattLow_.fill(0,1);
|
|
// increas X
|
|
chartT->appendNextX();
|
|
// then set all new Ys
|
|
chartT->appendYToSeries(TemperatureMax, valTMax);
|
|
chartT->appendYToSeries(TemperatureMid, valTMid);
|
|
chartT->appendYToSeries(TemperatureMin, valTMin);
|
|
|
|
ui->lbTMaxLegendVal->setText(QString("%1").arg(valTMax));
|
|
ui->lbTMidLegendVal->setText(QString("%1").arg(valTMid));
|
|
ui->lbTMinLegendVal->setText(QString("%1").arg(valTMin));
|
|
|
|
chartT->update();
|
|
ui->chartViewTemperature->repaint();
|
|
*/
|
|
|
|
// C
|
|
/*
|
|
double valCMax = mCellVHigh.last();
|
|
double valCMid = mCellVAverage.last();
|
|
double valCMin = mCellVLow.last();
|
|
*/
|
|
/*// increas X
|
|
chartC->appendNextX();
|
|
// then set all new Ys
|
|
chartC->appendYToSeries(CellsMax, valCMax);
|
|
chartC->appendYToSeries(CellsMid, valCMid);
|
|
chartC->appendYToSeries(CellsMin, valCMin);
|
|
|
|
ui->lbCMaxLegendVal->setText(QString("%1").arg(valCMax));
|
|
ui->lbCMidLegendVal->setText(QString("%1").arg(valCMid));
|
|
ui->lbCMinLegendVal->setText(QString("%1").arg(valCMin));
|
|
|
|
chartC->update();
|
|
ui->chartViewCells->repaint();
|
|
*/
|
|
|
|
|
|
/*
|
|
ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBattHigh);
|
|
ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBattAverage);
|
|
ui->tempGraph->graph(graphIndex++)->setData(xAxis, mTempBattLow);
|
|
|
|
///if (ui->autoscaleButton->isChecked())
|
|
{
|
|
///ui->ivLCGraph->rescaleAxes();
|
|
///ui->ivHCGraph->rescaleAxes();
|
|
///ui->cellGraph->rescaleAxes();
|
|
ui->tempGraph->rescaleAxes();
|
|
}
|
|
|
|
///ui->ivLCGraph->replot();
|
|
///ui->ivHCGraph->replot();
|
|
///ui->cellGraph->replot();
|
|
ui->tempGraph->replot();
|
|
///ui->cellBarGraph->replot();
|
|
|
|
mUpdateValPlot = false;
|
|
*/
|
|
//}
|
|
}
|
|
|
|
|
|
void AkbMonitorPage::valuesReceived(BMS_VALUES values)
|
|
{
|
|
//ui->rtText->setValues(values);
|
|
|
|
const int maxS = 500;
|
|
|
|
appendDoubleAndTrunc(&mSoc, values.soC, maxS);
|
|
appendDoubleAndTrunc(&mPackCurrent, values.packCurrent, maxS);
|
|
appendDoubleAndTrunc(&mPackVoltage, values.packVoltage, maxS);
|
|
|
|
appendDoubleAndTrunc(&mLCLoadVoltage, values.loadLCVoltage, maxS);
|
|
appendDoubleAndTrunc(&mLCLoadCurrent, values.loadLCCurrent, maxS);
|
|
|
|
appendDoubleAndTrunc(&mHCLoadVoltage, values.loadHCVoltage, maxS);
|
|
appendDoubleAndTrunc(&mHCLoadCurrent, values.loadHCCurrent, maxS);
|
|
|
|
appendDoubleAndTrunc(&mAuxVoltage, values.auxVoltage, maxS);
|
|
appendDoubleAndTrunc(&mAuxCurrent, values.auxCurrent, maxS);
|
|
|
|
appendDoubleAndTrunc(&mCellVHigh, values.cVHigh, maxS);
|
|
appendDoubleAndTrunc(&mCellVAverage, values.cVAverage, maxS);
|
|
appendDoubleAndTrunc(&mCellVLow, values.cVLow, maxS);
|
|
|
|
appendDoubleAndTrunc(&mTempBMSHigh, values.tempBMSHigh, maxS);
|
|
appendDoubleAndTrunc(&mTempBMSAverage, values.tempBMSAverage, maxS);
|
|
//appendDoubleAndTrunc(&mTempBMSLow_, values.tempBMSLow_, maxS);
|
|
|
|
appendDoubleAndTrunc(&mTempBattHigh, values.tempBattHigh, maxS);
|
|
appendDoubleAndTrunc(&mTempBattAverage, values.tempBattAverage, maxS);
|
|
//appendDoubleAndTrunc(&mTempBattLow_, values.tempBattLow_, maxS);
|
|
|
|
|
|
qint64 tNow = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
double elapsed = (double)(tNow - mLastUpdateTime) / 1000.0;
|
|
if (elapsed > 1.0) {
|
|
elapsed = 1.0;
|
|
}
|
|
|
|
mSecondCounter += elapsed;
|
|
|
|
appendDoubleAndTrunc(&mSeconds, mSecondCounter, maxS);
|
|
|
|
mLastUpdateTime = tNow;
|
|
|
|
mUpdateValPlot = true;
|
|
}
|
|
|
|
/*
|
|
void AkbMonitorPage::cellsReceived(int cellCount, QVector<double> cellVoltageArray)
|
|
{
|
|
QVector<double> dataxNew;
|
|
dataxNew.clear();
|
|
QVector<double> datayNormal;
|
|
datayNormal.clear();
|
|
QVector<double> datayBalance;
|
|
datayBalance.clear();
|
|
QVector<QString> labels;
|
|
int indexPointer;
|
|
|
|
double cellHardUnder = mDieBieMS->bmsConfig()->getParamDouble("cellHardUnderVoltage");
|
|
double cellHardOver = mDieBieMS->bmsConfig()->getParamDouble("cellHardOverVoltage");
|
|
|
|
for(indexPointer = 0; indexPointer < cellCount; indexPointer++){
|
|
dataxNew.append(indexPointer + 1);
|
|
|
|
if(cellVoltageArray[indexPointer] < 0.0){
|
|
datayNormal.append(0.0);
|
|
datayBalance.append(fabs(cellVoltageArray[indexPointer]));
|
|
}else{
|
|
datayNormal.append(fabs(cellVoltageArray[indexPointer]));
|
|
datayBalance.append(0.0);
|
|
}
|
|
|
|
QString voltageString = QStringLiteral("%1V (C").arg(fabs(cellVoltageArray[indexPointer]), 0, 'f',3);
|
|
labels.append(voltageString + QString::number(indexPointer) + ")");
|
|
}
|
|
|
|
//QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);
|
|
//textTicker->addTicks(dataxNew, labels);
|
|
|
|
//ui->cellBarGraph->xAxis->setTicker(textTicker);
|
|
//ui->cellBarGraph->xAxis->setRange(0.5, indexPointer + 0.5);
|
|
//ui->cellBarGraph->yAxis->setRange(cellHardUnder, cellHardOver);
|
|
//barsNormal->setData(dataxNew, datayNormal);
|
|
//barsBalance->setData(dataxNew, datayBalance);
|
|
}
|
|
*/
|
|
|
|
void AkbMonitorPage::appendDoubleAndTrunc(QVector<double> *vec, double num, int maxSize)
|
|
{
|
|
vec->append(num);
|
|
|
|
if(vec->size() > maxSize) {
|
|
vec->remove(0, vec->size() - maxSize);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void AkbMonitorPage::clearControlls()
|
|
{
|
|
// 1 // L
|
|
ui->editBatteryChargeLevel ->clear(); // mValues.soC
|
|
ui->editBatteryVoltage ->clear(); // mValues.packVoltage
|
|
ui->editNominalCapacity ->clear(); //, mDieBieMS->bmsConfig(), "batteryCapacity");
|
|
ui->editBatteryTemperature ->clear(); // mValues.tempBattHigh
|
|
// 1 // R ->clear();
|
|
ui->editModulesNumber ->clear(); //, mDieBieMS->bmsConfig(), "cellMonitorICCount");
|
|
ui->editCellsNumber ->clear(); //, mDieBieMS->bmsConfig(), "noOfCellsSeries");
|
|
ui->editActualCapacity ->clear(); // NOT IN SETTINGS
|
|
ui->editBmsTemperature ->clear(); // mValues.tempBMSHigh
|
|
// ->clear();
|
|
// 2 // L ->clear();
|
|
ui->editMaxVoltageOnCell ->clear(); // mValues.cVHigh
|
|
ui->editCellNumberLeft ->clear(); // NOT IN SETTINGS
|
|
ui->editCircuit ->clear(); // mValues.packCurrent
|
|
// 2 // R ->clear();
|
|
ui->editMinVoltageOnCell ->clear(); // mValues.cVLow
|
|
ui->editCellNumberRight ->clear(); // NOT IN SETTINGS
|
|
}
|
|
|
|
void AkbMonitorPage::onLoadParams()
|
|
{
|
|
///QHash<QString, ConfigParam> mParams;
|
|
|
|
//addParamRow(ui->editPointZeroDetectorValue, mDieBieMS->bmsConfig(), "shuntLCFactor");
|
|
//ui->specificationsTab->addRowSeparator(tr("Pack configuration"));
|
|
|
|
// Серийный номер * //ui->editSerialNumber
|
|
getSerialNumber(ui->editSerialNumber, mDieBieMS->bmsConfig(), "notUsedCurrentThreshold");
|
|
// 1 // L
|
|
// ui->editBatteryChargeLevel // mValues.soC
|
|
// ui->editBatteryVoltage // mValues.packVoltage
|
|
addParamRow(ui->editNominalCapacity, mDieBieMS->bmsConfig(), "batteryCapacity");
|
|
// ui->editBatteryTemperature // mValues.tempBattHigh
|
|
// 1 // R
|
|
addParamRow(ui->editModulesNumber, mDieBieMS->bmsConfig(), "cellMonitorICCount");
|
|
addParamRow(ui->editCellsNumber, mDieBieMS->bmsConfig(), "noOfCellsSeries");
|
|
//// ui->editActualCapacity // NOT IN BMS_CONFIG
|
|
// ui->editBmsTemperature // mValues.tempBMSHigh
|
|
|
|
// 2 // L
|
|
// ui->editMaxVoltageOnCell // mValues.cVHigh
|
|
//// ui->editCellNumberLeft // NOT IN BMS_CONFIG
|
|
// ui->editCircuit // mValues.packCurrent
|
|
// 2 // R
|
|
// ui->editMinVoltageOnCell // mValues.cVLow
|
|
//// ui->editCellNumberRight // NOT IN BMS_CONFIG
|
|
}
|
|
|
|
/*
|
|
bool AkbMonitorPage::addParamRow(QLineEdit* edit, ConfigParams *params, QString paramName)
|
|
{
|
|
bool res = false;
|
|
QWidget *editor = params->getEditor(paramName);
|
|
QString name = params->getLongName(paramName);
|
|
|
|
if (editor) {
|
|
//int row = rowCount();
|
|
//setRowCount(row + 1);
|
|
//QTableWidgetItem *item = new QTableWidgetItem(name);
|
|
//item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
|
//setItem(row, 0, item);
|
|
//setCellWidget(row, 1, editor);
|
|
//edit->setText(editor->);
|
|
|
|
auto vtype = editor->property("type");
|
|
auto type = vtype.toInt();
|
|
//CFG_T_UNDEFINED = 0,
|
|
if(type == 1) // CFG_T_DOUBLE,
|
|
{
|
|
auto vval = editor->property("value");
|
|
auto val = vval.toDouble();
|
|
edit->setText(QString::number(val));
|
|
}
|
|
else if(type == 2) // CFG_T_INT,
|
|
{
|
|
auto vval = editor->property("value");
|
|
auto val = vval.toInt();
|
|
edit->setText(QString::number(val));
|
|
}
|
|
else if(type == 3) // CFG_T_QSTRING,
|
|
{
|
|
auto vval = editor->property("value");
|
|
auto val = vval.toString();
|
|
edit->setText(val);
|
|
}
|
|
else if(type == 4) // CFG_T_ENUM,
|
|
{
|
|
auto vval = editor->property("value");
|
|
auto val = vval.toInt();
|
|
edit->setText(QString::number(val));
|
|
}
|
|
else if(type == 5) // CFG_T_BOOL
|
|
{
|
|
auto vval = editor->property("value");
|
|
auto val = vval.toBool();
|
|
edit->setText(val ? "true" : "false");
|
|
}
|
|
|
|
auto vsuffix = editor->property("suffix");
|
|
auto suffix = vsuffix.toString();
|
|
auto veditorDecimalsDouble = editor->property("editorDecimalsDouble");
|
|
auto editorDecimalsDouble = veditorDecimalsDouble.toDouble();
|
|
auto veditAsPercentage = editor->property("editAsPercentage");
|
|
auto editAsPercentage = veditAsPercentage.toBool();
|
|
|
|
res = true;
|
|
//resizeColumnToContents(0);
|
|
//resizeRowsToContents();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
//*/
|
|
|