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.