Mercurial > public > ostc_companion
view OSTC3Operations.cpp @ 4:e30f00f760d3 default tip
Cleanup OSTC label and removed url
The computer type will now show OSTC 4/5 instead of only 4. The url has
been removed because it is no longer maintained. The ui header have been
deleted because they are generated files shich should not be under
version controll. Delete locally if you want to force an update of the
dialog layout.
| author | Ideenmodellierer |
|---|---|
| date | Sun, 30 Nov 2025 18:37:32 +0100 |
| parents | 0b3630a29ad8 |
| 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; }
