Expérience en rédaction d'IDL pour embarqué

Préface

En travaillant avec des microcontrôleurs, je suis souvent tombé sur des protocoles binaires. Surtout quand il y a plusieurs contrôleurs. Ou bien le bluetooth à faible consommation d'énergie est utilisé et il est nécessaire d'écrire un code pour traiter les données binaires dans la caractéristique. En plus du code, une documentation claire est toujours requise.





La question se pose toujours: est-il possible de décrire le protocole d'une manière ou d'une autre et de générer du code et de la documentation pour toutes les plates-formes? IDL peut vous aider.





1. Qu'est-ce que l'IDL

La définition de l'IDL est assez simple et déjà présentée sur wikipedia





L'IDL , ou  Interface Definition Language  ( Engl.  Interface the Description the Language  and  Interface Definition the Language ) -  un langage de spécification  pour décrire les  interfaces , syntaxiquement similaire à la description des classes dans le langage du  C ++ .





La chose la plus importante dans IDL est qu'il doit bien décrire l'interface d'interaction, l'API, le protocole. Il doit être suffisamment clair pour servir de documentation aux autres ingénieurs.





Un bonus est également - génération de documentation, de structures, de code.





2. Motivation

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.

Ceci est mon premier article sur Habr. Je serais heureux de recevoir des commentaires - que ce sujet soit intéressant ou non. Si quelqu'un a de bons exemples de générateurs de protocoles binaires kodo + doko pour Embedded, il serait intéressant de lire dans les commentaires. Ou une pratique réussie de mise en œuvre de systèmes similaires pour décrire des protocoles binaires.





Dans ce projet, je n'ai pas accordé beaucoup d'attention à la vitesse de travail. J'ai fait certaines choses pour "résoudre le problème plus rapidement". Il était plus important d'obtenir un code fonctionnel que vous pouvez déjà essayer d'appliquer à différents projets.








All Articles