comparison OSTCFrogOperations.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
comparison
equal deleted inserted replaced
0:76ccd6ce50c0 1:0b3630a29ad8
1 //////////////////////////////////////////////////////////////////////////////
2 /// \file OSTCFrogOperations.cpp
3 /// \brief Implementing various operations for H&W Frog dive computer
4 /// \author JD Gascuel.
5 ///
6 /// \copyright (c) 2011-2016 JD Gascuel. All rights reserved.
7 /// $Id$
8 //////////////////////////////////////////////////////////////////////////////
9 //
10 // BSD 2-Clause License:
11 //
12 // Redistribution and use in source and binary forms, with or without
13 // modification, are permitted provided that the following conditions
14 // are met:
15 //
16 // 1. Redistributions of source code must retain the above copyright notice,
17 // this list of conditions and the following disclaimer.
18 //
19 // 2. Redistributions in binary form must reproduce the above copyright notice,
20 // this list of conditions and the following disclaimer in the documentation
21 // and/or other materials provided with the distribution.
22 //
23 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33 // THE POSSIBILITY OF SUCH DAMAGE.
34 //
35 //////////////////////////////////////////////////////////////////////////////
36
37 #include "OSTCFrogOperations.h"
38
39 #include "Utils/Log.h"
40 #include "Utils/Exception.h"
41
42 #include "HexFile.h"
43 #include "SettingsDialog.h"
44
45 #include <QApplication>
46 #include <QDateTime>
47 #include <QProgressBar>
48 #include <QStringList>
49
50 // Byte extration, compatible littleendian or bigendian.
51 #define LOW(x) ((unsigned char)((x) % 256))
52 #define HIGH(x) ((unsigned char)((x / (1<<8)) % 256))
53 #define UPPER(x) ((unsigned char)((x / (1<<16)) % 256))
54 #define UP32(x) ((unsigned char)((x / (1<<24)) % 256))
55
56 #define IMAGE_ROUNDING 1024
57 #define FIRMWARE_AREA 0x3E0000
58 #define FIRMWARE_SIZE 0x01D000
59
60 extern QProgressBar* progress;
61
62 //////////////////////////////////////////////////////////////////////////////
63
64 OSTCFrogOperations::OSTCFrogOperations()
65 : _firmware(0),
66 _serialNumber(0),
67 _isOpen(false),
68 _commandMode(false)
69 {
70 }
71
72 OSTCFrogOperations::~OSTCFrogOperations()
73 {
74 if( _isOpen )
75 disconnect(true);
76 }
77
78 //////////////////////////////////////////////////////////////////////////////
79 /// ///
80 /// PORT management ///
81 /// ///
82 //////////////////////////////////////////////////////////////////////////////
83
84 //QRegExp OSTCFrogOperations::portTemplate() const
85 QRegularExpression OSTCFrogOperations::portTemplate() const
86 {
87 #if defined(Q_OS_MAC)
88 return QRegExp("tty[.]frog.*", Qt::CaseInsensitive);
89 #elif defined(Q_OS_LINUX)
90 // Seems ok for debian, ubuntu, and SUSE (google dixit).
91 // Obviously, needs the rfcomm package. "hcitool scan" or lsusb to have
92 // a list of connected stuff...
93 return QRegExp("rfcomm.*", Qt::CaseInsensitive);
94 #elif defined(Q_OS_WIN)
95 // return QRegExp("COM.*", Qt::CaseSensitive);
96 return QRegularExpression(
97 "COM([0-9]+)",
98 QRegularExpression::CaseInsensitiveOption
99 );
100 #endif
101 }
102
103 QStringList OSTCFrogOperations::listPorts() const
104 {
105 return listBluetoothPorts();
106 }
107
108 QString OSTCFrogOperations::firmwareTemplate() const
109 {
110 return "*frog.firmware.hex";
111 }
112
113 QString OSTCFrogOperations::model() const
114 {
115 return "Frog";
116 }
117
118 QString OSTCFrogOperations::description()
119 {
120 return _description;
121 }
122
123 QImage OSTCFrogOperations::dumpScreen() const
124 {
125 LOG_THROW("Not implemented...");
126 return QImage();
127 }
128
129 HardwareOperations::CompanionFeatures OSTCFrogOperations::supported() const
130 {
131 // No ICON, no PARAMETER, no FIRMWARE, no DUMPSCREEN yet...
132 return CompanionFeatures(NAME|DATE);
133 }
134
135 bool OSTCFrogOperations::connect()
136 {
137 try {
138 //---- Open the serial port-------------------------------------------
139 LOG_TRACE( QString("Open port %1...").arg(Settings::port) );
140 _isOpen = false;
141
142 //---- Execute the start protocol ------------------------------------
143 static char animation[] = "/-\\|";
144
145 int reply = 0;
146 for(int retry=0; retry < 30; ++retry) {
147 _serial.open( Settings::port, "Frog");
148 _serial.sleep(100);
149 _serial.purge();
150 _serial.writeByte(0xBB);
151 try {
152 reply = _serial.readByte();
153 if( reply == 0x4D )
154 break;
155 }
156 catch(...) {}
157 LOG_TRACE( QString("Starting... %1").arg(animation[retry % sizeof animation]));
158 }
159 if( reply != 0x4D )
160 LOG_THROW("Not started");
161
162 //---- Enquire about Frog id -----------------------------------------
163 getIdentity();
164
165 //---- Everything is ok ----------------------------------------------
166 _isOpen = true;
167 return true;
168 }
169 catch(const Exception& e) {
170 _serial.close();
171 LOG_THROW("Cannot connect " << Settings::port << ": " << e.what());
172 }
173 _isOpen = false;
174 return false;
175 }
176
177 void OSTCFrogOperations::connectServiceMode()
178 {
179 connect();
180 }
181
182 bool OSTCFrogOperations::disconnect(bool /*closing*/)
183 {
184 if( !_isOpen ) return false;
185
186 _serial.purge();
187 _serial.writeByte(0xFF);
188 _serial.sleep(100);
189
190 _serial.purge();
191 _serial.close();
192
193 _description.clear(); // cleanup for interface updateStatus()
194 _isOpen = false;
195
196 return true;
197 }
198
199 //////////////////////////////////////////////////////////////////////////////
200 /// ///
201 /// LOW Level commands ///
202 /// ///
203 //////////////////////////////////////////////////////////////////////////////
204
205 void OSTCFrogOperations::beginCommands()
206 {
207 Q_ASSERT( !_commandMode );
208 static char animation[] = "/-\\|";
209 for(int i=0;; ++i)
210 {
211 if( i == 100 ) // 20.0 sec loop ?
212 LOG_THROW("Bad reply to open command");
213
214 _serial.sleep(100);
215 _serial.purge();
216 _serial.writeByte(0xAA); // Start byte
217
218 int reply = 0;
219 try {
220 reply = _serial.readByte();
221 } catch(...) {}
222 if( reply == 0x4B )
223 goto Started;
224
225 LOG_TRACE(QString("Connecting %1")
226 .arg(animation[i%4]));
227 _serial.sleep(200);
228 continue;
229
230 Started:
231 unsigned char buffer[] = "\xAA\xAB\xAC";
232 _serial.writeBlock(buffer, 3);
233
234 try {
235 unsigned char reply = _serial.readByte();
236 if( reply == 0x4C ) {
237 _commandMode = true;
238 return;
239 }
240 } catch(...) {}
241
242 _serial.sleep(200);
243 }
244 }
245
246 //////////////////////////////////////////////////////////////////////////////
247
248 void OSTCFrogOperations::endCommands()
249 {
250 Q_ASSERT( _commandMode );
251
252 _serial.sleep(100);
253 _serial.purge();
254 _serial.writeByte(0xFF); // Exit service mode
255 _serial.sleep(10);
256
257 unsigned char buffer = _serial.readByte();
258 if( buffer != 0xFF )
259 LOG_THROW("End failed");
260
261 _commandMode = false;
262 disconnect();
263 }
264
265 //////////////////////////////////////////////////////////////////////////////
266
267 void OSTCFrogOperations::eraseRange(unsigned int addr, unsigned int size)
268 {
269 Q_ASSERT( _commandMode );
270
271 // Convert size to number of pages, rounded up.
272 size = ((size + 4095) / 4096);
273 if( size < 256 || addr != 0x300000 )
274 {
275 unsigned char buffer[4];
276 // Erase just the needed pages.
277 buffer[0] = UPPER(addr);
278 buffer[1] = HIGH(addr);
279 buffer[2] = LOW(addr);
280 buffer[3] = LOW(size);
281 _serial.writeByte(0x42); // Command
282 _serial.sleep(10);
283
284 _serial.writeBlock(buffer, 4);
285 // Wait (120/4)ms by block of 4K, plus 3% VAT to be sure.
286 _serial.sleep(40 + size * 31);
287 }
288 else
289 {
290 // Erase the whole 512KB of icon memory...
291 _serial.writeByte(0x41);
292 _serial.sleep(3000);
293 }
294
295 try {
296 (void)_serial.readByte();
297 } catch(...) {}
298 }
299
300 //////////////////////////////////////////////////////////////////////////////
301
302 void OSTCFrogOperations::startWrite(unsigned int addr)
303 {
304 Q_ASSERT( _commandMode );
305
306 unsigned char buffer[3];
307 buffer[0] = UPPER(addr);
308 buffer[1] = HIGH(addr);
309 buffer[2] = LOW(addr);
310
311 _serial.writeByte(0x30);
312 _serial.sleep(10);
313
314 _serial.writeBlock(buffer, 3);
315 _serial.sleep(10);
316 }
317
318 //////////////////////////////////////////////////////////////////////////////
319
320 void OSTCFrogOperations::stopWrite()
321 {
322 Q_ASSERT( _commandMode );
323
324 _serial.flush();
325 _serial.sleep(200); // Should be > 100ms.
326
327 unsigned char reply = _serial.readByte();
328 if( reply != 0x4C )
329 LOG_THROW("stopWrite");
330 }
331
332 //////////////////////////////////////////////////////////////////////////////
333
334 void OSTCFrogOperations::readBytes(unsigned int addr,
335 unsigned char* ptr,
336 unsigned int size)
337 {
338 Q_ASSERT( _commandMode );
339
340 unsigned char buffer[6];
341 buffer[0] = UPPER(addr);
342 buffer[1] = HIGH(addr);
343 buffer[2] = LOW(addr);
344 buffer[3] = UPPER(size);
345 buffer[4] = HIGH(size);
346 buffer[5] = LOW(size);
347
348 _serial.writeByte(0x20);
349 _serial.sleep(10);
350
351 _serial.writeBlock(buffer, 6);
352 _serial.sleep(10);
353
354 unsigned int len = _serial.readBlock(ptr, size);
355 if( len < size )
356 LOG_THROW("readBytes too short");
357
358 unsigned char reply = _serial.readByte();
359 if( reply != 0x4C )
360 LOG_THROW("readBytes");
361 }
362
363 //////////////////////////////////////////////////////////////////////////////
364 /// ///
365 /// HIGH Level commands ///
366 /// ///
367 //////////////////////////////////////////////////////////////////////////////
368
369 void OSTCFrogOperations::getIdentity()
370 {
371 //---- get model
372 HardwareDescriptor hw = hardwareDescriptor();
373 if( hw != HW_UNKNOWN_OSTC && hw != HW_Frog )
374 LOG_THROW("Not a Frog.");
375
376 //---- get identity
377 _serial.sleep(100); // Make sure last command is finished.
378 _serial.purge();
379 _serial.writeByte('i'); // 0x63
380
381 unsigned char buffer[1+2+2+13+1] = {0};
382 unsigned len = _serial.readBlock(buffer, sizeof buffer);
383
384 if( len != sizeof buffer || buffer[0] != 'i' || buffer[18] != 0x4D )
385 LOG_THROW("get identity data");
386
387 _serialNumber = buffer[1] + buffer[2]*256;
388 _firmware = buffer[3]*256 + buffer[4];
389
390 _description = QString("%1 #%2, v%3.%4, %5")
391 .arg(model())
392 .arg(_serialNumber, 4, 10, QChar('0'))
393 .arg(_firmware / 256).arg(_firmware % 256)
394 .arg( QString::fromLatin1((char*)buffer+5, 13)
395 .replace(QChar('\0'), "")
396 .trimmed() );
397
398 LOG_TRACE("Found " << _description);
399 }
400
401 //////////////////////////////////////////////////////////////////////////////
402
403 void OSTCFrogOperations::writeText(const QString& _msg)
404 {
405 // Pad to 15 chars:
406 QByteArray ascii = (_msg + QString(15, QChar(' '))).left(15).toLatin1();
407
408 _serial.sleep(100); // Make sure last command is finished.
409 _serial.purge();
410 _serial.writeByte('n'); // 0x6E
411
412 unsigned char reply = _serial.readByte();
413 if( reply != 'n' )
414 LOG_THROW("message start");
415
416 _serial.writeBlock((unsigned char *)ascii.constData(), 15);
417 reply = _serial.readByte();
418 if( reply != 0x4D )
419 LOG_THROW("message end");
420 }
421
422 //////////////////////////////////////////////////////////////////////////////
423
424 void OSTCFrogOperations::setDate(const QDateTime &date)
425 {
426 unsigned char buffer[6];
427 buffer[0] = date.time().hour();
428 buffer[1] = date.time().minute();
429 buffer[2] = date.time().second();
430 buffer[3] = date.date().month();
431 buffer[4] = date.date().day();
432 buffer[5] = date.date().year() % 100;
433
434 _serial.sleep(100); // Make sure last command is finished.
435 _serial.purge();
436 _serial.writeByte('b'); // 0x62
437
438 unsigned char reply = _serial.readByte();
439 if( reply != 'b' )
440 LOG_THROW("sync time");
441
442 _serial. writeBlock( buffer, sizeof buffer);
443 reply = _serial.readByte();
444 if( reply != 0x4D )
445 LOG_THROW("sync time end");
446
447 writeText("Set " + date.toString("MM/dd hh:mm"));
448 }
449
450 //////////////////////////////////////////////////////////////////////////////
451
452 QSize OSTCFrogOperations::nameSize() const
453 {
454 return QSize(13, 1);
455 }
456
457 //////////////////////////////////////////////////////////////////////////////
458
459 void OSTCFrogOperations::setName(const QString &newName)
460 {
461 QByteArray padded = (newName+QString(13, QChar(' '))).left(13).toLatin1();
462
463 _serial.sleep(100); // Make sure last command is finished.
464 _serial.purge();
465 _serial.writeByte('c'); // 0x63
466
467 unsigned char reply = _serial.readByte();
468 if( reply != 'c' )
469 LOG_THROW("set custom text");
470
471 _serial.writeBlock((unsigned char*)padded.constData(), 13);
472 reply = _serial.readByte();
473 if( reply != 0x4D )
474 LOG_THROW("custom text end");
475
476 // Re-read new name:
477 getIdentity();
478 writeText(customText());
479 }
480
481 //////////////////////////////////////////////////////////////////////////////
482
483 void OSTCFrogOperations::setIcons(const QString &/*fileName*/)
484 {
485 // beginCommands();
486 // eraseRange(0x000000, 0x00000);
487 // startWrite(0x000000);
488 // stopWrite();
489 // endCommands();
490
491 LOG_THROW( "Set icons: Not yet implemented." );
492 }
493
494 int OSTCFrogOperations::firmware() const
495 {
496 return _firmware;
497 }
498
499 int OSTCFrogOperations::serialNumber() const
500 {
501 return _serialNumber;
502 }
503
504 QString OSTCFrogOperations::customText() const
505 {
506 return _description.section(',', 2).trimmed();
507 }
508
509 ///////////////////////////////////////////////////////////////////////////////
510
511 static unsigned char frogSecretKey[16] = {
512 111, 85, 190, 69,
513 108,254, 242, 19,
514 231, 49, 248,255,
515 233, 48, 176,241
516 };
517
518 void OSTCFrogOperations::loadFirmware(HexFile &hex, const QString &fileName) const
519 {
520 hex.allocate(FIRMWARE_SIZE);
521 hex.loadEncrypted(fileName, frogSecretKey);
522 }
523
524 ///////////////////////////////////////////////////////////////////////////////
525
526 void OSTCFrogOperations::upgradeFW(const QString &fileName)
527 {
528 try {
529 //---- Load and check firmware ---------------------------------------
530 LOG_TRACE("Loading firmware...");
531
532 HexFile hex;
533 loadFirmware(hex, fileName);
534 unsigned int checksum = hex.checksum();
535
536 beginCommands();
537 writeText("Frog Companion");
538 getIdentity();
539
540 unsigned char buffer[5];
541 buffer[0] = LOW(checksum);
542 buffer[1] = HIGH(checksum);
543 buffer[2] = UPPER(checksum);
544 buffer[3] = UP32(checksum);
545
546 // Compute magic checksum's checksum.
547 buffer[4] = 0x55;
548 buffer[4] ^= buffer[0]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
549 buffer[4] ^= buffer[1]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
550 buffer[4] ^= buffer[2]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
551 buffer[4] ^= buffer[3]; buffer[4] =(buffer[4]<<1 | buffer[4]>>7);
552
553 _serial.sleep(100); // Make sure last command is finished.
554 _serial.purge();
555 _serial.writeByte('P'); // 0x50
556
557 unsigned char reply = _serial.readByte();
558 if( reply != 'P' )
559 LOG_THROW( "Programming start" );
560
561 _serial.writeBlock(buffer, sizeof buffer);
562 _serial.sleep(4000);
563
564 // NOTE: the device never return, because it always to a reset,
565 // with ot without reprogramming...
566 _serial.close();
567 _isOpen = false;
568 }
569 catch(const Exception& e) {
570 LOG_TRACE(QString("Cannot upgrade: <font color='red'>%1</font>")
571 .arg(e.what()));
572
573 // Unknown state: so make a hard cleanup:
574 _commandMode = false;
575 _isOpen = false;
576 _serial.close();
577 }
578 }