Prefácio
Ao trabalhar com microcontroladores, muitas vezes me deparei com protocolos binários. Especialmente quando existem vários controladores. Ou se utiliza bluetooth de baixa energia e é necessário escrever um código para processar os dados binários na característica. Além do código, sempre é necessária uma documentação clara.
A questão sempre surge - é possível descrever o protocolo de alguma forma e gerar código e documentação para todas as plataformas? IDL pode ajudar com isso.
1. O que é IDL
A definição de IDL é bastante simples e já apresentada na wikipedia
O IDL , ou Linguagem de Definição de Interface ( Inglês Interface a Descrição a Linguagem e Definição de Interface a Linguagem ) - uma linguagem de especificação para descrever interfaces , sintaticamente semelhante à descrição das classes na linguagem do C ++ .
O mais importante em IDL é que deve descrever bem a interface de interação, API, protocolo. Deve ser claro o suficiente para servir como documentação para outros engenheiros.
Um bônus também é - geração de documentação, estruturas, código.
2. Motivação
IDL. , - QFace (https://github.com/Pelagicore/qface), swagger ( IDL, API development tool). : https://www.protlr.com/.
Swagger REST API. . cbor ( json ).
QFace , "" embedded, . , enum-.
, .
IDL "" , . QFace. - .
2.1 QFace
IDL, , :
module <module> <version>
import <module> <version>
interface <Identifier> {
<type> <identifier>
<type> <operation>(<parameter>*)
signal <signal>(<parameter>*)
}
struct <Identifier> {
<type> <identifier>;
}
enum <Identifier> {
<name> = <value>,
}
flag <Identifier> {
<name> = <value>,
}
jinja2. :
{% for module in system.modules %}
{%- for interface in module.interfaces -%}
INTERFACE, {{module}}.{{interface}}
{% endfor -%}
{%- for struct in module.structs -%}
STRUCT , {{module}}.{{struct}}
{% endfor -%}
{%- for enum in module.enums -%}
ENUM , {{module}}.{{enum}}
{% endfor -%}
{% endfor %}
. "" "", . sly IDL .
3. sly
sly - .
. . :
class CalcLexer(Lexer):
# Set of token names. This is always required
tokens = { ID, NUMBER, PLUS, MINUS, TIMES,
DIVIDE, ASSIGN, LPAREN, RPAREN }
# String containing ignored characters between tokens
ignore = ' \t'
# Regular expression rules for tokens
ID = r'[a-zA-Z_][a-zA-Z0-9_]*'
NUMBER = r'\d+'
PLUS = r'\+'
MINUS = r'-'
TIMES = r'\*'
DIVIDE = r'/'
ASSIGN = r'='
LPAREN = r'\('
RPAREN = r'\)'
Lexer, tokens
- . - , .
- . . - -/ . - . - .
( ):
class CalcParser(Parser):
# Get the token list from the lexer (required)
tokens = CalcLexer.tokens
# Grammar rules and actions
@_('expr PLUS term')
def expr(self, p):
return p.expr + p.term
@_('expr MINUS term')
def expr(self, p):
return p.expr - p.term
@_('term')
def expr(self, p):
return p.term
@_('term TIMES factor')
def term(self, p):
return p.term * p.factor
@_('term DIVIDE factor')
def term(self, p):
return p.term / p.factor
@_('factor')
def term(self, p):
return p.factor
@_('NUMBER')
def factor(self, p):
return p.NUMBER
@_('LPAREN expr RPAREN')
def factor(self, p):
return p.expr
. @_
, . sly .
.
: https://sly.readthedocs.io/en/latest/sly.html
4.
yml . sly . . - jinja2 .
, .
, :
@_('term term')
def term(self, p):
t0 = p.term0
t1 = p.term1
t0.extend(t1)
return t0
, , ";"(SEPARATOR
):
@_('enum_def SEPARATOR')
def term(self, p):
return [p.enum_def]
@_('statement SEPARATOR')
def term(self, p):
return [p.statement]
@_('interface SEPARATOR')
def term(self, p):
return [p.interface]
@_('struct SEPARATOR')
def term(self, p):
return [p.struct]
. (term
term
) .
:
@_('STRUCT NAME LBRACE struct_items RBRACE')
def struct(self, p):
return Struct(p.NAME, p.struct_items, lineno=p.lineno)
@_('decorator_item STRUCT NAME LBRACE struct_items RBRACE')
def struct(self, p):
return Struct(p.NAME, p.struct_items, lineno=p.lineno, tags=p.decorator_item)
@_('struct_items struct_items')
def struct_items(self, p):
si0 = p.struct_items0
si0.extend(p.struct_items1)
return si0
@_('type_def NAME SEPARATOR')
def struct_items(self, p):
return [StructField(p.type_def, p.NAME, lineno=p.lineno)]
@_('type_def NAME COLON NUMBER SEPARATOR')
def struct_items(self, p):
return [StructField(p.type_def, p.NAME, bitsize=p.NUMBER, lineno=p.lineno)]
@_('decorator_item type_def NAME SEPARATOR')
def struct_items(self, p):
return [StructField(p.type_def, p.NAME, lineno=p.lineno, tags=p.decorator_item)]
@_('decorator_item type_def NAME COLON NUMBER SEPARATOR')
def struct_items(self, p):
return [StructField(p.type_def, p.NAME, bitsize=p.NUMBER, lineno=p.lineno, tags=p.decorator_item)]
- (struct
) (struct_items
). :
(
type_def
), (NAME
), (SEPARATOR
)
(
type_def
), , (COLON
), (NUMBER
- , ),
(
decorator_item
), , ,
, , , (
COLON
), (NUMBER
- ),
QFace ( protlr) - . - alias.
@_('DECORATOR ALIAS NAME COLON expr struct SEPARATOR')
def term(self, p):
return [Alias(p.NAME, p.expr, p.struct), p.struct]
:
enum Opcode {
Start = 0x00,
Stop = 0x01
};
@alias Payload: Opcode.Start
struct StartPayload {
...
};
@alias Payload: Opcode.Stop
struct StopPayload {
...
};
struct Message {
Opcode opcode: 8;
Payload<opcode> payload;
};
, opcode
= Opcode.Start
(0x00) - payload
StartPayload
. opcode
= Opcode.Stop
(0x01) - payload
StopPayload
. .
- . - , git. . flag enum, . , , .
python- . . .
- Solvable
. , . , SymbolType
( ). , reference. jinja enum . Solvable
solve
. .. .
solve :
def solve(self, scopes: list):
scopes = scopes + [self]
for i in self.items:
if isinstance(i, Solvable):
i.solve(scopes=scopes)
, solve
- scopes
. . :
struct SomeStruct {
i32 someNumber;
@setter: someNumber;
void setInteger(i32 integer);
};
- someNumber
, SomeStruct.someNumber
.
QFace - , . .
examples/uart - , html . uart . , put_u32 - MCU.
: https://gitlab.com/volodyaleo/volk-idl
P.S.
Este é meu primeiro artigo sobre Habr. Eu ficaria feliz em receber feedback - seja este tópico interessante ou não. Se alguém tiver bons exemplos de geradores de protocolo binário kodo + doko para Embedded, seria interessante ler nos comentários. Ou alguma prática bem-sucedida de implementação de sistemas semelhantes para descrever protocolos binários.
Neste projeto, não prestei muita atenção à velocidade de trabalho. Fiz algumas coisas para "resolver o problema mais rápido". Era mais importante obter um código funcional que você já pode tentar aplicar a diferentes projetos.