Mercurial > public > ostc_companion
view OSTCFrogOperations.cpp @ 14:e47e0f59101d default tip
Enable OSTC 4/5 Icon option
The button for uploading the icon is now activated based on the first FW
version supporting this function
| author | Ideenmodellierer |
|---|---|
| date | Mon, 12 Jan 2026 18:47:00 +0100 |
| parents | 21ce6187d32e |
| 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/Exception.h" #include "Utils/Log.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 #if 0 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 } #endif QRegularExpression OSTCFrogOperations::portTemplate() const { #if defined(Q_OS_MAC) // Mac-spezifischer regulärer Ausdruck return QRegularExpression("tty[.]frog.*", QRegularExpression::CaseInsensitiveOption); #elif defined(Q_OS_LINUX) // Linux-spezifischer regulärer Ausdruck für rfcomm // Funktioniert für Debian, Ubuntu, und SUSE (wie von Google beschrieben) return QRegularExpression("rfcomm.*", QRegularExpression::CaseInsensitiveOption); #elif defined(Q_OS_WIN) // Windows-spezifischer regulärer Ausdruck für COM-Ports 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(); } }
