view OSTC4Operations.cpp @ 3:4ace58a7c03c

Send disconnect command before closing the connection The old BT module transmitted a notification in case a connection were closed which cause the ostc to exit the uart loop. The new one doesn't do this => send disconnect command to avoid waiting in the installation loop till timeout or button press.
author Ideenmodellierer
date Fri, 28 Nov 2025 20:00:02 +0100
parents 0b3630a29ad8
children e30f00f760d3
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 ) {
        disconnect(true);
    }

    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)");
}

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