zeerd's blog         Search     Categories     Tags     Feed

闲来生雅趣,无事乐逍遥。对窗相望雪,一盏茶香飘。

一个简易的DLT服务

#COVESA #DLT @Program


Contents:

当目标系统不支持Dlt时,可以作为一个备选方案。

要读懂下面代码的话,可能需要先阅读一下AUTOSAR的DLT标准协议。 注释中标注的类似宏定义的大写字母单词都是来自于AUTOSAR协议的,可以直接使用它们进行搜索。

//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
// Copyright (c) 2020 Charles Chan (charles at zeerd dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

/*
    g++ simple-dlt-server.cpp -o simple-dlt-server \
                              -lboost_system -lboost_thread \
                              -pthread
*/

/*
    https://www.autosar.org/fileadmin/user_upload/standards/foundation/1-0/
                                AUTOSAR_PRS_DiagnosticLogAndTraceProtocol.pdf
    https://at.projects.genivi.org/wiki/display/PROJ/
                                GENIVI+DLT+Protocol+Extensions
    https://github.com/GENIVI/dlt-viewer
*/


#include <sys/types.h>                  // getpid()
#include <unistd.h>                     // getpid()

#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/thread/recursive_mutex.hpp>

#define _WORD_BYTE0(x) (uint8_t((x) & 0xFF))
#define _WORD_BYTE1(x) (uint8_t((x) >> 8))

#define _LONG_BYTE0(x) (uint8_t((x) & 0xFF))
#define _LONG_BYTE1(x) (uint8_t(((x) >> 8) & 0xFF))
#define _LONG_BYTE2(x) (uint8_t(((x) >> 16) & 0xFF))
#define _LONG_BYTE3(x) (uint8_t(((x) >> 24) & 0xFF))

const char * ecuid = "ECU1";
const char * apid = "TST";
const char * apds = "simple dlt server";
const char * ctid = "SMP";
const char * ctds = "simple dlt server context";
const char * ver = "Simple DLT Server Version 0.0.1";

using boost::asio::ip::tcp;

