Erfahrung im Schreiben von IDL für Embedded

Vorwort

Bei der Arbeit mit Mikrocontrollern bin ich oft auf Binärprotokolle gestoßen. Besonders wenn es mehrere Controller gibt. Oder es wird Bluetooth Low Energy verwendet und es ist erforderlich, einen Code zu schreiben, um Binärdaten in der Charakteristik zu verarbeiten. Zusätzlich zum Code ist immer eine klare Dokumentation erforderlich.





Es stellt sich immer die Frage: Ist es möglich, das Protokoll irgendwie zu beschreiben und Code und Dokumentation für alle Plattformen zu generieren? IDL kann dabei helfen.





1. Was ist IDL?

Die Definition von IDL ist ziemlich einfach und bereits auf Wikipedia vorgestellt





Die IDL oder  Interface Definition Language  ( engl.  Interface the Description the Language  und  Interface Definition the Language ) -  eine Spezifikationssprache  zur Beschreibung von  Schnittstellen , die syntaktisch der Beschreibung der Klassen in der Sprache von C ++ ähnelt  .





Das Wichtigste in IDL ist, dass es die Interaktionsschnittstelle, die API und das Protokoll gut beschreibt. Es sollte klar genug sein, um anderen Ingenieuren als Dokumentation zu dienen.





Ein Bonus ist auch - Generierung von Dokumentation, Strukturen, 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.

Dies ist mein erster Artikel über Habr. Ich würde mich über Feedback freuen - ob dieses Thema interessant ist oder nicht. Wenn jemand gute Beispiele für Kodo + Doko-Binärprotokollgeneratoren für Embedded hat, wäre es interessant, in den Kommentaren zu lesen. Oder eine erfolgreiche Praxis der Implementierung ähnlicher Systeme zur Beschreibung von Binärprotokollen.





In diesem Projekt habe ich der Arbeitsgeschwindigkeit nicht viel Aufmerksamkeit geschenkt. Ich habe einige Dinge getan, um "das Problem schneller zu lösen". Es war wichtiger, Arbeitscode zu erhalten, den Sie bereits auf verschiedene Projekte anwenden können.








All Articles