view OSTCFrogOperations.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
line wrap: on
line source

//////////////////////////////////////////////////////////////////////////////
/// \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();
    }
}