view OSTC4Operations.cpp @ 2:177f640940f2

Update exception class and cleanup redifinitions During firmware download and exception caused the application to stop. Rootcause was the defference between QT5 and QT6 exception and string handling which is updated now. In addition some old definitions were removed to avoid compiler warnings.
author Ideenmodellierer
date Fri, 28 Nov 2025 19:57:35 +0100
parents 0b3630a29ad8
children 4ace58a7c03c
line wrap: on
line source

//////////////////////////////////////////////////////////////////////////////
/// \file   OSTC4Operations.h
/// \brief  Implementing various operations for H&W OSTC4 dive computer
/// \author JD Gascuel.
/// \sa     OSTC3Operations, HardwareOperations.h
///
/// \copyright (c) 2016 JD Gascuel. All rights reserved.
/// $Id$
//////////////////////////////////////////////////////////////////////////////
//
// BSD 2-Clause License:
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////
#include <QSettings>
#include <QRegularExpression>
#include "OSTC4Operations.h"

#include "Utils/Log.h"
#include "Utils/Exception.h"
#include "Utils/ProgressEvent.h"

extern QSettings* settings;

#define FIRMWARE_BLOCK            0x40        // 64 bytes
#define FIRMWARE_BLOCK_DELAY        15        // 15 msec.
#define FIRMWARE_BLOCK_FAST      0x300        // 4096 bytes

//////////////////////////////////////////////////////////////////////////////

QSize OSTC4Operations::nameSize() const
{
    return QSize(12, 4);
}

//////////////////////////////////////////////////////////////////////////////

QString OSTC4Operations::model() const
{
    return "OSTC4";
}

HardwareOperations::CompanionFeatures OSTC4Operations::supported() const
{
    // No ICON, no DUMPSCREEN
    return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
                     |HELIUM_DIVE|CCR_DIVE|BLUETOOTH|VPM_MODEL|SIGNAL_CHECK);
}

QString OSTC4Operations::firmwareTemplate() const
{
    return "ostc4*.bin";
}

QStringList OSTC4Operations::listPorts() const
{
    return listBluetoothPorts();
}
#if 0
QRegExp OSTC4Operations::portTemplate() const
{
#if defined(Q_OS_MAC)
    return QRegExp("tty[.]OSTC4-.*", Qt::CaseInsensitive);
#elif defined(Q_OS_LINUX)
    // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
    return QRegExp("ttyUSB.*", Qt::CaseSensitive);
#elif defined(Q_OS_WIN)
    return QRegExp("COM.*", Qt::CaseSensitive);
#endif
}
#endif


QRegularExpression OSTC4Operations::portTemplate() const
{
#if defined(Q_OS_MAC)
    return QRegularExpression("tty[.]OSTC4-.*",
                              QRegularExpression::CaseInsensitiveOption);
#elif defined(Q_OS_LINUX)
    // Debian, Ubuntu, RedHat, CentOS, SUSE
    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
#elif defined(Q_OS_WIN)
    return QRegularExpression("COM.*");    // default: case-sensitive
#endif
}
//////////////////////////////////////////////////////////////////////////////

