Use PROTOCOL_TELEMETRY_MULTIMODULE for internal multi when available (#7147)
[opentx.git] / companion / src / radiointerface.cpp
blob63befdd0ca9af0700250b9196c7dcde8ff2fffcb
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "radiointerface.h"
22 #include "appdata.h"
23 #include "eeprominterface.h"
24 #include "process_flash.h"
25 #include "radionotfound.h"
26 #include "burnconfigdialog.h"
27 #include "helpers.h"
28 #include "process_copy.h"
29 #include "storage.h"
30 #include "progresswidget.h"
32 QString getRadioInterfaceCmd()
34 burnConfigDialog bcd;
35 Board::Type board = getCurrentBoard();
36 if (IS_STM32(board)) {
37 return bcd.getDFU();
39 else if (IS_SKY9X(board)) {
40 return bcd.getSAMBA();
42 else {
43 return bcd.getAVRDUDE();
47 QStringList getAvrdudeArgs(const QString & cmd, const QString & filename)
49 QStringList args;
50 burnConfigDialog bcd;
51 QString programmer = bcd.getProgrammer();
52 QString mcu = bcd.getMCU();
53 Board::Type board = getCurrentBoard();
55 args << "-c" << programmer << "-p";
56 if (IS_2560(board))
57 args << "m2560";
58 else if (board == Board::BOARD_9X_M128)
59 args << "m128";
60 else
61 args << mcu;
63 args << bcd.getAvrdudeArgs();
65 QString fullcmd = cmd + filename;
66 if (QFileInfo(filename).suffix().toUpper() == "HEX")
67 fullcmd += ":i";
68 else if (QFileInfo(filename).suffix().toUpper()=="BIN")
69 fullcmd += ":r";
70 else
71 fullcmd += ":a";
73 args << "-U" << fullcmd;
75 return args;
78 QStringList getDfuArgs(const QString & cmd, const QString & filename)
80 QStringList args;
81 burnConfigDialog bcd;
82 args << bcd.getDFUArgs();
83 if (!filename.endsWith(".dfu"))
84 args << "--dfuse-address" << "0x08000000";
85 if (cmd == "-U")
86 args.last().append(":" % QString::number(Boards::getFlashSize(getCurrentBoard())));
87 args << "--device" << "0483:df11";
88 args << "" << cmd % filename;
89 return args;
92 QStringList getSambaArgs(const QString & tcl)
94 QStringList result;
96 QString tclFilename = generateProcessUniqueTempFileName("temp.tcl");
97 if (QFile::exists(tclFilename)) {
98 qunlink(tclFilename);
100 QFile tclFile(tclFilename);
101 if (!tclFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
102 QMessageBox::warning(NULL, CPN_STR_TTL_ERROR,
103 QCoreApplication::translate("RadioInterface", "Cannot write file %1:\n%2.").arg(tclFilename).arg(tclFile.errorString()));
104 return result;
107 QTextStream outputStream(&tclFile);
108 outputStream << tcl;
110 burnConfigDialog bcd;
111 result << bcd.getSambaPort() << bcd.getArmMCU() << tclFilename ;
112 return result;
115 QStringList getReadEEpromCmd(const QString & filename)
117 QStringList result;
118 EEPROMInterface *eepromInterface = getCurrentEEpromInterface();
119 if (IS_STM32(eepromInterface->getBoard())) {
120 // impossible
122 else if (IS_SKY9X(eepromInterface->getBoard())) {
123 result = getSambaArgs(QString("SERIALFLASH::Init 0\n") + "receive_file {SerialFlash AT25} \"" + filename + "\" 0x0 0x80000 0\n");
125 else {
126 result = getAvrdudeArgs("eeprom:r:", filename);
128 return result;
131 QStringList getWriteEEpromCmd(const QString & filename)
133 Board::Type board = getCurrentBoard();
134 if (IS_STM32(board)) {
135 // impossible
136 return QStringList();
138 else if (IS_SKY9X(board)) {
139 return getSambaArgs(QString("SERIALFLASH::Init 0\n") + "send_file {SerialFlash AT25} \"" + filename + "\" 0x0 0\n");
141 else {
142 return getAvrdudeArgs("eeprom:w:", filename);
146 QStringList getWriteFirmwareArgs(const QString & filename)
148 Board::Type board = getCurrentBoard();
149 if (IS_STM32(board)) {
150 return getDfuArgs("-D", filename);
152 else if (board == Board::BOARD_SKY9X) {
153 return getSambaArgs(QString("send_file {Flash} \"") + filename + "\" 0x400000 0\n" + "FLASH::ScriptGPNMV 2\n");
155 else if (board == Board::BOARD_9XRPRO || board == Board::BOARD_AR9X) {
156 return getSambaArgs(QString("send_file {Flash} \"") + filename + "\" 0x400000 0\n" + "FLASH::ScriptGPNMV 2\n");
158 else {
159 return getAvrdudeArgs("flash:w:", filename);
163 QStringList getReadFirmwareArgs(const QString & filename)
165 Board::Type board = getCurrentBoard();
166 if (IS_STM32(board)) {
167 return getDfuArgs("-U", filename);
169 else if (board == Board::BOARD_SKY9X) {
170 return getSambaArgs(QString("receive_file {Flash} \"") + filename + "\" 0x400000 0x40000 0\n");
172 else if (board == Board::BOARD_9XRPRO) {
173 return getSambaArgs(QString("receive_file {Flash} \"") + filename + "\" 0x400000 0x80000 0\n");
175 else {
176 return getAvrdudeArgs("flash:r:", filename);
180 void readAvrdudeFuses(ProgressWidget * progress)
182 burnConfigDialog bcd;
183 QStringList args;
184 args << "-c" << bcd.getProgrammer() << "-p" << bcd.getMCU() << bcd.getAvrdudeArgs() << "-U" << "lfuse:r:-:i" << "-U" << "hfuse:r:-:i" << "-U" << "efuse:r:-:i";
185 FlashProcess flashProcess(bcd.getAVRDUDE(), args, progress);
186 flashProcess.run();
189 void resetAvrdudeFuses(bool eepromProtect, ProgressWidget * progress)
191 //fuses
192 //avrdude -c usbasp -p m64 -U lfuse:w:<0x0E>:m
193 //avrdude -c usbasp -p m64 -U hfuse:w:<0x89>:m 0x81 for eeprom protection
194 //avrdude -c usbasp -p m64 -U efuse:w:<0xFF>:m
196 burnConfigDialog bcd;
197 QMessageBox::StandardButton ret = QMessageBox::No;
198 ret = QMessageBox::warning(NULL, CPN_STR_APP_NAME,
199 QCoreApplication::translate("RadioInterface",
200 "<b><u>WARNING!</u></b>" \
201 "<br>This will reset the fuses of %1 to the factory settings.<br>"
202 "Writing fuses can mess up your radio.<br>Do this only if you are sure they are wrong!<br>"
203 "Are you sure you want to continue?").arg(bcd.getMCU()),
204 QMessageBox::Yes | QMessageBox::No);
205 if (ret == QMessageBox::Yes) {
206 QStringList args = bcd.getAvrdudeArgs();
207 QStringList str;
208 if (bcd.getMCU() == "m2560") {
209 args << "-B8";
210 QString erStr = eepromProtect ? "hfuse:w:0x51:m" : "hfuse:w:0x59:m";
211 str << "-U" << "lfuse:w:0xD7:m" << "-U" << erStr << "-U" << "efuse:w:0xFC:m";
212 //use hfuse = 0x81 to prevent eeprom being erased with every flashing
214 else {
215 QString lfuses;
216 QString tempFile = generateProcessUniqueTempFileName("ftemp.bin");
217 QStringList argread;
218 argread << "-c" << bcd.getProgrammer() << "-p" << bcd.getMCU() << args <<"-U" << "lfuse:r:"+tempFile+":r";
219 FlashProcess flashProcess(bcd.getAVRDUDE(), argread, progress);
220 flashProcess.run();
221 QFile file(tempFile);
222 if (file.exists() && file.size()==1) {
223 file.open(QIODevice::ReadOnly);
224 char bin_flash[1];
225 file.read(bin_flash, 1);
226 if (bin_flash[0]==0x0E) {
227 lfuses = "lfuse:w:0x0E:m";
229 else {
230 lfuses = "lfuse:w:0x3F:m";
232 file.close();
233 qunlink(tempFile);
235 else {
236 lfuses = "lfuse:w:0x3F:m";
239 QString erStr = eepromProtect ? "hfuse:w:0xC1:m" : "hfuse:w:0xC9:m";
240 str << "-U" << lfuses << "-U" << erStr << "-U" << "efuse:w:0xFF:m";
241 //use hfuse = 0x81 to prevent eeprom being erased with every flashing
244 QStringList arguments;
245 if (bcd.getMCU() == "m2560") {
246 arguments << "-c" << bcd.getProgrammer() << "-p" << bcd.getMCU() << args << "-u" << str;
248 else {
249 arguments << "-c" << bcd.getProgrammer() << "-p" << bcd.getMCU() << args << "-B" << "100" << "-u" << str;
251 FlashProcess flashProcess(bcd.getAVRDUDE(), arguments, progress);
252 flashProcess.run();
257 bool readFirmware(const QString & filename, ProgressWidget * progress)
259 bool result = false;
261 QFile file(filename);
262 if (file.exists() && !file.remove()) {
263 QMessageBox::warning(NULL, CPN_STR_TTL_ERROR,
264 QCoreApplication::translate("RadioInterface", "Could not delete temporary file: %1").arg(filename));
265 return false;
268 if (IS_ARM(getCurrentBoard())) {
269 QString path = findMassstoragePath("FIRMWARE.BIN");
270 if (!path.isEmpty()) {
271 qDebug() << "readFirmware: reading" << path << "into" << filename;
272 CopyProcess copyProcess(path, filename, progress);
273 result = copyProcess.run();
277 if (result == false) {
278 qDebug() << "readFirmware: reading" << filename << "with" << getRadioInterfaceCmd() << getReadFirmwareArgs(filename);
279 FlashProcess flashProcess(getRadioInterfaceCmd(), getReadFirmwareArgs(filename), progress);
280 result = flashProcess.run();
283 if (!QFileInfo(filename).exists()) {
284 result = false;
287 return result;
290 bool writeFirmware(const QString & filename, ProgressWidget * progress)
292 if (IS_ARM(getCurrentBoard())) {
293 QString path = findMassstoragePath("FIRMWARE.BIN");
294 if (!path.isEmpty()) {
295 qDebug() << "writeFirmware: writing" << path << "from" << filename;
296 CopyProcess copyProcess(filename, path, progress);
297 return copyProcess.run();
301 qDebug() << "writeFirmware: writing" << filename << "with" << getRadioInterfaceCmd() << getWriteFirmwareArgs(filename);
302 FlashProcess flashProcess(getRadioInterfaceCmd(), getWriteFirmwareArgs(filename), progress);
303 return flashProcess.run();
306 bool readEeprom(const QString & filename, ProgressWidget * progress)
308 Board::Type board = getCurrentBoard();
310 QFile file(filename);
311 if (file.exists() && !file.remove()) {
312 QMessageBox::warning(NULL, CPN_STR_TTL_ERROR,
313 QCoreApplication::translate("RadioInterface", "Could not delete temporary file: %1").arg(filename));
314 return false;
317 if (IS_HORUS(board)) {
318 QString radioPath = findMassstoragePath("RADIO", true);
319 qDebug() << "Searching for SD card, found" << radioPath;
320 if (radioPath.isEmpty()) {
321 QMessageBox::critical(progress, CPN_STR_TTL_ERROR,
322 QCoreApplication::translate("RadioInterface", "Unable to find Horus radio SD card!"));
323 return false;
325 RadioData radioData;
326 Storage inputStorage(radioPath);
327 if (!inputStorage.load(radioData)) {
328 QMessageBox::critical(progress, CPN_STR_TTL_ERROR, inputStorage.error());
329 return false;
331 Storage outputStorage(filename);
332 if (!outputStorage.write(radioData)) {
333 QMessageBox::critical(progress, CPN_STR_TTL_ERROR, outputStorage.error());
334 return false;
337 if (getCurrentBoard() == Board::BOARD_JUMPER_T16 && inputStorage.getBoard() == Board::BOARD_X10) {
338 if (displayT16ImportWarning() == false)
339 return false;
342 else {
343 if (IS_ARM(board)) {
344 QString path = findMassstoragePath("EEPROM.BIN");
345 if (path.isEmpty()) {
346 // On previous OpenTX we called the EEPROM file "TARANIS.BIN" :(
347 path = findMassstoragePath("TARANIS.BIN");
349 if (path.isEmpty()) {
350 // Mike's bootloader calls the EEPROM file "ERSKY9X.BIN" :(
351 path = findMassstoragePath("ERSKY9X.BIN");
353 if (path.isEmpty()) {
354 RadioNotFoundDialog dialog(progress);
355 dialog.exec();
356 return false;
358 CopyProcess copyProcess(path, filename, progress);
359 if (!copyProcess.run()) {
360 return false;
364 if (!IS_STM32(board)) {
365 FlashProcess flashProcess(getRadioInterfaceCmd(), getReadEEpromCmd(filename), progress);
366 if (!flashProcess.run()) {
367 return false;
372 return QFileInfo(filename).exists();
375 bool writeEeprom(const QString & filename, ProgressWidget * progress)
377 Board::Type board = getCurrentBoard();
379 if (IS_ARM(board)) {
380 QString path = findMassstoragePath("EEPROM.BIN");
381 if (path.isEmpty()) {
382 // On previous OpenTX we called the EEPROM file "TARANIS.BIN" :(
383 path = findMassstoragePath("TARANIS.BIN");
385 if (path.isEmpty()) {
386 // Mike's bootloader calls the EEPROM file "ERSKY9X.BIN" :(
387 path = findMassstoragePath("ERSKY9X.BIN");
389 if (!path.isEmpty()) {
390 CopyProcess copyProcess(filename, path, progress);
391 return copyProcess.run();
395 if (!IS_TARANIS(board)) {
396 FlashProcess flashProcess(getRadioInterfaceCmd(), getWriteEEpromCmd(filename), progress);
397 return flashProcess.run();
400 if (IS_ARM(board)) {
401 RadioNotFoundDialog dialog(progress);
402 dialog.exec();
405 return false;
408 #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0))
409 #if defined WIN32 || !defined __GNUC__
410 #include <windows.h>
411 bool isRemovableMedia(const QString & vol)
413 char szDosDeviceName[MAX_PATH];
414 QString volume = vol;
415 UINT driveType = GetDriveType(volume.replace("/", "\\").toLatin1());
416 if (driveType != DRIVE_REMOVABLE)
417 return false;
418 QueryDosDevice(volume.replace("/", "").toLatin1(), szDosDeviceName, MAX_PATH);
419 if (strstr(szDosDeviceName, "\\Floppy") != NULL) { // it's a floppy
420 return false;
422 return true;
424 #else
425 #include "mountlist.h"
426 #endif // defined WIN32 || !defined __GNUC__
427 #endif // (QT_VERSION < QT_VERSION_CHECK(5, 4, 0))
429 QString findMassstoragePath(const QString & filename, bool onlyPath)
431 QString temppath;
432 QString eepromfile;
433 bool found = false;
434 QRegularExpression fstypeRe("^(v?fat|msdos)", QRegularExpression::CaseInsensitiveOption); // Linux: "vfat"; macOS: "msdos"; Win: "FAT32"
435 #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
436 foreach(const QStorageInfo & si, QStorageInfo::mountedVolumes()) {
437 //qDebug() << si.rootPath() << si.name() << si.device() << si.displayName() << si.fileSystemType() << si.isReady();
438 if (!si.isReady() || !QString(si.fileSystemType()).contains(fstypeRe))
439 continue;
440 temppath = si.rootPath();
441 eepromfile = temppath % "/" % filename;
442 qDebug() << "Searching for" << eepromfile;
443 if (QFile::exists(eepromfile)) {
444 found = true;
445 break;
448 #elif defined WIN32 || !defined __GNUC__
449 static QStringList blacklist;
450 foreach(const QFileInfo & drive, QDir::drives()) {
451 temppath = drive.absolutePath();
452 if (blacklist.contains(temppath))
453 continue;
454 if (!isRemovableMedia(temppath)) {
455 blacklist.append(temppath);
456 continue;
458 WCHAR szVolumeName[256] ;
459 WCHAR szFileSystemName[256];
460 DWORD dwSerialNumber = 0;
461 DWORD dwMaxFileNameLength=256;
462 DWORD dwFileSystemFlags=0;
463 if (!GetVolumeInformationW( (WCHAR *) drive.absolutePath().utf16(),szVolumeName,256,&dwSerialNumber,&dwMaxFileNameLength,&dwFileSystemFlags,szFileSystemName,256))
464 continue;
465 eepromfile = temppath % "/" % filename;
466 qDebug() << "Searching for" << eepromfile;
467 if (QFile::exists(eepromfile)) {
468 found = true;
469 break;
472 #else
473 const static QStringList blacklist = QStringList() << "/" << "/net" << "/proc" << "/run";
474 QStringList drives;
475 struct mount_entry *entry;
476 struct mount_entry *firstEntry;
477 firstEntry = entry = read_file_system_list(true);
478 while (entry != NULL) {
479 temppath = entry->me_mountdir;
480 if (!drives.contains(entry->me_devname) && !blacklist.contains(temppath) && QString(entry->me_type).contains(fstypeRe)) {
481 drives.append(entry->me_devname);
482 eepromfile = temppath % "/" % filename;
483 qDebug() << "Searching for" << eepromfile;
484 if (QFile::exists(eepromfile)) {
485 found = true;
486 break;
489 entry = entry->me_next;
491 free_file_system_list(firstEntry);
492 #endif
494 if (found)
495 return onlyPath ? temppath : eepromfile;
496 else
497 return QString();