Experiência em escrever IDL para embarcado

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.








All Articles