Mercurial > public > ostc_companion
view OSTCFrogOperations.cpp @ 3:4ace58a7c03c
Send disconnect command before closing the connection
The old BT module transmitted a notification in case a connection were
closed which cause the ostc to exit the uart loop. The new one doesn't
do this => send disconnect command to avoid waiting in the installation
loop till timeout or button press.
| author | Ideenmodellierer |
|---|---|
| date | Fri, 28 Nov 2025 20:00:02 +0100 |
| parents | 0b3630a29ad8 |
| 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/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(); } }
