view OSTC2cOperations.cpp @ 2:177f640940f2

Update exception class and cleanup redifinitions During firmware download and exception caused the application to stop. Rootcause was the defference between QT5 and QT6 exception and string handling which is updated now. In addition some old definitions were removed to avoid compiler warnings.
author Ideenmodellierer
date Fri, 28 Nov 2025 19:57:35 +0100
parents 0b3630a29ad8
children
line wrap: on
line source

//////////////////////////////////////////////////////////////////////////////
/// \file   OSTC2cOperations.h
/// \brief  Implementing various operations for H&W OSTC2, mk2, 2n, 2c dive computer
/// \author JD Gascuel.
/// \sa     ComputerOperations.h
///
/// \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 "OSTC2cOperations.h"

#include "HexFile.h"

#include "SettingsDialog.h"

#include "Utils/Exception.h"
#include "Utils/Log.h"
#include "Utils/ProgressEvent.h"

#include <QDateTime>
#include <QRegularExpression>

#define FIRMWARE_SIZE       0x17F40

// 64 bytes on Mk.2/2n/2c:
#define FIRMWARE_BLOCK_SIZE 0x40

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

OSTC2cOperations::OSTC2cOperations()
  : HardwareOperations(),
    _computerFirmware(0),
    _computerSerial(0)
{
}

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

void OSTC2cOperations::readBank0(byte bank0[])
{
    _serial.sleep(100);
    _serial.purge();
    _serial.writeByte('g');
    _serial.readBlock(bank0, 256);
}

void OSTC2cOperations::writeBank0(const byte bank0[])
{
    _serial.sleep(100);
    _serial.purge();
    _serial.writeByte('d');
    int reply = _serial.readByte();
    if( reply != 'd' )
        LOG_THROW("Write start");

    for(int a=4; a<256; ++a) {
        _serial.writeByte(bank0[a]);
        reply = _serial.readByte();
        if( reply != bank0[a] )
            LOG_THROW("Write bank0 @ " << a);
    }

    _serial.sleep(500); // Allow OSTC2c some time to reboot
}

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

void OSTC2cOperations::readBank1(byte bank1[256])
{
    _serial.sleep(100);
    _serial.purge();
    _serial.writeByte('j');
    _serial.readBlock(bank1, 256);
}

void OSTC2cOperations::getIdentity()
{
    byte bank0[256], bank1[256];
    memset(bank0, 0xFF, sizeof bank0);
    memset(bank1, 0xFF, sizeof bank1);

    //---- Get a memory dump:
    try {
        readBank0(bank0);
        readBank1(bank1);
    } catch(ReadTimeout) {
        LOG_THROW("No reply from OSTC Mk.2, 2n or 2c...");
    }

    // Sanity check:
    if( bank0[65] == 0xFF || bank1[1] > 99 || bank1[2] > 99 )
        LOG_THROW("Not an OSTC Mk.2, 2n or 2c...");

    _computerSerial   = bank0[0] + bank0[1]*256;
    _computerFirmware = bank1[1] * 100 + bank1[2];
    _customText       = QString::fromLatin1((char*)bank0+65, 25).section('}', 0,0);

    _description = QString("%1 #%2, v%3.%4, %5")
        .arg(model())
        .arg(_computerSerial)
        .arg(bank1[1]).arg(bank1[2], 2, 10, QChar('0'))
        .arg( _customText );
}

int OSTC2cOperations::firmware() const
{
    return _computerFirmware;
}

int OSTC2cOperations::serialNumber() const
{
    return _computerSerial;
}

QString OSTC2cOperations::customText() const
{
    return _customText;
}

