Mercurial > public > ostc_companion
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)"); +} + +//////////////////////////////////////////////////////////////////////////////
