c++serial-portboost-asiocom-port

Reading com port using asio library. The checksum does not match. Python->c++


I have a small project in C++. Reading the COM port. I have code that opens a COM port and reads values from it. When compressing the buffer during checksum check with the generated checksum the values do not match.

It means that

 if (checksum != generated_checksum) {
     return;
 }

is fulfilled.

This is my code.

#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;
namespace asio = boost::asio;
using std::this_thread::sleep_for;

// mocking question code
struct {
    void info(std::string const& msg) const {
        static auto const start = std::chrono::steady_clock::now();

        auto ts = (std::chrono::steady_clock::now() - start);
        if (ts > 1s)
            std::cout << std::setw(6) << ts / 1.s << "s  " << msg << std::endl;
        else
            std::cout << std::setw(6) << ts / 1ms << "ms " << msg << std::endl;
    }
} static g_logger;
// end mocks

struct COM {
    asio::thread_pool m_ioc{1};
    asio::serial_port m_port{m_ioc};
    std::atomic_int   m_Data = 0;

    std::weak_ptr<void> m_readoperation; // only when read operation active

    void ReadLoop(std::shared_ptr<std::vector<unsigned char>> buffer = {}) {
        if (!buffer) {
            assert(m_readoperation.expired()); // do not post overlapping read operations
            buffer          = std::make_shared<std::vector<unsigned char>>();
            m_readoperation = buffer;
        }

        asio::async_read(
            m_port, asio::dynamic_buffer(*buffer), asio::transfer_at_least(1),
            [this, buffer](boost::system::error_code ec, size_t /*length*/) {
                if (!ec) {
                    try {
                        // Directly process binary data from buffer here.
                        size_t length = buffer->size();
                        m_Data.store(length);

                        // Example processing based on your description
                        for (size_t i = 0; i < length; ++i) {
                            if (i + 2 < buffer->size() && (*buffer)[i] == 170 && (*buffer)[i + 1] == 170) {
                                size_t packetLength = (*buffer)[i + 2];
                                if (packetLength < 4 || packetLength >= 170) {
                                    continue; // Invalid packet length, move to next byte.
                                }
                                if (i + 2 + packetLength <= buffer->size()) {
                                    // Assume parse_packet processes a packet starting from a specific index
                                    // and uses packetLength to determine the end.
                                    // This function needs to be adapted to accept a start index and length,
                                    // or you need to create a sub-vector to pass to it.
                                    parse_packet({buffer->begin() + i, buffer->begin() + i + packetLength});
                                    i += packetLength - 1; // Move to the end of the packet.
                                }
                            }
                        }

                        // Clear buffer after processing, or handle remaining bytes as needed.
                        buffer->clear();
                        // Continue reading into the same buffer for new data.
                        ReadLoop(buffer);
                    } catch (const std::exception& e) {
                        g_logger.info("Error processing received data: " + std::string(e.what()));
                    }
                } else {
                    // Handle read errors.
                    g_logger.info("ReadLoop error: " + ec.message());
                }
            });
    }

    void parse_packet(std::vector<unsigned char> const& data) {
        if (data.size() < 4)
            return; // Minimal packet length

        int generated_checksum = 0;
        for (size_t i = 0; i < data.size() - 1; ++i) { // Exclude the last byte, which is the checksum
            generated_checksum += data[i];
        }
        generated_checksum = 255 - (generated_checksum % 256);

        int checksum = data.back(); // Assume the last byte is the checksum
        g_logger.info("Checksum: " + std::to_string(checksum) +
                      ", Generated checksum: " + std::to_string(generated_checksum));
        if (checksum != generated_checksum) {
            g_logger.info("Checksum failed ");
            return; // Checksum mismatch
        }

        // Iterate through packet data, excluding the checksum
        size_t i = 0;
        while (i < data.size() - 1) {
            unsigned char data_type = data[i++];
            switch (data_type) {
            case 1: { // Battery level
                //..rest of code..//
                //
            }
            }
        }
    }

    void initialize(bool enable = true) {
        sleep_for(100ms);

        m_port.open("COM5");

        // Configure serial port settings
        using P = asio::serial_port;
        m_port.set_option(P::baud_rate(115200));
        m_port.set_option(P::character_size(8));
        m_port.set_option(P::parity(P::parity::none));
        m_port.set_option(P::stop_bits(P::stop_bits::one));
        m_port.set_option(P::flow_control(P::flow_control::none));

        g_logger.info("Connected");

        setEnabled(enable);
    }

    void setEnabled(bool enable) {
        if (enable == !m_readoperation.expired())
            return; // nothing to do

        if (!enable)
            m_port.cancel(); // cancels read operation (asio::error::operation_aborted)
        else
            ReadLoop();
    }

    void terminate() {
        setEnabled(false);
        g_logger.info("Bye!");
    }
};

