Mercurial > public > ostc_companion
view OSTC3Operations.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 OSTC3Operations.cpp /// \brief Implementing various operations for OSTC3 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 "OSTC3Operations.h" #include "Utils/Exception.h" #include "Utils/Log.h" #include "Utils/ProgressEvent.h" #include "SettingsDialog.h" #include "HexFile.h" #include <QApplication> #include <QDateTime> #include <QDir> #include <QRegularExpression> // 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 FIRMWARE_AREA 0x3E0000 #define FIRMWARE_SIZE 0x01E000 // 120KB #define FIRMWARE_BLOCK 0x1000 // 4KB ////////////////////////////////////////////////////////////////////////////// OSTC3Operations::OSTC3Operations() : descriptionString(""), emulatorName("OSTC3"), _computerFirmware(0), _computerSerial(0), _connectMode(CLOSED_MODE) { memset(computerText, 0, sizeof computerText); } OSTC3Operations::~OSTC3Operations() { if( _connectMode != CLOSED_MODE ) disconnect(true); } ////////////////////////////////////////////////////////////////////////////// QStringList OSTC3Operations::listPorts() const { return listUSBPorts(); } ////////////////////////////////////////////////////////////////////////////// bool OSTC3Operations::connect() { LOG_TRACE( "Enter download mode..." ); try { _connectMode = CLOSED_MODE; _serial.open( Settings::port, emulatorName); _serial.sleep(333); // Initial 1/3 sec. delay to first comm. for(int retry=0; retry < 10; ++retry) { // Allow for 0.1sec extra delay try { _serial.writeByte(0xBB); } catch(const WriteTimeout& ) { // Bluetooth not present: one can open the pseudo COM port, // but we will have a timeout on first write byte... if( retry < 9 ) { LOG_INFO("Cannot connect to " << Settings::port <<" (" << (retry+1) << "/10)..."); _serial.sleep(1000); continue; } LOG_THROW("Cannot connect to " << model() <<"."); return false; } _serial.sleep(100); //---- Check acknowledge, w/o fatal timeouts. unsigned char ok = 0; unsigned char echo = 0; try { echo = _serial.readByte(); // Already in connect() mode ??? if( echo == 'M' ) break; ok = _serial.readByte(); } catch(const ReadTimeout&) { LOG_INFO("Retry " << (retry+1) << "/10..."); } if( echo != 0xBB || ok != 0x4D ) { // DOWNLOAD modes only. if( retry < 9 ) { _serial.purge(); _serial.sleep(400); continue; } LOG_THROW("Unable to enter hwOS service mode"); return false; } break; } getIdentity(); QString banner = Log::applicationName(); writeText(banner); LOG_TRACE("Connected."); _connectMode = DOWNLOAD_MODE; return true; } catch(const Exception& e) { disconnect(); LOG_THROW("Port " << Settings::port << ": " << e.what()); } return false; } ////////////////////////////////////////////////////////////////////////////// bool OSTC3Operations::disconnect(bool /*closing*/) { if( _connectMode == CLOSED_MODE ) return false; descriptionString.clear(); // cleanup for interface updateStatus() _connectMode = CLOSED_MODE; _serial.purge(); _serial.writeByte(0xFF); // Exit communications, just in case... _serial.sleep(100); _serial.purge(); _serial.close(); return true; } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::getIdentity() { descriptionString.clear(); LOG_TRACE("Getting model..."); HardwareDescriptor hw = hardwareDescriptor(); // if( hw != HW_OSTC3 ) // LOG_THROW("Not an OSTC3."); LOG_TRACE("Getting identity..."); getCommonIdentity(); LOG_INFO("Found " << descriptionString.trimmed()); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::getCommonIdentity() { unsigned char echo = retryCommand(_serial, 'i'); // 0x69 if( echo != 'i' ) LOG_THROW("Bad identity reply (1)"); unsigned char header[4 + 60 + 1] = {0}; _serial.readBlock(header, sizeof header); if( header[64] != 0x4D ) // DOWNLOAD modes only. LOG_THROW("Bad identity reply (2)"); _computerSerial = header[0] + header[1]*256; _computerFirmware = header[2] * 100 + header[3]; memcpy(computerText, header+4, sizeof computerText); descriptionString = QString("%1 #%2, fw %3.%4, %5") .arg(model()) .arg(serialNumber(), 4, 10, QChar('0')) .arg(firmware() / 100).arg(firmware() % 100, 2, 10, QChar('0')) .arg( QString::fromLatin1((char*)(computerText), 60) .replace(QChar('\0'), " ") ); } ////////////////////////////////////////////////////////////////////////////// int OSTC3Operations::firmware() const { return _computerFirmware; } int OSTC3Operations::serialNumber() const { return _computerSerial; } QString OSTC3Operations::customText() const { return QString::fromLatin1(computerText, sizeof computerText); } ////////////////////////////////////////////////////////////////////////////// QSize OSTC3Operations::nameSize() const { return QSize(12, 5); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::writeText(const QString& msg) { QByteArray buffer = msg.leftJustified(16, ' ', true).toLatin1(); LOG_TRACE("Echoing string '" << buffer << "'"); // 2014-10-27 jDG: On OSTC3 v1.60, after an ERASE AREA, we do get // a spurious L here (instead of n)... unsigned char echo = retryCommand(_serial, 'n'); // 0x6E Echo string. if( echo != 'n' ) LOG_THROW("Bad message reply (1)"); _serial.writeBlock((const unsigned char*)buffer.data(), 16); _serial.sleep(25); // Allow 25msec to display the message... unsigned char ok = _serial.readByte(); if( ok != 0x4C && ok != 0x4D ) // DOWNLOAD or SERVICE modes. LOG_THROW("Bad message reply (2)"); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::setDate(const QDateTime &date) { LOG_TRACE("Set Date " << date.toString("MM/dd/yyyy hh:mm")); 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; unsigned char echo = retryCommand(_serial, 'b'); // 0x62 Sync date if( echo != 'b' ) LOG_THROW("Bad clock reply (1)"); _serial.writeBlock(buffer, sizeof buffer); _serial.sleep(5); unsigned char ok = _serial.readByte(); if( ok != 0x4D ) // DOWNLOAD mode only. LOG_THROW("Bad clock reply (2)"); writeText( "Set " + date.toString("MM/dd hh:mm") ); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::setName(const QString &newName) { LOG_TRACE("Set Name '" << newName << "'"); char buffer[60]; memset(buffer, 0, sizeof buffer); strncpy(buffer, newName.toLatin1().constData(), sizeof buffer); unsigned char echo = retryCommand(_serial, 'c'); // 0x63 Send custom text if( echo != 'c' ) LOG_THROW("Bad text reply (1)"); _serial.writeBlock((unsigned char*)buffer, sizeof buffer); _serial.sleep(5); unsigned char ok = _serial.readByte(); if( ok != 0x4D ) // DOWNLOAD modes only. LOG_THROW("Bad text reply (2)"); getIdentity(); // Echo the first line of customtext: writeText(newName.left(12).trimmed()); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::setIcons(const QString &) { LOG_THROW("Set icons: Not yet implemented"); } QImage OSTC3Operations::dumpScreen() const { LOG_THROW("Dump screen: Not yet implemented"); return QImage(); } ////////////////////////////////////////////////////////////////////////////// static unsigned char ostc3SecretKey[16] = { 241,233, 176, 48, 69,111, 190, 85, 255,231, 248, 49, 19,108, 242,254 }; ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::eraseRange(unsigned int addr, unsigned int size) { // Convert size to number of pages, rounded up. size = ((size + 4095) / 4096); // Erase just the needed pages. unsigned char buffer[4]; buffer[0] = UPPER(addr); buffer[1] = HIGH(addr); buffer[2] = LOW(addr); buffer[3] = LOW(size); unsigned char reply = retryCommand(_serial, 'B'); // Command 'B' if( reply != 0x42 ) LOG_THROW("eraseRange (1)"); _serial.writeBlock(buffer, 4); // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure. _serial.sleep(40 + size * 31); unsigned char ok = _serial.readByte(); if( ok != 0x4c ) // SERVICE MODE acknowledge. LOG_THROW("eraseRange (2)"); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::writeBlock(unsigned int addr, const unsigned char *data, unsigned int size) { unsigned char buffer[3]; buffer[0] = UPPER(addr); buffer[1] = HIGH(addr); buffer[2] = LOW(addr); unsigned char reply = retryCommand(_serial, '0'); // 0x30 if( reply != '0' ) LOG_THROW("startWrite"); _serial.writeBlock(buffer, sizeof buffer); _serial.sleep(2); _serial.writeBlock(data, size); // Approximated EEPROM write time some 1sec timeout ?? // 1KB = 240 + 1000 = 1240 : Ok. // 4KB = 1.1sec : Ok. _serial.sleep(1100); unsigned char ok = _serial.readByte(); if( ok != 0x4c && ok != 0x4d ) // DOWNLOAD or SERVICE modes. LOG_THROW("stopWrite"); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::readBlock(unsigned int addr, unsigned char* ptr, unsigned int size) { 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); unsigned char reply = retryCommand(_serial, 0x20); // Command ' ' if( reply != 0x20 ) LOG_THROW("readBytes"); _serial.writeBlock(buffer, sizeof buffer); _serial.sleep(500); // Allow some time to start emitting... _serial.readBlock(ptr, size); // Note: in that case, the OK byte is send AFTER the data block. unsigned char ok = _serial.readByte(); if( ok != 0x4C ) // SERVICE modes only. LOG_THROW("readBytes (2)"); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::upgradeFirmware(unsigned int checksum) { 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); unsigned char reply = retryCommand(_serial, 0x50); // 'P' : send FW to bootloader if( reply != 0x50 ) LOG_THROW("Flashing start (1)"); _serial.writeBlock(buffer, sizeof buffer); unsigned char ok = _serial.readByte(); if( ok != 0x4C ) // SERVICE modes only. LOG_THROW("Flashing start (2)"); // NOTE: the device never return, because it always do a reset, // with ot without reprogramming... _serial.sleep(500); _serial.close(); descriptionString.clear(); } QString OSTC3Operations::firmwareTemplate() const { return "*_firmware.hex"; } #if 0 QRegExp OSTC3Operations::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 OSTC3Operations::portTemplate() const { #if defined(Q_OS_MAC) return QRegularExpression("tty.usbserial-.*", QRegularExpression::CaseInsensitiveOption); #elif defined(Q_OS_LINUX) // Debian, Ubuntu, RedHat, CentOS, SUSE return QRegularExpression("ttyUSB.*"); // default: case-sensitive #elif defined(Q_OS_WIN) return QRegularExpression("COM.*"); // default: case-sensitive #endif } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::connectServiceMode() { LOG_TRACE( "Enter service mode..." ); // NOTE: Service mode requires a special starting sequence, different from // the usual download mode state. // Also, the general acknowledge byte is changed to 0x4c 'L'. assert( _connectMode != SERVICE_MODE ); _serial.open( Settings::port, emulatorName); _serial.sleep(333); // Initial 1/3 sec before trying service mode... for(int retry=0; retry < 10; ++retry) { unsigned char serviceMode[] = { 0xAA, 0xAB, 0xCD, 0xEF }; try { _serial.writeBlock(serviceMode, sizeof serviceMode); } catch(const WriteTimeout&) { // Bluetooth not present: one can open the pseudo COM port, // but we will have a timeout on first write byte... if( retry < 9 ) { LOG_INFO("Cannot connect to " << Settings::port <<" (" << (retry+1) << "/10)..."); _serial.sleep(1000); continue; } LOG_THROW("Cannot connect to " << model() << "."); return; } // Allow for 0.1sec extra delay _serial.sleep(100); //---- Check acknowledge: unsigned char echo = 0; try { echo = _serial.readByte(); } catch(...) {} if( echo != 0x4b ) { serviceModeFailed: if( retry < 9 ) { _serial.purge(); _serial.sleep(400); continue; } LOG_THROW("Unable to enter " << model() <<" service mode"); } echo = _serial.readByte(); if( echo != 0xAB ) goto serviceModeFailed; echo = _serial.readByte(); if( echo != 0xCD ) goto serviceModeFailed; echo = _serial.readByte(); if( echo != 0xEF ) goto serviceModeFailed; echo = _serial.readByte(); if( echo != 0x4c ) // SERVICE modes only. goto serviceModeFailed; break; } _connectMode = SERVICE_MODE; } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::upgradeFW(const QString &fileName) { //---- Load and check firmware ------------------------------------------- LOG_INFO("Loading firmware."); HexFile hex; loadFirmware(hex, fileName); //---- Enter Service Mode ------------------------------------------------ connectServiceMode(); //---- Erase old Firmware ------------------------------------------------ PROGRESS(0, FIRMWARE_SIZE); LOG_INFO("Erasing Firmware."); writeText(" Erasing FW..."); eraseRange(FIRMWARE_AREA, FIRMWARE_SIZE); //---- Upload Firmware --------------------------------------------------- LOG_INFO("Uploading firmware."); writeText(" Uploading..."); for(int len = 0x00000; len < FIRMWARE_SIZE; len += FIRMWARE_BLOCK) { unsigned char percent = int(len * 100.0f / FIRMWARE_SIZE + 0.5f); writeText( QString(" Uploading %1%") .arg(percent, 2) ); PROGRESS(percent, 100); writeBlock(FIRMWARE_AREA+len, hex.data()+len, FIRMWARE_BLOCK); } PROGRESS(100, 100); //---- Verify firmware --------------------------------------------------- LOG_INFO("Verify firmware."); writeText(" Verifying..."); { unsigned char* buffer = new unsigned char[FIRMWARE_SIZE]; Q_CHECK_PTR(buffer); for(int len = 0x00000; len < FIRMWARE_SIZE; len += FIRMWARE_BLOCK) { unsigned char percent = int(len * 100.0f / FIRMWARE_SIZE + 0.5f); writeText( QString(" Verifying %1%") .arg(percent, 2) ); PROGRESS(percent, 100); readBlock(FIRMWARE_AREA+len, buffer+len, FIRMWARE_BLOCK); } PROGRESS(100, 100); qApp->processEvents(QEventLoop::ExcludeUserInputEvents, 10); for(int i=0; i<FIRMWARE_SIZE; ++i) if( buffer[i] != hex.data()[i] ) { writeText(" Verify FAILED"); LOG_THROW("readback is different"); } delete[] buffer; } PROGRESS_THROTTLE(); //---- Flashing firmware ------------------------------------------------- LOG_INFO("Programming."); writeText(" Programming..."); upgradeFirmware( hex.checksum() ); //---- Done -------------------------------------------------------------- // Low-level close, to avoid trying to send a 0xFF byte... _serial.close(); _connectMode = CLOSED_MODE; LOG_INFO("Upgrade done."); PROGRESS_RESET(); } void OSTC3Operations::loadFirmware(HexFile& hex, const QString& fileName) const { hex.allocate(FIRMWARE_SIZE); hex.loadEncrypted(fileName, ostc3SecretKey); } ////////////////////////////////////////////////////////////////////////////// QString OSTC3Operations::model() const { return "OSTC hwOS (USB)"; } QString OSTC3Operations::description() { return descriptionString; } ////////////////////////////////////////////////////////////////////////////// HardwareOperations::CompanionFeatures OSTC3Operations::supported() const { // No ICON, no DUMPSCREEN, no VPM return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE |HELIUM_DIVE|CCR_DIVE); } ////////////////////////////////////////////////////////////////////////////// void OSTC3Operations::getSignal() { return; } void OSTC3Operations::getAllHeader(unsigned char* pBuffer) { return; } void OSTC3Operations::writeAllHeader(unsigned char* pBuffer) { return; } void OSTC3Operations::getAllSamples(unsigned char* pBuffer) { return; } void OSTC3Operations::writeAllSamples(unsigned char* pBuffer) { return; }
