view OSTC2cOperations.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 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;
}
//////////////////////////////////////////////////////////////////////////////