class session
  : public std::enable_shared_from_this<session>
{
public:
    session(boost::asio::io_context& io_context, tcp::socket socket)
        : io_context_(io_context)
        , socket_(std::move(socket))
        , timer_(std::make_shared<boost::asio::steady_timer>(io_context_))
        , counter(0) {
    }

    void start()
    {
        say_connected();
        do_read();

        /**
         * This timer used to send a demo-message to DLT-Viewer.
         * It's not a part of the core.
         */
        timer_->expires_from_now(std::chrono::seconds(1));
        timer_->async_wait(std::bind(&session::do_dlt_demo,
                    shared_from_this(), std::placeholders::_1));
    }

private:
    /**
     * The DLT Viewer might want to get some information of our DLT server.
     * The do_read() function used to receive the requests from DLT viewer
     * and give callbacks.
     */
    void do_read()
     {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t _length) {
                if (!ec) {
                    // TODO: I am not sure if there would be a half-PDU coming
                    //       Maybe need to store the remains for next time.
                    int offset = 0;
                    char dlt[max_length] = {0};
                    int length = data_[2] << 8 | data_[3];
                    while(offset + length <= _length) {
                        length = data_[offset+2] << 8 | data_[offset+3];
                        uint8_t type = data_[offset+12];
                        uint8_t command = data_[offset+22];

                        if(type == 0x16) { // DLT_CONTROL_REQUEST
                            if(command == 0x13) { // Get ECU Software Version
                                get_ecu_software_version();
                            }
                            else if(command == 0x03) { // Get Log Info
                                get_log_info();
                            }
                            else {
                                // Do not support any other requests.
                                memcpy(dlt, &data_[offset], 0x1a);
                                dlt[ 2] = 0;
                                dlt[ 3] = _WORD_BYTE0(27);
                                dlt[ 7] = 0x31; // modify the ECU ID
                                dlt[12] = 0x26;
                                dlt[26] = 1; // NOT_SUPPORTED
                                do_write(dlt, 27);
                            }
                        }
                        offset += length;
                    }

                    do_read();
                }
            });
    }

    /**
     * Send data from DLT-Server on ECU to DLT-Viewer on PC.
     */
    void do_write(char *_data, std::size_t length)
    {
        boost::recursive_mutex::scoped_lock lock(g_rec_mutex_);
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(_data, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    // Do something when error occur.
                }
            });
    }

    /**
     * Send a DLT-Log with random log level.
     */
    void do_dlt_demo(const boost::system::error_code &_error)
    {
        if(_error.value() == boost::system::errc::errc_t::success) {

            send_dlt_message((random() % 6), "Hello, DLT!");

            timer_->expires_from_now(std::chrono::seconds(1));
            timer_->async_wait(std::bind( &session::do_dlt_demo,
                    shared_from_this(), std::placeholders::_1));
        }
    }

    /**
     * This information is not defined from the AUTOSAR.
     * It mIght be defined by GENIVI for DLT-Viewer Only.
     */
    void say_connected()
    {
        char dlt[max_length] = {0};

        uint32_t timestamp = dlt_timestamp();

        dlt[ 0] = 0x35;
        dlt[ 1] = 0;
        dlt[ 2] = _WORD_BYTE1(0x20); /* header + extend header */
        dlt[ 3] = _WORD_BYTE0(0x20); /* header + extend header */
        memcpy(&dlt[4], ecuid, 4);
        dlt[ 8] = _LONG_BYTE3(timestamp);
        dlt[ 9] = _LONG_BYTE2(timestamp);
        dlt[10] = _LONG_BYTE1(timestamp);
        dlt[11] = _LONG_BYTE0(timestamp);
        dlt[12] = 0x26; // DLT_CONTROL_RESPONSE
        dlt[13] = 0x01; // Number of Arguments = 1
        memcpy(&dlt[14], apid, 4);
        memcpy(&dlt[18], ctid, 4);
        dlt[22] = 0x02; // dlt-viewer defined command
        dlt[23] = 0x0f; // dlt-viewer defined command
        dlt[24] = 0x00; // dlt-viewer defined command
        dlt[25] = 0x00; // dlt-viewer defined command
        dlt[26] = 0x00;
        dlt[27] = 0x02;
        dlt[28] = 0x00;
        dlt[29] = 0x00;
        dlt[30] = 0x00;
        dlt[31] = 0x00;

        do_write(dlt, 32);
    }

    /**
     * Tell The DLT-Viewer who we are, if it asked.
     */
    void get_ecu_software_version()
    {
        char dlt[max_length] = {0};

        uint32_t timestamp = dlt_timestamp();

        int o = 0;
        dlt[o++] = 0x35;
        dlt[o++] = 0;
        o+=2; // reserved for length;
        memcpy(&dlt[o], ecuid, 4); o += 4;
        dlt[o++] = _LONG_BYTE3(timestamp);
        dlt[o++] = _LONG_BYTE2(timestamp);
        dlt[o++] = _LONG_BYTE1(timestamp);
        dlt[o++] = _LONG_BYTE0(timestamp);
        dlt[o++] = 0x26; //
        dlt[o++] = 0x01; // Number of Arguments = 1
        memcpy(&dlt[o], apid, 4); o += 4;
        memcpy(&dlt[o], ctid, 4); o += 4;
        dlt[o++] = 0x13; // 5.3.10 Get ECU Software Version
        dlt[o++] = 0x00;
        dlt[o++] = 0x00;
        dlt[o++] = 0x00;
        dlt[o++] = 0; // 0 == OK
        dlt[o++] = strlen(ver); // Length of the string swVersion
        dlt[o++] = 0; // Length of the string swVersion
        dlt[o++] = 0; // Length of the string swVersion
        dlt[o++] = 0; // Length of the string swVersion
        memcpy(&dlt[o], ver, strlen(ver)); o += strlen(ver);

        dlt[ 2] = _WORD_BYTE1(o);
        dlt[ 3] = _WORD_BYTE0(o);

        do_write(dlt, o);
    }

    /**
     * Send all the registered application and contents to DLT-Viewer.
     * We got only one application and only one content here, so it's simple.
     * Need more loops for more application and more contents.
     */
    void get_log_info()
    {
        char dlt[max_length] = {0};

        uint32_t timestamp = dlt_timestamp();

        int o = 0;
        dlt[o++] = 0x35;
        dlt[o++] = 0;
        o+=2; // reserved for length;
        memcpy(&dlt[o], ecuid, 4); o += 4;
        dlt[o++] = _LONG_BYTE3(timestamp);
        dlt[o++] = _LONG_BYTE2(timestamp);
        dlt[o++] = _LONG_BYTE1(timestamp);
        dlt[o++] = _LONG_BYTE0(timestamp);
        dlt[o++] = 0x26; //
        dlt[o++] = 0x01; // Number of Arguments = 1
        memcpy(&dlt[o], apid, 4); o += 4;
        memcpy(&dlt[o], ctid, 4); o += 4;
        dlt[o++] = 0x03; // 5.3.3 Get Log Info
        dlt[o++] = 0x00;
        dlt[o++] = 0x00;
        dlt[o++] = 0x00;
        dlt[o++] = 7; // all apid & cnid
        dlt[o++] = 1; // only one app
        dlt[o++] = 0; // split
        memcpy(&dlt[o], apid, 4); o += 4;
        dlt[o++] = 1; // only one content id
        dlt[o++] = 0; // split
        memcpy(&dlt[o], ctid, 4); o += 4;
        dlt[o++] = 0xff; // level of log by default
        dlt[o++] = 0xff; // status of trace by default
        dlt[o++] = strlen(ctds); // length
        dlt[o++] = 0; // length
        memcpy(&dlt[o], ctds, strlen(ctds)); o += strlen(ctds);
        dlt[o++] = strlen(apds); // length
        dlt[o++] = 0; // length
        memcpy(&dlt[o], apds, strlen(apds)); o += strlen(apds);

        dlt[ 2] = _WORD_BYTE1(o);
        dlt[ 3] = _WORD_BYTE0(o);
        do_write(dlt, o);
    }

    /**
     * Core function to send a log.
     */
    void send_dlt_message(int level, const char * _msg)
    {
        char dlt[max_length] = {0};

        uint16_t length = strlen(_msg);
        uint32_t session_id = getpid();
        uint32_t timestamp = dlt_timestamp();

        int o=0;
        dlt[o++] = 0x3d; // 001(Version Number = 1)
                        // 111(With Timestamp)(With Session ID)(With ECUID)
                        // 0(Non-Most Significant Byte First)
                        // 1(extended header is used)
        dlt[o++] = ++counter;
        o+=2; // reserved for length;
        memcpy(&dlt[o], ecuid, 4); o += 4;
        dlt[o++] = _LONG_BYTE3(session_id);
        dlt[o++] = _LONG_BYTE2(session_id);
        dlt[o++] = _LONG_BYTE1(session_id);
        dlt[o++] = _LONG_BYTE0(session_id);
        dlt[o++] = _LONG_BYTE3(timestamp);
        dlt[o++] = _LONG_BYTE2(timestamp);
        dlt[o++] = _LONG_BYTE1(timestamp);
        dlt[o++] = _LONG_BYTE0(timestamp);
        dlt[o++] = 0x11 + (level << 4); // (DLT_LOG_xxx by random)
                                        // (Dlt Log Message) (Verbose)
        dlt[o++] = 0x01; // Number of Arguments = 1
        memcpy(&dlt[o], apid, 4); o += 4;
        memcpy(&dlt[o], ctid, 4); o += 4;
        dlt[o++] = 0x00;
        dlt[o++] = 0x02;
        dlt[o++] = 0x00;
        dlt[o++] = 0x00; // Bit9=1 : Type String = ASCII
                         // (String Coding=0, Type Length = not defined)
        dlt[o++] = _WORD_BYTE0(length);
        dlt[o++] = _WORD_BYTE1(length);
        memcpy(&dlt[o], _msg, length); o += length;
        dlt[o++] = '\0';

        dlt[2] = _WORD_BYTE1(o); /* header + extend header */
        dlt[3] = _WORD_BYTE0(o); /* header + extend header */
        do_write(dlt, o);
    }

    uint32_t dlt_timestamp()
    {
        struct timespec ts;

        if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
            return (uint32_t)ts.tv_sec  * 10000
                 + (uint32_t)ts.tv_nsec / 100000; /* in 0.1 ms = 100 us */
        else
            return 0;
    }

    boost::asio::io_context& io_context_;
    tcp::socket socket_;
    std::shared_ptr<boost::asio::steady_timer> timer_;
    boost::recursive_mutex g_rec_mutex_;
    enum { max_length = 1024 };
    char data_[max_length];
    int counter;
};

/**
 * DLT-Server iS a TCP server. DLT-Viewer is a TCP Client.
 * Those codes come from the example of boost TCP server:
 * async_tcp_echo_server.cpp
 */
class server
{
public:
    server(boost::asio::io_context& io_context, short port)
        : io_context_(io_context)
        , acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }

private:
    void do_accept()
    {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<session>
                            (io_context_, std::move(socket))->start();
                }

                do_accept();
            });
    }

    boost::asio::io_context& io_context_;
    tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
    try {
        uint32_t port = 3490;
        if (argc == 2) {
            port = std::atoi(argv[1]);
            if(port == 0) {
                std::cerr << "Usage: simple-dlt-server [port default:3490]\n";
                return 1;
            }
            std::cout << "Listening on " << port << std::endl;
        }

        boost::asio::io_context io_context;

        server s(io_context, port);

        io_context.run();
    }
    catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}