//////////////////////////////////////////////////////////////////////////////
#if 0
QRegExp OSTC2cOperations::portTemplate() const
{
#if defined(Q_OS_MAC)
    return QRegExp("tty.usbserial-.*", 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 OSTC2cOperations::portTemplate() const
{
#if defined(Q_OS_MAC)
    return QRegularExpression("tty.usbserial-.*",
                              QRegularExpression::CaseInsensitiveOption);
#elif defined(Q_OS_LINUX)
    return QRegularExpression("ttyUSB.*"); // default: case-sensitive
#elif defined(Q_OS_WIN)
    return QRegularExpression("COM.*");    // default: case-sensitive
#endif
}
QStringList OSTC2cOperations::listPorts() const
{
    return listUSBPorts();
}

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

QString OSTC2cOperations::model() const
{
    return "OSTC2c";
}

QString OSTC2cOperations::description()
{
    return _description;
}

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

HardwareOperations::CompanionFeatures OSTC2cOperations::supported() const
{
    // No ICON.
    return CompanionFeatures(PARAMETERS|DATE|NAME|DUMPSCREEN|FIRMWARE
                     |HELIUM_DIVE|CCR_DIVE);
}

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

QString OSTC2cOperations::firmwareTemplate() const
{
    return "mk2_*.hex";
}

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

bool OSTC2cOperations::connect()
{
    try {
        LOG_TRACE("Connecting " << Settings::port);
        _serial.open(Settings::port, "OSTC2c");
        getIdentity();
        return true;
    }
    catch(const Exception& e){
        _serial.close();
        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
    }
    return false;
}

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

QSize OSTC2cOperations::nameSize() const
{
    return QSize(25, 1);
}

void OSTC2cOperations::writeText(const QString& msg)
{
    // No hardware support for that. Just skip it, as it is done when
    // writting bank1...
    LOG_TRACE(msg);
}

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

void OSTC2cOperations::connectServiceMode()
{
    try {
        _serial.open(Settings::port, "OSTC2c");

        _serial.writeByte(0xC1);
        _serial.sleep(500);                    // 0.50 sec for bootloader to start.

        //---- Send C1C1 to start upgrading firmware -----------------------------
        unsigned char pic = 0;
        unsigned char ok = '?';
        for(int retry=0; retry < 10; ++retry) {
            _serial.writeByte(0xC1);
            _serial.sleep(5);
            _serial.writeByte(0xC1);
            _serial.sleep(5);

            try {
                pic = _serial.readByte();
                ok  = _serial.readByte();
                break;
            }
            catch(const ReadTimeout&) {
                if( retry == 9 )
                    LOG_THROW("Cannot start firmware upgrade: timeout.");

                LOG_INFO("Connecting OSTC2c (" << (retry+1) << "/10)...");
            }
        }

        //---- Check PIC type ----------------------------------------------------
        LOG_TRACE("Pic = " << int(pic));
        if( ! pic )
            LOG_THROW("Cannot sync firmware upgrade. Cannot detect chip");

        if( pic != 0x57
         || ok  != 'K'
        )
            LOG_THROW( "Cannot sync firmware upgrade. Bad chip " << int(pic) << " " << ok);

        _serial.sleep(50);                       // Wait 0.050 sec here.
        return;
    }
    catch(const Exception& e) {
        _serial.close();
        LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
    }
    return;
}

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

bool OSTC2cOperations::disconnect(bool /*closing*/)
{
    if( ! _serial.isOpen() ) return false;

    LOG_TRACE("Disconnecting.");

    _serial.purge();
    _serial.close();

    return true;
}

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

void OSTC2cOperations::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);
    _serial.sleep(100);
}

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

void OSTC2cOperations::setName(const QString &newName)
{
    QByteArray padded = (newName + QString(25, '}'))
            .left(25)
            .toLatin1();

    byte bank0[256] = {0};
    readBank0(bank0);
    memcpy(bank0+65, padded.constData(), 25);
    writeBank0(bank0);

    // Then get the new identity:
    getIdentity();
}

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

void OSTC2cOperations::setIcons(const QString &/*fileName*/)
{
    LOG_THROW("Not supported");
}

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

QImage OSTC2cOperations::dumpScreen() const
{
    QImage image(320, 240, QImage::Format_RGB32);

     //---- Send dump screen command -------------------------------------
    unsigned char reply;
    _serial.writeByte('l');
    reply = _serial.readByte();

    if( reply != 'l' )
        LOG_THROW( "Dumpscreen command failed: " << (int)reply );

    //---- Read image ----------------------------------------------------
    int percent = 0;
    try {
        for(int x=0; x<320; ++x)
        {
            int p = x/16;       // 5% steps
            if( p != percent )
            {
                PROGRESS(p, 320/16);
                percent = p;
            }
            int pix = 0, count = 0;

            for(int y=0; y<240; ++y, --count)
            {
                if( count <= 0 )
                {
                    count = _serial.readByte();

                    if( (count & 0x80) == 0 )
                        pix = 0;
                    else if( (count & 0xC0) == 0xC0 )
                    {
                        pix = 0xFFFF;
                        count &= 0x3F;
                    }
                    else
                    {
                        unsigned char bpix[2];
                        _serial.readBlock(bpix, 2);
                        pix = (bpix[0] << 8) + bpix[1];
                        count &= 0x3F;
                    }
                    count++;
                }
                // Bit extension 5bits --> 8bits:
                //      12345123.45 = (12345 * 0b100001) / 4
                //      12345612.3456 = (123456 * 0xb1000001) / 16
                int r = (31 & (pix >> 11)) * 255/31;
                int g = (63 & (pix >>  5)) * 255/63;
                int b = (31 &  pix       ) * 255/31;
                image.setPixel(x, y, qRgb( r, g, b));
            }
        }
    }
    catch( ReadTimeout ) {
        LOG_THROW("Missing image data...");
    }

    //---- Done ----------------------------------------------------------
    PROGRESS_RESET();
    LOG_INFO( "Screen dumped." );
    return image;
}

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

