Всем доброе время суток. Много работал с различными устройствами по последовательным интерфейсам (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) Пишу, что бы понять, на сколько данная тема интересна и стоит ли в дальнейшем развивать проект.
Под любую где стартует JVM (к примеру raspberry). По идее можно переписать интерпретатор под другие языки. В случае если речь про C/C++ под камень, то тут наверное ве же стоит сделать генератор С кода.
В сообщении как правило есть полезные данные (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>) Полезные данные забираемые из сообщения