void OSTC4Operations::getIdentity()
{
    descriptionString.clear();

    LOG_TRACE("Getting model...");
    HardwareDescriptor hw = hardwareDescriptor();
    if( hw != HW_OSTC4 )
        LOG_THROW("Not an OSTC4.");

    LOG_TRACE("Getting identity...");
    getCommonIdentity();

    //---- Main firmware -----------------------------------------------------
    QString mainFW;
    {
        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
        if( echo != 'k' )
            LOG_THROW("Bad firmware details.");

        unsigned char reply[4+1];
        _serial.writeByte(0xFF);    // Main firmware
        _serial.readBlock(reply, sizeof reply);
        if( reply[4] != 0x4D )
            LOG_THROW("Bad main firmware reply.");

        mainFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
    }

    //---- RTE firmware ------------------------------------------------------
    QString rteFW;
    {
        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
        if( echo != 'k' )
            LOG_THROW("Bad firmware details.");

        unsigned char reply[4+1];
        _serial.writeByte(0xFE);    // RTE firmware
        _serial.readBlock(reply, sizeof reply);
        if( reply[4] != 0x4D )
            LOG_THROW("Bad RTE firmware reply.");

        rteFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
    }

    //---- Font Package ------------------------------------------------------
    QString fontPKG;
    {
        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
        if( echo != 'k' )
            LOG_THROW("Bad firmware details.");

        unsigned char reply[4+1];
        _serial.writeByte(0x10);    // Font Package
        _serial.readBlock(reply, sizeof reply);
        if( reply[4] != 0x4D )
            LOG_THROW("Bad Font package reply.");

        fontPKG = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
    }

    //---- OSTC4 specific description ----------------------------------------
    descriptionString = QString("%1 #%2, fw %3 (main %4/RTE %5/Font %6), %7")
            .arg(model())
            .arg(serialNumber(), 4, 10, QChar('0'))
            .arg( QString::asprintf("%d.%02d", firmware() / 100, firmware() % 100))
            .arg( mainFW )
            .arg( rteFW )
            .arg( fontPKG )
            .arg( QString::fromLatin1((char*)(computerText), 60)
                .replace(QChar('\0'), "")
                .trimmed() );

    LOG_INFO("Found " << descriptionString);
}

//////////////////////////////////////////////////////////////////////////////

OSTC4Operations::OSTC4Operations()
  : fileChecksum(0,0)
{
    emulatorName = "OSTC4";
}

//////////////////////////////////////////////////////////////////////////////

