Язык описания модбасоподобных протоколов

Тема в разделе "Глядите, что я сделал", создана пользователем karpach2000, 14 июл 2024.

  1. karpach2000

    karpach2000 Нерд

    Всем доброе время суток. Много работал с различными устройствами по последовательным интерфейсам (RS485, RS232). Довольно часто сталкивался с необходимостью поддержать тот или иной модбасоподобный протокол и каждый раз приходилось почти что заново писать парсер и генератор сообщений. Пришла мысль сделать некий язык описания данных протоколов и конфигурировать интерпретатор и генератор на основе файла написаного на этом языке.
    Большинство проектов я реализую на Kotlin (да, я знаю что на нем сейчас пишут только бек и Android, но не смотря на это во многих встаиваемых устройства с более менее полноценной ОС используется JVM), по этому этот язык был выбран для написания предпрототипа интерпретатора.
    Что бы не изобретать свой синтаксис в качестве языка разметки я выбрал yaml. За пару вечеров написал первый предпрототип библиотеки, что бы оценить трудоемкость и подводные камни.
    Код (YAML):
    version: 0.0.1
    struct
    : [prefix, separator, address, separator, command, separator, data, separator, suffix]
    parts
    :
      prefix
    :
        value
    : 1
      suffix
    :
        value
    : 2
      address
    :
        bytesCount
    : 4
      command
    :
        bytesCount
    : 2
      separator
    :
        value
    : 255
      data:
    (Пример описания протокола 1)
    Код (YAML):
    version: 0.0.1
    struct
    : [prefix, address, length, command, data, suffix]
    parts
    :
      prefix
    :
        value
    : 1
      suffix
    :
        value
    : 2
      address
    :
        bytesCount
    : 4
      command
    :
        bytesCount
    : 2
      length
    :
        dependsOn
    : [command, data]
      data:
    (Пример описания протокола 2)
    Код (Java):
     @Test
        fun exampleTest()
        {
            //считываем структуру протокола
            val filename = "TestData/ExampleLength.yaml"
            val text = File(filename).readText()
            val messageStruct = MessageStructFactory.fromYaml(text)

            //генерируем сообщение (последовательность байт)
            val message : List<Int> = messageStruct.setAddress(listOf(13, 18)).setCommand(listOf(1)).setData(listOf(12,13,14,15,16,17)).build()
            println("Generated message : $message")// [1, 0, 0, 13, 18, 8, 0, 1, 12, 13, 14, 15, 16, 17, 2]

            //парсим сообщение
            val parser = Parser(messageStruct.parts)
            val checkResult = parser.check(message)
            checkResult.errorList.forEach { println(it) }
            //проверяем сообщение на случай если к примеру оно побилось при передаче
            if(checkResult.success) {
                //если сообщение прошло проверку извлекаем полезные данные
                val usefulData : UsefulData = parser.parse(message)
                println(usefulData)//UsefulData(address=[0, 0, 13, 18], command=[0, 1], data=[12, 13, 14, 15, 16, 17])
            }
            else
            {
                //предпринимаем что-то еще если не прошло
                //return error
            }
        }
    (Пример использования интепретатора 1)

    Пишу, что бы понять, на сколько данная тема интересна и стоит ли в дальнейшем развивать проект.
     
    Airbus нравится это.
  2. parovoZZ

    parovoZZ Гуру

    под какую платформу и что получаем на выходе?
     
  3. karpach2000

    karpach2000 Нерд

    Под любую где стартует JVM (к примеру raspberry). По идее можно переписать интерпретатор под другие языки.
    В случае если речь про C/C++ под камень, то тут наверное ве же стоит сделать генератор С кода.
     
  4. karpach2000

    karpach2000 Нерд

    В сообщении как правило есть полезные данные (command, data) и служебные (suffix, prefix, length).

    1) генератор сообщений (последовательность байт, отправляемых в порт) на основании полезных данных
    2) парсер сообщений (парсит входящее сообщение, вытаскивает из него полезные данные)
    3) метод проверки валидности сообщения (к примеру если сообщение дошло до нас не полностью.)
    Код (C++):
        /**
         * Соответствует ли данное сообщение протоколу.
         */

        fun check(data : List<Int>) : CheckResult

        /**
         * Получить полезные данные из сообщения.
         * прим. поля data, command
         */

        fun parse(data : List<Int>) : UsefulData
    Интерфейсы парсера
    Код (Java):
    data class CheckResult(val success : Boolean, val errorList: List<String> = listOf())
    Возвращаемое значение при проверки сообщения
    Код (Java):
    data class UsefulData(val address : List<Int>, val command : List<Int>, val data : List<Int>)
     
    Полезные данные забираемые из сообщения
     
    Airbus нравится это.