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

/////////////////////////////////////////////////////////////////////////////
/// \file   HardwareOperations.cpp
/// \brief  Abstract operations for HW dive computers.
/// \author JD Gascuel.
/// \sa OSTC3Operations.cpp
///
/// \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 "HardwareOperations.h"
#include "MainWindow.h"
#include "Utils/Log.h"
#include <QSerialPortInfo>

#ifdef WIN32
#   include <winreg.h>
#endif

#include <QDir>

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

unsigned char HardwareOperations::retryCommand(Serial& serial,
                                               unsigned char cmd,
                                               int retries)
{
    for(int retry=0; retry<retries; ++retry)
    {
        serial.writeByte( cmd );    // Send command
        serial.sleep(25);           // Allow 25msec lag.

        try {
            unsigned char echo = serial.readByte();
            if( echo == cmd || echo == 'M' || echo == 'L' )
                return echo;            // Got it, or unknown command...
        }
        catch(const ReadTimeout&) {
            continue;
        }

        serial.sleep(100);         // Cleanup any pending stuff,
        serial.purge();            // and retry...
    }

    return 0xFF;
}

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

HardwareOperations::HardwareDescriptor
HardwareOperations::hardwareDescriptor()
{
    unsigned char echo  = 0;
    unsigned int  hardFeatures = 0xFF; // timeout response...
    unsigned int  softFeatures = 0;
    unsigned int  model        = 0;
    unsigned char ok    = 0;

    try {
        //---- First: try the new extended hardware query --------------------
        echo = retryCommand(_serial, 0x60, 1); // BACKQUOTE char
        if( echo == 0x60 ) {
            uchar extendedDescriptor[6];
            _serial.readBlock(extendedDescriptor, sizeof extendedDescriptor);


            hardFeatures = extendedDescriptor[0] * 256 + extendedDescriptor[1];
            softFeatures = extendedDescriptor[2] * 256 + extendedDescriptor[3];
            model        = extendedDescriptor[4];
            ok           = extendedDescriptor[5];
        }
        else
        {
            // Did we have a timeout ?
            // In that case, some hwOS versions fails and need a reset of
            // the connection mode...
            if( echo == 0xFF )
            {
                echo = retryCommand(_serial, 0xBB);     // Try to reconnect
                if( echo == 0xBB )
                    echo = _serial.readByte();          // Eat 4d prompt
            }

            // Then try the OLD hardware descriptor command...
            echo = retryCommand(_serial, 'j');  // 0x6A
            if( echo == 'j' ) {
                hardFeatures = _serial.readByte();
                ok    = _serial.readByte();
            }
        }
    }
    catch(const ReadTimeout&) {}

    if( (echo != 0x60 && echo != 'j')
     || (ok != 'M' && ok != 'L')
    ) {
        LOG_TRACE("Old OSTC not responding...");
        return HW_UNKNOWN_OSTC;
    }

    switch(hardFeatures) {
    case HW_Frog        : LOG_TRACE("Frog found"); break;
    case HW_OSTCSport_a : LOG_TRACE("OSTC Sport found"); break;
    case HW_OSTC2c      : LOG_TRACE("OSTC 2c found"); break;
    case HW_OSTC2_a:
    case HW_OSTC2_c     : LOG_TRACE("OSTC 2 found"); break;
    case HW_OSTCcR_a:
    case HW_OSTCcR_b    : LOG_TRACE("OSTC cR found"); break;
    case HW_OSTC3       : LOG_TRACE("OSTC 3 found"); break;
    case HW_OSTC3p_a    : LOG_TRACE("OSTC 3+ found"); break;
    case HW_OSTC4       : LOG_TRACE("OSTC 4 found"); break;

    case HW_OSTCSport_b : LOG_TRACE("OSTC Sport, OSTC 2 or OSTC 3 found."); break;

    case 0xFF: case 0x4C: case 0x4D:
        LOG_TRACE("old OSTC not responding...");
        return HW_UNKNOWN_OSTC;

    default:
     //   LOG_TRACE("Unknown hardware feature =" << QString().sprintf("0x%04x", hardFeatures));
        LOG_TRACE("Unknown hardware feature =" << QString::asprintf("0x%04x", hardFeatures));
        break;
    }

    if( echo == 0x60 ) {
        LOG_TRACE("    software feature = "  <<  QString::asprintf("0x%04x", softFeatures));
        LOG_TRACE("    model = "  <<  QString::asprintf("0x%02x", model));
    }

    return (HardwareDescriptor)hardFeatures;
}

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