void OSTC4Operations::upgradeFW(const QString &fileName)
{
    LOG_INFO("Starting OSTC4 firmware upgrade...");

    openFirmware(fileName, true);
    openFirmware(fileName, false);

    LOG_INFO("Please wait for OSTC4 to complete installation...");
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
///     FIRMWARE UPGRADE                                                   ///
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

#define SWAP4BYTES(x)  \
    uint( (((x) & 0x000000FF) << 24)  \
        | (((x) & 0x0000FF00) <<  8)  \
        | (((x) & 0x00FF0000) >>  8)  \
        | (((x) & 0xFF000000) >> 24))

//////////////////////////////////////////////////////////////////////////////

void OSTC4Operations::openFirmware(const QString &fileName, bool dryRun)
{
    LOG_TRACE("Opening OSTC4 firmware '" << fileName << "'...");

    bool forceFirmwareUpdate;
    bool forceRTEUpdate;
    bool forceFontlibUpdate;
    bool useFastMode;
    bool firmwareSupportFastMode = false;
    int size;
    int blockSize;
    int transferDelay;

    // Previous trial failed ?
    if( _file.isOpen() ) _file.close();

    _file.setFileName(fileName);
    if( ! _file.open(QIODevice::ReadOnly) )
        LOG_THROW( "Cannot open BIN file " << fileName );


    //  Get settings to overwrite version check
    forceFirmwareUpdate = settings->value("OSTC/forceFirmwareUpdate", false).toBool();
    forceRTEUpdate = settings->value("OSTC/forceRTEUpdate", false).toBool();
    forceFontlibUpdate = settings->value("OSTC/forceFontlibUpdate", false).toBool();

    useFastMode = settings->value("OSTC/useFastMode", false).toBool();

    //---- Check consistency -------------------------------------------------
    fileChecksum.reset(0, 0);
    for(int part=1; part<4; ++part)
    {
        // Less than 3 parts ?
        if( (_file.size() - _file.pos()) < 16 )
            break;

        if( dryRun )
            LOG_TRACE("Checking part " << part <<"...");

        FirmwareOSTC4 header;
        QByteArray bin;
        loadFirmwarePart(header, bin, part, dryRun);

        QString name = QString::asprintf("%s v%d.%02d.%02d%s",
             header.type==0x10 ? "Font"
           : header.type==0xFF ? "FW  "
           : header.type==0xFE ? "RTE "
           :                     "??? ",
            header.version.x, header.version.y, header.version.z,
            header.version.beta ? " beta" : "");

        //---- On first pass, just check file structure ----------------------
        if( dryRun )
            continue;

        //---- On second pass, do upload new stuff ---------------------------
        if( _connectMode != SERVICE_MODE )
            connectServiceMode();
        if( _connectMode != SERVICE_MODE  )
            LOG_THROW("Cannot connect OSTC4 service mode...");

        //---- Check if needed ? ---------------------------------------------
        LOG_TRACE("Check part " << part << " is needed ?");
        unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
        if( echo != 'k' )
            LOG_THROW("Bad firmware details.");

        unsigned char reply[4+1];
        _serial.writeByte(header.type);     // type of firmware part
        _serial.readBlock(reply, sizeof reply);
        if( reply[4] != 0x4C )              // SERVICE MODE only.
            LOG_THROW("Bad firmware reply.");

        if(((reply[0] *100 + reply[1] * 10 + reply[2]) > 151 ) && (header.type==0xFF)) /* suitable firmware version? First supported in 1.5.2 */
        {
            firmwareSupportFastMode = true;
        }

        if(( header.version.x == reply[0]
         && header.version.y == reply[1]
         && header.version.z == reply[2] )
                && (!(forceFirmwareUpdate && header.type==0xFF))
                && (!(forceRTEUpdate && header.type==0xFE))
                && (!(forceFontlibUpdate && header.type==0x10))
                )
        {
            LOG_INFO("    part " << part << ": " << name << " already up-to-date.");
            continue;
        }
        if((useFastMode) && (firmwareSupportFastMode))      /* is fast mode supported ? */
        {
            blockSize = FIRMWARE_BLOCK_FAST;
            transferDelay = 1;
        }
        else {
            blockSize = FIRMWARE_BLOCK;
            transferDelay = FIRMWARE_BLOCK_DELAY;
        }
        //---- Upload firwmare -----------------------------------------------
        const int steps = 3 + (bin.size()+blockSize-1) / blockSize;
        int step = 0;
        PROGRESS(step++, steps);

        LOG_INFO("    uploading part " << part
                 << ": " << name
                 << "...");
        writeText( QString("Upload Part %1").arg(part) );

        //---- Command
        PROGRESS(step++, steps);
        echo = retryCommand(_serial, 's'); // 0x73 OSTC4 FW Upgrade
        if( echo != 's' )
            LOG_THROW("Bad OSTC4 FW upgrade commande.");

        //---- Header
        PROGRESS(step++, steps);
        _serial.writeBlock((uchar*)&header, sizeof header);
         _serial.sleep(500);
        //---- Data
        for(int len = 0x00000; len < bin.size(); len += blockSize)
        {
            if(transferDelay)
            {
                _serial.sleep(transferDelay);  // 15 msec between 64B blocks...
            }
            PROGRESS(step++, steps);

            size = qMin(blockSize,
                            bin.size() - len);

            LOG_TRACE("Fill " << size);
            _serial.writeBlock((uchar*)bin.data()+len, (unsigned int)size);
        }

        PROGRESS(steps, steps); // 100%

        //---- Wait acknowledge ----------------------------------------------
        // RTE seems to miss the acknowledge byte ...
        if( header.type != 0xFE ) {
            PROGRESS_THROTTLE();

            for(int wait=0; wait<2*60; ++wait)  // Up to 2 min...
            {
                try {
                    unsigned char reply = _serial.readByte();
                    if( reply == 0x4C )
                        break;
                }
                catch( const ReadTimeout&) {}
            }
        }

        LOG_INFO("    part " << part << ": " << name << " uploaded.");
    }

    //---- Done --------------------------------------------------------------
    // Low-level close, to avoid trying to send a 0xFF byte...
    if( !dryRun ) {
        _serial.close();
        _connectMode = CLOSED_MODE;
    }

    PROGRESS_RESET();

    //---- Check FILE checksum on first pass ---------------------------------
    if( dryRun ) {
        uint expected;
        if( 4 != _file.read((char*)&expected, sizeof expected) )
            LOG_THROW("Missing file checksum.");

        uint checksum = fileChecksum.a() | (fileChecksum.b() << 16);

        if( checksum != expected )
            LOG_ERROR( QString::asprintf("File checksum ERROR: expected = %04X, actual = %04X", expected, checksum) );
        else if( dryRun )
            LOG_TRACE( QString::asprintf("File checksum = %04X (OK)", checksum) );

        if( ! _file.atEnd() )
            LOG_THROW("Extra data after last block.");
    }

    _file.close();
}

//////////////////////////////////////////////////////////////////////////////

void OSTC4Operations::loadFirmwarePart(FirmwareOSTC4& header,
                                       QByteArray& bin,
                                       int part,
                                       bool dryRun)
{
    memset(&header, 0, sizeof header);

    //---- Read part header and check consistency ----------------------------
    if( sizeof header != _file.read((char*)&header, sizeof header) )
        LOG_THROW("EOF in header.");
    fileChecksum.add(&header, sizeof header);

    uint checksum = SWAP4BYTES(header.length)
                  + SWAP4BYTES(*(uint*)&header.type);
    if( checksum != header.checksum )
        LOG_THROW( QString::asprintf("Inconsistent header (%04X != %04X).", checksum, header.checksum) );

    if( SWAP4BYTES(header.length) > (1024 * 1024) || (SWAP4BYTES(header.length) % 4) )
        LOG_THROW("Inconsistent header (part=" << part << ", size=" << SWAP4BYTES(header.length) << ").");

    if( dryRun )
        switch( header.type ) {
        case 0x10:  LOG_TRACE("... font");       break;
        case 0xFF:  LOG_TRACE("... firmware");   break;
        case 0xFE:  LOG_TRACE("... RTE");        break;
        default:
    //        LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf(int(header.type), 16) << ").");
            LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf("Value = %d", header.type) << ").");
            break;
        }

    if( dryRun )
        LOG_TRACE("... version " << QString::asprintf("%d.%02d.%02d.%s",
            header.version.x, header.version.y, header.version.z,
            header.version.beta ? " beta" : ""));

    //---- Read Binary Data --------------------------------------------------
    if( dryRun )
        LOG_TRACE("... size " << SWAP4BYTES(header.length) << " bytes.");
    bin.resize(SWAP4BYTES(header.length) + 4);
    assert((uint)bin.size() == SWAP4BYTES(header.length) + 4);

    if( bin.size() != _file.read(bin.data(), bin.size()) )
        LOG_THROW("EOF in data.");

    if( dryRun )
        LOG_TRACE("... checksum " << QString::asprintf("%08x",
            *(uint*)(bin.data() + SWAP4BYTES(header.length))));
    fileChecksum.add(bin);
}