void OSTC2cOperations::loadFirmware(HexFile& hex, const QString& fileName) const
{
    hex.allocate(FIRMWARE_SIZE);
    hex.load(fileName);

    //---- Patch Firmware intialization GOTO ---------------------------------
    memcpy((void*)(hex.data() + 0x17F38),   // To bootloader vector
           (void*)(hex.data() + 0x00000),   // From fw reset code
           8);                              // Up to 8 bytes...

    static unsigned char byteCode[8] = {
        0xA0, 0xEF, 0xBF, 0xF0,             // goto 0x1F740 (bootloader 19k)
        0x00, 0x00,                         // nop
        0x00, 0x00                          // nop
    };
    memcpy((void*)(hex.data() + 0x00000),   // To OSTC reset vector
           (void*)(byteCode),               // From go back to bootloader.
           8);                              // Up to 8 bytes...
}

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

static void
uploadBlock(Serial& serial, const HexFile& hex, size_t addr)
{
    const unsigned char count = FIRMWARE_BLOCK_SIZE;
    assert( 0 < count && count < 255 );
    assert((addr+count) <= FIRMWARE_SIZE );
    unsigned char reply = 0;

    unsigned char header[4];
    header[0] = 0x1F & (addr >> 16);
    header[1] = 0xFF & (addr >>  8);
    header[2] = 0xFF & (addr);
    header[3] = count;

    unsigned char crc = header[0] + header[1] + header[2] + header[3];
    for(int i=0; i<count; ++i)
        crc += hex.data()[addr+i];
    crc = -crc; // Sum should make zero.

    try {
        serial.writeBlock(header, sizeof header);
        serial.writeBlock(hex.data()+addr, count);
        serial.writeByte(crc);
    } catch(...) {
        goto WriteFailed;
    }

    serial.sleep(20);        // 18msec for a FLASH row write, plus VAT.

    reply = serial.readByte();
    if( reply != 'K' )
    {
        serial.close();
        LOG_THROW( QString("Bad checksum at 0x%1").arg((unsigned int)addr, 0, 16, QChar('0')));
    }
    return;

WriteFailed:
    serial.close();

    LOG_THROW( QString("Write failed") );
}

void OSTC2cOperations::upgradeFW(const QString& fileName)
{
    //---- Load and check firmware ---------------------------------------
    LOG_TRACE("Loading firmware '" << fileName << "'.");
    HexFile hex;
    loadFirmware(hex, fileName);

    //---- Enter uart_115k_bootloader ----------------------------------------
    connectServiceMode();

    //---- Let's do it -------------------------------------------------------
    int percent = 0;

    for(size_t addr = FIRMWARE_BLOCK_SIZE; addr < FIRMWARE_SIZE; addr += FIRMWARE_BLOCK_SIZE)
    {
        int p = int((addr*200.0f) / float(FIRMWARE_SIZE) + 0.5f);
        if( p > percent )
        {
            PROGRESS(percent, 200);
            percent = p;
        }
        uploadBlock(_serial, hex, addr);
    }
    PROGRESS(200, 200);
    uploadBlock(_serial, hex, 0);

    PROGRESS_RESET();
    LOG_INFO("Upgrade FW send.");

    // More than 500ms --> receive timeout.
    _serial.sleep(600);
}

void OSTC2cOperations::getSignal()
{
    return;
}

void OSTC2cOperations::getAllHeader(unsigned char* pBuffer)
{
    return;
}
void OSTC2cOperations::writeAllHeader(unsigned char* pBuffer)
{
    return;
}

void OSTC2cOperations::getAllSamples(unsigned char* pBuffer)
{
    return;
}
void OSTC2cOperations::writeAllSamples(unsigned char* pBuffer)
{
    return;
}
//////////////////////////////////////////////////////////////////////////////