QStringList HardwareOperations::listBluetoothPorts() const
{
    assert(supported() & BLUETOOTH);
    QStringList list;
    QString PortDesc;
    const auto serialPortInfos = QSerialPortInfo::availablePorts();

#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
    // TODO: Linux USB search...
    QRegExp pTemplate = portTemplate();
    QDir dev("/dev");
    QStringList all = dev.entryList(QStringList() << "tty.*",
                                    QDir::NoDotAndDotDot|QDir::System|QDir::Writable,
                                    QDir::Name);

    for(int i=0; i<(int)all.count(); ++i) {
        if( pTemplate.indexIn(all[i]) >= 0 ) {
            LOG_TRACE("Port " << all[i]);
            list += all[i];
        }
        else
            LOG_DEBUG("...  " << all[i]);
    }
#else
    /* Check the descriptors of the available COMs for Bluetooth tag */
    for (const QSerialPortInfo &serialPortInfo : serialPortInfos) {
         PortDesc = serialPortInfo.description();
         if( PortDesc.contains("Bluetooth"))
             list += serialPortInfo.portName();
    }

    if( list.isEmpty() ) /* no port identified => fallback to old detection function */
    {
        for(int i=1; i<300; ++i)
        {
            QString port = QString("COM%1").arg(i);

            // First: try to read default configuration...
            COMMCONFIG config = {0};
            config.dwSize   = sizeof config;
            config.wVersion = 1;
            DWORD len = sizeof config;

            QByteArray fixed = "\\\\.\\" + port.toLocal8Bit();
            if( GetDefaultCommConfigA(fixed.constData(), &config, &len) ) {
                if( config.dwProviderSubType == PST_RS232 )
                    list += port;
            }
        }

        //---- Second chance
        //     overide usual MS bug, by looking into the registry for more
        //     BLUETOOTH ports...
        {
            HKEY key;
            const char registryPath[] = "HARDWARE\\DEVICEMAP\\SERIALCOMM";
            if( RegOpenKeyExA(HKEY_LOCAL_MACHINE,           // PWD
                             registryPath,                  // "SOFTWARE\Intel\PSIB"
                             0,                             // Options
                             KEY_READ,                      // Desired SAM: See 32bits view.
                             &key) == ERROR_SUCCESS
            ) {
                for(DWORD i = 0; ++i;) {
                    char nameBuffer[128] = {0};
                    DWORD nameLen = sizeof nameBuffer;
                    unsigned char dataBuffer[128] = {0};
                    DWORD dataLen = sizeof dataBuffer;
                    long rc = RegEnumValueA(key, i,
                                  nameBuffer, &nameLen,
                                  nullptr,
                                  nullptr,
                                  dataBuffer, &dataLen);
                    if( rc != ERROR_SUCCESS )
                        break;

                    QString name = QString(nameBuffer);
                    QString port = QString((char*)dataBuffer);
                    LOG_TRACE("Resource " << i << ": " << name << ", " << port);
                    if( name.contains("\\BtModem") || name.contains("\\BthModem") || name.contains("\\BtPort") ) {
                        list += port + " (Bluetooth)";
                        LOG_TRACE("Port " << name);
                    }
                    else
                        LOG_DEBUG("...  " << name);
                }
                RegCloseKey(key);
            }
        }
    }
#endif

    return list;
}

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