void OSTC4Operations::getSignal()
{
    char buffer[60];
    memset(buffer, 0, sizeof buffer);
    unsigned char echo = retryCommand(_serial, 'l'); // 0x6c request bluetooth signal
    if( echo != 0x6C )
        LOG_THROW("Bad text reply (1)" << echo);

    _serial.sleep(1000);

    unsigned char ok = _serial.readByte();
    if( ok != 0x4D )    // DOWNLOAD mode only.
        LOG_THROW("Bad clock reply (2)");
}


void OSTC4Operations::getAllHeader(unsigned char* pBuffer)
{
    unsigned char index = 0;
    char buffer[60];
    memset(buffer, 0, sizeof buffer);

    if( _connectMode != SERVICE_MODE )
        connectServiceMode();
    if( _connectMode != SERVICE_MODE  )
        LOG_THROW("Cannot connect OSTC4 service mode...");


    unsigned char echo = retryCommand(_serial, 0x85); // 0x85 request all headers (dump)
    if( echo != 0x85 )
        LOG_THROW("Bad text reply (1)" << echo);

    _serial.sleep(1000);

    for(index = 0; index < 8; index++)      // Expect 8 blocks a 0x8000
    {
        _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
        LOG_INFO(("."));
    }
    *(pBuffer + (index * 0x8000)) = _serial.readByte();  // get lastdiveID
    LOG_INFO(QString::asprintf("%d",*(pBuffer + (index * 0x8000))));
    unsigned char ok = _serial.readByte();
    if( ok != 0x4C )    //Service mode only.
        LOG_THROW("Bad clock reply (2)");
}

