Experiencia en redacción de IDL para sistemas integrados

Prefacio

Cuando trabajaba con microcontroladores, a menudo me encontraba con protocolos binarios. Especialmente cuando hay varios controladores. O se utiliza bluetooth de baja energía y es necesario escribir un código para procesar datos binarios en la característica. Además del código, siempre se requiere documentación clara.





Siempre surge la pregunta: ¿es posible describir el protocolo de alguna manera y generar código y documentación para todas las plataformas? IDL puede ayudar con esto.





1. ¿Qué es IDL?

La definición de IDL es bastante simple y ya se presenta en wikipedia





El IDL , o  lenguaje de definición de interfaz  ( Engl.  Interfaz la descripción el lenguaje  y  Definición de interfaz el lenguaje ) -  un lenguaje de especificación  para describir  interfaces , sintácticamente similar a la descripción de las clases en el lenguaje de  C ++ .





Lo más importante en IDL es que debe describir bien la interfaz de interacción, API, protocolo. Debe ser lo suficientemente claro para que sirva como documentación para otros ingenieros.





Una ventaja también es la generación de documentación, estructuras, código.





2. Motivación

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 es mi primer artículo sobre Habr. Me encantaría recibir comentarios, ya sea que este tema sea interesante o no. Si alguien tiene buenos ejemplos de generadores de protocolos binarios kodo + doko para Embedded, sería interesante leerlos en los comentarios. O alguna práctica exitosa de implementar sistemas similares para describir protocolos binarios.





En este proyecto, no presté mucha atención a la velocidad del trabajo. Hice algunas cosas para "resolver el problema más rápido". Era más importante conseguir un código que funcionara y que ya pudieras intentar aplicar a diferentes proyectos.








All Articles