comparison OSTC4Operations.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 4ace58a7c03c
comparison
equal deleted inserted replaced
0:76ccd6ce50c0 1:0b3630a29ad8
1 //////////////////////////////////////////////////////////////////////////////
2 /// \file OSTC4Operations.h
3 /// \brief Implementing various operations for H&W OSTC4 dive computer
4 /// \author JD Gascuel.
5 /// \sa OSTC3Operations, HardwareOperations.h
6 ///
7 /// \copyright (c) 2016 JD Gascuel. All rights reserved.
8 /// $Id$
9 //////////////////////////////////////////////////////////////////////////////
10 //
11 // BSD 2-Clause License:
12 //
13 // Redistribution and use in source and binary forms, with or without
14 // modification, are permitted provided that the following conditions
15 // are met:
16 //
17 // 1. Redistributions of source code must retain the above copyright notice,
18 // this list of conditions and the following disclaimer.
19 //
20 // 2. Redistributions in binary form must reproduce the above copyright notice,
21 // this list of conditions and the following disclaimer in the documentation
22 // and/or other materials provided with the distribution.
23 //
24 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
28 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
34 // THE POSSIBILITY OF SUCH DAMAGE.
35 //
36 //////////////////////////////////////////////////////////////////////////////
37 #include <QSettings>
38 #include <QRegularExpression>
39 #include "OSTC4Operations.h"
40
41 #include "Utils/Log.h"
42 #include "Utils/Exception.h"
43 #include "Utils/ProgressEvent.h"
44
45 extern QSettings* settings;
46
47 #define FIRMWARE_BLOCK 0x40 // 64 bytes
48 #define FIRMWARE_BLOCK_DELAY 15 // 15 msec.
49 #define FIRMWARE_BLOCK_FAST 0x300 // 4096 bytes
50
51 //////////////////////////////////////////////////////////////////////////////
52
53 QSize OSTC4Operations::nameSize() const
54 {
55 return QSize(12, 4);
56 }
57
58 //////////////////////////////////////////////////////////////////////////////
59
60 QString OSTC4Operations::model() const
61 {
62 return "OSTC4";
63 }
64
65 HardwareOperations::CompanionFeatures OSTC4Operations::supported() const
66 {
67 // No ICON, no DUMPSCREEN
68 return CompanionFeatures(PARAMETERS|DATE|NAME|FIRMWARE
69 |HELIUM_DIVE|CCR_DIVE|BLUETOOTH|VPM_MODEL|SIGNAL_CHECK);
70 }
71
72 QString OSTC4Operations::firmwareTemplate() const
73 {
74 return "ostc4*.bin";
75 }
76
77 QStringList OSTC4Operations::listPorts() const
78 {
79 return listBluetoothPorts();
80 }
81 #if 0
82 QRegExp OSTC4Operations::portTemplate() const
83 {
84 #if defined(Q_OS_MAC)
85 return QRegExp("tty[.]OSTC4-.*", Qt::CaseInsensitive);
86 #elif defined(Q_OS_LINUX)
87 // Seems ok for debian, ubuntu, redhat, CentOS and SUSE (google dixit)
88 return QRegExp("ttyUSB.*", Qt::CaseSensitive);
89 #elif defined(Q_OS_WIN)
90 return QRegExp("COM.*", Qt::CaseSensitive);
91 #endif
92 }
93 #endif
94
95
96 QRegularExpression OSTC4Operations::portTemplate() const
97 {
98 #if defined(Q_OS_MAC)
99 return QRegularExpression("tty[.]OSTC4-.*",
100 QRegularExpression::CaseInsensitiveOption);
101 #elif defined(Q_OS_LINUX)
102 // Debian, Ubuntu, RedHat, CentOS, SUSE
103 return QRegularExpression("ttyUSB.*"); // default: case-sensitive
104 #elif defined(Q_OS_WIN)
105 return QRegularExpression("COM.*"); // default: case-sensitive
106 #endif
107 }
108 //////////////////////////////////////////////////////////////////////////////
109
110 void OSTC4Operations::getIdentity()
111 {
112 descriptionString.clear();
113
114 LOG_TRACE("Getting model...");
115 HardwareDescriptor hw = hardwareDescriptor();
116 if( hw != HW_OSTC4 )
117 LOG_THROW("Not an OSTC4.");
118
119 LOG_TRACE("Getting identity...");
120 getCommonIdentity();
121
122 //---- Main firmware -----------------------------------------------------
123 QString mainFW;
124 {
125 unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
126 if( echo != 'k' )
127 LOG_THROW("Bad firmware details.");
128
129 unsigned char reply[4+1];
130 _serial.writeByte(0xFF); // Main firmware
131 _serial.readBlock(reply, sizeof reply);
132 if( reply[4] != 0x4D )
133 LOG_THROW("Bad main firmware reply.");
134
135 mainFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
136 }
137
138 //---- RTE firmware ------------------------------------------------------
139 QString rteFW;
140 {
141 unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
142 if( echo != 'k' )
143 LOG_THROW("Bad firmware details.");
144
145 unsigned char reply[4+1];
146 _serial.writeByte(0xFE); // RTE firmware
147 _serial.readBlock(reply, sizeof reply);
148 if( reply[4] != 0x4D )
149 LOG_THROW("Bad RTE firmware reply.");
150
151 rteFW = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
152 }
153
154 //---- Font Package ------------------------------------------------------
155 QString fontPKG;
156 {
157 unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
158 if( echo != 'k' )
159 LOG_THROW("Bad firmware details.");
160
161 unsigned char reply[4+1];
162 _serial.writeByte(0x10); // Font Package
163 _serial.readBlock(reply, sizeof reply);
164 if( reply[4] != 0x4D )
165 LOG_THROW("Bad Font package reply.");
166
167 fontPKG = QString::asprintf("%d.%02d.%02d%s", reply[0], reply[1], reply[2], reply[3] ? "beta" : "");
168 }
169
170 //---- OSTC4 specific description ----------------------------------------
171 descriptionString = QString("%1 #%2, fw %3 (main %4/RTE %5/Font %6), %7")
172 .arg(model())
173 .arg(serialNumber(), 4, 10, QChar('0'))
174 .arg( QString::asprintf("%d.%02d", firmware() / 100, firmware() % 100))
175 .arg( mainFW )
176 .arg( rteFW )
177 .arg( fontPKG )
178 .arg( QString::fromLatin1((char*)(computerText), 60)
179 .replace(QChar('\0'), "")
180 .trimmed() );
181
182 LOG_INFO("Found " << descriptionString);
183 }
184
185 //////////////////////////////////////////////////////////////////////////////
186
187 OSTC4Operations::OSTC4Operations()
188 : fileChecksum(0,0)
189 {
190 emulatorName = "OSTC4";
191 }
192
193 //////////////////////////////////////////////////////////////////////////////
194
195 void OSTC4Operations::upgradeFW(const QString &fileName)
196 {
197 LOG_INFO("Starting OSTC4 firmware upgrade...");
198
199 openFirmware(fileName, true);
200 openFirmware(fileName, false);
201
202 LOG_INFO("Please wait for OSTC4 to complete installation...");
203 }
204
205 //////////////////////////////////////////////////////////////////////////////
206 //////////////////////////////////////////////////////////////////////////////
207 /// FIRMWARE UPGRADE ///
208 //////////////////////////////////////////////////////////////////////////////
209 //////////////////////////////////////////////////////////////////////////////
210
211 #define SWAP4BYTES(x) \
212 uint( (((x) & 0x000000FF) << 24) \
213 | (((x) & 0x0000FF00) << 8) \
214 | (((x) & 0x00FF0000) >> 8) \
215 | (((x) & 0xFF000000) >> 24))
216
217 //////////////////////////////////////////////////////////////////////////////
218
219 void OSTC4Operations::openFirmware(const QString &fileName, bool dryRun)
220 {
221 LOG_TRACE("Opening OSTC4 firmware '" << fileName << "'...");
222
223 bool forceFirmwareUpdate;
224 bool forceRTEUpdate;
225 bool forceFontlibUpdate;
226 bool useFastMode;
227 bool firmwareSupportFastMode = false;
228 int size;
229 int blockSize;
230 int transferDelay;
231
232 // Previous trial failed ?
233 if( _file.isOpen() ) _file.close();
234
235 _file.setFileName(fileName);
236 if( ! _file.open(QIODevice::ReadOnly) )
237 LOG_THROW( "Cannot open BIN file " << fileName );
238
239
240 // Get settings to overwrite version check
241 forceFirmwareUpdate = settings->value("OSTC/forceFirmwareUpdate", false).toBool();
242 forceRTEUpdate = settings->value("OSTC/forceRTEUpdate", false).toBool();
243 forceFontlibUpdate = settings->value("OSTC/forceFontlibUpdate", false).toBool();
244
245 useFastMode = settings->value("OSTC/useFastMode", false).toBool();
246
247 //---- Check consistency -------------------------------------------------
248 fileChecksum.reset(0, 0);
249 for(int part=1; part<4; ++part)
250 {
251 // Less than 3 parts ?
252 if( (_file.size() - _file.pos()) < 16 )
253 break;
254
255 if( dryRun )
256 LOG_TRACE("Checking part " << part <<"...");
257
258 FirmwareOSTC4 header;
259 QByteArray bin;
260 loadFirmwarePart(header, bin, part, dryRun);
261
262 QString name = QString::asprintf("%s v%d.%02d.%02d%s",
263 header.type==0x10 ? "Font"
264 : header.type==0xFF ? "FW "
265 : header.type==0xFE ? "RTE "
266 : "??? ",
267 header.version.x, header.version.y, header.version.z,
268 header.version.beta ? " beta" : "");
269
270 //---- On first pass, just check file structure ----------------------
271 if( dryRun )
272 continue;
273
274 //---- On second pass, do upload new stuff ---------------------------
275 if( _connectMode != SERVICE_MODE )
276 connectServiceMode();
277 if( _connectMode != SERVICE_MODE )
278 LOG_THROW("Cannot connect OSTC4 service mode...");
279
280 //---- Check if needed ? ---------------------------------------------
281 LOG_TRACE("Check part " << part << " is needed ?");
282 unsigned char echo = retryCommand(_serial, 'k'); // 0x69 OSTC4 FW Details.
283 if( echo != 'k' )
284 LOG_THROW("Bad firmware details.");
285
286 unsigned char reply[4+1];
287 _serial.writeByte(header.type); // type of firmware part
288 _serial.readBlock(reply, sizeof reply);
289 if( reply[4] != 0x4C ) // SERVICE MODE only.
290 LOG_THROW("Bad firmware reply.");
291
292 if(((reply[0] *100 + reply[1] * 10 + reply[2]) > 151 ) && (header.type==0xFF)) /* suitable firmware version? First supported in 1.5.2 */
293 {
294 firmwareSupportFastMode = true;
295 }
296
297 if(( header.version.x == reply[0]
298 && header.version.y == reply[1]
299 && header.version.z == reply[2] )
300 && (!(forceFirmwareUpdate && header.type==0xFF))
301 && (!(forceRTEUpdate && header.type==0xFE))
302 && (!(forceFontlibUpdate && header.type==0x10))
303 )
304 {
305 LOG_INFO(" part " << part << ": " << name << " already up-to-date.");
306 continue;
307 }
308 if((useFastMode) && (firmwareSupportFastMode)) /* is fast mode supported ? */
309 {
310 blockSize = FIRMWARE_BLOCK_FAST;
311 transferDelay = 1;
312 }
313 else {
314 blockSize = FIRMWARE_BLOCK;
315 transferDelay = FIRMWARE_BLOCK_DELAY;
316 }
317 //---- Upload firwmare -----------------------------------------------
318 const int steps = 3 + (bin.size()+blockSize-1) / blockSize;
319 int step = 0;
320 PROGRESS(step++, steps);
321
322 LOG_INFO(" uploading part " << part
323 << ": " << name
324 << "...");
325 writeText( QString("Upload Part %1").arg(part) );
326
327 //---- Command
328 PROGRESS(step++, steps);
329 echo = retryCommand(_serial, 's'); // 0x73 OSTC4 FW Upgrade
330 if( echo != 's' )
331 LOG_THROW("Bad OSTC4 FW upgrade commande.");
332
333 //---- Header
334 PROGRESS(step++, steps);
335 _serial.writeBlock((uchar*)&header, sizeof header);
336 _serial.sleep(500);
337 //---- Data
338 for(int len = 0x00000; len < bin.size(); len += blockSize)
339 {
340 if(transferDelay)
341 {
342 _serial.sleep(transferDelay); // 15 msec between 64B blocks...
343 }
344 PROGRESS(step++, steps);
345
346 size = qMin(blockSize,
347 bin.size() - len);
348
349 LOG_TRACE("Fill " << size);
350 _serial.writeBlock((uchar*)bin.data()+len, (unsigned int)size);
351 }
352
353 PROGRESS(steps, steps); // 100%
354
355 //---- Wait acknowledge ----------------------------------------------
356 // RTE seems to miss the acknowledge byte ...
357 if( header.type != 0xFE ) {
358 PROGRESS_THROTTLE();
359
360 for(int wait=0; wait<2*60; ++wait) // Up to 2 min...
361 {
362 try {
363 unsigned char reply = _serial.readByte();
364 if( reply == 0x4C )
365 break;
366 }
367 catch( const ReadTimeout&) {}
368 }
369 }
370
371 LOG_INFO(" part " << part << ": " << name << " uploaded.");
372 }
373
374 //---- Done --------------------------------------------------------------
375 // Low-level close, to avoid trying to send a 0xFF byte...
376 if( !dryRun ) {
377 _serial.close();
378 _connectMode = CLOSED_MODE;
379 }
380
381 PROGRESS_RESET();
382
383 //---- Check FILE checksum on first pass ---------------------------------
384 if( dryRun ) {
385 uint expected;
386 if( 4 != _file.read((char*)&expected, sizeof expected) )
387 LOG_THROW("Missing file checksum.");
388
389 uint checksum = fileChecksum.a() | (fileChecksum.b() << 16);
390
391 if( checksum != expected )
392 LOG_ERROR( QString::asprintf("File checksum ERROR: expected = %04X, actual = %04X", expected, checksum) );
393 else if( dryRun )
394 LOG_TRACE( QString::asprintf("File checksum = %04X (OK)", checksum) );
395
396 if( ! _file.atEnd() )
397 LOG_THROW("Extra data after last block.");
398 }
399
400 _file.close();
401 }
402
403 //////////////////////////////////////////////////////////////////////////////
404
405 void OSTC4Operations::loadFirmwarePart(FirmwareOSTC4& header,
406 QByteArray& bin,
407 int part,
408 bool dryRun)
409 {
410 memset(&header, 0, sizeof header);
411
412 //---- Read part header and check consistency ----------------------------
413 if( sizeof header != _file.read((char*)&header, sizeof header) )
414 LOG_THROW("EOF in header.");
415 fileChecksum.add(&header, sizeof header);
416
417 uint checksum = SWAP4BYTES(header.length)
418 + SWAP4BYTES(*(uint*)&header.type);
419 if( checksum != header.checksum )
420 LOG_THROW( QString::asprintf("Inconsistent header (%04X != %04X).", checksum, header.checksum) );
421
422 if( SWAP4BYTES(header.length) > (1024 * 1024) || (SWAP4BYTES(header.length) % 4) )
423 LOG_THROW("Inconsistent header (part=" << part << ", size=" << SWAP4BYTES(header.length) << ").");
424
425 if( dryRun )
426 switch( header.type ) {
427 case 0x10: LOG_TRACE("... font"); break;
428 case 0xFF: LOG_TRACE("... firmware"); break;
429 case 0xFE: LOG_TRACE("... RTE"); break;
430 default:
431 // LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf(int(header.type), 16) << ").");
432 LOG_THROW("Inconsistent header (part=" << part << ", type=0x" << QString::asprintf("Value = %d", header.type) << ").");
433 break;
434 }
435
436 if( dryRun )
437 LOG_TRACE("... version " << QString::asprintf("%d.%02d.%02d.%s",
438 header.version.x, header.version.y, header.version.z,
439 header.version.beta ? " beta" : ""));
440
441 //---- Read Binary Data --------------------------------------------------
442 if( dryRun )
443 LOG_TRACE("... size " << SWAP4BYTES(header.length) << " bytes.");
444 bin.resize(SWAP4BYTES(header.length) + 4);
445 assert((uint)bin.size() == SWAP4BYTES(header.length) + 4);
446
447 if( bin.size() != _file.read(bin.data(), bin.size()) )
448 LOG_THROW("EOF in data.");
449
450 if( dryRun )
451 LOG_TRACE("... checksum " << QString::asprintf("%08x",
452 *(uint*)(bin.data() + SWAP4BYTES(header.length))));
453 fileChecksum.add(bin);
454 }
455
456 void OSTC4Operations::getSignal()
457 {
458 char buffer[60];
459 memset(buffer, 0, sizeof buffer);
460 unsigned char echo = retryCommand(_serial, 'l'); // 0x6c request bluetooth signal
461 if( echo != 0x6C )
462 LOG_THROW("Bad text reply (1)" << echo);
463
464 _serial.sleep(1000);
465
466 unsigned char ok = _serial.readByte();
467 if( ok != 0x4D ) // DOWNLOAD mode only.
468 LOG_THROW("Bad clock reply (2)");
469 }
470
471
472 void OSTC4Operations::getAllHeader(unsigned char* pBuffer)
473 {
474 unsigned char index = 0;
475 char buffer[60];
476 memset(buffer, 0, sizeof buffer);
477
478 if( _connectMode != SERVICE_MODE )
479 connectServiceMode();
480 if( _connectMode != SERVICE_MODE )
481 LOG_THROW("Cannot connect OSTC4 service mode...");
482
483
484 unsigned char echo = retryCommand(_serial, 0x85); // 0x85 request all headers (dump)
485 if( echo != 0x85 )
486 LOG_THROW("Bad text reply (1)" << echo);
487
488 _serial.sleep(1000);
489
490 for(index = 0; index < 8; index++) // Expect 8 blocks a 0x8000
491 {
492 _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
493 LOG_INFO(("."));
494 }
495 *(pBuffer + (index * 0x8000)) = _serial.readByte(); // get lastdiveID
496 LOG_INFO(QString::asprintf("%d",*(pBuffer + (index * 0x8000))));
497 unsigned char ok = _serial.readByte();
498 if( ok != 0x4C ) //Service mode only.
499 LOG_THROW("Bad clock reply (2)");
500 }
501
502 void OSTC4Operations::writeAllHeader(unsigned char* pBuffer)
503 {
504 unsigned char index = 0;
505 char buffer[60];
506 memset(buffer, 0, sizeof buffer);
507
508 if( _connectMode != SERVICE_MODE )
509 connectServiceMode();
510 if( _connectMode != SERVICE_MODE )
511 LOG_THROW("Cannot connect OSTC4 service mode...");
512
513
514 unsigned char echo = retryCommand(_serial, 0x86); // 0x86 request all headers (dump)
515 if( echo != 0x86 )
516 LOG_THROW("Bad text reply (1)" << echo);
517
518 _serial.sleep(1000);
519
520 for(index = 0; index < 8; index++) // Expect 8 blocks a 0x8000
521 {
522 _serial.writeBlock(pBuffer + (index * 0x8000), 0x8000);
523 LOG_INFO(("."));
524 }
525 _serial.writeByte(*(pBuffer + (index * 0x8000))); // write lastdiveID
526
527 unsigned char ok = _serial.readByte();
528 if( ok != 0x4C ) //Service mode only.
529 LOG_THROW("Bad clock reply (2)");
530 }
531
532 void OSTC4Operations::getAllSamples(unsigned char* pBuffer)
533 {
534 unsigned short index = 0;
535 char buffer[60];
536 memset(buffer, 0, sizeof buffer);
537
538 if( _connectMode != SERVICE_MODE )
539 connectServiceMode();
540 if( _connectMode != SERVICE_MODE )
541 LOG_THROW("Cannot connect OSTC4 service mode...");
542
543
544 unsigned char echo = retryCommand(_serial, 0x88); // 0x88 request all samples (dump)
545 if( echo != 0x88 )
546 LOG_THROW("Bad text reply (1)" << echo);
547
548 _serial.sleep(500);
549
550 for(index = 0; index < 384; index++) // Expect 8 blocks a 0x8000
551 {
552 _serial.readBlock(pBuffer + (index * 0x8000), 0x8000);
553 LOG_INFO(QString::asprintf("%d",index));
554 }
555 _serial.readBlock(pBuffer + (index * 0x8000), 4); // get lastdiveID
556 unsigned char ok = _serial.readByte();
557 if( ok != 0x4C ) //Service mode only.
558 LOG_THROW("Bad clock reply (2)");
559 }
560
561
562 void OSTC4Operations::writeAllSamples(unsigned char* pBuffer)
563 {
564 unsigned short index = 0;
565 char buffer[60];
566 memset(buffer, 0, sizeof buffer);
567
568 if( _connectMode != SERVICE_MODE )
569 connectServiceMode();
570 if( _connectMode != SERVICE_MODE )
571 LOG_THROW("Cannot connect OSTC4 service mode...");
572
573
574 unsigned char echo = retryCommand(_serial, 0x89); // 0x88 request all samples (dump)
575 if( echo != 0x89 )
576 LOG_THROW("Bad text reply (1)" << echo);
577
578 _serial.sleep(500);
579
580 for(index = 0; index < 384; index++) // Expect 8 blocks a 0x8000 384
581 {
582 _serial.writeBlock((const unsigned char*)(pBuffer + (index * 0x8000)), 0x8000);
583 LOG_INFO(QString::asprintf("%d",index));
584 }
585 _serial.writeBlock(pBuffer + (index * 0x8000), 4); // set lastdiveID
586 unsigned char ok = _serial.readByte();
587 if( ok != 0x4C ) //Service mode only.
588 LOG_THROW("Bad clock reply (2)");
589 }
590
591 //////////////////////////////////////////////////////////////////////////////