void OSTC4Operations::writeAllHeader(unsigned char* pBuffer)
{
    unsigned char index = 0;
    char buffer[60];
    memset(buffer, 0, sizeof buffer);

    if( _connectMode != SERVICE_MODE )
        connectServiceMode();
    if( _connectMode != SERVICE_MODE  )
        LOG_THROW("Cannot connect OSTC4 service mode...");


    unsigned char echo = retryCommand(_serial, 0x86); // 0x86 request all headers (dump)
    if( echo != 0x86 )
        LOG_THROW("Bad text reply (1)" << echo);

    _serial.sleep(1000);

    for(index = 0; index < 8; index++)      // Expect 8 blocks a 0x8000
    {
        _serial.writeBlock(pBuffer + (index * 0x8000), 0x8000);
        LOG_INFO(("."));
    }
    _serial.writeByte(*(pBuffer + (index * 0x8000)));  // write lastdiveID

    unsigned char ok = _serial.readByte();
    if( ok != 0x4C )    //Service mode only.
        LOG_THROW("Bad clock reply (2)");
}

void OSTC4Operations::getAllSamples(unsigned char* pBuffer)
{
    unsigned short index = 0;
    char buffer[60];
    memset(buffer, 0, sizeof buffer);

    if( _connectMode != SERVICE_MODE )
        connectServiceMode();
    if( _connectMode != SERVICE_MODE  )
        LOG_THROW("Cannot connect OSTC4 service mode...");


    unsigned char echo = retryCommand(_serial, 0x88); // 0x88 request all samples (dump)
    if( echo != 0x88 )
        LOG_THROW("Bad text reply (1)" << echo);

    _serial.sleep(500);

    for(index = 0; index < 384; index++)      // Expect 8 blocks a 0x8000
    {
        _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
        LOG_INFO(QString::asprintf("%d",index));
    }
    _serial.readBlock(pBuffer + (index * 0x8000), 4);  // get lastdiveID
    unsigned char ok = _serial.readByte();
    if( ok != 0x4C )    //Service mode only.
        LOG_THROW("Bad clock reply (2)");
}


void OSTC4Operations::writeAllSamples(unsigned char* pBuffer)
{
    unsigned short index = 0;
    char buffer[60];
    memset(buffer, 0, sizeof buffer);

    if( _connectMode != SERVICE_MODE )
        connectServiceMode();
    if( _connectMode != SERVICE_MODE  )
        LOG_THROW("Cannot connect OSTC4 service mode...");


    unsigned char echo = retryCommand(_serial, 0x89); // 0x88 request all samples (dump)
    if( echo != 0x89 )
        LOG_THROW("Bad text reply (1)" << echo);

    _serial.sleep(500);

    for(index = 0; index < 384; index++)      // Expect 8 blocks a 0x8000 384
    {
        _serial.writeBlock((const unsigned char*)(pBuffer + (index * 0x8000)), 0x8000);
        LOG_INFO(QString::asprintf("%d",index));
    }
    _serial.writeBlock(pBuffer + (index * 0x8000), 4);  // set lastdiveID
    unsigned char ok = _serial.readByte();
    if( ok != 0x4C )    //Service mode only.
        LOG_THROW("Bad clock reply (2)");
}

//////////////////////////////////////////////////////////////////////////////