// demo code
int main() {
    std::cout << std::fixed << std::setprecision(2);
    g_logger.info("Demo start");
    COM g_com;

    g_com.initialize(false);

    for (auto i = 0; i < 10; ++i) {
        sleep_for(3.5s);
        g_logger.info("Main thread observed atomic int value: " + to_string(g_com.m_Data));

        bool enable = i % 2; // enable if odd, disable if even
        g_logger.info(enable ? "Enabling read operation" : "Disabling read operation");
        g_com.setEnabled(i % 2);
    }

    g_com.terminate();
}

This is output

Checksum: 128, Generated checksum: 167
Checksum failed 
Checksum: 128, Generated checksum: 167
Checksum failed 
Checksum: 128, Generated checksum: 167
Checksum failed 
Checksum: 128, Generated checksum: 167
Checksum failed 

The checksum does not change at all and neither does the generated checksum.

In Python, checksums look like this:

91 , 91
109 , 109
114 , 114
83 , 83
38 , 38
25 , 25
45 , 45
57 , 57
76 , 76
82 , 82
83 , 83
84 , 84
67 , 67
69 , 69
100 , 100

I can't find the cause of the checksum error

Python Code:

def receive_byte():
    global CurrentPosition
    if CurrentPosition >= len(ReceivedString):
        return 0
    result = ReceivedString[CurrentPosition]
    CurrentPosition += 1
    return result

def skip_byte():
    global CurrentPosition
    CurrentPosition += 1

def drop_parsing():
    global UnparsedRemainingString, ReceivedString, CurrentPosition
    UnparsedRemainingString = b""
    if CurrentPosition > 3:
        UnparsedRemainingString = ReceivedString[CurrentPosition-3:]
    if len(UnparsedRemainingString) > 128:
        UnparsedRemainingString = b""
    ReceivedString = b""

def parse_packet(length):
    global Raw_count, needSave, SaveFile
    generated_checksum = 0
    payload_data = [0 for _ in range(171)]  # Initializing payloadData array
    for i in range(length):
        payload_data[i] = receive_byte()
        generated_checksum += payload_data[i]
    generated_checksum = 255 - generated_checksum % 256
    checksum = receive_byte()
    print(checksum,",", generated_checksum)
    if checksum != generated_checksum:
        return
    
    if length < 4:
        return

    i = 0
    while i < length - 1:
        data_type = payload_data[i]
        i += 1
        if data_type == 1:  # battery level
            battery_level = payload_data[i]
            i += 1
            print(f"battery_level: {battery_level}") 
 //....//
def read_data_from_serial(port='/dev/ttyUSB0', baudrate=9600):
    global ReceivedString, CurrentPosition, UnparsedRemainingString
    ser = serial.Serial(port, baudrate, timeout=1)
    while True:
        data = ser.read(ser.in_waiting or 1)
        if data:
            ReceivedString = UnparsedRemainingString + data
            CurrentPosition = 0
            while CurrentPosition < len(ReceivedString) - 2:
                if receive_byte() == 170 and receive_byte() == 170:
                    length = receive_byte()
                    if length < 4 or length >= 170:
                        continue
                    if CurrentPosition + length > len(ReceivedString):
                        drop_parsing()
                        break
                    else:
                        parse_packet(length)

if __name__ == "__main__":
    read_data_from_serial('COM6', 115200)  # Example usage

What am I doing wrong?


