mirror of
https://github.com/caperren/school_archives.git
synced 2025-11-09 13:41:13 +00:00
Added 2016-2017 rover code, and a ROB assignment
This commit is contained in:
289
OSU Robotics Club/Mars Rover 2016-2017/common/docparse.py
Normal file
289
OSU Robotics Club/Mars Rover 2016-2017/common/docparse.py
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python3
|
||||
#Parses a markdown file (first command line argument)
|
||||
#containing a table with the header whose header starts with:
|
||||
#| Name | RW | Command Code
|
||||
import textwrap
|
||||
import sys
|
||||
import math
|
||||
|
||||
def extract_table(file_str):
|
||||
"""Extract the command table from the text of
|
||||
specification.md. Returns a list of dictionaries with the following
|
||||
members:
|
||||
"name" : str, name of command
|
||||
"rw" : str, contains r is command can be read, w if command can be written, - if neither
|
||||
"code" : int, command code (0-127)
|
||||
"argument" : list( #list of 0 or more tuples, each with the following structure:
|
||||
tuple(format code:str, #command format code, u8, i16, etc, or * to indicate a
|
||||
#a variable number of bytes controlled by the previous argument.
|
||||
name:str))
|
||||
"default" : list of default values of arguments, as strings
|
||||
"notes" : str, notes on command."""
|
||||
s = file_str.find("\n| Name | RW | Command Code")
|
||||
s+=1 #Skip over first newline
|
||||
e = file_str[s:].find("\n\n")
|
||||
if e < s:
|
||||
table_str = file_str[s:]
|
||||
else:
|
||||
table_str = file_str[s:s + e]
|
||||
commands = []
|
||||
for l in table_str.split("\n"):
|
||||
if len(l) == 0: continue
|
||||
if l[0] == "|":
|
||||
l = l[1:]
|
||||
col = [c.strip() for c in l.split("|")]
|
||||
if col[0][0:3] == "---": continue
|
||||
if col[0] == "Name": continue
|
||||
name = col[0]
|
||||
rw = col[1].lower()
|
||||
code = int(col[2], 0)
|
||||
notes = col[5]
|
||||
argument = []
|
||||
for a in [c.strip() for c in col[3].split(",")]:
|
||||
a = a.split(" ")
|
||||
if len(a) == 2:
|
||||
argument.append((a[0], a[1]))
|
||||
default = []
|
||||
if len(argument) > 0 and col[4].strip() != "-":
|
||||
for d in [c.strip() for c in col[4].split(",")]:
|
||||
default.append(d)
|
||||
if len(default) != len(argument) and len(default) != 0:
|
||||
sys.stderr.write("warning: default list length mismatch on command '%s'\n"%name)
|
||||
commands.append({
|
||||
"name" : name,
|
||||
"rw" : rw,
|
||||
"code" : code,
|
||||
"argument" : argument,
|
||||
"default" : default,
|
||||
"notes" : notes})
|
||||
return commands
|
||||
|
||||
def format_code_to_cstdint(fmt_name):
|
||||
"""Convert a format code (i8, u16, *, etc) to a c standard int (int8_t, uint16_t, uint8_t *)."""
|
||||
if fmt_name == "*":
|
||||
return "uint8_t *"
|
||||
elif fmt_name[0] == "i":
|
||||
return "int%s_t"%fmt_name[1:]
|
||||
elif fmt_name[0] == "u":
|
||||
return "uint%s_t"%fmt_name[1:]
|
||||
else:
|
||||
raise ValueError("Unknown format code \"%s\""%fmt_name)
|
||||
|
||||
def format_code_size(fmt_name):
|
||||
"""Return the number of bytes required for a type with the given format code."""
|
||||
if fmt_name == "*":
|
||||
return 255
|
||||
else:
|
||||
return int(fmt_name[1:])//8
|
||||
|
||||
def cannon_name(command):
|
||||
"""Given the name of a command, return the canonical function name
|
||||
(lowercase, with spaces converted to underscores)."""
|
||||
return command.lower().replace(" ", "_")
|
||||
|
||||
def gen_send_proto(cmd_dict):
|
||||
"""Given a command dictionary (from extract_table), return the prototype
|
||||
of a function that transmits the command."""
|
||||
cmd = cmd_dict
|
||||
|
||||
#Comment
|
||||
comment = "/* Note: %s */\n"%textwrap.fill(cmd["notes"], 80).replace("\n", "\n * ")
|
||||
|
||||
#Prototype
|
||||
need_trigger = False
|
||||
proto = "void "
|
||||
proto += "send_" + cannon_name(cmd["name"]) + "("
|
||||
for a in cmd["argument"]:
|
||||
proto += "" + format_code_to_cstdint(a[0]) + " " + a[1] + ", "
|
||||
if a[0] == "*": need_trigger = True
|
||||
proto = proto[0:-2] #Remove last commma
|
||||
proto += ");\n"
|
||||
|
||||
#Trigger
|
||||
trigger = ""
|
||||
if need_trigger:
|
||||
trigger = "void " + cannon_name(cmd["name"]) + "_trigger(void);\n"
|
||||
|
||||
return comment + proto + trigger
|
||||
|
||||
def gen_send_func(cmd_dict, write_mode):
|
||||
"""Given a command dictionary (from extract_table()), return a function that constructs
|
||||
a packet of that type. If write_mode is true, the command is transmitted in the
|
||||
write form; otherwise, the read form is used.
|
||||
|
||||
The function relies on the following functions:
|
||||
send_packet(uint8_t *data, uint16_t count) - send the given packet across the radio link.
|
||||
The send_packet() function must add a start and end byte and
|
||||
escape characters where necessary.
|
||||
pack*(uint8_t *data, uint16_t pos, uint*_t value) - pack the given value (where * = 8, 16, 32, or 64 bits)
|
||||
into the given buffer position in a little-endian format.
|
||||
memcpy(void *dest, void *src, uint16_t count) - copy bytes"""
|
||||
cmd = cmd_dict
|
||||
|
||||
#Comment
|
||||
comment = "/* Note: %s */\n"%textwrap.fill(cmd["notes"], 80).replace("\n", "\n * ")
|
||||
|
||||
#Prototype
|
||||
proto = "void "
|
||||
proto += "send_" + cannon_name(cmd["name"]) + "("
|
||||
for a in cmd["argument"]:
|
||||
proto += "" + format_code_to_cstdint(a[0]) + " " + a[1] + ", "
|
||||
proto = proto[0:-2] #Remove last commma
|
||||
proto += "){\n"
|
||||
|
||||
#Command
|
||||
command = "\tbuf[0] = 0x%X;\n"%(cmd["code"] | (int(not write_mode) << 7))
|
||||
|
||||
#Packet stuffing
|
||||
body = ""
|
||||
totalsize = 1 #current byte in packet buffer
|
||||
prev_a = None
|
||||
for a in cmd["argument"]:
|
||||
if a[0] == "*":
|
||||
if prev_a == None:
|
||||
raise ValueError("In command %s, variable-length argument used before length controlling argument.")
|
||||
body += "\tmemcpy(buf + b, %s, %s);\n"%(a[1], prev_a[1])
|
||||
body += "\tb += %s;\n"%prev_a[1]
|
||||
totalsize += 255
|
||||
else:
|
||||
s = format_code_size(a[0])
|
||||
body += "\tpack%d(buf, b, %s);\n"%(s*8, a[1])
|
||||
body += "\tb += %d;\n"%s
|
||||
totalsize += s
|
||||
prev_a = a
|
||||
|
||||
#Send command
|
||||
send = "\tsend_packet(buf, b);\n}\n"
|
||||
|
||||
#Declarations
|
||||
declarations = "\tuint16_t b = 1;\n\tuint8_t buf[%d];\n"%totalsize
|
||||
|
||||
return comment + proto + declarations + command + body + send
|
||||
|
||||
def gen_parse_proto(cmd_dict):
|
||||
"""Given a command dictionary (from extract_table()), return a function that
|
||||
Extracts a packet of that type from a buffer. The first byte in the buffer
|
||||
should be the command byte, escape characters must be removed, and it need
|
||||
not contain the end byte.
|
||||
|
||||
The generated function relies on the following functions:
|
||||
unpack*(uint8_t *data, uint16_t pos, uint*_t *result) - (where * = 8, 16, 32, etc.)
|
||||
unpack the little-endian value from the buffer and write it
|
||||
to a variable.
|
||||
memcpy(void *dest, void *src, uint16_t count) - copy bytes"""
|
||||
cmd = cmd_dict
|
||||
|
||||
#Comment
|
||||
comment = "/* Note: %s */\n"%textwrap.fill(cmd["notes"], 80).replace("\n", "\n * ")
|
||||
|
||||
#Prototype
|
||||
proto = "void "
|
||||
proto += "parse_" + cannon_name(cmd["name"]) + "("
|
||||
proto += "uint8_t *packet, "
|
||||
for a in cmd["argument"]:
|
||||
typename = "" + format_code_to_cstdint(a[0]).strip()
|
||||
if typename[-1] == "*":
|
||||
typename = typename[0:-1]
|
||||
proto += typename + " *" + a[1] + ", "
|
||||
proto = proto[0:-2] #Remove last commma
|
||||
proto += ";\n"
|
||||
|
||||
return comment + proto
|
||||
|
||||
def gen_parse_func(cmd_dict):
|
||||
"""Given a command dictionary (from extract_table()), return a function that
|
||||
Extracts a packet of that type from a buffer. The first byte in the buffer
|
||||
should be the command byte, escape characters must be removed, and it need
|
||||
not contain the end byte.
|
||||
|
||||
The generated function relies on the following functions:
|
||||
unpack*(uint8_t *data, uint16_t pos, uint*_t *result) - (where * = 8, 16, 32, etc.)
|
||||
unpack the little-endian value from the buffer and write it
|
||||
to a variable.
|
||||
memcpy(void *dest, void *src, uint16_t count) - copy bytes"""
|
||||
cmd = cmd_dict
|
||||
|
||||
#Comment
|
||||
comment = "/* Note: %s */\n"%textwrap.fill(cmd["notes"], 80).replace("\n", "\n * ")
|
||||
|
||||
#Prototype
|
||||
proto = "void "
|
||||
proto += "parse_" + cannon_name(cmd["name"]) + "("
|
||||
proto += "uint8_t *packet, "
|
||||
for a in cmd["argument"]:
|
||||
typename = "" + format_code_to_cstdint(a[0]).strip()
|
||||
if typename[-1] == "*":
|
||||
typename = typename[0:-1]
|
||||
proto += typename + " *" + a[1] + ", "
|
||||
proto = proto[0:-2] #Remove last commma
|
||||
proto += "){\n"
|
||||
|
||||
#Packet stuffing
|
||||
body = ""
|
||||
totalsize = 1 #current byte in packet buffer
|
||||
prev_a = None
|
||||
for a in cmd["argument"]:
|
||||
if a[0] == "*":
|
||||
if prev_a == None:
|
||||
raise ValueError("In command %s, variable-length argument used before length controlling argument."%cmd["name"])
|
||||
body += "\tmemcpy(%s, packet + b, *%s);\n"%(a[1], prev_a[1])
|
||||
body += "\tb += *%s;\n"%prev_a[1]
|
||||
totalsize += 255
|
||||
else:
|
||||
s = format_code_size(a[0])
|
||||
body += "\tunpack%d(packet, b, (uint%d_t *) %s);\n"%(s*8, s*8, a[1])
|
||||
body += "\tb += %d;\n"%s
|
||||
totalsize += s
|
||||
prev_a = a
|
||||
|
||||
#Declarations
|
||||
declarations = "\tuint16_t b = 1;\n"
|
||||
|
||||
return comment + proto + declarations + body + "}\n"
|
||||
|
||||
def gen_packing_protos():
|
||||
"""Return a string containing the prototypes of the functions:
|
||||
unpack*(uint8_t *data, uint16_t pos, uint*_t *result) - (where * = 8, 16, 32, etc.)
|
||||
unpack the little-endian value from the buffer and write it
|
||||
to a variable.
|
||||
pack*(uint8_t *data, uint16_t pos, uint*_t value) - pack the given value (where * = 8, 16, 32, or 64 bits)
|
||||
into the given buffer position in a little-endian format.
|
||||
where * = 8, 16, 32, and 64."""
|
||||
s = ""
|
||||
|
||||
for n in [8, 16, 32, 64]:
|
||||
s += "void pack%d(uint8_t *data, uint16_t pos, uint%d_t value);\n"%(n,n)
|
||||
|
||||
s += "void unpack%d(uint8_t *data, uint16_t pos, uint%d_t *result);\n"%(n,n)
|
||||
return s
|
||||
|
||||
def gen_packing_funcs():
|
||||
"""Return a string containing the source code to the functions:
|
||||
unpack*(uint8_t *data, uint16_t pos, uint*_t *result) - (where * = 8, 16, 32, etc.)
|
||||
unpack the little-endian value from the buffer and write it
|
||||
to a variable.
|
||||
pack*(uint8_t *data, uint16_t pos, uint*_t value) - pack the given value (where * = 8, 16, 32, or 64 bits)
|
||||
into the given buffer position in a little-endian format.
|
||||
where * = 8, 16, 32, and 64."""
|
||||
s = ""
|
||||
|
||||
for n in [8, 16, 32, 64]:
|
||||
s += "void pack%d(uint8_t *data, uint16_t pos, uint%d_t value){\n"%(n,n)
|
||||
for i in range(0, n//8):
|
||||
s += "\t*(data + pos + %d) = (value >> %d) & 0xFF;\n"%(i,i*8)
|
||||
s += "}\n"
|
||||
|
||||
s += "void unpack%d(uint8_t *data, uint16_t pos, uint%d_t *result){\n"%(n,n)
|
||||
s += "\t*result = "
|
||||
for i in range(0, n//8):
|
||||
s += "(((uint%d_t) *(data + pos + %d) << %d)) | "%(n,i, i * 8)
|
||||
s = s[0:-3] #trim last " | "
|
||||
s += ";\n}\n"
|
||||
return s
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
with open(sys.argv[1], "r") as f:
|
||||
cmds = extract_table(f.read())
|
||||
for c in cmds:
|
||||
print(c["argument"])
|
||||
Reference in New Issue
Block a user