Mercurial > public > ostc_companion
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(); + } +}