Solution

  • Reverse engineering from the C++ code, your message structure is:

    \xaa \xaa LEN DATA[LEN]

    When matching "\xaa\xaa" at i you pass only the buffer range [i, i+LEN) to parser_packet. That's 3 bytes short. This fixes it for me:

    asio::async_read(
        m_port, asio::dynamic_buffer(*pbuf), asio::transfer_at_least(1),
        [this, pbuf](boost::system::error_code ec, size_t /*length*/) {
            if (!ec) {
                try {
                    // Directly process binary data from buffer here.
                    auto&        buffer = *pbuf;
                    size_t const length = buffer.size();
                    m_Data.store(length);
    
                    // message \xaa \xaa LEN DATA[LEN]
    
                    // Example processing based on your description
                    if (length > 2)
                        for (size_t i = 0; i < length - 2; ++i) {
                            if (buffer[i] == 170 && buffer[i + 1] == 170) {
                                size_t packetLength = buffer[i + 2];
                                if (packetLength < 4 || packetLength >= 170) {
                                    continue; // Invalid packet length, move to next byte.
                                }
                                if (i + 3 + packetLength <= buffer.size()) {
                                    parse_packet({buffer.begin() + i + 3, packetLength});
                                    i += packetLength - 1; // Move to the end of the packet.
                                }
                            }
                        }
    
                    // Clear buffer after processing, or handle remaining bytes as needed.
                    buffer.clear();
                    // Continue reading into the same buffer for new data.
                    ReadLoop(pbuf);
                } catch (const std::exception& e) {
                    g_logger.info("Error processing received data: " + std::string(e.what()));
                }
            } else {
                // Handle read errors.
                g_logger.info("ReadLoop error: " + ec.message());
            }
        });
    

    That is to say, when testing with a payload of "\xaa\xaa\x04abc\xd9" it decodes back to 217 (0xd9) correctly.

    I also suggest using span to pass to parse_packet instead of copying the data:

    void parse_packet(std::span<uint8_t const> data) {
    

    Live Demo

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iostream>
    using namespace std::chrono_literals;
    using namespace std::string_literals;
    namespace asio = boost::asio;
    using std::this_thread::sleep_for;
    
    // mocking question code
    struct {
        void info(std::string const& msg) const {
            static auto const start = std::chrono::steady_clock::now();
    
            auto ts = (std::chrono::steady_clock::now() - start);
            if (ts > 1s)
                std::cout << std::setw(6) << ts / 1.s << "s  " << msg << std::endl;
            else
                std::cout << std::setw(6) << ts / 1ms << "ms " << msg << std::endl;
        }
    } static g_logger;
    // end mocks
    
    struct COM {
        asio::thread_pool m_ioc{1};
        asio::serial_port m_port{m_ioc};
        std::atomic_int   m_Data = 0;
    
        std::weak_ptr<void> m_readoperation; // only when read operation active
    
        ~COM() { m_ioc.join(); }
    
        void ReadLoop(std::shared_ptr<std::vector<unsigned char>> pbuf = {}) {
            g_logger.info("Readloop" + (pbuf ? " with buffer"s : ""));
            if (!pbuf) {
                assert(m_readoperation.expired()); // do not post overlapping read operations
                pbuf          = std::make_shared<std::vector<unsigned char>>();
                m_readoperation = pbuf;
            }
    
            asio::async_read(
                m_port, asio::dynamic_buffer(*pbuf), asio::transfer_at_least(1),
                [this, pbuf](boost::system::error_code ec, size_t /*length*/) {
                    if (!ec) {
                        try {
                            // Directly process binary data from buffer here.
                            auto&        buffer = *pbuf;
                            size_t const length = buffer.size();
                            m_Data.store(length);
    
                            // message \xaa \xaa LEN DATA[LEN]
    
                            // Example processing based on your description
                            if (length > 2)
                                for (size_t i = 0; i < length - 2; ++i) {
                                    if (buffer[i] == 170 && buffer[i + 1] == 170) {
                                        size_t packetLength = buffer[i + 2];
                                        if (packetLength < 4 || packetLength >= 170) {
                                            continue; // Invalid packet length, move to next byte.
                                        }
                                        if (i + 3 + packetLength <= buffer.size()) {
                                            parse_packet({buffer.begin() + i + 3, packetLength});
                                            i += packetLength - 1; // Move to the end of the packet.
                                        }
                                    }
                                }
    
                            // Clear buffer after processing, or handle remaining bytes as needed.
                            buffer.clear();
                            // Continue reading into the same buffer for new data.
                            ReadLoop(pbuf);
                        } catch (const std::exception& e) {
                            g_logger.info("Error processing received data: " + std::string(e.what()));
                        }
                    } else {
                        // Handle read errors.
                        g_logger.info("ReadLoop error: " + ec.message());
                    }
                });
        }
    
        void parse_packet(std::span<uint8_t const> data) {
            if (data.size() < 4)
                return; // Minimal packet length
    
            int generated_checksum = 0;
            for (size_t i = 0; i < data.size() - 1; ++i) { // Exclude the last byte, which is the checksum
                generated_checksum += data[i];
            }
            generated_checksum = 255 - (generated_checksum % 256);
    
            int checksum = data.back(); // Assume the last byte is the checksum
            g_logger.info("Checksum: " + std::to_string(checksum) +
                          ", Generated checksum: " + std::to_string(generated_checksum));
            if (checksum != generated_checksum) {
                g_logger.info("Checksum failed ");
                return; // Checksum mismatch
            }
    
            // Iterate through packet data, excluding the checksum
            size_t i = 0;
            while (i < data.size() - 1) {
                unsigned char data_type = data[i++];
                switch (data_type) {
                case 1: { // Battery level
                    //..rest of code..//
                    //
                }
                }
            }
        }
    
        void initialize(bool enable = true) {
            sleep_for(100ms);
    
            m_port.open("COM5");
    
            // Configure serial port settings
            using P = asio::serial_port;
            m_port.set_option(P::baud_rate(115200));
            m_port.set_option(P::character_size(8));
            m_port.set_option(P::parity(P::parity::none));
            m_port.set_option(P::stop_bits(P::stop_bits::one));
            m_port.set_option(P::flow_control(P::flow_control::none));
    
            g_logger.info("Connected");
    
            setEnabled(enable);
        }
    
        void setEnabled(bool enable) {
            if (enable == !m_readoperation.expired())
                return; // nothing to do
    
            if (!enable)
                m_port.cancel(); // cancels read operation (asio::error::operation_aborted)
            else
                ReadLoop();
        }
    
        void terminate() {
            setEnabled(false);
            g_logger.info("Bye!");
        }
    };
    
    // demo code
    int main() {
        std::cout << std::fixed << std::setprecision(2);
        g_logger.info("Demo start");
        COM g_com;
    
        g_com.initialize();
        // g_com.terminate();
    }
    

    enter image description here