diff OSTCFrogOperations.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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OSTCFrogOperations.cpp	Thu Nov 27 18:40:28 2025 +0100
@@ -0,0 +1,578 @@
+//////////////////////////////////////////////////////////////////////////////
+/// \file   OSTCFrogOperations.cpp
+/// \brief  Implementing various operations for H&W Frog dive computer
+/// \author JD Gascuel.
+///
+/// \copyright (c) 2011-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 "OSTCFrogOperations.h"
+
+#include "Utils/Log.h"
+#include "Utils/Exception.h"
+
+#include "HexFile.h"
+#include "SettingsDialog.h"
+
+#include <QApplication>
+#include <QDateTime>
+#include <QProgressBar>
+#include <QStringList>
+
+// Byte extration, compatible littleendian or bigendian.
+#define LOW(x)      ((unsigned char)((x) % 256))
+#define HIGH(x)     ((unsigned char)((x / (1<<8)) % 256))
+#define UPPER(x)    ((unsigned char)((x / (1<<16)) % 256))
+#define UP32(x)     ((unsigned char)((x / (1<<24)) % 256))
+
+#define IMAGE_ROUNDING  1024
+#define FIRMWARE_AREA   0x3E0000
+#define FIRMWARE_SIZE   0x01D000
+
+extern QProgressBar* progress;
+
+//////////////////////////////////////////////////////////////////////////////
+
+OSTCFrogOperations::OSTCFrogOperations()
+  : _firmware(0),
+    _serialNumber(0),
+    _isOpen(false),
+    _commandMode(false)
+{
+}
+
+OSTCFrogOperations::~OSTCFrogOperations()
+{
+    if( _isOpen )
+        disconnect(true);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     PORT management                                                    ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+//QRegExp OSTCFrogOperations::portTemplate() const
+QRegularExpression OSTCFrogOperations::portTemplate() const
+{
+#if defined(Q_OS_MAC)
+    return QRegExp("tty[.]frog.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_LINUX)
+    // Seems ok for debian, ubuntu, and SUSE (google dixit).
+    // Obviously, needs the rfcomm package. "hcitool scan" or lsusb to have
+    // a list of connected stuff...
+    return QRegExp("rfcomm.*", Qt::CaseInsensitive);
+#elif defined(Q_OS_WIN)
+//    return QRegExp("COM.*", Qt::CaseSensitive);
+    return QRegularExpression(
+        "COM([0-9]+)",
+        QRegularExpression::CaseInsensitiveOption
+        );
+#endif
+}
+
+QStringList OSTCFrogOperations::listPorts() const
+{
+    return listBluetoothPorts();
+}
+
+QString OSTCFrogOperations::firmwareTemplate() const
+{
+    return "*frog.firmware.hex";
+}
+
+QString OSTCFrogOperations::model() const
+{
+    return "Frog";
+}
+
+QString OSTCFrogOperations::description()
+{
+    return _description;
+}
+
+QImage OSTCFrogOperations::dumpScreen() const
+{
+    LOG_THROW("Not implemented...");
+    return QImage();
+}
+
+HardwareOperations::CompanionFeatures OSTCFrogOperations::supported() const
+{
+    // No ICON, no PARAMETER, no FIRMWARE, no DUMPSCREEN yet...
+    return CompanionFeatures(NAME|DATE);
+}
+
+bool OSTCFrogOperations::connect()
+{
+    try {
+        //---- Open the serial port-------------------------------------------
+        LOG_TRACE( QString("Open port %1...").arg(Settings::port) );
+        _isOpen = false;
+
+        //---- Execute the start protocol ------------------------------------
+        static char animation[] = "/-\\|";
+
+        int reply = 0;
+        for(int retry=0; retry < 30; ++retry) {
+            _serial.open( Settings::port, "Frog");
+            _serial.sleep(100);
+            _serial.purge();
+            _serial.writeByte(0xBB);
+            try {
+                reply = _serial.readByte();
+                if( reply == 0x4D )
+                    break;
+            }
+            catch(...) {}
+            LOG_TRACE( QString("Starting... %1").arg(animation[retry % sizeof animation]));
+        }
+        if( reply != 0x4D )
+            LOG_THROW("Not started");
+
+        //---- Enquire about Frog id -----------------------------------------
+        getIdentity();
+
+        //---- Everything is ok ----------------------------------------------
+        _isOpen = true;
+        return true;
+    }
+    catch(const Exception& e) {
+        _serial.close();
+        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
+    }
+    _isOpen = false;
+    return false;
+}
+
+void OSTCFrogOperations::connectServiceMode()
+{
+    connect();
+}
+
+bool OSTCFrogOperations::disconnect(bool /*closing*/)
+{
+    if( !_isOpen ) return false;
+
+    _serial.purge();
+    _serial.writeByte(0xFF);
+    _serial.sleep(100);
+
+    _serial.purge();
+    _serial.close();
+
+    _description.clear();       // cleanup for interface updateStatus()
+    _isOpen = false;
+
+    return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     LOW Level commands                                                 ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::beginCommands()
+{
+    Q_ASSERT( !_commandMode );
+    static char animation[] = "/-\\|";
+    for(int i=0;; ++i)
+    {
+        if( i == 100 ) // 20.0 sec loop ?
+            LOG_THROW("Bad reply to open command");
+
+        _serial.sleep(100);
+        _serial.purge();
+        _serial.writeByte(0xAA);        // Start byte
+
+        int reply = 0;
+        try {
+            reply = _serial.readByte();
+        } catch(...) {}
+        if(  reply == 0x4B )
+            goto Started;
+
+        LOG_TRACE(QString("Connecting %1")
+            .arg(animation[i%4]));
+        _serial.sleep(200);
+        continue;
+
+Started:
+        unsigned char buffer[] = "\xAA\xAB\xAC";
+        _serial.writeBlock(buffer, 3);
+
+        try {
+            unsigned char reply = _serial.readByte();
+            if( reply == 0x4C ) {
+                _commandMode = true;
+                return;
+            }
+        } catch(...) {}
+
+        _serial.sleep(200);
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::endCommands()
+{
+    Q_ASSERT( _commandMode );
+
+    _serial.sleep(100);
+    _serial.purge();
+    _serial.writeByte(0xFF);                // Exit service mode
+    _serial.sleep(10);
+
+    unsigned char buffer = _serial.readByte();
+    if( buffer != 0xFF )
+        LOG_THROW("End failed");
+
+    _commandMode = false;
+    disconnect();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::eraseRange(unsigned int addr, unsigned int size)
+{
+    Q_ASSERT( _commandMode );
+
+    // Convert size to number of pages, rounded up.
+    size = ((size + 4095) / 4096);
+    if( size < 256 || addr != 0x300000 )
+    {
+        unsigned char buffer[4];
+        // Erase just the needed pages.
+        buffer[0] = UPPER(addr);
+        buffer[1] = HIGH(addr);
+        buffer[2] = LOW(addr);
+        buffer[3] = LOW(size);
+        _serial.writeByte(0x42);            // Command
+        _serial.sleep(10);
+
+        _serial.writeBlock(buffer, 4);
+        // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure.
+        _serial.sleep(40 + size * 31);
+    }
+    else
+    {
+        // Erase the whole 512KB of icon memory...
+        _serial.writeByte(0x41);
+        _serial.sleep(3000);
+    }
+
+    try {
+        (void)_serial.readByte();
+    } catch(...) {}
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::startWrite(unsigned int addr)
+{
+    Q_ASSERT( _commandMode );
+
+    unsigned char buffer[3];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+
+    _serial.writeByte(0x30);
+    _serial.sleep(10);
+
+    _serial.writeBlock(buffer, 3);
+    _serial.sleep(10);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::stopWrite()
+{
+    Q_ASSERT( _commandMode );
+
+    _serial.flush();
+    _serial.sleep(200); // Should be > 100ms.
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 0x4C )
+        LOG_THROW("stopWrite");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::readBytes(unsigned int addr,
+                                   unsigned char* ptr,
+                                   unsigned int size)
+{
+    Q_ASSERT( _commandMode );
+
+    unsigned char buffer[6];
+    buffer[0] = UPPER(addr);
+    buffer[1] = HIGH(addr);
+    buffer[2] = LOW(addr);
+    buffer[3] = UPPER(size);
+    buffer[4] = HIGH(size);
+    buffer[5] = LOW(size);
+
+    _serial.writeByte(0x20);
+    _serial.sleep(10);
+
+    _serial.writeBlock(buffer, 6);
+    _serial.sleep(10);
+
+    unsigned int len = _serial.readBlock(ptr, size);
+    if( len < size )
+        LOG_THROW("readBytes too short");
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 0x4C )
+        LOG_THROW("readBytes");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+///                                                                        ///
+///     HIGH Level commands                                                ///
+///                                                                        ///
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::getIdentity()
+{
+    //---- get model
+    HardwareDescriptor hw = hardwareDescriptor();
+    if( hw != HW_UNKNOWN_OSTC && hw != HW_Frog )
+        LOG_THROW("Not a Frog.");
+
+    //---- get identity
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('i'); // 0x63
+
+    unsigned char buffer[1+2+2+13+1] = {0};
+    unsigned len = _serial.readBlock(buffer, sizeof buffer);
+
+    if( len != sizeof buffer || buffer[0] != 'i' || buffer[18] != 0x4D )
+        LOG_THROW("get identity data");
+
+    _serialNumber = buffer[1] + buffer[2]*256;
+    _firmware     = buffer[3]*256 + buffer[4];
+
+    _description = QString("%1 #%2, v%3.%4, %5")
+            .arg(model())
+            .arg(_serialNumber, 4, 10, QChar('0'))
+            .arg(_firmware / 256).arg(_firmware % 256)
+            .arg( QString::fromLatin1((char*)buffer+5, 13)
+                    .replace(QChar('\0'), "")
+                    .trimmed() );
+
+    LOG_TRACE("Found " << _description);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::writeText(const QString& _msg)
+{
+    // Pad to 15 chars:
+    QByteArray ascii = (_msg + QString(15, QChar(' '))).left(15).toLatin1();
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('n');     // 0x6E
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'n' )
+        LOG_THROW("message start");
+
+    _serial.writeBlock((unsigned char *)ascii.constData(), 15);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("message end");
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setDate(const QDateTime &date)
+{
+    unsigned char buffer[6];
+    buffer[0] = date.time().hour();
+    buffer[1] = date.time().minute();
+    buffer[2] = date.time().second();
+    buffer[3] = date.date().month();
+    buffer[4] = date.date().day();
+    buffer[5] = date.date().year() % 100;
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('b'); // 0x62
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'b' )
+        LOG_THROW("sync time");
+
+    _serial. writeBlock( buffer, sizeof buffer);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("sync time end");
+
+    writeText("Set " + date.toString("MM/dd hh:mm"));
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+QSize OSTCFrogOperations::nameSize() const
+{
+    return QSize(13, 1);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setName(const QString &newName)
+{
+    QByteArray padded = (newName+QString(13, QChar(' '))).left(13).toLatin1();
+
+    _serial.sleep(100);         // Make sure last command is finished.
+    _serial.purge();
+    _serial.writeByte('c');     // 0x63
+
+    unsigned char reply = _serial.readByte();
+    if( reply != 'c' )
+        LOG_THROW("set custom text");
+
+    _serial.writeBlock((unsigned char*)padded.constData(), 13);
+    reply = _serial.readByte();
+    if( reply != 0x4D )
+        LOG_THROW("custom text end");
+
+    // Re-read new name:
+    getIdentity();
+    writeText(customText());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::setIcons(const QString &/*fileName*/)
+{
+    //    beginCommands();
+    //    eraseRange(0x000000, 0x00000);
+    //    startWrite(0x000000);
+    //    stopWrite();
+    //    endCommands();
+
+    LOG_THROW( "Set icons: Not yet implemented." );
+}
+
+int OSTCFrogOperations::firmware() const
+{
+    return _firmware;
+}
+
+int OSTCFrogOperations::serialNumber() const
+{
+    return _serialNumber;
+}
+
+QString OSTCFrogOperations::customText() const
+{
+    return _description.section(',', 2).trimmed();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static unsigned char frogSecretKey[16] = {
+    111, 85, 190, 69,
+    108,254, 242, 19,
+    231, 49, 248,255,
+    233, 48, 176,241
+};
+
+void OSTCFrogOperations::loadFirmware(HexFile &hex, const QString &fileName) const
+{
+    hex.allocate(FIRMWARE_SIZE);
+    hex.loadEncrypted(fileName, frogSecretKey);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void OSTCFrogOperations::upgradeFW(const QString &fileName)
+{
+    try {
+        //---- Load and check firmware ---------------------------------------
+        LOG_TRACE("Loading firmware...");
+
+        HexFile hex;
+        loadFirmware(hex, fileName);
+        unsigned int checksum = hex.checksum();
+
+        beginCommands();
+        writeText("Frog Companion");
+        getIdentity();
+
+        unsigned char buffer[5];
+        buffer[0] = LOW(checksum);
+        buffer[1] = HIGH(checksum);
+        buffer[2] = UPPER(checksum);
+        buffer[3] = UP32(checksum);
+
+        // Compute magic checksum's checksum.
+        buffer[4] = 0x55;
+        buffer[4] ^= buffer[0]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[1]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[2]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+        buffer[4] ^= buffer[3]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
+
+        _serial.sleep(100);         // Make sure last command is finished.
+        _serial.purge();
+        _serial.writeByte('P'); // 0x50
+
+        unsigned char reply = _serial.readByte();
+        if( reply != 'P' )
+            LOG_THROW( "Programming start" );
+
+        _serial.writeBlock(buffer, sizeof buffer);
+        _serial.sleep(4000);
+
+        // NOTE: the device never return, because it always to a reset,
+        //       with ot without reprogramming...
+        _serial.close();
+        _isOpen = false;
+    }
+    catch(const Exception& e) {
+        LOG_TRACE(QString("Cannot upgrade: <font color='red'>%1</font>")
+            .arg(e.what()));
+
+        // Unknown state: so make a hard cleanup:
+        _commandMode = false;
+        _isOpen = false;
+        _serial.close();
+    }
+}