Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
Code works, generators are tested as functional
  • Loading branch information
Jeff-Ciesielski committed May 14, 2014
0 parents commit baf3c4e
Show file tree
Hide file tree
Showing 18 changed files with 1,890 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions README.md
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)

39 changes: 39 additions & 0 deletions emitters/EmitterBase.py
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 added emitters/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions emitters/_emitter_registry.py
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
}
184 changes: 184 additions & 0 deletions emitters/c_emitter.py
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
20 changes: 20 additions & 0 deletions emitters/emitter_factory.py
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()

23 changes: 23 additions & 0 deletions emitters/py_emitter.py
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)
Loading

0 comments on commit baf3c4e

Please sign in to comment.