Mercurial > public > ostc_companion
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OSTC3Operations.cpp Thu Nov 27 18:40:28 2025 +0100 @@ -0,0 +1,685 @@ +////////////////////////////////////////////////////////////////////////////// +/// \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; +}