QStringList HardwareOperations::listUSBPorts() const
{
    assert( !(supported() & BLUETOOTH) );
    QStringList list;

#if defined(Q_OS_MAC) || defined(Q_OS_LINUX)
    // TODO: Linux USB search...
    QDir dev("/dev");
    QRegExp pTemplate = portTemplate();
    QStringList all = dev.entryList(QStringList() << "tty.*",
                                    QDir::System|QDir::Writable|QDir::NoDotAndDotDot,
                                    QDir::Name);
    for(int i=0; i<(int)all.count(); ++i) {
        if( pTemplate.indexIn(all[i]) >= 0 ) {
            LOG_TRACE("Port " << all[i]);
            list += all[i];
        }
        else
            LOG_TRACE("... " << all[i]);
    }
#else
    //---- First chance: Try the normal port list:
    for(int i=1; i<300; ++i)
    {
        QString port = QString("COM%1").arg(i);

        // First: try to read default configuration...
        COMMCONFIG config;
        memset(&config, 0, sizeof config);
        config.dwSize   = sizeof config;
        config.wVersion = 1;
        DWORD len = sizeof config;

        QByteArray fixed = "\\\\.\\" + port.toLocal8Bit();
        if( GetDefaultCommConfigA(fixed.constData(), &config, &len) ) {
            LOG_TRACE("Port " << port << " subtype=" << int(config.dwProviderSubType) );
            if( config.dwProviderSubType == PST_RS232 )
                list += port;
        } else if( len != sizeof config )
            LOG_THROW("Required " << len << " bytes.");
        else if( HRESULT rc = GetLastError() )
            if( rc != 87 )
                LOG_TRACE("Port " << port << "  error=" << rc );

    }
    //---- Second chance
    //     overide usual MS bug, by looking into the registry for more
    //     USB serial ports...
    {
        HKEY key;
        const char registryPath[] = "HARDWARE\\DEVICEMAP\\SERIALCOMM";
        if( RegOpenKeyExA(HKEY_LOCAL_MACHINE,           // PWD
                         registryPath,                  // path
                         0,                             // Options
                         KEY_READ,                      // Desired SAM: See 32bits view.
                         &key) == ERROR_SUCCESS
        ) {
            for(DWORD i = 0;; ++i) {
                char nameBuffer[128] = {0};
                DWORD nameLen = sizeof nameBuffer;
                unsigned char dataBuffer[128] = {0};
                DWORD dataLen = sizeof dataBuffer;
                long rc = RegEnumValueA(key, i,
                              nameBuffer, &nameLen,
                              nullptr,
                              nullptr,
                              dataBuffer, &dataLen);

                if( rc == ERROR_NO_MORE_ITEMS )
                    break;

                if( rc != ERROR_SUCCESS )
                    LOG_THROW( "Enumeration error" );

                QString name = QString(nameBuffer);
                QString port = QString((char*)dataBuffer);
                LOG_TRACE("Resource " << i << ": " << name << ", " << port);

                if( name.contains("\\VCP") )
                    list += port + " (USB)";
            }
            RegCloseKey(key);
        }
    }
#endif

    return list;
}

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

//QString HardwareOperations::scanNewPort()
//{
//    static QStringList oldPorts;

//    //---- Get current ports, type-less (ie. strip (USB) or (Bluetooth) ------
//    QStringList newPorts;
//    foreach(QString port, listPorts())
//        newPorts += port.section(" ", 0, 0);

//    //---- Simplify all ports not in the list anymore -----------------------
//    for(int p=0; p<oldPorts.count(); ++p) {
//        QString port = oldPorts[p];
//        if( ! newPorts.contains(port) )
//            oldPorts.removeAll(port);
//    }

//    //---- Check for new port in the list -----------------------------------
//    for(int p=0; p<newPorts.count(); ++p) {
//        QString port = newPorts[p];
//        // Did we find a new port ?
//        if( ! oldPorts.contains(port) ) {
//            oldPorts += port;
//            return port;
//        }
//    }

//    return QString::null;
//}