-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Code works, generators are tested as functional
- Loading branch information
0 parents
commit baf3c4e
Showing
18 changed files
with
1,890 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.pyc |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
XBVC: eXtensible Bit Vector Communication (protocol...) | ||
======================================================= | ||
|
||
The 'e' is silent. | ||
|
||
XBVC is a compact, endian independent, framed message passing protocol | ||
suitable for use in embedded systems, or whatever you feel like | ||
throwing it in. | ||
|
||
Messages and Enumerations are defined in YAML using the following | ||
syntax: | ||
|
||
#Example enumeration | ||
# enumeration values are parsed in order | ||
get_target: { | ||
RPM, | ||
TPS, | ||
MAP, | ||
IAT, | ||
} | ||
|
||
# Example Messages | ||
# Note that there are two special fields in each command | ||
# _targets: this specifies which target (i.e. which end of the message pipe) | ||
# will be responsible for generating this message. Since this protocol was made | ||
# with embedded targets in mind, this is important for space savings in the code | ||
# Note that it is not necessary for all language targets to adhere to this directive | ||
# _id: This specifies the unique message id and allows for somppe minor extensibility | ||
get_command: | ||
- _targets: {host} | ||
- _id: 0 | ||
- Target: u32 | ||
- Fluff: u8[10] | ||
|
||
The xbvcgen.py tool then converts this yaml definition into source | ||
code for any of the supported platforms (c and python for now, more to | ||
follow). | ||
|
||
This source code contains the message definitions, enumerations, and the | ||
framer / message arbitration mechanism. The only things the user must | ||
provide are read/write/init functions, and the individual message | ||
handlers. (Note: The message handling paradigm varies by language) | ||
|
||
For example: | ||
1. In the c implementation, the user must provide: xbvc_platform_init, | ||
xbvc_platform_write, and xbvc_platform_read | ||
|
||
2. in the python implementation, the user must subclass XBVCEdgePoint | ||
and provide connect, _read, and _write functions (see the generated | ||
python code for an example that provides a loopback test) | ||
|
||
## Prerequisites | ||
1. Python 2.7 | ||
2. pyyaml (via pip) | ||
3. jinja2 (via pip) | ||
|
||
## Use | ||
1. Create a yaml file containing your messages and enumerations using | ||
the schema outlined above | ||
2. Call xbvcgen.py, passing in your yaml file as the input parameter, | ||
a destination folder as the output parameter (this will be created | ||
for you), and the targets and languages | ||
|
||
./xbvcgen.py -i msgs.yaml -o code -t device -l python | ||
|
||
3. Integrate the generated files into your build (I suggest making | ||
this a phony target in your makefile and copying the files into | ||
their final destination) | ||
|
||
4. Provide the platform specific functionality functions and implement | ||
your message handlers. | ||
|
||
## License | ||
This software, including all language target templates is provided | ||
under the GPLv2 license. HOWEVER, all generated code is the property | ||
of it's respective owner to be licensed however they wish. Please see | ||
the LICENSE file for a copy of the GPL. | ||
|
||
## TODO | ||
1. Provide a python package | ||
2. Whip up some platform examples (pyserial, c loopback, c socket, c | ||
serial) | ||
3. Add more platforms (java, c++, go) | ||
4. Think about providing some baked in messages (unknown message / | ||
problem decoding) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import os.path | ||
from jinja2 import Environment, FileSystemLoader | ||
import inspect, os | ||
|
||
class SourceFile(object): | ||
def __init__(self, name, content): | ||
self.content = content | ||
self.name = name | ||
|
||
def save_to_disk(self, path): | ||
with open(os.path.join(path, self.name), 'wb') as f: | ||
f.write(self.content) | ||
|
||
def __str__(self): | ||
return self.content | ||
|
||
# Base emitter class, doesn't do much, just provides stubs for child | ||
# classes and provides a convenient way to expand a template | ||
class Emitter(object): | ||
def __init__(self, language): | ||
self.language = language | ||
|
||
def generate_source(self, commspec, targets): | ||
pass | ||
|
||
def expand_template(self, template_name, template_vars = {}): | ||
base_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) | ||
tmpl_path = os.path.abspath(os.path.join(base_path, 'templates')) | ||
|
||
env = Environment(loader=FileSystemLoader(tmpl_path)) | ||
|
||
env.trim_blocks = True | ||
env.lstrip_blocks = True | ||
|
||
tmpl = env.get_template(template_name) | ||
return tmpl.render(template_vars) | ||
|
||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from c_emitter import CEmitter | ||
from py_emitter import PyEmitter | ||
|
||
emitter_registry = { | ||
'c':CEmitter, | ||
'python':PyEmitter | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
from objects import CommSpec, Message, Enum | ||
from jinja2 import FileSystemLoader | ||
from EmitterBase import SourceFile, Emitter | ||
|
||
type_map = { | ||
'u32': 'uint32_t', | ||
's32': 'int32_t', | ||
'u16': 'uint16_t', | ||
's16': 'int16_t', | ||
'u8': 'uint8_t', | ||
's8': 'int8_t', | ||
} | ||
|
||
rl_size_map = { | ||
# '0' is for padding bytes | ||
'0': 0b000, | ||
'8': 0b001, | ||
'16': 0b010, | ||
'32': 0b011, | ||
'64': 0b100, | ||
} | ||
|
||
rl_alignment_map = { | ||
'8': 1, | ||
'16': 2, | ||
'32': 4, | ||
'64': 8, | ||
} | ||
|
||
class AlignmentMember(object): | ||
def __init__(self, length, pos): | ||
self.d_len = length | ||
self.d_type = 'u8' | ||
self.name = '_align{}'.format(pos) | ||
|
||
# TODO: remove packed attribute and add padding fields | ||
class CStructure: | ||
def __init__(self, message, msg_id): | ||
self.name = message.name | ||
self.value_pairs = [] | ||
self._msg = message | ||
self.msg_id = msg_id | ||
|
||
self._generate_encode_array() | ||
self._get_values() | ||
|
||
def _get_values(self): | ||
for mem in self._msg.members: | ||
#Get the type and length of the member | ||
if not mem.d_type in type_map: | ||
raise TypeError("Invalid Type: {}".format(t_st)) | ||
|
||
mem_type = type_map[mem.d_type] | ||
|
||
# Properly lay out the structure, if it is an array, make it so | ||
if mem.d_len == 1: | ||
self.value_pairs.append({'type':mem_type, 'field_name':mem.name}) | ||
else: | ||
self.value_pairs.append({'type':mem_type, | ||
'field_name': '{}[{}]'.format(mem.name, mem.d_len)}) | ||
|
||
def _generate_encode_array(self): | ||
msg_ary = [] | ||
alignment = 0 | ||
# We build a new member list dynamically to account for | ||
# alignment bytes | ||
new_mem = [] | ||
align_count = 0 | ||
|
||
for mem in self._msg.members: | ||
m_len = int(mem.d_len) | ||
rleb = rl_size_map[mem.d_type[1:]] << 5 | ||
if m_len <=31: | ||
#run length encoder byte | ||
msg_ary.append(rleb | int(m_len)) | ||
|
||
#adjust current alignment | ||
alignment += rl_alignment_map[mem.d_type[1:]] * m_len | ||
else: | ||
#loop through every 31 instances of the member | ||
#(2^5 bits) and add them to the list as a separate | ||
#run | ||
while m_len > 0: | ||
if m_len >= 31: | ||
msg_ary.append(rleb | 31) | ||
else: | ||
msg_ary.append(rleb | m_len) | ||
alignment += rl_alignment_map[mem.d_type[1:]] * m_len | ||
m_len -= 31 | ||
|
||
new_mem.append(mem) | ||
#If we aren't on an even alignment, add some padding bytes | ||
if alignment % 4: | ||
msg_ary.append(alignment % 4) | ||
new_mem.append(AlignmentMember(alignment % 4, align_count)) | ||
alignment += alignment % 4 | ||
align_count += 1 | ||
|
||
self._msg._member_list = new_mem | ||
|
||
self.enc_ary = msg_ary | ||
|
||
@property | ||
def encode_array(self): | ||
#This is a little nasty, but basically it takes an array of | ||
#integers: [1, 2, 3, 4, 5] and returns a hex string in c: | ||
# "\x01\x02\x03\x04\x05" | ||
rs = '"{}"'.format("".join(["\\x{0:02x}".format(x) for x in self.enc_ary])) | ||
return rs | ||
|
||
|
||
class CEmitter(Emitter): | ||
def __init__(self): | ||
super(CEmitter, self).__init__('C') | ||
|
||
def generate_source(self, commspec, targets): | ||
self.targets = targets | ||
self.cs = commspec | ||
source_files = [] | ||
|
||
self.dec_msgs = self.cs.get_decode_messages(self.targets) | ||
self.enc_msgs = self.cs.get_encode_messages(self.targets) | ||
|
||
self.all_msgs = [] | ||
|
||
for idx in range(len(self.cs.messages)): | ||
self.all_msgs.append(CStructure(self.cs.messages[idx], idx)) | ||
|
||
self._verify_targets() | ||
|
||
source_files.append(self._generate_header_file()) | ||
source_files.append(self._generate_encoder_decoder()) | ||
|
||
source_files.extend(self._retrieve_cobs_files()) | ||
return source_files | ||
|
||
def _verify_targets(self): | ||
cs_tgts = [] | ||
|
||
for m in self.dec_msgs + self.enc_msgs: | ||
cs_tgts.extend(m.targets) | ||
|
||
diff = set(self.targets).difference(cs_tgts) | ||
if len(diff): | ||
raise Exception("Unknown targets: {}".format(list(diff))) | ||
|
||
|
||
def _retrieve_cobs_files(self): | ||
header = SourceFile('cobs.h', self.expand_template('cobs.h')) | ||
src = SourceFile('cobs.c', self.expand_template('cobs.c')) | ||
return [header, src] | ||
|
||
|
||
def _generate_handler_prototype(self, st_name): | ||
rs = "void xbvc_handle_{0}(struct x_{0} *msg)"\ | ||
.format(st_name) | ||
|
||
return rs | ||
|
||
def _generate_encoder_decoder(self): | ||
content = self.expand_template('c_interface.jinja2', | ||
{'msgs': self.all_msgs, | ||
'dec_msgs': self.dec_msgs}) | ||
return SourceFile('xbvc_core.c', content) | ||
|
||
def _generate_header_file(self): | ||
#message ids | ||
msgs = [] | ||
msg_types = [x.name.upper() for x in self.cs.messages] | ||
|
||
protos = [self._generate_handler_prototype(x.name) for x in self.dec_msgs] | ||
header_vars = {'enumerations': self.cs.enums, | ||
'prototypes': protos, | ||
'msgs': self.all_msgs, | ||
} | ||
|
||
header_content = self.expand_template('c_header.jinja2', header_vars) | ||
|
||
return SourceFile('xbvc_core.h', header_content) | ||
|
||
def _generate_redirect_stub(self, st_name): | ||
rs = "static void _xbvc_redirect_{}(void *s)\n{{\n".format(st_name) | ||
rs += "\txbvc_handle_{0}((struct x_{0} *)s);\n}}\n".format(st_name) | ||
return rs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from _emitter_registry import emitter_registry as er | ||
|
||
class EmitterFactory(object): | ||
def __init__(self): | ||
pass | ||
|
||
def get_emitter(self, lang): | ||
""" | ||
Returns an Emitter instance for the language specified | ||
@param string lang target programming language | ||
""" | ||
if lang.lower() in er.keys(): | ||
return er[lang.lower()]() | ||
else: | ||
raise TypeError() | ||
|
||
@property | ||
def supported_languages(self): | ||
return er.keys() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from objects import CommSpec, Message, Enum | ||
from EmitterBase import SourceFile, Emitter | ||
|
||
|
||
class PyEmitter(Emitter): | ||
def __init__(self): | ||
super(PyEmitter, self).__init__('Python') | ||
|
||
def generate_source(self, commspec, targets): | ||
self.cs = commspec | ||
source_files = [] | ||
source_files.append(self._generate_interface()) | ||
source_files.append(self._retrieve_cobs()) | ||
|
||
return source_files | ||
|
||
def _retrieve_cobs(self): | ||
return SourceFile('cobs.py', self.expand_template('cobs.py')) | ||
|
||
def _generate_interface(self): | ||
src = self.expand_template('py_interface.jinja2', | ||
{'messages': self.cs.messages}) | ||
return SourceFile('xbvc_py.py', src) |
Oops, something went wrong.