diff OSTC4Operations.cpp @ 1:0b3630a29ad8

Initial version based on previous repository. Project was ported to QT6 and in now cmake based.
author Ideenmodellierer <tiefenrauscher@web.de>
date Thu, 27 Nov 2025 18:40:28 +0100
parents
children 4ace58a7c03c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTC4Operations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,591 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \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)");
+}
+
+//////////////////////////////////////////////////////////////////////////////