diff --git a/.gitmodules b/.gitmodules index 71d030326..34dfe064c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/fmt/fmt"] path = lib/fmt/fmt url = https://github.com/fmtlib/fmt +[submodule "lib/libcintelhex"] + path = lib/libcintelhex + url = https://github.com/jaxxzer/libcintelhex diff --git a/CMakeLists.txt b/CMakeLists.txt index a0d226a81..201e0f190 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.12) -project(ping-viewer LANGUAGES CXX) +project(ping-viewer LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CXX_STANDARD_REQUIRED ON) @@ -50,11 +50,12 @@ message(STATUS "Qt Version: ${Qt5_VERSION}") # global include directories include_directories( lib/fmt/fmt/include/ - lib/ping-cpp/ping-cpp/src/message/ + lib/libcintelhex/include lib/maddy/maddy/include/ lib/mavlink/c_library_v2/ lib/mavlink/c_library_v2/minimal lib/mavlink/c_library_v2/common + lib/ping-cpp/ping-cpp/src/message/ ) add_subdirectory(lib/fmt/fmt) diff --git a/lib/libcintelhex b/lib/libcintelhex new file mode 160000 index 000000000..2db480fae --- /dev/null +++ b/lib/libcintelhex @@ -0,0 +1 @@ +Subproject commit 2db480fae1f3cd2c107d6c8a9dbe1b14790cbe86 diff --git a/qml/DeviceManagerViewer.qml b/qml/DeviceManagerViewer.qml index 757965c57..7432a01fc 100644 --- a/qml/DeviceManagerViewer.qml +++ b/qml/DeviceManagerViewer.qml @@ -21,7 +21,14 @@ PingPopup { closePolicy: Popup.NoAutoClose onVisibleChanged: { - visible ? DeviceManager.startDetecting() : DeviceManager.stopDetecting(); + print(stack.depth); + if (visible) { + if (stack.depth == 1) + DeviceManager.startDetecting(); + + } else { + DeviceManager.stopDetecting(); + } } Component.onCompleted: { if (!DeviceManager.primarySensor) @@ -57,10 +64,13 @@ PingPopup { Layout.fillWidth: true onClicked: { print(stack.depth); - if (stack.depth == 1) + if (stack.depth == 1) { + DeviceManager.stopDetecting(); stack.push(connectionMenu); - else + } else { + DeviceManager.startDetecting(); stack.pop(); + } } } diff --git a/qml/FirmwareUpdate.qml b/qml/FirmwareUpdate.qml index 6901a09b8..e19242658 100644 --- a/qml/FirmwareUpdate.qml +++ b/qml/FirmwareUpdate.qml @@ -70,8 +70,7 @@ RowLayout { PingComboBox { id: baudComboBox - // This should use the same values in Flasher::_validBaudRates - model: [57600, 115200, 230400] + model: ping.flasher.validBaudRates Layout.fillWidth: true visible: SettingsManager.debugMode } @@ -159,7 +158,7 @@ RowLayout { Layout.fillWidth: true enabled: !running && ((fwCombo.currentText != "" && !browseBt.visible) || (fileDialog.fileName != "" && browseBt.visible)) onClicked: { - var baud = SettingsManager.debugMode ? baudComboBox.model[baudComboBox.currentIndex] : 57600; + var baud = SettingsManager.debugMode ? baudComboBox.model[baudComboBox.currentIndex] : 115200; var verify = SettingsManager.debugMode ? verifyCB.checked : true; var path = automaticUpdateCB.currentIndex ? fileDialog.fileUrl : ping.firmwaresAvailable[fwCombo.currentText]; running = true; diff --git a/qml/MainPage.qml b/qml/MainPage.qml index cdb1bc9eb..8345b9d24 100644 --- a/qml/MainPage.qml +++ b/qml/MainPage.qml @@ -148,7 +148,6 @@ Item { PingItem { id: firmwareUpdateItem - visible: ping ? ping.link.configuration.deviceType() == PingEnumNamespace.PingDeviceType.PING1D : false isSubItem: true icon: StyleManager.firmwareUpdateIcon() onHideItemChanged: { diff --git a/qml/Ping360Visualizer.qml b/qml/Ping360Visualizer.qml index fb587a0b6..dcb7fbb72 100644 --- a/qml/Ping360Visualizer.qml +++ b/qml/Ping360Visualizer.qml @@ -246,6 +246,22 @@ Item { visible: false } + Text { + id: bootloaderWarning + + visible: ping.isBootloader + anchors.fill: waterfall + anchors.margins: 5 + font.bold: true + font.family: "Arial" + font.pointSize: 15 + text: "Ping360 is in Bootloader Mode. Please flash firmware." + color: "red" + z: 100 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + Text { id: mouseReadout diff --git a/qml/PingStatus.qml b/qml/PingStatus.qml index 792dee296..4b63194e8 100644 --- a/qml/PingStatus.qml +++ b/qml/PingStatus.qml @@ -25,7 +25,7 @@ Item { if (!ping) return ; - baseModel.model = ["FW: " + ping.firmware_version_major + "." + ping.firmware_version_minor, "SRC: " + ping.srcId + " DST: " + ping.dstId, "Device type: " + ping.device_type, "Device Revision: " + ping.device_revision, "Connection: " + ping.link.configuration.string, "RX Packets (#): " + ping.parsed_msgs, "RX Errors (#): " + ping.parser_errors, "TX speed (Bytes/s): " + ping.link.upSpeed, "RX speed (Bytes/s): " + ping.link.downSpeed, "Lost messages (#): " + ping.lost_messages, "Ascii text:\\n" + ping.ascii_text, "Error message:\\n" + ping.nack_message]; + baseModel.model = ["FW: " + ping.firmware_version_major + "." + ping.firmware_version_minor + "." + ping.firmware_version_patch, "SRC: " + ping.srcId + " DST: " + ping.dstId, "Device type: " + ping.device_type, "Device Revision: " + ping.device_revision, "Connection: " + ping.link.configuration.string, "RX Packets (#): " + ping.parsed_msgs, "RX Errors (#): " + ping.parser_errors, "TX speed (Bytes/s): " + ping.link.upSpeed, "RX speed (Bytes/s): " + ping.link.downSpeed, "Lost messages (#): " + ping.lost_messages, "Ascii text:\\n" + ping.ascii_text, "Error message:\\n" + ping.nack_message]; } } diff --git a/src/flash/CMakeLists.txt b/src/flash/CMakeLists.txt index 7f8f93e4d..0c975122d 100644 --- a/src/flash/CMakeLists.txt +++ b/src/flash/CMakeLists.txt @@ -1,7 +1,17 @@ +file(GLOB LIBCINTELHEXFILES + ${PROJECT_SOURCE_DIR}/lib/libcintelhex/src/ihex_parse.c + ${PROJECT_SOURCE_DIR}/lib/libcintelhex/src/ihex_record.c +) + add_library( flash STATIC flasher.cpp + pic-hex.cpp + ping360bootloaderpacket.cpp + ping360flasher.cpp + ping360flashworker.cpp + ${LIBCINTELHEXFILES} ) target_link_libraries( diff --git a/src/flash/flasher.cpp b/src/flash/flasher.cpp index 1d0e1a159..45da0ac17 100644 --- a/src/flash/flasher.cpp +++ b/src/flash/flasher.cpp @@ -12,8 +12,9 @@ PING_LOGGING_CATEGORY(FLASH, "ping.flash") -Flasher::Flasher(QObject* parent) +Flasher::Flasher(QObject* parent, const QList validBaudRates) : QObject(parent) + , _validBaudRates(validBaudRates) { _binRelativePath = #ifdef Q_OS_OSX diff --git a/src/flash/flasher.h b/src/flash/flasher.h index 56ee4dd8b..f5bc85bf5 100644 --- a/src/flash/flasher.h +++ b/src/flash/flasher.h @@ -18,8 +18,9 @@ class Flasher : public QObject { * @brief Construct a new Flasher object * * @param parent + * @param validBaudRates */ - Flasher(QObject* parent = nullptr); + Flasher(QObject* parent = nullptr, const QList validBaudRates = {57600, 115200, 230400}); /** * @brief Destroy the Flasher object @@ -72,11 +73,17 @@ class Flasher : public QObject { Flasher::States state() const { return _state; }; Q_PROPERTY(Flasher::States state READ state NOTIFY stateChanged) + /** + * @brief Return the valid baud rates for device communication + */ + const QVariantList& validBaudRates() const { return _validBaudRates; }; + Q_PROPERTY(QVariant validBaudRates READ validBaudRates CONSTANT); + /** * @brief Start the flash procedure * */ - void flash(); + virtual void flash(); /** * @brief Set the flash baud rate @@ -111,17 +118,21 @@ class Flasher : public QObject { void flashProgress(float progress); void stateChanged(Flasher::States state); +protected: + int _baudRate = 57600; + QString _firmwareFilePath; + LinkConfiguration _link; + const QList _validBaudRates; + bool _verify = true; + private: void firmwareUpdatePercentage(const QString& output); static QString stm32flashPath(); - int _baudRate = 57600; QString _binRelativePath; - QString _firmwareFilePath; QSharedPointer _firmwareProcess; - LinkConfiguration _link; QString _message; States _state = Idle; - const QList _validBaudRates = {57600, 115200, 230400}; - bool _verify = true; }; + +Q_DECLARE_METATYPE(Flasher::States); diff --git a/src/flash/pic-hex.cpp b/src/flash/pic-hex.cpp new file mode 100644 index 000000000..d25e0370a --- /dev/null +++ b/src/flash/pic-hex.cpp @@ -0,0 +1,138 @@ +#include "pic-hex.h" + +#include +#include + +bool PicHex::pic_hex_read_file(const char* filename) +{ + printf("extracting pic hex application data\n"); + if (!pic_hex_extract_application(filename)) { + return false; + } + printf("extracting pic hex configuration data\n"); + if (!pic_hex_extract_configuration(filename)) { + return false; + } + return true; +} + +bool PicHex::pic_hex_extract_application(const char* filename) +{ + ihex_recordset_t* record_set = ihex_rs_from_file(filename); + + if (record_set == 0) { + printf("failed to read recordset from file %s\n", filename); + return false; + } + + bool ret = pic_hex_mem_cpy( + record_set, pic_hex_application_data, sizeof(pic_hex_application_data), PROGRAM_MEMORY_OFFSET); + ihex_rs_free(record_set); + return ret; +} + +bool PicHex::pic_hex_extract_configuration(const char* filename) +{ + ihex_recordset_t* record_set = ihex_rs_from_file(filename); + + if (record_set == 0) { + printf("failed to read recordset from file %s\n", filename); + return false; + } + + bool ret = pic_hex_mem_cpy( + record_set, pic_hex_configuration_data, sizeof(pic_hex_configuration_data), CONFIGURATION_MEMORY_OFFSET); + ihex_rs_free(record_set); + return ret; +} + +/* + * Address equations for your convenience: + * + * | ihex | pic | data | + * | 0x000000 | 0x000000 | 0x000000 | + * | 0x000800 | 0x000400 | 0x000600 | + * | 0x001000 | 0x000800 | 0x000c00 | + * | 0x001800 | 0x000c00 | 0x001200 | + * | 0x002000 | 0x001000 | 0x001800 | + */ +bool PicHex::pic_hex_mem_cpy(ihex_recordset_t* record_set, uint8_t* destination, uint32_t length, uint32_t offset) +{ + uint_t i = 0; // current record # + uint32_t record_offset, record_address = 0x00; + + // record iterator + ihex_record_t* record; + + // zero-initialize destination data + memset(destination, 0, length); + + do { + int r = ihex_rs_iterate_data(record_set, &i, &record, &record_offset); + + // check error code + if (r) { + return false; + // check record is valid + } else if (record == 0) { + // we reached the end of the list + break; + } + + //////////////////////////// + // hex contains 16 bit words, addressed to 16 bit memory + //////////////////////////// + + // get the record address + record_address = (record_offset + record->ihr_address); + + /////////////////////////////////////////// + // memory contains 24 bit words, addressed to 32 bit memory (address/2) + /////////////////////////////////////////// + + record_address = record_address / 2; + + // record out of range low + if (record_address < offset) { + // printf("warn, record %d address %08x below offset %08x\r\n", i, record_address, offset); + continue; + } + + // record out of range high + if (record_address >= offset + length) { + // printf("warn, record %d address %08x beyond range %08x + %08x\r\n", i, record_address, offset, length); + continue; + } + + ////////////////////////////////////////////////// + // transmit data array contains 24 bit words *(3/2) + ////////////////////////////////////////////////// + + uint32_t destination_address = (3 * (record_address - offset)) / 2; + + // record out of range high + if (destination_address >= length) { + // printf("warn, destination address %08x beyond range %08x + %08x\r\n", destination_address, offset, + // length); + continue; + } + + for (int j = 0; j < record->ihr_length; j += 4) { + uint32_t destination_offset = destination_address + j - (j / 4); + if (destination_offset > length) { + // printf("warn, destination offset %08x beyond destination length %d", destination_offset, length); + continue; + } + // pointer to destination byte + uint8_t* target = (uint8_t*)(destination + destination_offset); + + // copy a 24 bit word + for (int l = 0; (l < 3); l++) { + + *(target++) = record->ihr_data[j + l]; + } + } + } while (i > 0); + + return true; +} diff --git a/src/flash/pic-hex.h b/src/flash/pic-hex.h new file mode 100644 index 000000000..93c3bb85d --- /dev/null +++ b/src/flash/pic-hex.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +/** + * @brief facilities to convert an intel hex to PIC microcontroller memory space + */ +class PicHex { +public: + // 86 pages * 512 words * 3 bytes + static const uint32_t PROGRAM_MEMORY_SIZE = 0x20400; + static const uint32_t PROGRAM_MEMORY_OFFSET = 0x0; + + // 24 bytes configuration in separate memory region + static const uint32_t CONFIGURATION_MEMORY_SIZE = 24; + static const uint32_t CONFIGURATION_MEMORY_OFFSET = 0x00F80000; + + /** + * @brief read a hex file and extract the firmware application and configuration data + * @param filename + * @return true if application and configuration data were successfully extracted + */ + bool pic_hex_read_file(const char* filename); + + /** + * @brief The hex file data for the application memory region + * this data is valid if pic_hex_read_file returns true + */ + uint8_t pic_hex_application_data[PROGRAM_MEMORY_SIZE]; + + /** + * @brief The hex file data for the configuration memory region + * The configuration memory region is located at CONFIGURATION_MEMORY_OFFSET + * and is CONFIGURATION_MEMORY_SIZE bytes wide + * this data is valid if pic_hex_read_file returns true + */ + uint8_t pic_hex_configuration_data[CONFIGURATION_MEMORY_SIZE]; + +private: + bool pic_hex_mem_cpy(ihex_recordset_t* record_set, uint8_t* destination, uint32_t length, uint32_t offset); + bool pic_hex_extract_application(const char* filename); + bool pic_hex_extract_configuration(const char* filename); +}; diff --git a/src/flash/ping360bootloaderpacket.cpp b/src/flash/ping360bootloaderpacket.cpp new file mode 100644 index 000000000..b1f064ccf --- /dev/null +++ b/src/flash/ping360bootloaderpacket.cpp @@ -0,0 +1,110 @@ +#include "ping360bootloaderpacket.h" + +uint16_t Ping360BootloaderPacket::packet_get_payload_length(packet_t packet) { return *(uint16_t*)(packet + 2); } + +uint16_t Ping360BootloaderPacket::packet_get_length(packet_t packet) { return packet_get_payload_length(packet) + 7; } + +uint8_t Ping360BootloaderPacket::packet_calculate_checksum(packet_t packet) +{ + uint8_t checksum = packet[1] ^ packet[2]; + checksum = checksum ^ packet[3]; + for (int i = 0; i < packet_get_payload_length(packet); i++) { + checksum ^= packet[4 + i]; + } + return checksum; +} + +Ping360BootloaderPacket::packet_id_e Ping360BootloaderPacket::packet_get_id(packet_t packet) +{ + return static_cast(packet[1]); +} + +uint8_t Ping360BootloaderPacket::packet_calculate_complement(packet_t packet) { return ~packet_get_id(packet) + 1; } + +void Ping360BootloaderPacket::packet_update_footer(packet_t packet) +{ + uint8_t* checksum_p = packet + 4 + packet_get_payload_length(packet); + *checksum_p = packet_calculate_checksum(packet); + *(checksum_p + 1) = packet_calculate_complement(packet); +} + +Ping360BootloaderPacket::packet_parse_state_e Ping360BootloaderPacket::packet_parse_byte(const uint8_t byte) +{ + // don't overflow the parse buffer + if (parser.rxTail >= PACKET_MAX_LENGTH) { + return ERROR; + } + + switch (parser.parseState) { + case ERROR: + case NEW_MESSAGE: + case WAIT_START: + parser.packetLength = 0; + parser.rxTail = 0; + if (byte != PACKET_FRAMING_START) { + parser.parseState = ERROR; + } else { + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = WAIT_ID; + } + break; + case WAIT_ID: + if (byte < ID_LOW || byte >= ID_HIGH) { + parser.parseState = ERROR; + } else { + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = WAIT_LENGTH_L; + } + break; + case WAIT_LENGTH_L: + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = WAIT_LENGTH_H; + break; + case WAIT_LENGTH_H: + parser.rxBuffer[parser.rxTail++] = byte; + parser.packetLength = packet_get_payload_length(parser.rxBuffer); + if (parser.packetLength > PACKET_MAX_LENGTH) { + parser.parseState = ERROR; + } else if (parser.packetLength == 0) { + parser.parseState = WAIT_CHECKSUM; + } else { + parser.parseState = WAIT_DATA; + } + break; + case WAIT_DATA: + parser.rxBuffer[parser.rxTail++] = byte; + parser.packetLength--; + if (!parser.packetLength) { + parser.parseState = WAIT_CHECKSUM; + } + break; + case WAIT_CHECKSUM: + if (byte != packet_calculate_checksum(parser.rxBuffer)) { + parser.parseState = ERROR; + } else { + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = WAIT_COMPLEMENT; + } + break; + case WAIT_COMPLEMENT: + if (byte != packet_calculate_complement(parser.rxBuffer)) { + parser.parseState = ERROR; + } else { + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = WAIT_END; + } + break; + case WAIT_END: + if (byte != PACKET_FRAMING_END) { + parser.parseState = ERROR; + } else { + parser.rxBuffer[parser.rxTail++] = byte; + parser.parseState = NEW_MESSAGE; + } + break; + default: + parser.parseState = ERROR; + break; + } + return parser.parseState; +} diff --git a/src/flash/ping360bootloaderpacket.h b/src/flash/ping360bootloaderpacket.h new file mode 100644 index 000000000..255104e9a --- /dev/null +++ b/src/flash/ping360bootloaderpacket.h @@ -0,0 +1,339 @@ +#pragma once + +#include + +/** + * @brief A class to provide packet definitions, and to + * facilitate creating and parsing packets + */ +class Ping360BootloaderPacket { +public: + static const uint16_t PACKET_MAX_LENGTH = 1600; + + // the data length for read/write program memory + static const uint16_t PACKET_ROW_LENGTH = 1536; + + /** + * @brief packet framing bytes + * every packet begins with PACKET_FRAMING_START + * and ends with PACKET_FRAMING_END + */ + typedef enum { + PACKET_FRAMING_START = 0x5a, + PACKET_FRAMING_END = 0xa5, + } packet_framing_e; + + /** + * @brief packet IDs + * a packet is either a command or a response + */ + typedef enum { + ID_LOW = 0x30, + RSP_NACK = 0x30, + RSP_ACK = 0x31, + RSP_PGM_MEM = 0x32, + RSP_DEV_ID = 0x33, + RSP_VERSION = 0x34, + + CMD_READ_DEV_ID = 0x41, + CMD_READ_PGM_MEM = 0x42, + CMD_WRITE_PGM_MEM = 0x43, + CMD_WRITE_CFG_MEM = 0x44, + CMD_RESET_PROCESSOR = 0x45, + CMD_JUMP_START = 0x46, + CMD_RESTART_BOOTLOADER = 0x47, + CMD_READ_VERSION = 0x48, + ID_HIGH + } packet_id_e; + + /** + * @brief a packet is a sequence of bytes + */ + typedef uint8_t* packet_t; + + /** + * @brief packet headers consist of: + * - a start byte: 0x5A + * - a byte indicating the command or response ID + * - an unsigned 16 bit number indicating the packet payload length + */ + typedef struct { + uint8_t start; + uint8_t id; + uint16_t length; + } packet_header_t; + + /** + * @brief packet footers consist of: + * - a checksum byte: the xor of bytes between + * start and checksum non-inclusive + * - a complement byte: 2's complement of command or response byte 1 + * - a terminator byte: 0xA5 + */ + typedef struct { + uint8_t checksum; + uint8_t complement; + uint8_t end; + } packet_footer_t; + + /** + * @brief empty packets consist of a header and a footer + * with no payload + */ + typedef union { + struct { + packet_header_t header; + packet_footer_t footer; + } message; + uint8_t data[7]; + } packet_empty_t; + + // empty packets (no payload) // + + /** + * @brief command to read the device ID: CMD_READ_DEV_ID + */ + typedef packet_empty_t packet_cmd_read_dev_id_t; + + /** + * @brief command to reset the processor: CMD_RESET_PROCESSOR + */ + typedef packet_empty_t packet_cmd_reset_processor_t; + + /** + * @brief command to jump to the start of the application firmware: CMD_JUMP_START + */ + typedef packet_empty_t packet_cmd_jump_start_t; + + /** + * @brief command to restart the bootloader: CMD_RESTART_BOOTLOADER + */ + typedef packet_empty_t packet_cmd_restart_bootloader_t; + + /** + * @brief command to read the bootloader version: CMD_READ_VERSION + */ + typedef packet_empty_t packet_cmd_read_version_t; + + /** + * @brief a Not ACKnowledged response: RSP_NACK + */ + typedef packet_empty_t packet_rsp_nack_t; + + /** + * @brief an ACKnowledged response: RSP_ACK + */ + typedef packet_empty_t packet_rsp_ack_t; + + static constexpr packet_cmd_read_dev_id_t packet_cmd_read_dev_id_init = {{ + { + PACKET_FRAMING_START, CMD_READ_DEV_ID, + 0 // length + }, // header + {0, 0, PACKET_FRAMING_END} // footer + }}; + + static constexpr packet_cmd_reset_processor_t packet_cmd_reset_processor_init = {{ + { + PACKET_FRAMING_START, CMD_RESET_PROCESSOR, + 0 // length + }, // header + {0, 0, PACKET_FRAMING_END} // footer + }}; + + static constexpr packet_cmd_jump_start_t packet_cmd_jump_start_init = {{ + { + PACKET_FRAMING_START, CMD_JUMP_START, + 0 // length + }, // header + {0, 0, PACKET_FRAMING_END} // footer + }}; + + static constexpr packet_cmd_restart_bootloader_t packet_cmd_restart_bootloader_init = {{ + { + PACKET_FRAMING_START, CMD_RESTART_BOOTLOADER, + 0 // length + }, // header + {0, 0, PACKET_FRAMING_END} // footer + }}; + + static constexpr packet_cmd_read_version_t packet_cmd_read_version_init = { + { + { + PACKET_FRAMING_START, CMD_READ_VERSION, + 0 // length + }, // header + {0, 0, PACKET_FRAMING_END} // footer + }, + }; + + /** + * @brief command to read the program memory region + */ + typedef union { + struct { + packet_header_t header; + uint32_t address; + packet_footer_t footer; + } message; + uint8_t data[4 + 7]; + } packet_cmd_read_pgm_mem_t; + + static constexpr packet_cmd_read_pgm_mem_t packet_cmd_read_pgm_mem_init = {{ + { + PACKET_FRAMING_START, CMD_READ_PGM_MEM, + 4 // length + }, // header + 0, // address + {0, 0, PACKET_FRAMING_END} // footer + }}; + + /** + * @brief command to write the program memory region + */ + typedef union { + struct { + packet_header_t header; + uint32_t address; + uint8_t rowData[PACKET_ROW_LENGTH]; + packet_footer_t footer; + } message; + uint8_t data[4 + PACKET_ROW_LENGTH + 7]; + } packet_cmd_write_pgm_mem_t; + + static constexpr packet_cmd_write_pgm_mem_t packet_cmd_write_pgm_mem_init = {{ + { + PACKET_FRAMING_START, CMD_WRITE_PGM_MEM, + 4 + PACKET_ROW_LENGTH // length + }, // header + 0, // address + {0}, {0, 0, PACKET_FRAMING_END} // footer + }}; + + /** + * @brief command to write the configuration memory region + */ + typedef union { + struct { + packet_header_t header; + uint8_t cfgData[24]; + packet_footer_t footer; + } message; + uint8_t data[24 + 7]; + } packet_cmd_write_cfg_mem_t; + + static constexpr packet_cmd_write_cfg_mem_t packet_cmd_write_cfg_mem_init = {{ + { + PACKET_FRAMING_START, CMD_WRITE_CFG_MEM, + 24 // length + }, // header + {0}, {0, 0, PACKET_FRAMING_END} // footer + }}; + + /** + * @brief a response to the CMD_READ_PGM_MEM command + */ + typedef union { + struct { + packet_header_t header; + uint8_t rowData[PACKET_ROW_LENGTH]; + packet_footer_t footer; + } message; + uint8_t data[PACKET_ROW_LENGTH + 7]; + } packet_rsp_pgm_mem_t; + + /** + * @brief a response to the CMD_READ_DEV_ID command + */ + typedef union { + struct { + packet_header_t header; + uint16_t deviceId; + packet_footer_t footer; + } message; + uint8_t data[2 + 7]; + } packet_rsp_dev_id_t; + + /** + * @brief a response to the CMD_READ_VERSION command + */ + typedef union { + struct { + packet_header_t header; + uint8_t device_type; + uint8_t device_revision; + uint8_t version_major; + uint8_t version_minor; + uint8_t version_patch; + packet_footer_t footer; + } message; + uint8_t data[5 + 7]; + } packet_rsp_version_t; + + /** + * @brief states for parsing a packet + */ + typedef enum { + ERROR, + WAIT_START, + WAIT_ID, + WAIT_LENGTH_L, + WAIT_LENGTH_H, + WAIT_DATA, + WAIT_CHECKSUM, + WAIT_COMPLEMENT, + WAIT_END, + NEW_MESSAGE, + } packet_parse_state_e; + + /** + * @brief a struct for parsing a packet with an internal + * buffer and parse state + */ + typedef struct packet_parser { + packet_parse_state_e parseState; + uint16_t rxTail; + uint16_t packetLength; + uint8_t rxBuffer[PACKET_MAX_LENGTH]; + } packet_parser_t; + + packet_parser_t parser = {WAIT_START}; + + /** + * @brief reset the parser to start parsing a new packet + */ + void reset() { parser.parseState = WAIT_START; }; + + /** + * @brief parse a byte to form a ping360 bootloader packet + * use reset() to start parsing a new packet + * @param byte + * @return NEW_MESSAGE if the byte completes a ping360 bootloader packet + */ + packet_parse_state_e packet_parse_byte(const uint8_t byte); + + /** + * @brief get the packet id for a packet + * @param packet + * @return the packet id + */ + static packet_id_e packet_get_id(packet_t packet); + + /** + * @brief get the length in bytes of the entire packet, header and footer included + * @param packet + * @return the packet data length in bytes + */ + static uint16_t packet_get_length(packet_t packet); + + /** + * @brief update the packet footer data with the calculated complement and checksum + * @param packet + */ + static void packet_update_footer(packet_t packet); + +private: + static uint16_t packet_get_payload_length(packet_t packet); + static uint8_t packet_calculate_checksum(packet_t packet); + static uint8_t packet_calculate_complement(packet_t packet); +}; diff --git a/src/flash/ping360flasher.cpp b/src/flash/ping360flasher.cpp new file mode 100644 index 000000000..04c7b9b0a --- /dev/null +++ b/src/flash/ping360flasher.cpp @@ -0,0 +1,24 @@ +#include "ping360flasher.h" + +Ping360Flasher::Ping360Flasher(QObject* parent) + : Flasher(parent, {115200}) +{ + connect( + &_worker, &Ping360FlashWorker::flashProgressChanged, this, + [this](float flashProgressPct) { emit flashProgress(flashProgressPct); }, Qt::QueuedConnection); + connect( + &_worker, &Ping360FlashWorker::stateChanged, this, [this](Flasher::States newState) { setState(newState); }, + Qt::QueuedConnection); + connect( + &_worker, &Ping360FlashWorker::messageChanged, this, [this](QString message) { setMessage(message); }, + Qt::QueuedConnection); +} + +void Ping360Flasher::flash() +{ + _worker.setBaudRate(_baudRate); + _worker.setFirmwarePath(_firmwareFilePath); + _worker.setLink(_link); + _worker.setVerify(_verify); + _worker.start(); +} diff --git a/src/flash/ping360flasher.h b/src/flash/ping360flasher.h new file mode 100644 index 000000000..fc475fc81 --- /dev/null +++ b/src/flash/ping360flasher.h @@ -0,0 +1,23 @@ +#pragma once + +#include "flasher.h" +#include "ping360flashworker.h" + +/** + * @brief Flasher implementation for Ping360 sensor device + * + */ +class Ping360Flasher : public Flasher { + Q_OBJECT +public: + /** + * @brief Construct a new Ping360Flasher object + * @param parent + */ + Ping360Flasher(QObject* parent); + + void flash() override final; + +private: + Ping360FlashWorker _worker; +}; diff --git a/src/flash/ping360flashworker.cpp b/src/flash/ping360flashworker.cpp new file mode 100644 index 000000000..09e4f060a --- /dev/null +++ b/src/flash/ping360flashworker.cpp @@ -0,0 +1,345 @@ +#include "ping360flashworker.h" +#include "logger.h" +#include "pic-hex.h" + +#include +#include +#include + +PING_LOGGING_CATEGORY(PING360FLASHWORKER, "ping360.flash.worker") + +Ping360FlashWorker::Ping360FlashWorker() + : QThread() +{ +} + +void Ping360FlashWorker::run() +{ + // The flash progress is divided into two, or three process phases depending + // on if _verify is true: + // 1. Erase + // 2. Flash + // 3. Verify (optional) + // the flash progress percent factor is 100 divided by the number of process phases + float flashProgressPercentFactor = _verify ? 33.3f : 50.0f; + emit stateChanged(Flasher::StartingFlash); + + _port = new QSerialPort(); + QSerialPortInfo pInfo(_link.serialPort()); + _port->setPort(pInfo); + _port->setBaudRate(_baudRate); + + for (int i = 0; i < 10; i++) { + QThread::msleep(10); + if (_port->open(QIODevice::ReadWrite)) { + break; + } + } + + if (!_port->isOpen()) { + error("error opening port"); + return; + } + + qCInfo(PING360FLASHWORKER) << "fetch device id..."; + uint16_t id = 0; + bool id_read = false; + for (int i = 0; i < 10; i++) { + QThread::msleep(10); + if (bl_read_device_id(&id)) { + id_read = true; + break; + } + } + + if (id_read) { + qCInfo(PING360FLASHWORKER) << QString::asprintf(" > device id: 0x%04x <\n", id); + } else { + error("error fetching device id"); + return; + } + + qCInfo(PING360FLASHWORKER) << "fetch version..."; + Ping360BootloaderPacket::packet_rsp_version_t version; + if (bl_read_version(&version)) { + if (version.message.version_major != _expectedVersionMajor + || version.message.version_minor != _expectedVersionMinor + || version.message.version_patch != _expectedVersionPatch) { + error(QString::asprintf("error, bootloader version is v%d.%d.%d, expected v%d.%d.%d", + version.message.version_major, version.message.version_minor, version.message.version_patch, + _expectedVersionMajor, _expectedVersionMinor, _expectedVersionPatch)); + return; + } + + qCInfo(PING360FLASHWORKER) << QString::asprintf( + " > device type 0x%02x : hardware revision %c : bootloader v%d.%d.%d <\n", version.message.device_type, + version.message.device_revision, version.message.version_major, version.message.version_minor, + version.message.version_patch); + + } else { + error("error fetching version"); + return; + } + + qCInfo(PING360FLASHWORKER) << QString::asprintf( + "loading ping360 firmware from %s...", _firmwareFilePath.toLocal8Bit().data()); + + // PicHex hex = PicHex(_firmwareFilePath.toLocal8Bit().data()); + PicHex hex = PicHex(); + hex.pic_hex_read_file(_firmwareFilePath.toLocal8Bit().constData()); + emit stateChanged(Flasher::Flashing); + + qCInfo(PING360FLASHWORKER) << "erasing program memory"; + + uint8_t fill[Ping360BootloaderPacket::PACKET_ROW_LENGTH]; + memset(fill, 0xff, sizeof(fill)); + for (int i = 0; i < 86; i++) { + if (i >= 1 && i <= 3) { + continue; + } + if (bl_write_program_memory(fill, i * 0x400)) { + qCInfo(PING360FLASHWORKER) << QString("erase memory address 0x%1...ok").arg(i * 0x400, 8, 16, QChar('0')); + } else { + error(QString("error erasing memory address 0x%1").arg(i * 0x400, 8, 16, QChar('0'))); + return; + } + emit flashProgressChanged(flashProgressPercentFactor * i / 86.0f); + } + qCInfo(PING360FLASHWORKER) << "writing application..."; + for (int i = 0; i < 86; i++) { + if (i >= 1 && i <= 3) { + continue; // protected boot code + } + if (i == 0) { + continue; // we write this page last, to prevent booting after failed programming + } + + if (bl_write_program_memory( + hex.pic_hex_application_data + i * Ping360BootloaderPacket::PACKET_ROW_LENGTH, i * 0x400)) { + qCInfo(PING360FLASHWORKER) << QString("write 0x%1: ok").arg(i * 0x400, 8, 16, QChar('0')); + } else { + error(QString("write memory address 0x%1: error").arg(i * 0x400, 8, 16, QChar('0'))); + return; + } + emit flashProgressChanged(flashProgressPercentFactor + flashProgressPercentFactor * i / 86.0f); + } + + uint16_t bootAddress = 0x0000; + if (bl_write_program_memory(hex.pic_hex_application_data, bootAddress)) { + qCInfo(PING360FLASHWORKER) << QString("write boot memory address 0x%1...").arg(bootAddress, 8, 16, QChar('0')) + << "ok"; + } else { + error(QString("error writing boot memory address 0x%1...").arg(bootAddress, 8, 16, QChar('0'))); + return; + } + + if (_verify) { + qCInfo(PING360FLASHWORKER) << "verifying application..."; + uint8_t* verify; + for (int i = 0; i < 86; i++) { + if (i >= 1 && i <= 3) { + continue; // protected bootloader code + } + uint32_t offset = i * 0x400; + bool verify_ok = true; + if (bl_read_program_memory(&verify, offset)) { + for (int j = 0; j < Ping360BootloaderPacket::PACKET_ROW_LENGTH; j++) { + if (verify[j] != hex.pic_hex_application_data[i * Ping360BootloaderPacket::PACKET_ROW_LENGTH + j]) { + qCWarning(PING360FLASHWORKER) + << QString::asprintf("error: program data differs at 0x%08llx: 0x%02x != 0x%02x\n", + i * Ping360BootloaderPacket::PACKET_ROW_LENGTH + j, verify[j], + hex.pic_hex_application_data[i * Ping360BootloaderPacket::PACKET_ROW_LENGTH + j]); + } + } + qCInfo(PING360FLASHWORKER) << QString::asprintf("verify 0x%08x: ok", offset); + } else { + error(QString::asprintf("verify 0x%08x: error reading program memory", offset)); + return; + } + emit flashProgressChanged(2 * flashProgressPercentFactor + flashProgressPercentFactor * i / 86.0f); + } + } + + if (bl_write_configuration_memory(hex.pic_hex_configuration_data)) { + qCInfo(PING360FLASHWORKER) << "writing configuration...ok"; + } else { + error("error writing configuration memory"); + return; + } + + // flash is complete + emit flashProgressChanged(100.0f); + + if (bl_reset()) { + qCInfo(PING360FLASHWORKER) << "starting application...ok"; + } else { + qCCritical(PING360FLASHWORKER) << "starting application...error"; + error("error starting pin360 main application"); + return; + } + + _port->close(); + delete _port; + + emit stateChanged(Flasher::FlashFinished); +} + +#define BL_TIMEOUT_DEFAULT_US 750000 +#define BL_TIMEOUT_WRITE_US 500000 +#define BL_TIMEOUT_READ_US 5000000 + +void Ping360FlashWorker::bl_write_packet(const packet_t packet) +{ + // for (int i = 0; i < bl_parser.packet_get_length(packet); i++) { + // qCCritical(PING360FLASHWORKER) << i << packet[i]; + // } + port_write(packet, bl_parser.packet_get_length(packet)); +} + +Ping360BootloaderPacket::packet_t Ping360FlashWorker::bl_wait_packet(uint8_t id, uint32_t timeout_us) +{ + bl_parser.reset(); + + uint64_t tstop = time_us() + timeout_us; + // qCCritical(PING360FLASHWORKER) << "waiting for packet" << time_us() << tstop; + + uint8_t b; + while (time_us() < tstop) { + // QThread::msleep(10); + _port->waitForReadyRead(10); + for (int i = 0; i < _port->bytesAvailable(); i++) { + if (port_read(&b, 1) > 0) { + Ping360BootloaderPacket::packet_parse_state_e parseResult = bl_parser.packet_parse_byte(b); + // qCInfo(PING360FLASHWORKER) << parseResult; + if (parseResult == Ping360BootloaderPacket::NEW_MESSAGE) { + if (Ping360BootloaderPacket::packet_get_id(bl_parser.parser.rxBuffer) == id) { + return bl_parser.parser.rxBuffer; + } else { + printf("bootloader error: got unexpected id 0x%02x while waiting for 0x%02x", + Ping360BootloaderPacket::packet_get_id(bl_parser.parser.rxBuffer), id); + return NULL; + } + } else if (parseResult == Ping360BootloaderPacket::ERROR) { + printf("bootloader error: parse error while waiting for 0x%02x!\n", id); + return NULL; + } + } + } + } + printf("bootloader error: timed out waiting for 0x%02x!\n", id); + return NULL; +} + +// false on nack or error +bool Ping360FlashWorker::bl_read_device_id(uint16_t* device_id) +{ + Ping360BootloaderPacket::packet_cmd_read_dev_id_t readDevId = Ping360BootloaderPacket::packet_cmd_read_dev_id_init; + Ping360BootloaderPacket::packet_update_footer(readDevId.data); + bl_write_packet(readDevId.data); + Ping360BootloaderPacket::packet_t ret = bl_wait_packet(Ping360BootloaderPacket::RSP_DEV_ID, BL_TIMEOUT_DEFAULT_US); + if (ret) { + Ping360BootloaderPacket::packet_rsp_dev_id_t* resp = (Ping360BootloaderPacket::packet_rsp_dev_id_t*)ret; + *device_id = resp->message.deviceId; + return true; + } + return false; +} + +bool Ping360FlashWorker::bl_read_version(Ping360BootloaderPacket::packet_rsp_version_t* version) +{ + Ping360BootloaderPacket::packet_cmd_read_version_t readVersion + = Ping360BootloaderPacket::packet_cmd_read_version_init; + Ping360BootloaderPacket::packet_update_footer(readVersion.data); + bl_write_packet(readVersion.data); + Ping360BootloaderPacket::packet_t ret = bl_wait_packet(Ping360BootloaderPacket::RSP_VERSION, BL_TIMEOUT_DEFAULT_US); + if (ret) { + *version = *(Ping360BootloaderPacket::packet_rsp_version_t*)ret; + return true; + } + return false; +} + +// false on nack or error +bool Ping360FlashWorker::bl_read_program_memory(uint8_t** data, uint32_t address) +{ + Ping360BootloaderPacket::packet_cmd_read_pgm_mem_t pkt = Ping360BootloaderPacket::packet_cmd_read_pgm_mem_init; + pkt.message.address = address; + Ping360BootloaderPacket::packet_update_footer(pkt.data); + bl_write_packet(pkt.data); + Ping360BootloaderPacket::packet_t ret = bl_wait_packet(Ping360BootloaderPacket::RSP_PGM_MEM, BL_TIMEOUT_READ_US); + if (ret) { + Ping360BootloaderPacket::packet_rsp_pgm_mem_t* resp = (Ping360BootloaderPacket::packet_rsp_pgm_mem_t*)ret; + uint8_t* rowData = resp->message.rowData; + + // rotate data to fix endianness (read is opposite endianness of write) + for (uint16_t i = 0; i < 512; i++) { + uint16_t idx = i * 3; + uint8_t tmp = rowData[idx]; + rowData[idx] = rowData[idx + 2]; + rowData[idx + 2] = tmp; + } + + *data = rowData; + + return true; + } + return false; +} + +// false on nack or error +bool Ping360FlashWorker::bl_write_program_memory(const uint8_t* data, uint32_t address) +{ + Ping360BootloaderPacket::packet_cmd_write_pgm_mem_t pkt = Ping360BootloaderPacket::packet_cmd_write_pgm_mem_init; + memcpy(pkt.message.rowData, data, Ping360BootloaderPacket::PACKET_ROW_LENGTH); + pkt.message.address = address; + Ping360BootloaderPacket::packet_update_footer(pkt.data); + bl_write_packet(pkt.data); + return bl_wait_packet(Ping360BootloaderPacket::RSP_ACK, BL_TIMEOUT_WRITE_US); +} + +// false on nack or error +bool Ping360FlashWorker::bl_write_configuration_memory(const uint8_t* data) +{ + Ping360BootloaderPacket::packet_cmd_write_cfg_mem_t pkt = Ping360BootloaderPacket::packet_cmd_write_cfg_mem_init; + memcpy(pkt.message.cfgData, data, 24); + Ping360BootloaderPacket::packet_update_footer(pkt.data); + bl_write_packet(pkt.data); + return bl_wait_packet(Ping360BootloaderPacket::RSP_ACK, BL_TIMEOUT_DEFAULT_US); +} + +bool Ping360FlashWorker::bl_reset() +{ + Ping360BootloaderPacket::packet_cmd_reset_processor_t pkt + = Ping360BootloaderPacket::packet_cmd_reset_processor_init; + Ping360BootloaderPacket::packet_update_footer(pkt.data); + bl_write_packet(pkt.data); + return bl_wait_packet(Ping360BootloaderPacket::RSP_ACK, BL_TIMEOUT_DEFAULT_US); +} + +int Ping360FlashWorker::port_write(const uint8_t* buffer, int nBytes) +{ + // _link.self()->sendData(QByteArray(reinterpret_cast(buffer), nBytes)); + int bytes = _port->write(reinterpret_cast(buffer), nBytes); + _port->flush(); + // if (!_port->waitForBytesWritten(1000)) { + // qCCritical(PING360FLASHWORKER) << "write timeout"; + // } + // qCCritical(PING360FLASHWORKER) << "wrote" << bytes; + return bytes; +} + +int Ping360FlashWorker::port_read(uint8_t* data, int nBytes) +{ + int bytes = _port->read(reinterpret_cast(data), nBytes); + // if (bytes > 0) qCCritical(PING360FLASHWORKER) << "read" << bytes << "/" << nBytes; + return bytes; +} + +void Ping360FlashWorker::error(const QString message) +{ + if (_port) { + _port->close(); + } + emit messageChanged(message); + emit stateChanged(Flasher::Error); +} diff --git a/src/flash/ping360flashworker.h b/src/flash/ping360flashworker.h new file mode 100644 index 000000000..b7a2914c7 --- /dev/null +++ b/src/flash/ping360flashworker.h @@ -0,0 +1,107 @@ +#pragma once + +#include "flasher.h" +#include "ping360bootloaderpacket.h" + +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(PING360FLASHWORKER) + +/** + * @brief Worker thread for flashing Ping360 devices + * + */ +class Ping360FlashWorker : public QThread { + Q_OBJECT +public: + /** + * @brief Construct a new Ping360FlashWorker object + * + */ + Ping360FlashWorker(); + + /** + * @brief Destroy the Ping360FlashWorker object + * + */ + ~Ping360FlashWorker() = default; + + /** + * @brief Set the baud rate to be used for device communication + * + * @param baudRate + * + */ + void setBaudRate(int baudRate) { _baudRate = baudRate; } + + /** + * @brief Set the path for the firmware hex file + * + * @param firmwareFilePath + * + */ + void setFirmwarePath(QString firmwareFilePath) { _firmwareFilePath = firmwareFilePath; } + + /** + * @brief Set the link configuration to use for device communication + * + * @param link the link configuration to use for device communication + * + */ + void setLink(LinkConfiguration link) { _link = link; } + + /** + * @brief Set the verify behavior of the flash process + * + * @param verify set to true to read back and verify the firmware after programming the device + * + */ + void setVerify(bool verify) { _verify = verify; } + + /** + * @brief run the flashing process, overrides QThread::run + */ + void run() override final; + +signals: + void messageChanged(QString message); + void stateChanged(Flasher::States state); + void flashProgressChanged(float flashPercent); + +private: + int _baudRate; + QString _firmwareFilePath; + LinkConfiguration _link; + bool _verify = true; + + QSerialPort* _port; + + typedef Ping360BootloaderPacket::packet_t packet_t; + void bl_write_packet(const packet_t packet); + packet_t bl_wait_packet(uint8_t id, uint32_t timeout_us); + + bool bl_read_device_id(uint16_t* device_id); + bool bl_read_version(Ping360BootloaderPacket::packet_rsp_version_t* version); + bool bl_read_program_memory(uint8_t** data, uint32_t address); + + bool bl_write_program_memory(const uint8_t* data, uint32_t address); + bool bl_write_configuration_memory(const uint8_t* data); + + bool bl_reset(); + + static const uint16_t _expectedDeviceId = 0x062f; + static const uint8_t _expectedVersionMajor = 2; + static const uint8_t _expectedVersionMinor = 1; + static const uint8_t _expectedVersionPatch = 2; + + Ping360BootloaderPacket bl_parser; + + qint64 time_us() { return QDateTime::currentMSecsSinceEpoch() * 1000; }; + int port_write(const uint8_t* buffer, int nBytes); + int port_read(uint8_t* data, int nBytes); + + void error(QString message); +}; diff --git a/src/link/abstractlink.h b/src/link/abstractlink.h index 427128473..e592fa9c7 100644 --- a/src/link/abstractlink.h +++ b/src/link/abstractlink.h @@ -229,7 +229,11 @@ class AbstractLink : public QObject { */ void write(const char* data, int size) { - if (size > 0) { + // size may be zero in the case of an empty datagram + // empty datagram signals to a serial bridge program + // to send a line break and perform the auto-baudrate + // procedure + if (size >= 0) { emit sendData(QByteArray(data, size)); }; } diff --git a/src/link/seriallink.cpp b/src/link/seriallink.cpp index f95ed4403..ba82dbd65 100644 --- a/src/link/seriallink.cpp +++ b/src/link/seriallink.cpp @@ -273,7 +273,7 @@ void SerialLink::forceSensorAutomaticBaudRateDetection() _port.setBreakEnabled(true); QThread::msleep(10); _port.setBreakEnabled(false); - QThread::usleep(10); + QThread::msleep(10); _port.write(QByteArray("U").repeated(10)); _port.flush(); QThread::msleep(11); diff --git a/src/link/seriallink.h b/src/link/seriallink.h index d3a03aa45..ec65e8fc7 100644 --- a/src/link/seriallink.h +++ b/src/link/seriallink.h @@ -78,6 +78,12 @@ class SerialLink : public AbstractLink { */ bool startConnection() final override; + /** + * @brief get the serial port baudrate + * @return the serial port baudrate + */ + qint32 getBaudRate() { return _port.baudRate(); } + /** * @brief Set a valid baudrate * diff --git a/src/sensor/ping.cpp b/src/sensor/ping.cpp index 3463822cd..99dc87af0 100644 --- a/src/sensor/ping.cpp +++ b/src/sensor/ping.cpp @@ -35,6 +35,8 @@ Ping::Ping() : PingSensor(PingDeviceType::PING1D) , _points(_num_points, 0) { + _flasher = new Flasher(nullptr); + setName("Ping1D"); setControlPanel({"qrc:/Ping1DControlPanel.qml"}); setSensorVisualizer({"qrc:/Ping1DVisualizer.qml"}); @@ -392,7 +394,7 @@ void Ping::flash(const QString& fileUrl, bool sendPingGotoBootloader, int baud, QTimer::singleShot(1000, finishConnection); // Clear last configuration src ID to detect device as a new one - connect(&_flasher, &Flasher::stateChanged, this, [this] { + connect(_flasher, &Flasher::stateChanged, this, [this] { if (flasher()->state() == Flasher::States::FlashFinished) { QThread::msleep(500); // Clear last configuration src ID to detect device as a new one diff --git a/src/sensor/ping360.cpp b/src/sensor/ping360.cpp index cd1bbff08..7b8c86c62 100644 --- a/src/sensor/ping360.cpp +++ b/src/sensor/ping360.cpp @@ -22,6 +22,7 @@ #include "networkmanager.h" #include "networktool.h" #include "notificationmanager.h" +#include "ping360flasher.h" #include "settingsmanager.h" #include @@ -55,22 +56,29 @@ const float Ping360::_angularSpeedGradPerMs = 400.0f / 2400.0f; Ping360::Ping360() : PingSensor(PingDeviceType::PING360) { + _flasher = new Ping360Flasher(nullptr); + // QVector crashs when constructed in initialization list _data = QVector(_maxNumberOfPoints, 0); setName("Ping360"); + setControlPanel({"qrc:/Ping360ControlPanel.qml"}); setSensorVisualizer({"qrc:/Ping360Visualizer.qml"}); setSensorStatusModel({"qrc:/Ping360StatusModel.qml"}); - connect(this, &Sensor::connectionOpen, this, &Ping360::startPreConfigurationProcess); + connect(this, &Sensor::connectionOpen, this, &Ping360::checkBootloader); // Add timer for worst case scenario _timeoutProfileMessage.setInterval(_sensorTimeout); _baudrateConfigurationTimer.setInterval(100); connect(&_timeoutProfileMessage, &QTimer::timeout, this, [this] { - qCWarning(PING_PROTOCOL_PING360) << "Profile message timeout, new request will be done."; + qCWarning(PING_PROTOCOL_PING360) << QString::asprintf( + "Profile message timeout (%d), new request will be done.", _timeoutProfileMessage.interval()); + // reset the baudrate to reinitialize serial communicaiton on the device side + // otherwise, 3.1.1 gets hung up here and doesn't respond to the requests + resetBaudrate(); requestNextProfile(); static int timeoutedTime = 0; @@ -105,15 +113,13 @@ Ping360::Ping360() _commonVariables.deviceInformation.firmware_version_patch); qCDebug(PING_PROTOCOL_PING360) << "Firmware version:" << version; - // Wait for firmware information to be available before looking for new versions - static bool once = false; - if (!once && _commonVariables.deviceInformation.initialized) { - once = true; - + if (_commonVariables.deviceInformation.initialized) { if (version > QVersionNumber(3, 3, 0)) { _profileRequestLogic.type = Ping360RequestStateStruct::Type::AutoTransmitAsync; + qCInfo(PING_PROTOCOL_PING360) << "using asynchronous automatic transmit strategy"; } else { _profileRequestLogic.type = Ping360RequestStateStruct::Type::Legacy; + qCInfo(PING_PROTOCOL_PING360) << "using synchronous transmit strategy"; } } }); @@ -125,6 +131,63 @@ Ping360::Ping360() // By default heading integration is enabled enableHeadingIntegration(true); + + connect(this, &Ping360::firmwareVersionMinorChanged, this, [this] { + // Wait for firmware information to be available before looking for new versions + static bool once = false; + if (!once) { + once = true; + NetworkTool::self()->checkNewFirmware( + "ping360", std::bind(&Ping360::checkNewFirmwareInGitHubPayload, this, std::placeholders::_1)); + } + }); +} + +void Ping360::checkBootloader() +{ + _isBootloader = false; + emit isBootloaderChanged(); + if (!link()) { + return; + } + + // bootloader may only be present on serial links, it does not communicate + // by ethernet + if (link()->type() != LinkType::Serial) { + startPreConfigurationProcess(); + return; + } + if (!link()->isOpen()) { + return; + } + if (!link()->isWritable()) { + return; + } + + qCWarning(PING_PROTOCOL_PING360) << "checking for bootloader..."; + Ping360BootloaderPacket::packet_cmd_read_version_t readVersion + = Ping360BootloaderPacket::packet_cmd_read_version_init; + Ping360BootloaderPacket::packet_update_footer(readVersion.data); + link()->write( + reinterpret_cast(readVersion.data), Ping360BootloaderPacket::packet_get_length(readVersion.data)); + _ping360BootloaderPacketParser.reset(); + + QMetaObject::Connection blScanCallback + = connect(link(), &AbstractLink::newData, this, [this](const QByteArray& data) { + for (auto byte : data) { + if (_ping360BootloaderPacketParser.packet_parse_byte(byte) == Ping360BootloaderPacket::NEW_MESSAGE) { + qCWarning(PING_PROTOCOL_PING360) << "bootloader found!"; + _isBootloader = true; + emit isBootloaderChanged(); + emit firmwareVersionMinorChanged(); + } + } + }); + + QTimer::singleShot(250, [=] { + disconnect(blScanCallback); + startConfiguration(); + }); } void Ping360::startPreConfigurationProcess() @@ -244,32 +307,45 @@ void Ping360::legacyProfileRequest() void Ping360::asyncProfileRequest() { if (!_sensorSettings.valid) { - qCDebug(PING_PROTOCOL_PING360) << "Invalid automatic sensor configuration, sending message."; - ping360_auto_transmit auto_transmit; + qCDebug(PING_PROTOCOL_PING360) << "Invalid automatic sensor configuration."; _sensorSettings.start_angle = angle_offset() - _sectorSize / 2; _sensorSettings.end_angle = (angle_offset() + _sectorSize / 2 - 1); // Make sure that we are still inside our valid space _sensorSettings.end_angle %= _angularResolutionGrad; + } - auto_transmit.set_mode(1); - auto_transmit.set_gain_setting(_sensorSettings.gain_setting); - auto_transmit.set_transmit_duration(_sensorSettings.transmit_duration); - auto_transmit.set_sample_period(_sensorSettings.sample_period); - auto_transmit.set_transmit_frequency(_sensorSettings.transmit_frequency); - auto_transmit.set_number_of_samples(_sensorSettings.num_points); + qCDebug(PING_PROTOCOL_PING360) << QString::asprintf( + "Sending auto transmit message. start: %d end: %d", _sensorSettings.start_angle, _sensorSettings.end_angle); - auto_transmit.set_start_angle(_sensorSettings.start_angle); - auto_transmit.set_stop_angle(_sensorSettings.end_angle); - auto_transmit.set_num_steps(_angular_speed); - auto_transmit.set_delay(0); - auto_transmit.updateChecksum(); + ping360_auto_transmit auto_transmit; + auto_transmit.set_mode(1); + auto_transmit.set_gain_setting(_sensorSettings.gain_setting); + auto_transmit.set_transmit_duration(_sensorSettings.transmit_duration); + auto_transmit.set_sample_period(_sensorSettings.sample_period); + auto_transmit.set_transmit_frequency(_sensorSettings.transmit_frequency); + auto_transmit.set_number_of_samples(_sensorSettings.num_points); - writeMessage(auto_transmit); - } + auto_transmit.set_start_angle(_sensorSettings.start_angle); + auto_transmit.set_stop_angle(_sensorSettings.end_angle); + auto_transmit.set_num_steps(_sensorSettings.num_steps); + auto_transmit.set_delay(0); + auto_transmit.updateChecksum(); + + char msgString[1024]; + auto_transmit.getMessageAsString(msgString, 1024); + qCDebug(PING_PROTOCOL_PING360) << "Sending auto transmit message:" << msgString; + + // reset the baudrate to stop any ongoing async data transmission from the device and avoid collisions + resetBaudrate(); + writeMessage(auto_transmit); + + _timeoutProfileMessage.start(_sensorTimeout); } void Ping360::handleMessage(const ping_message& msg) { + static uint8_t _waitRetryMessages = 1; + qCDebug(PING_PROTOCOL_PING360) << QStringLiteral("Handling Message: %1 [%2]") .arg(PingHelper::nameFromMessageId( static_cast(msg.message_id()))) @@ -350,19 +426,15 @@ void Ping360::handleMessage(const ping_message& msg) } case Ping360Id::AUTO_DEVICE_DATA: { + // Stop profile message timeout, the data will be automatically sent + _timeoutProfileMessage.stop(); + // Parse message const ping360_auto_device_data autoDeviceData = *static_cast(&msg); // Get angle to request next message _angle = autoDeviceData.angle(); - // Restart timer, if the channel allows it - if (link()->isWritable()) { - // Use 200ms for network delay - const int profileRunningTimeout = _angular_speed / _angularSpeedGradPerMs + 200; - _timeoutProfileMessage.start(profileRunningTimeout); - } - _data.resize(autoDeviceData.data_length()); #pragma omp for for (int i = 0; i < autoDeviceData.data_length(); i++) { @@ -385,7 +457,7 @@ void Ping360::handleMessage(const ping_message& msg) _sensorSettings.checkSector = true; _sensorSettings.checkValidation({autoDeviceData.transmit_duration(), autoDeviceData.gain_setting(), autoDeviceData.data_length(), autoDeviceData.sample_period(), autoDeviceData.transmit_frequency(), - autoDeviceData.start_angle(), autoDeviceData.stop_angle()}); + autoDeviceData.start_angle(), autoDeviceData.stop_angle(), autoDeviceData.num_steps()}); // Everything should be valid, otherwise the sensor is not in sync if (!link()->isWritable() && _sensorSettings.valid) { @@ -394,10 +466,28 @@ void Ping360::handleMessage(const ping_message& msg) set_sample_period(autoDeviceData.sample_period()); set_transmit_frequency(autoDeviceData.transmit_frequency()); set_number_of_points(autoDeviceData.data_length()); + set_angular_speed(autoDeviceData.num_steps()); } + // If the settings in the received message do not match the + // settings selected by the user, send a new request with + // the correct settings. + // Given the nature of UDP and the asynchronous handling of + // the program, some messages with the old settings may be + // recieved after sending the new request. + // _waitRetryMessages will prevent multiple redundant requests + // from being sent while the program and the device resynchronize + // with the new settings. if (!_sensorSettings.valid) { - requestNextProfile(); + if (!--_waitRetryMessages) { + // Allow 5 messages with old settings to be recieved + // before sending a new request. + _waitRetryMessages = 5; + qCDebug(PING_PROTOCOL_PING360) << "AUTO_DEVICE_DATA parameters do not match settings (invalid)"; + requestNextProfile(); + } + } else { + _waitRetryMessages = 1; } break; @@ -405,7 +495,10 @@ void Ping360::handleMessage(const ping_message& msg) case CommonId::NACK: { const common_nack nack(msg); - if (nack.nacked_id() == Ping360Id::TRANSDUCER) { + char msgString[1024]; + nack.getMessageAsString(msgString, 1024); + qCWarning(PING_PROTOCOL_PING360) << "Got NACK" << msgString; + if (nack.nacked_id() == Ping360Id::TRANSDUCER || nack.nacked_id() == Ping360Id::AUTO_TRANSMIT) { qCWarning(PING_PROTOCOL_PING360) << "transducer control was NACKED, reverting to default settings"; set_gain_setting(_firmwareDefaultGainSetting); @@ -442,20 +535,114 @@ void Ping360::handleMessage(const ping_message& msg) void Ping360::firmwareUpdate(QString fileUrl, bool sendPingGotoBootloader, int baud, bool verify) { - Q_UNUSED(fileUrl) - Q_UNUSED(sendPingGotoBootloader) - Q_UNUSED(baud) - Q_UNUSED(verify) - // TODO + if (fileUrl.contains("http")) { + NetworkManager::self()->download(fileUrl, [this, sendPingGotoBootloader, baud, verify](const QString& path) { + qCDebug(FLASH) << "Downloaded firmware:" << path; + flash(path, sendPingGotoBootloader, baud, verify); + }); + } else { + flash(fileUrl, sendPingGotoBootloader, baud, verify); + } } void Ping360::flash(const QString& fileUrl, bool sendPingGotoBootloader, int baud, bool verify) { - Q_UNUSED(fileUrl) - Q_UNUSED(sendPingGotoBootloader) - Q_UNUSED(baud) - Q_UNUSED(verify) - // TODO + flasher()->setState(Flasher::Idle); + flasher()->setState(Flasher::StartingFlash); + if (!HexValidator::isValidFile(fileUrl)) { + auto errorMsg = QStringLiteral("File does not contain a valid Intel Hex format: %1").arg(fileUrl); + qCWarning(PING_PROTOCOL_PING360) << errorMsg; + flasher()->setState(Flasher::Error, errorMsg); + return; + }; + + SerialLink* serialLink = dynamic_cast(link()); + if (!serialLink) { + auto errorMsg = QStringLiteral("It's only possible to flash via serial."); + qCWarning(PING_PROTOCOL_PING360) << errorMsg; + flasher()->setState(Flasher::Error, errorMsg); + return; + } + + // cache the current baudrate so that we can connect with the same + // after flashing + qint32 oldBaudRate = serialLink->getBaudRate(); + + if (!link()->isOpen()) { + auto errorMsg = QStringLiteral("Link is not open to do the flash procedure."); + qCWarning(PING_PROTOCOL_PING360) << errorMsg; + flasher()->setState(Flasher::Error, errorMsg); + return; + } + + // Stop requests and messages from the sensor + _timeoutProfileMessage.stop(); + + if (sendPingGotoBootloader) { + qCDebug(PING_PROTOCOL_PING360) << "Put it in bootloader mode."; + + // set the baudrate for two reasons: + // 1: if doing an auto scan, this will break the sensor out of + // automatic transmission mode + // 2: use a low baudrate to decrease chances of corruption in transmission + ping360_reset m; + m.set_bootloader(1); + m.updateChecksum(); + // send the message 5x + for (int i = 0; i < 5; i++) { + setBaudRate(115200); + QThread::msleep(25); + writeMessage(m); + while (serialLink->port()->bytesToWrite()) { + // We are not changing the connection structure, only waiting for bytes to be written + const_cast(serialLink->port())->waitForBytesWritten(); + } + } + } + + // Wait for bytes to be written before finishing the connection + while (serialLink->port()->bytesToWrite()) { + qCDebug(PING_PROTOCOL_PING360) << "Waiting for bytes to be written..."; + // We are not changing the connection structure, only waiting for bytes to be written + const_cast(serialLink->port())->waitForBytesWritten(); + qCDebug(PING_PROTOCOL_PING360) << "Done !"; + } + + qCDebug(PING_PROTOCOL_PING360) << "Finish connection."; + auto flashSensor = [=] { + flasher()->setBaudRate(baud); + flasher()->setFirmwarePath(fileUrl); + flasher()->setLink(link()->configuration()[0]); + flasher()->setVerify(verify); + flasher()->flash(); + }; + + auto finishConnection = [=] { + link()->finishConnection(); + + qCDebug(PING_PROTOCOL_PING360) << "Save sensor configuration."; + updateSensorConfigurationSettings(); + + qCDebug(PING_PROTOCOL_PING360) << "Start flash."; + + // we stop the timer again after the link is closed + // in case a device data message came in asynchronously and restarted + // the timer + _timeoutProfileMessage.stop(); + + QTimer::singleShot(500, flashSensor); + }; + QTimer::singleShot(250, finishConnection); + + connect(_flasher, &Flasher::stateChanged, this, [this, oldBaudRate] { + if (flasher()->state() == Flasher::States::FlashFinished) { + QThread::msleep(500); + // Clear last configuration src ID to detect device as a new one + resetSensorLocalVariables(); + setBaudRate(oldBaudRate); + Sensor::connectLink(*link()->configuration()); + } + }); } void Ping360::setLastSensorConfiguration() @@ -476,8 +663,33 @@ void Ping360::printSensorInformation() const void Ping360::checkNewFirmwareInGitHubPayload(const QJsonDocument& jsonDocument) { - Q_UNUSED(jsonDocument) - // TODO + float lastVersionAvailable = 0.0; + + auto filesPayload = jsonDocument.array(); + for (const QJsonValue& filePayload : filesPayload) { + qCDebug(PING_PROTOCOL_PING360) << filePayload["name"].toString(); + + // Get version from Ping[_|-]V(major).(patch)*.hex where (major).(patch) is + static const QRegularExpression versionRegex(QStringLiteral(R"(Ping360[_|-]V(?\d+\.\d+).*\.hex)")); + auto filePayloadVersion = versionRegex.match(filePayload["name"].toString()).captured("version").toFloat(); + _firmwares[filePayload["name"].toString()] = filePayload["download_url"].toString(); + + if (filePayloadVersion > lastVersionAvailable) { + lastVersionAvailable = filePayloadVersion; + } + } + emit firmwaresAvailableChanged(); + + auto sensorVersion = QString("%1.%2") + .arg(_commonVariables.deviceInformation.firmware_version_major) + .arg(_commonVariables.deviceInformation.firmware_version_minor) + .toFloat(); + static QString firmwareUpdateSteps {"https://github.com/bluerobotics/ping-viewer/wiki/firmware-update"}; + if (lastVersionAvailable > sensorVersion) { + QString newVersionText = QStringLiteral("Firmware update for Ping available: %1
").arg(lastVersionAvailable) + + QStringLiteral("Check firmware update steps here!").arg(firmwareUpdateSteps); + NotificationManager::self()->create(newVersionText, "green", StyleManager::infoIcon()); + } } void Ping360::resetSensorLocalVariables() @@ -498,6 +710,22 @@ const QList& Ping360::validBaudRates() const QVariantList& Ping360::validBaudRatesAsVariantList() const { return _validBaudRates; } +void Ping360::resetBaudrate() +{ + SerialLink* serialLink = dynamic_cast(link()); + if (serialLink) { + setBaudRate(serialLink->getBaudRate()); + } else { + // send an empty datagram on network links to signal + // the serial bridge program to send a line break and + // perform the auto-baudrate procedure + if (link() && link()->isOpen() && link()->isWritable()) { + link()->write(nullptr, 0); + } + } + QThread::msleep(200); +} + void Ping360::setBaudRate(int baudRate) { // It's only possible to change baudrates in serial connections @@ -681,13 +909,19 @@ Ping360::~Ping360() { updateSensorConfigurationSettings(); - // TODO: Find a better way - // Force sensor to stop sensor if running with anything different from Legacy mode - // The sensor will stop any automatic behaviour when receiving a normal profile request message - if (_profileRequestLogic.type != Ping360RequestStateStruct::Type::Legacy) { - for (int i {0}; i < 10; i++) { - deltaStep(0); - QThread::msleep(100); - } + ping360_motor_off message; + message.updateChecksum(); + + // set the baudrate for two reasons: + // 1: if doing an auto scan, this will break the sensor out of + // automatic transmission mode + // 2: use a low baudrate to decrease chances of corruption in transmission + setBaudRate(115200); + resetBaudrate(); // in case of network connection + QThread::msleep(100); + // Stop scanning and turn off the stepper motor + for (int i {0}; i < 10; i++) { + writeMessage(message); + QThread::msleep(100); } } diff --git a/src/sensor/ping360.h b/src/sensor/ping360.h index 5524b7335..a86c139d1 100644 --- a/src/sensor/ping360.h +++ b/src/sensor/ping360.h @@ -12,6 +12,7 @@ #include "parser.h" #include "ping-message-common.h" #include "ping-message-ping360.h" +#include "ping360bootloaderpacket.h" #include "pingparserext.h" #include "pingsensor.h" #include "protocoldetector.h" @@ -250,9 +251,20 @@ class Ping360 : public PingSensor { double desiredRange = round(range()); _speed_of_sound = speed_of_sound; _sensorSettings.sample_period = calculateSamplePeriod(desiredRange); + + // reduce _sample period until we are within operational parameters + // maximize the number of points + while (_sensorSettings.sample_period < _firmwareMinSamplePeriod) { + _sensorSettings.num_points--; + _sensorSettings.sample_period = calculateSamplePeriod(desiredRange); + } + + emit numberOfPointsChanged(); emit speedOfSoundChanged(); emit samplePeriodChanged(); - emit rangeChanged(); + emit rangeChanged(); // does this belong here? + emit transmitDurationMaxChanged(); + adjustTransmitDuration(); } } Q_PROPERTY(int speed_of_sound READ speed_of_sound WRITE set_speed_of_sound NOTIFY speedOfSoundChanged) @@ -294,6 +306,7 @@ class Ping360 : public PingSensor { { if (angular_speed != _angular_speed) { _angular_speed = angular_speed; + _sensorSettings.num_steps = angular_speed; emit angularSpeedChanged(); } } @@ -435,6 +448,12 @@ class Ping360 : public PingSensor { float heading() const { return _heading; } Q_PROPERTY(float heading READ heading NOTIFY headingChanged) + /** + * @brief flag is true if the device is in bootloader mode + */ + bool isBootloader() { return _isBootloader; } + Q_PROPERTY(bool isBootloader READ isBootloader NOTIFY isBootloaderChanged) + /** * @brief adjust the transmit duration according to automatic mode, and current configuration */ @@ -483,6 +502,14 @@ class Ping360 : public PingSensor { const QList& validBaudRates(); Q_PROPERTY(QVariant validBaudRates READ validBaudRatesAsVariantList CONSTANT) + /** + * @brief Send a line break and renegoatiate the current baudrate + * this works for serial and network connections (the bridge program + * will renegotiate the baudrate for network connections) + * + */ + void resetBaudrate(); + /** * @brief Set the communication baud rate * @@ -548,6 +575,7 @@ class Ping360 : public PingSensor { void transmitDurationChanged(); void transmitDurationMaxChanged(); void transmitFrequencyChanged(); + void isBootloaderChanged(); ///@} private: @@ -606,7 +634,8 @@ class Ping360 : public PingSensor { uint16_t transmit_frequency = _viewerDefaultTransmitFrequency; uint16_t start_angle = 0; - uint16_t end_angle = 400; + uint16_t end_angle = 399; + uint8_t num_steps = 1; bool checkSector = false; bool valid = true; @@ -624,12 +653,16 @@ class Ping360 : public PingSensor { && other.num_points == num_points && other.sample_period == sample_period && other.transmit_frequency == transmit_frequency; if (checkSector && valid) { - valid = other.start_angle == start_angle && other.end_angle == end_angle; + valid + = other.start_angle == start_angle && other.end_angle == end_angle && other.num_steps == num_steps; } return valid; } } _sensorSettings; + // Counter for recieving new messages before retrying a request + uint8_t _waitRetryMessages; + // This variables are not user configuration settings uint16_t _angle = 200; QVector _data; @@ -866,4 +899,21 @@ class Ping360 : public PingSensor { * @param message */ void processMavlinkMessage(const mavlink_message_t& message); + + /** + * @brief Check the link to see if the sonar is stuck in the bootloader + * this is performed once when the link is opened + */ + void checkBootloader(); + + /** + * @brief This is used to probe/communicate with the bootloader + */ + Ping360BootloaderPacket _ping360BootloaderPacketParser; + + /** + * @brief This is used to flag the qml so that the user can be notified that the + * device is stuck in the bootloader + */ + bool _isBootloader = false; }; diff --git a/src/sensor/protocoldetector.cpp b/src/sensor/protocoldetector.cpp index 641d13362..424dec3e2 100644 --- a/src/sensor/protocoldetector.cpp +++ b/src/sensor/protocoldetector.cpp @@ -169,6 +169,47 @@ bool ProtocolDetector::checkSerial(LinkConfiguration& linkConf) _detected = checkBuffer(port.readAll(), linkConf); } + // no ping device, check for ping360 bootloader + if (!_detected) { + + // Probe for Ping360 Bootloader + Ping360BootloaderPacket::packet_cmd_read_dev_id_t readDevId + = Ping360BootloaderPacket::packet_cmd_read_dev_id_init; + Ping360BootloaderPacket::packet_update_footer(readDevId.data); + port.write( + reinterpret_cast(readDevId.data), Ping360BootloaderPacket::packet_get_length(readDevId.data)); + port.waitForBytesWritten(100); + + // Try to get a valid response, timeout after 5 * 50 ms + _ping360BootloaderPacket.reset(); + attempts = 0; + while (_active && !_detected && attempts++ < 5) { + port.waitForReadyRead(50); + _detected = checkBuffer(port.readAll(), linkConf); + } + + // The device may have just been plugged in, and will stay in the bootloader + // after bootloader contact has been made. Send a reset command to start the main + // firmware application it has a valid firmware + if (_detected) { + qCInfo(PING_PROTOCOL_PROTOCOLDETECTOR) << "resetting ping360 processor"; + + Ping360BootloaderPacket::packet_cmd_jump_start_t jumpStart + = Ping360BootloaderPacket::packet_cmd_jump_start_init; + Ping360BootloaderPacket::packet_update_footer(jumpStart.data); + port.write(reinterpret_cast(jumpStart.data), + Ping360BootloaderPacket::packet_get_length(jumpStart.data)); + + port.waitForBytesWritten(100); + port.waitForReadyRead(100); + _ping360BootloaderPacket.reset(); + + if (checkBuffer(port.readAll(), linkConf)) { + qCInfo(PING_PROTOCOL_PROTOCOLDETECTOR) << "got response to reset command"; + } + } + } + port.close(); return _detected; @@ -201,12 +242,17 @@ bool ProtocolDetector::checkUdp(LinkConfiguration& linkConf) return _detected; } + // Send an empty datagram to signal to the serial bridge + // program to send a line break and perform the auto-baudrate + // procedure + socket.write(nullptr, 0); + QThread::msleep(100); // Send message socket.write(_deviceInformationMessageByteArray); int attempts = 0; - // Try to get a valid response, timeout after 10 * 50 ms + // Try to get a valid response, timeout after 20 * 50 ms while (_active && !_detected && attempts++ < 20) { socket.waitForReadyRead(50); /** @@ -234,7 +280,10 @@ bool ProtocolDetector::checkUdp(LinkConfiguration& linkConf) bool ProtocolDetector::checkBuffer(const QByteArray& buffer, LinkConfiguration& linkConf) { - qCDebug(PING_PROTOCOL_PROTOCOLDETECTOR) << buffer; + if (buffer.isEmpty()) { + return false; + } + qCDebug(PING_PROTOCOL_PROTOCOLDETECTOR) << "received buffer:" << buffer; for (const auto& byte : buffer) { if (_parser.parseByte(byte) == Parser::NEW_MESSAGE) { // Print information from detected devices @@ -259,6 +308,11 @@ bool ProtocolDetector::checkBuffer(const QByteArray& buffer, LinkConfiguration& } return true; } + if (_ping360BootloaderPacket.packet_parse_byte(byte) == Ping360BootloaderPacket::NEW_MESSAGE) { + qCCritical(PING_PROTOCOL_PROTOCOLDETECTOR) << "received ping360 bootloader packet"; + linkConf.setDeviceType(PingDeviceType::PING360); + return true; + } } return false; } diff --git a/src/sensor/protocoldetector.h b/src/sensor/protocoldetector.h index f39bfcf59..b617b3a67 100644 --- a/src/sensor/protocoldetector.h +++ b/src/sensor/protocoldetector.h @@ -2,8 +2,8 @@ #include -#include "abstractlink.h" #include "linkconfiguration.h" +#include "ping360bootloaderpacket.h" #include "pingparserext.h" class QSerialPortInfo; @@ -124,4 +124,5 @@ private slots: static const QStringList _invalidSerialPortNames; QByteArray _deviceInformationMessageByteArray; PingParserExt _parser; + Ping360BootloaderPacket _ping360BootloaderPacket; }; diff --git a/src/sensor/sensor.cpp b/src/sensor/sensor.cpp index c7efc1d4f..b9dd24336 100644 --- a/src/sensor/sensor.cpp +++ b/src/sensor/sensor.cpp @@ -16,6 +16,7 @@ const QUrl Sensor::_defaultControlPanelUrl("qrc:/NoControlPanel.qml"); Sensor::Sensor(SensorInfo sensorInfo) : _connected(false) , _controlPanelUrl(_defaultControlPanelUrl) + , _flasher(nullptr) , _linkIn(new Link()) , _linkOut(nullptr) , _parser(nullptr) diff --git a/src/sensor/sensor.h b/src/sensor/sensor.h index 7c9112907..d273c7638 100644 --- a/src/sensor/sensor.h +++ b/src/sensor/sensor.h @@ -92,7 +92,7 @@ class Sensor : public QObject { * * @return Flasher* */ - Flasher* flasher() { return &_flasher; }; + Flasher* flasher() { return _flasher; }; Q_PROPERTY(Flasher* flasher READ flasher CONSTANT) /** @@ -157,7 +157,7 @@ class Sensor : public QObject { QMap _firmwares; // This class should be a singleton that will work with the future DeviceManager class // TODO: Move to a singleton and integrate with DeviceManager - Flasher _flasher; + Flasher* _flasher; QSharedPointer _linkIn; QSharedPointer _linkOut; Parser* _parser; // communication implementation