From 9b6f5db8fe4858e35e7a54259e0426c6061df9bd Mon Sep 17 00:00:00 2001 From: Gerhard Hoffmann Date: Wed, 27 Nov 2024 15:54:42 +0100 Subject: [PATCH] start with downloading dc: parsing command arguments. started to implement the acrual download --- DownloadDCFirmware/main.cpp | 5 + DownloadDCFirmware/update.cpp | 334 +++++++++++++++++++----- DownloadDCFirmware/update.h | 73 +----- UpdatePTUDevCtrl/commandline_parser.cpp | 46 +++- UpdatePTUDevCtrl/commandline_parser.h | 8 +- 5 files changed, 338 insertions(+), 128 deletions(-) diff --git a/DownloadDCFirmware/main.cpp b/DownloadDCFirmware/main.cpp index 7c39038..354cb86 100644 --- a/DownloadDCFirmware/main.cpp +++ b/DownloadDCFirmware/main.cpp @@ -73,6 +73,7 @@ int main(int argc, char **argv) { QString workingDir = parser.workingDir(); QString psaConfigDir = parser.psaConfigDir(); QString psaTariffDir = parser.psaTariffDir(); + QString dcDir = parser.dcDir(); QString iniFileName = parser.iniFileName(); bool const dryRun = parser.dryRun(); bool const noUpdatePsaHardware = parser.noUpdatePsaHardware(); @@ -81,6 +82,7 @@ int main(int argc, char **argv) { bool const showExtendedVersion = parser.extendedVersion(); bool const alwaysDownloadConfig = parser.alwaysDownloadConfig(); bool const alwaysDownloadDC = parser.alwaysDownloadDC(); + bool const readDCVersion = parser.readDCVersion(); QString const rtPath = QCoreApplication::applicationDirPath(); @@ -109,6 +111,9 @@ int main(int argc, char **argv) { qInfo() << "machineNr ................" << machineNr; qInfo() << "customerNr ..............." << customerNr; qInfo() << "zoneNr ..................." << zoneNr; + qInfo() << "readDCVersion ............" << readDCVersion; + qInfo() << "dcDir ...................." << dcDir; + if (showExtendedVersion) { printf(APP_EXTENDED_VERSION"\n"); diff --git a/DownloadDCFirmware/update.cpp b/DownloadDCFirmware/update.cpp index c42103f..c8fd39d 100644 --- a/DownloadDCFirmware/update.cpp +++ b/DownloadDCFirmware/update.cpp @@ -118,19 +118,8 @@ Update::Update(QString customerRepository, if (!m_hw) { qCritical() << "(" << __func__ << ":" << __LINE__ << ") m_hw == nullptr -> ca-slave plugin loaded ???"; } else { - int tries = 20; - while ((m_sys_areDCdataValid = m_hw->sys_areDCdataValid()) == false) { - // must deliver 'true', only then are all data from hwapi valid - if (--tries < 0) { - qCritical() << "ERROR!!! DC DATA NOT VALID -> CA-MASTER-PLUGIN NOT CONNECTED"; - break; - } - m_hw->dc_autoRequest(true); - QThread::msleep(500); - } - qCritical() << "(" << __func__ << ":" << __LINE__ << ") m_sys_areDCDataValid ..." - << m_sys_areDCdataValid; + // carun stoppen } } @@ -138,7 +127,233 @@ Update::~Update() { unloadDCPlugin(); } -bool Update::doUpdate(QStringList const &filesToWorkOn, bool usbStickDetected) { +Update::DownloadResult Update::sendStatus(int ret) const { + switch (ret) { // return values of dc are: + case 0: // 0: no answer by now + return DownloadResult::NOP; // 1: error + case 10: // 10: success + return DownloadResult::OK; + default:; + } + return DownloadResult::ERROR; +} + +Update::DownloadResult +Update::sendNextAddress(int bNum) const { + // sends address only if blockNumber is one of 0, 1024, 2048, 3072, 4096 + int noAnswerCount = 0; + int errorCount = 0; + if ( bNum==0 || bNum==1024 || bNum==2048 || bNum==3072 || bNum==4096 ) { + // qDebug() << "addr-block" << bNum << "..."; + while (noAnswerCount <= 250) { + m_hw->bl_sendAddress(bNum); + QThread::msleep(100); + DownloadResult const res = sendStatus(m_hw->bl_wasSendingAddOK()); + if (res != DownloadResult::NOP) { + if (res == DownloadResult::ERROR) { + if (++errorCount >= 10) { + qCritical() << "addr-block" << bNum << "...FAILED"; + return res; + } + } else { // res == DownloadResult::OK + qCritical() << "addr-block" << bNum << "...OK"; + return res; + } + } else { + noAnswerCount += 1; // no answer by now + } + } + // wait max. about 3 seconds + return DownloadResult::TIMEOUT; + } + // blockNumber is not one of 0, 1024, 2048, 3072, 4096 -> do nothing + return DownloadResult::NOP; +} + +Update::DownloadResult +Update::sendNextDataBlock(QByteArray const &binary, int bNum) const { + uint8_t local[66]; + int const bAddr = bNum * 64; + int noAnswerCount = 0; + int errorCount = 0; + + memcpy(local, binary.constData() + bAddr, 64); + local[64] = local[65] = 0x00; + + // QByteArray b((const char *)(&local[0]), 64); + // qCritical() << "SNDB" << bNum << b.size() << b.toHex(); + + while (noAnswerCount <= 250) { + m_hw->bl_sendDataBlock(64, local); + QThread::msleep(10); + DownloadResult const res = sendStatus(m_hw->bl_wasSendingDataOK()); + if (res != DownloadResult::NOP) { + if (res == DownloadResult::ERROR) { + if (++errorCount >= 10) { + qCritical() << "data for block" << bNum << "...FAILED"; + return res; + } + } else { + qCritical() << "data for block" << bNum << "OK"; + return res; + } + } else { + noAnswerCount += 1; // no answer by now + } + } + // wait max. about 3 seconds + return DownloadResult::TIMEOUT; +} + +bool Update::startBootloader() const { + qDebug() << "starting bootloader..."; + int nTry = 5; + while (--nTry >= 0) { + m_hw->bl_startBL(); + QThread::msleep(5000); + m_hw->bl_checkBL(); + if (m_hw->bl_isUp()) { + qInfo() << "starting bootloader...OK"; + QThread::msleep(5000); + return true; + } else { + qCritical() << "bootloader not up (" << nTry << ")"; + } + } + qCritical() << "starting bootloader...FAILED"; + return false; +} + +bool Update::stopBootloader() const { + qDebug() << "stopping bootloader..."; + int nTry = 5; + while (--nTry >= 0) { + m_hw->bl_stopBL(); + QThread::msleep(500); + if (!m_hw->bl_isUp()) { + qInfo() << "stopping bootloader...OK"; + return true; + } + } + qCritical() << "stopping bootloader...FAILED"; + return false; +} + +bool Update::resetDeviceController() const { + qDebug() << "resetting device controller..."; + m_hw->bl_rebootDC(); + // wait maximally 3 seconds, before starting bootloader + QThread::sleep(1); + qInfo() << "resetting device controller...OK"; + return true; +} + + +QByteArray Update::loadBinaryDCFile(QString const &filename) const { + qCritical() << "(" << __func__ << ":" << __LINE__ << ")" + << "loading dc binary" << filename << "..."; + + QFile file(filename); // closed in destructor call + if (!file.exists()) { + qCritical() << "(" << __func__ << ":" << __LINE__ << ")" + << file.fileName() << "does not exist"; + return QByteArray(); + } + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "(" << __func__ << ":" << __LINE__ << ")" + << "cannot open file" << file.fileName(); + return QByteArray(); + } + qCritical() << "(" << __func__ << ":" << __LINE__ << ")" + << "loading dc binary" << filename << "...OK"; + + return file.readAll(); +} +/* + /////////////////////////////////////////////////////////////////////////////// + // + // USING THE DC BOOTLOADER + // + /////////////////////////////////////////////////////////////////////////////// + + 1 : bl_reboot() // send to application, want DC2 to reset (in order to + // start the bootloader) + // + // NOTE: this function is NOT reliable !!! Sometimes it + // simply does not work, in which case bl_startBL, + // bl_checkBL and bl_isUp do not work as well. + // Alas, there is no feedback if bl_reboot worked! + // + // NOTE: this function can be called only once per + // minute, because once called again, the controller + // performs some self-checks consuming some time. + // + // NOTE: after a successful bl_reboot(), the device is + // waiting about 4 seconds in the bootloader. To stay in + // the bootloader, we have to send the command + // bl_startBL(), which is kind of a misnomer, as it + // should be bl_doNotLeaveBL(). + // + 2 : bl_startBL(): // send within 4s after DC power-on, otherwise + // bootloader is left. + // + // NOTE: a running bootloader is a MUST for the download + // process of a device controller firmware as it does + // the actual writing of the memory (the bl_reboot() + // from above erases the available memory). + // + 3 : bl_check(): // send command to verify if bl is up + // + // NOTE: this command is kind of a request that we want + // to check if the bootloader is up. The device + // (actually the bootloader) responds with its version. + // + 4 : bl_isUp(): // returns true if bl is up and running + // + // NOTE: we know what the bootloader version actually is + // as the bootloader does not change. By comparing the + // string received in the previous step with this known + // version string we know if the bootloader is up. + // + // NOTE FOR ALL PREVIOUS STEPS: execute them in their + // own slots each to be sure to receive any possible + // responds from the device. + // + 5 : bl_sendAddress(blockNumber) + // send start address, nr of 64-byte block, start with 0 + // will be sent only for following block-numbers: + // 0, 1024, 2048, 3072 and 4096, so basically every + // 64kByte. + // for other addresses nothing happens + + 6 : bl_wasSendingAddOK() + // return val: 0: no response by now + // 1: error + // 10: OK + + 7 : bl_sendDataBlock() + // send 64 byte from bin file + + 8 : bl_sendLastBlock() + // send this command after all data are transferred + + 9 : bl_wasSendingDataOK() + // return val: 0: no response by now + // 1: error + // 10: OK + + 10 : bl_stopBL() // leave bl and start (the new) application + // + // NOTE: this function MUST work under all conditions. + // Alas, there is no direct result for this command, so + // the only way of knowing it was successful is to ask + // the device if the bootloader is still running. + // There is no problem to repeat this command until the + // bootloader is really not running anymore. + */ +bool Update::doUpdate() { + //QString const &fToWorkOn = usbStickDetected ? QDir::cleanPath(it->trimmed()) + //: QDir::cleanPath(m_customerRepository + QDir::separator() + it->trimmed()); if (!m_hw) { qCritical() << "(" << __func__ << ":" << __LINE__ << "):" @@ -146,66 +361,58 @@ bool Update::doUpdate(QStringList const &filesToWorkOn, bool usbStickDetected) { return false; } - int tries = 20; - while ((m_sys_areDCdataValid = m_hw->sys_areDCdataValid()) == false) { - // must deliver 'true', only then are all data from hwapi valid - if (--tries < 0) { - qCritical() << "(" << __func__ << ":" << __LINE__ << "):" - << "ERROR!!! DC DATA NOT VALID -> CA-SLAVE-PLUGIN NOT CONNECTED"; - return false; - } - qCritical() << "(" << __func__ << ":" << __LINE__ << "):" - << "ERROR!!! DC DATA NOT VALID -> CA-SLAVE-PLUGIN NOT CONNECTED (" << tries << ")"; - m_hw->dc_autoRequest(true); - QThread::msleep(500); - } + QByteArray ba = loadBinaryDCFile(m_hw->dcDownloadFileName()); + if (ba.size() > 0) { + uint16_t const totalBlocks = (((ba.size())%64)==0) ? (ba.size()/64) : (ba.size()/64)+1; + m_hw->dcDownloadSetTotalBlockNumber(totalBlocks); - bool res = false; + // fill last block of data to be sent with 0xFF + ba = ba.leftJustified(totalBlocks*64, (char)(0xFF)); - QList::const_iterator it; - for (it = filesToWorkOn.cbegin(); it != filesToWorkOn.cend(); ++it) { - QString const &fToWorkOn = usbStickDetected ? QDir::cleanPath(it->trimmed()) - : QDir::cleanPath(m_customerRepository + QDir::separator() + it->trimmed()); - if (fToWorkOn.contains("DC2C_print", Qt::CaseInsensitive) - && fToWorkOn.endsWith(".json", Qt::CaseInsensitive)) { - res = true; - int i = fToWorkOn.indexOf("DC2C_print", Qt::CaseInsensitive); - int const templateIdx = fToWorkOn.mid(i).midRef(10, 2).toInt(); - if ((templateIdx < 1) || (templateIdx > 32)) { - qCritical() << "WRONG TEMPLATE INDEX" << templateIdx; - res = false; - } else { - if ((res = updatePrinterTemplate(templateIdx, fToWorkOn))) { - qCritical() << - QString("DOWNLOADED PRINTER TEMPLATE %1 WITH INDEX=%2") - .arg(fToWorkOn) - .arg(templateIdx); + resetDeviceController(); + if (startBootloader()) { + + qCritical() << "DownloadThread::run(): TOTAL NUMBER OF BYTES TO SEND TO DC" << ba.size(); + qCritical() << "DownloadThread::run(): TOTAL NUMBER OF BLOCKS" << totalBlocks; + + int currentBlock = 0; + DownloadResult res = DownloadResult::OK; + qCritical() << "64-byte block " << currentBlock; + while (res != DownloadResult::ERROR && currentBlock < totalBlocks) { + if ((res = sendNextAddress(currentBlock)) != DownloadResult::ERROR) { + if ((res = sendNextDataBlock(ba, currentBlock)) != DownloadResult::ERROR) { + m_hw->dcDownloadSetCurrentBlockNumber(currentBlock); + currentBlock += 1; + } } } - } else if (fToWorkOn.contains("DC2C_cash", Qt::CaseInsensitive) - && fToWorkOn.endsWith(".json", Qt::CaseInsensitive)) { - if ((res = updateCashConf(fToWorkOn))) { - qCritical() << QString("DOWNLOADED CASH TEMPLATE %1").arg(fToWorkOn); + + qCritical() << "DownloadThread::run(): last 64-byte block %04d" << currentBlock; + + int const rest = ba.size() % 64; + int const offset = ba.size() - rest; + char const *startAddress = ba.constData() + offset; + + if (rest > 0) { + // SHOULD NEVER HAPPEN !!! + uint8_t local[66]; + memset(local, 0xFF, sizeof(local)); + memcpy(local, startAddress, rest); + qCritical() << "DownloadThread::run(): ERROR SEND REMAINING" << rest << "BYTES"; + m_hw->bl_sendDataBlock(64, local); + } else { + m_hw->bl_sendLastBlock(); + m_hw->dcDownloadSetCurrentBlockNumber(currentBlock); } - } else if (fToWorkOn.contains("DC2C_conf", Qt::CaseInsensitive) - && fToWorkOn.endsWith(".json", Qt::CaseInsensitive)) { - if ((res = updateConfig(fToWorkOn))) { - qCritical() << QString("DOWNLOADED CONFIG TEMPLATE %1").arg(fToWorkOn); - } - } else if (fToWorkOn.contains("DC2C_device", Qt::CaseInsensitive) - && fToWorkOn.endsWith(".json", Qt::CaseInsensitive)) { - if ((res = updateDeviceConf(fToWorkOn))) { - qCritical() << QString("DOWNLOADED DEVICE TEMPLATE %1").arg(fToWorkOn); - } - } else { - qCritical() << "UNKNOWN JSON FILE NAME" << fToWorkOn; - res = false; + qCritical() << "DownloadThread::run(): last result" << (int)sendStatus(m_hw->bl_wasSendingDataOK()); } + stopBootloader(); // there is no harm in stopping the bootloader even } - return res; + return false; } +#if 0 bool Update::checkJsonVersions(QStringList const& jsonFileNames) { if (!m_hw) { qCritical() << "(" << __func__ << ":" << __LINE__ << "):" @@ -436,3 +643,4 @@ bool Update::updateCashConf(QString jsFile) { bool Update::updateDeviceConf(QString jsFile) { return downloadJson(FileTypeJson::DEVICE, 0, jsFile); } +#endif diff --git a/DownloadDCFirmware/update.h b/DownloadDCFirmware/update.h index daa3037..d8fb96b 100644 --- a/DownloadDCFirmware/update.h +++ b/DownloadDCFirmware/update.h @@ -58,72 +58,21 @@ public: virtual ~Update() override; - bool doUpdate(QStringList const &jsonFilesToDownload, bool usbStickDetected = false); + bool doUpdate(); - bool updatePrinterTemplate(int templateIdx, QString fname) const; - bool updateConfig(QString jsFileToSendToDC); - bool updateCashConf(QString jsFileToSendToDC); - bool updateDeviceConf(QString jsFileToSendToDC); +private: + DownloadResult sendStatus(int ret) const; + DownloadResult sendNextAddress(int bNum) const; + DownloadResult sendNextDataBlock(QByteArray const &binary, int bNum) const; + bool startBootloader() const; + bool stopBootloader() const; + QByteArray loadBinaryDCFile(QString const &dcFilename) const; + bool resetDeviceController() const; + DownloadResult dcDownloadBinary(QByteArray const &b) const; - bool downloadJson(enum FileTypeJson type, int templateIdx, - QString jsFileToSendToDC) const; + QString m_fileToDownload; - - QString getFileVersion(QString const& jsonFileName); - bool checkJsonVersions(QStringList const& jsonFileNames = - QStringList( - QList( - std::initializer_list{ - QString("etc/psa_config/DC2C_conf.json"), - QString("etc/psa_config/DC2C_cash.json"), - QString("etc/psa_config/DC2C_device.json"), - QString("etc/psa_config/DC2C_print01.json"), - QString("etc/psa_config/DC2C_print02.json"), - QString("etc/psa_config/DC2C_print03.json"), - QString("etc/psa_config/DC2C_print04.json"), - QString("etc/psa_config/DC2C_print05.json"), - QString("etc/psa_config/DC2C_print06.json"), - QString("etc/psa_config/DC2C_print07.json"), - QString("etc/psa_config/DC2C_print08.json"), - QString("etc/psa_config/DC2C_print09.json"), - QString("etc/psa_config/DC2C_print10.json"), - QString("etc/psa_config/DC2C_print11.json"), - QString("etc/psa_config/DC2C_print12.json"), - QString("etc/psa_config/DC2C_print13.json"), - QString("etc/psa_config/DC2C_print14.json"), - QString("etc/psa_config/DC2C_print15.json"), - QString("etc/psa_config/DC2C_print16.json"), - QString("etc/psa_config/DC2C_print17.json"), - QString("etc/psa_config/DC2C_print18.json"), - QString("etc/psa_config/DC2C_print19.json"), - QString("etc/psa_config/DC2C_print20.json"), - QString("etc/psa_config/DC2C_print21.json"), - QString("etc/psa_config/DC2C_print22.json"), - QString("etc/psa_config/DC2C_print23.json"), - QString("etc/psa_config/DC2C_print24.json"), - QString("etc/psa_config/DC2C_print25.json"), - QString("etc/psa_config/DC2C_print26.json"), - QString("etc/psa_config/DC2C_print27.json"), - QString("etc/psa_config/DC2C_print28.json"), - QString("etc/psa_config/DC2C_print29.json"), - QString("etc/psa_config/DC2C_print30.json"), - QString("etc/psa_config/DC2C_print31.json"), - QString("etc/psa_config/DC2C_print32.json")}))); /* - bool checkDownloadedJsonVersions(QStringList const& jsonFileNames); - - hwinf *hw() { return m_hw; } - hwinf const *hw() const { return m_hw; } - - //QString customerId() { return m_customerId; } - //QString const customerId() const { return m_customerId; } - - QString branchName() { return m_branchName; } - QString const branchName() const { return m_branchName; } - - //QString repositoryPath() { return m_repositoryPath; } - //QString const repositoryPath() const { return m_repositoryPath; } - private: static QString jsonType(enum FileTypeJson type); bool openSerial(int br, QString baudrate, QString comPort) const; diff --git a/UpdatePTUDevCtrl/commandline_parser.cpp b/UpdatePTUDevCtrl/commandline_parser.cpp index 3efb346..de3ecdf 100644 --- a/UpdatePTUDevCtrl/commandline_parser.cpp +++ b/UpdatePTUDevCtrl/commandline_parser.cpp @@ -85,7 +85,17 @@ CommandLineParser::CommandLineParser() , m_yoctoInstallStatusOption( QCommandLineOption( QStringList() << "Y" << "yocto-install", - QCoreApplication::translate("main", "Show yocto install status of ATBUpdateTool."))) { + QCoreApplication::translate("main", "Show yocto install status of ATBUpdateTool."))) + , m_dcDirectoryOption( + QCommandLineOption( + QStringList() << "dc-directory" << "dc-directory", + QCoreApplication::translate("main", "device controller directory."), + QCoreApplication::translate("main", "directory"))) + , m_readDCVersionOption( + QCommandLineOption( + QStringList() << "D" << "read-dc-version", + QCoreApplication::translate("main", "Show version of device controller."), + QCoreApplication::translate("main", "Show version of device controller."))) { configure(); } @@ -138,6 +148,12 @@ void CommandLineParser::configure() { m_yoctoInstallStatusOption.setDefaultValue("false"); m_parser.addOption(m_yoctoInstallStatusOption); + + m_dcDirectoryOption.setDefaultValue("etc/dc/"); + m_parser.addOption(m_dcDirectoryOption); + + m_readDCVersionOption.setDefaultValue("false"); + m_parser.addOption(m_readDCVersionOption); } void CommandLineParser::readSettings() { @@ -155,7 +171,7 @@ void CommandLineParser::readSettings() { for (QString const &key: keys) { QVariant v = settings.value(key); - qCritical() << __PRETTY_FUNCTION__ + qCritical() << "(" << __func__ << ":" << __LINE__ << ")" << key << " -> " << v.toString(); if (key.contains("repository-url")) { @@ -196,6 +212,12 @@ void CommandLineParser::readSettings() { } else if (key.contains("plugin-name")) { m_plugInName = v.toString(); + } else + if (key.contains("dc-directory")) { + m_dcDir = v.toString(); + } else + if (key.contains("read-dc-version")) { + m_readDCVersion = (v.toBool() ? "true" : "false"); } else { qCritical() << __PRETTY_FUNCTION__ << key << " -> (UNKNOWN) " << v.toString(); @@ -243,6 +265,26 @@ QString CommandLineParser::psaTariffDir() { return m_psaTariffDir; } +QString CommandLineParser::dcDir() { + if (m_parser.isSet(m_dcDirectoryOption)) { + m_dcDir = m_parser.value(m_dcDirectoryOption); + } + return m_dcDir; +} + +bool CommandLineParser::readDCVersion() { + + qCritical() << __func__ << __LINE__; + + if (m_parser.isSet(m_readDCVersionOption)) { + qCritical() << __func__ << __LINE__ << m_readDCVersion; + m_readDCVersion = m_parser.value(m_readDCVersionOption); + qCritical() << __func__ << __LINE__ << m_readDCVersion; + } + qCritical() << __func__ << __LINE__ << m_readDCVersion; + return m_readDCVersion == "false" ? false : true; +} + QString CommandLineParser::workingDir() { if (m_parser.isSet(m_workingDirectoryOption)) { m_workingDir = m_parser.value(m_workingDirectoryOption); diff --git a/UpdatePTUDevCtrl/commandline_parser.h b/UpdatePTUDevCtrl/commandline_parser.h index 22f906a..0a7e4c8 100644 --- a/UpdatePTUDevCtrl/commandline_parser.h +++ b/UpdatePTUDevCtrl/commandline_parser.h @@ -1,4 +1,4 @@ -#ifndef COMMAND_LINE_PARSER_H_INCLUDED +#ifndef COMMAND_LINE_PARSER_H_INCLUDED #define COMMAND_LINE_PARSER_H_INCLUDED #include @@ -21,6 +21,8 @@ class CommandLineParser : public QCommandLineParser { QString m_iniFileName; QString m_alwaysDownloadConfig; QString m_alwaysDownloadDC; + QString m_readDCVersion; + QString m_dcDir; QCommandLineOption m_repositoryUrlOption; QCommandLineOption m_iniFileDirectoryOption; @@ -37,6 +39,8 @@ class CommandLineParser : public QCommandLineParser { QCommandLineOption m_extendedVersionOption; QCommandLineOption m_yoctoVersionOption; QCommandLineOption m_yoctoInstallStatusOption; + QCommandLineOption m_dcDirectoryOption; + QCommandLineOption m_readDCVersionOption; QCommandLineParser m_parser; @@ -65,5 +69,7 @@ public: bool extendedVersion(); bool alwaysDownloadConfig(); bool alwaysDownloadDC(); + bool readDCVersion(); + QString dcDir(); }; #endif // COMMAND_LINE_PARSER_H_INCLUDED