The new spec is here: [KTag Beacon Specification v0.11](https://ktag.clubk.club/Technology/BLE/KTag%20Beacon%20Specification%20v0.11.pdf) Co-authored-by: Joe Kearney <joe@clubk.club> Reviewed-on: #1
393 lines
17 KiB
Python
393 lines
17 KiB
Python
#!/usr/bin/env python
|
|
"""Generates C code for the nonvolatile memory (NVM) data structures.
|
|
"""
|
|
|
|
# Imports
|
|
import argparse
|
|
import codecs
|
|
import datetime
|
|
import glob
|
|
import logging
|
|
import os
|
|
import pathlib
|
|
import pprint
|
|
import shutil
|
|
|
|
import colorama
|
|
import openpyxl
|
|
|
|
APP_NAME = "KTag Autocode Generator"
|
|
__author__ = "Joe Kearney"
|
|
__version__ = "00.03"
|
|
|
|
NVM_SPREADSHEET_FILENAME = "2020TPC Nonvolatile Memory.xlsx"
|
|
SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__))
|
|
DESTINATION_PATHS = ("2020TPCApp1.cydsn/NVM", "2020TPCAppNoDFU.cydsn/NVM")
|
|
|
|
# See https://developer.arm.com/documentation/dui0472/m/c-and-c---implementation-details/basic-data-types-in-arm-c-and-c--
|
|
# and
|
|
# https://en.wikibooks.org/wiki/C_Programming/inttypes.h
|
|
CortexM_type_sizes_in_bytes = {
|
|
"bool": 1,
|
|
"int8_t": 1,
|
|
"uint8_t": 1,
|
|
"int16_t": 2,
|
|
"uint16_t": 2,
|
|
"int32_t": 4,
|
|
"uint32_t": 4,
|
|
"float": 4,
|
|
"int64_t": 8,
|
|
"uint64_t": 8,
|
|
"double": 8,
|
|
}
|
|
|
|
# Initialize the pretty-printer.
|
|
pp = pprint.PrettyPrinter(indent=4)
|
|
|
|
|
|
# Openpyxl helper function from https://stackoverflow.com/questions/23562366/how-do-i-get-value-present-in-a-merged-cell
|
|
def getValueWithMergeLookup(sheet, cell):
|
|
idx = cell.coordinate
|
|
for my_range in sheet.merged_cells.ranges:
|
|
merged_cells = list(openpyxl.utils.rows_from_range(str(my_range)))
|
|
for row in merged_cells:
|
|
if idx in row:
|
|
# If this is a merged cell,
|
|
# return the first cell of the merge range
|
|
coordinates_of_first_cell = merged_cells[0][0]
|
|
return sheet[coordinates_of_first_cell].value
|
|
|
|
return sheet[idx].value
|
|
|
|
|
|
# Set up logging.
|
|
rootLogger = logging.getLogger()
|
|
rootLogger.setLevel(logging.DEBUG)
|
|
fileHandler = logging.FileHandler('Autocode.log', mode='w')
|
|
logFormatter = logging.Formatter(
|
|
'{asctime} {name} {levelname:8s} {message}', style='{')
|
|
fileHandler.setFormatter(logFormatter)
|
|
rootLogger.addHandler(fileHandler)
|
|
consoleHandler = logging.StreamHandler()
|
|
consoleHandler.setFormatter(logFormatter)
|
|
rootLogger.addHandler(consoleHandler)
|
|
logger = logging.getLogger('autocode.py')
|
|
|
|
|
|
def main():
|
|
version_information = APP_NAME + ' version ' + __version__
|
|
|
|
logger.info(version_information)
|
|
|
|
colorama.init(autoreset=True)
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description='Generate autocode', epilog=version_information)
|
|
commands = parser.add_mutually_exclusive_group()
|
|
commands.add_argument(
|
|
'-n', '--nvm', help='Generate nonvolatile memory autocode', action='store_true')
|
|
parser.add_argument('-v', '--version', action='version',
|
|
version=version_information)
|
|
|
|
args = parser.parse_args()
|
|
d = vars(args)
|
|
|
|
if (d['nvm'] == True):
|
|
generate_NVM_autocode()
|
|
else:
|
|
# If no commands were specified, show the help and exit.
|
|
parser.parse_args(['-h'])
|
|
|
|
|
|
def generate_NVM_autocode():
|
|
logger.info('Generating nonvolatile memory autocode...')
|
|
|
|
wb = openpyxl.load_workbook(
|
|
filename=NVM_SPREADSHEET_FILENAME)
|
|
ws = wb['NVM']
|
|
|
|
headerColumnsByName = dict()
|
|
for rowIndex, row in enumerate(ws.iter_rows(min_row=1, max_row=1), start=1):
|
|
for columnIndex, cell in enumerate(row, start=0):
|
|
headerColumnsByName[cell.value] = columnIndex
|
|
|
|
NVMItemsByID = dict()
|
|
NVMLocations = set()
|
|
NVMEntriesByLocation = dict()
|
|
|
|
for rowIndex, row in enumerate(ws.iter_rows(min_row=2)):
|
|
if (row[headerColumnsByName['ID']].value is not None):
|
|
item_ID = int(row[headerColumnsByName['ID']].value)
|
|
|
|
nvm_item = dict()
|
|
nvm_item['ID'] = item_ID
|
|
nvm_item['Location'] = row[headerColumnsByName['Location']].value
|
|
if nvm_item['Location'] != 'None':
|
|
if nvm_item['Location'] not in NVMLocations:
|
|
# This is a new location.
|
|
NVMLocations.add(nvm_item['Location'])
|
|
NVMEntriesByLocation[nvm_item['Location']] = list()
|
|
|
|
nvm_item['Name'] = row[headerColumnsByName['Item Name']].value
|
|
nvm_item['Item Name in Code'] = nvm_item['Name'].replace(
|
|
' ', '_')
|
|
nvm_item['Item Shorthand Macro'] = 'NVM_' + \
|
|
nvm_item['Name'].upper().replace(' ', '_')
|
|
nvm_item['Entry'] = row[headerColumnsByName['Entry Name']].value
|
|
nvm_item['Entry Name in Code'] = 'NVM_' + \
|
|
nvm_item['Entry'].replace(' ', '_')
|
|
nvm_item['Entry Type Name'] = nvm_item['Entry Name in Code'] + '_T'
|
|
nvm_item['RAM Entry Name in Code'] = 'NVM_' + \
|
|
nvm_item['Entry'].replace(' ', '_')
|
|
nvm_item['Datatype'] = row[headerColumnsByName['Datatype']].value
|
|
|
|
# Validate the datatype and get the size.
|
|
if nvm_item['Datatype'] not in CortexM_type_sizes_in_bytes.keys():
|
|
logger.error('NVM item ' + str(item_ID) + ' on row ' + str(rowIndex + 2) +
|
|
' has unknown datatype \"' + str(nvm_item['Datatype']) + '\"!')
|
|
else:
|
|
nvm_item['Data Size in Bytes'] = CortexM_type_sizes_in_bytes[nvm_item['Datatype']]
|
|
|
|
nvm_item['Default Value'] = row[headerColumnsByName['Default Value']].value
|
|
nvm_item['Description'] = row[headerColumnsByName['Description']].value
|
|
nvm_item['Notes'] = row[headerColumnsByName['Notes']].value
|
|
|
|
# pp.pprint(nvm_item)
|
|
if nvm_item['Entry'] not in NVMEntriesByLocation[nvm_item['Location']]:
|
|
NVMEntriesByLocation[nvm_item['Location']].append(
|
|
nvm_item['Entry'])
|
|
NVMItemsByID[item_ID] = nvm_item
|
|
|
|
for location in NVMLocations:
|
|
logger.info('Generating code for ' + location + ' NVM.')
|
|
|
|
with codecs.open('NVM_' + location + 'EEPROMEntries.h', 'w', "utf-8") as f:
|
|
f.write("/*\n")
|
|
f.write(
|
|
r" * __ ________ _____ ______ __ " + "\n")
|
|
f.write(
|
|
r" * / //_/_ __/___ _____ _ / ___/____ __ _______________ / ____/___ ____/ /__ " + "\n")
|
|
f.write(
|
|
r" * / ,< / / / __ `/ __ `/ \__ \/ __ \/ / / / ___/ ___/ _ \ / / / __ \/ __ / _ \ " + "\n")
|
|
f.write(
|
|
r" * / /| | / / / /_/ / /_/ / ___/ / /_/ / /_/ / / / /__/ __/ / /___/ /_/ / /_/ / __/ " + "\n")
|
|
f.write(
|
|
r" * /_/ |_|/_/ \__,_/\__, / /____/\____/\__,_/_/ \___/\___/ \____/\____/\__,_/\___/ " + "\n")
|
|
f.write(
|
|
r" * /____/ " + "\n")
|
|
f.write(" *\n")
|
|
f.write(
|
|
" * 🃞 THIS FILE IS PART OF THE KTAG SOURCE CODE. Visit https://ktag.clubk.club/ for more. 🃞\n")
|
|
f.write(" *\n")
|
|
f.write(" */\n")
|
|
|
|
f.write('\n')
|
|
f.write('/** \\file\n')
|
|
f.write(
|
|
' * \\brief [Autogenerated] This file declares the ' + location + ' EEPROM entries.\n')
|
|
f.write(' *\n')
|
|
f.write(
|
|
' * \\note AUTOGENERATED: This file was generated automatically on ' +
|
|
'{dt:%A}, {dt:%B} {dt.day}, {dt.year} at {dt:%r}'.format(dt=datetime.datetime.now()) +
|
|
'.\n')
|
|
f.write(' * DO NOT MODIFY THIS FILE MANUALLY!\n')
|
|
f.write(' */\n')
|
|
f.write('\n')
|
|
f.write('#ifndef NVM_' + location.upper() + 'EEPROMENTRIES_H\n')
|
|
f.write('#define NVM_' + location.upper() + 'EEPROMENTRIES_H\n')
|
|
f.write('\n')
|
|
f.write('#ifdef __cplusplus\n')
|
|
f.write('extern "C" {\n')
|
|
f.write('#endif\n')
|
|
f.write('\n')
|
|
f.write('/* Preprocessor and Type Definitions */\n')
|
|
f.write('\n')
|
|
|
|
for entry in NVMEntriesByLocation[location]:
|
|
entry_name_in_code = 'NVM_' + entry.replace(' ', '_')
|
|
entry_type_name = entry_name_in_code + '_T'
|
|
f.write('typedef struct __attribute__((packed))\n')
|
|
f.write('{\n')
|
|
# List all the items in this entry.
|
|
for item_ID in NVMItemsByID:
|
|
item = NVMItemsByID[item_ID]
|
|
if item['Entry'] == entry:
|
|
if item['Description'] is not None:
|
|
f.write(' //! ' + item['Description'] + '\n')
|
|
f.write(' ' + item['Datatype'] +
|
|
' ' + item['Item Name in Code'] + ';\n')
|
|
f.write('} ' + entry_type_name + ';\n')
|
|
f.write('\n')
|
|
|
|
f.write('\n')
|
|
f.write('/* Include Files */\n')
|
|
f.write('\n')
|
|
f.write('/* Public Variables */\n')
|
|
f.write('\n')
|
|
|
|
for entry in NVMEntriesByLocation[location]:
|
|
entry_name_in_code = 'NVM_' + entry.replace(' ', '_')
|
|
entry_type_name = entry_name_in_code + '_T'
|
|
f.write('extern NVM_EEPROMEntry_T ' +
|
|
entry_name_in_code + ';\n')
|
|
|
|
f.write('\n')
|
|
f.write('extern NVM_EEPROMEntry_T * const NVM_' +
|
|
location + 'EEPROMEntries[];\n')
|
|
f.write('extern const uint8_t NVM_N_' +
|
|
location.upper() + '_EEPROM_ENTRIES;\n')
|
|
f.write('\n')
|
|
f.write('// Shorthand macros, to save you time.\n')
|
|
|
|
for item_ID in NVMItemsByID:
|
|
item = NVMItemsByID[item_ID]
|
|
if item['Location'] == location:
|
|
f.write('#define ' + item['Item Shorthand Macro'] + ' (((' + item['Entry Type Name'] +
|
|
'*)' + item['Entry Name in Code'] + '.Value)->' + item['Item Name in Code'] + ')\n')
|
|
f.write('#define ' + item['Item Shorthand Macro'] +
|
|
'_ENTRY_PTR (&' + item['Entry Name in Code'] + ')\n')
|
|
f.write('\n')
|
|
|
|
f.write('\n')
|
|
f.write('#ifdef __cplusplus\n')
|
|
f.write('}\n')
|
|
f.write('#endif\n')
|
|
f.write('\n')
|
|
f.write('#endif // NVM_' + location.upper() + 'EEPROMENTRIES_H\n')
|
|
f.write('\n')
|
|
|
|
with codecs.open('NVM_' + location + 'EEPROMEntries.c', 'w', "utf-8") as f:
|
|
f.write("/*\n")
|
|
f.write(
|
|
r" * __ ________ _____ ______ __ " + "\n")
|
|
f.write(
|
|
r" * / //_/_ __/___ _____ _ / ___/____ __ _______________ / ____/___ ____/ /__ " + "\n")
|
|
f.write(
|
|
r" * / ,< / / / __ `/ __ `/ \__ \/ __ \/ / / / ___/ ___/ _ \ / / / __ \/ __ / _ \ " + "\n")
|
|
f.write(
|
|
r" * / /| | / / / /_/ / /_/ / ___/ / /_/ / /_/ / / / /__/ __/ / /___/ /_/ / /_/ / __/ " + "\n")
|
|
f.write(
|
|
r" * /_/ |_|/_/ \__,_/\__, / /____/\____/\__,_/_/ \___/\___/ \____/\____/\__,_/\___/ " + "\n")
|
|
f.write(
|
|
r" * /____/ " + "\n")
|
|
f.write(" *\n")
|
|
f.write(" * This file is part of the KTag project, a DIY laser tag game with customizable features and wide interoperability.\n")
|
|
f.write(
|
|
" * 🛡️ <https://ktag.clubk.club> 🃞\n")
|
|
f.write(" *\n")
|
|
f.write(" */\n")
|
|
|
|
f.write('\n')
|
|
f.write('/** \\file\n')
|
|
f.write(
|
|
' * \\brief [Autogenerated] This file defines the ' + location + ' EEPROM entries.\n')
|
|
f.write(' *\n')
|
|
f.write(
|
|
' * \\note AUTOGENERATED: This file was generated automatically on ' +
|
|
'{dt:%A}, {dt:%B} {dt.day}, {dt.year} at {dt:%r}'.format(dt=datetime.datetime.now()) +
|
|
'.\n')
|
|
f.write(' * DO NOT MODIFY THIS FILE MANUALLY!\n')
|
|
f.write(' */\n')
|
|
f.write('\n')
|
|
|
|
f.write('/* Include Files */\n')
|
|
f.write('#include "KTag.h"\n')
|
|
f.write('\n')
|
|
f.write('/* EEPROM Entries */\n')
|
|
f.write('\n')
|
|
f.write(r'/** \defgroup NVM_' + location.upper() +
|
|
'_EEPROM NVM ' + location + ' EEPROM\n')
|
|
f.write(' *\n')
|
|
f.write(' * The ' + location +
|
|
' EEPROM is divided into logical "entries", represented by instances of the #NVM_EEPROMEntry_T type.\n')
|
|
f.write(' * At startup, these entries are loaded into their respective RAM copies by NVM_Init' +
|
|
location + 'EEPROM(). The application\n')
|
|
f.write(' * then updates the RAM copies directly, and requests that the NVM_' +
|
|
location + 'EEPROMTask() save these back to the EEPROM\n')
|
|
f.write(' * when necessary.\n')
|
|
f.write(' * @{ */\n')
|
|
f.write('\n')
|
|
|
|
# Here is the magic: keep track of the locations in EE as we create the data structures.
|
|
current_EE_address_in_bytes = 0
|
|
|
|
for entry in NVMEntriesByLocation[location]:
|
|
entry_name_in_code = 'NVM_' + entry.replace(' ', '_')
|
|
entry_RAM_name_in_code = 'RAM_' + entry.replace(' ', '_')
|
|
entry_default_name_in_code = 'DEFAULT_' + \
|
|
entry.replace(' ', '_')
|
|
entry_type_name = entry_name_in_code + '_T'
|
|
f.write('static ' + entry_type_name + ' ' +
|
|
entry_RAM_name_in_code + ';\n')
|
|
f.write('\n')
|
|
f.write('static const ' + entry_type_name + ' ' +
|
|
entry_default_name_in_code + ' = \n')
|
|
f.write('{\n')
|
|
|
|
# Assign defaults to all the items in this entry, and calculate the total size.
|
|
entry_size_in_bytes = 0
|
|
CRC_size_in_bytes = 2
|
|
for item_ID in NVMItemsByID:
|
|
item = NVMItemsByID[item_ID]
|
|
if item['Entry'] == entry:
|
|
if item['Description'] is not None:
|
|
f.write(' //! ' + item['Description'] + '\n')
|
|
f.write(
|
|
' .' + item['Item Name in Code'] + ' = ' + str(item['Default Value']) + ',\n')
|
|
entry_size_in_bytes += item['Data Size in Bytes']
|
|
|
|
f.write('};\n')
|
|
f.write('\n')
|
|
|
|
f.write('NVM_EEPROMEntry_T ' + entry_name_in_code + ' = \n')
|
|
f.write('{\n')
|
|
f.write(' //! Size == sizeof(' + entry_type_name + ')\n')
|
|
f.write(' .Size = ' + str(entry_size_in_bytes) + ',\n')
|
|
f.write(' .EE_Address = ' +
|
|
str(current_EE_address_in_bytes) + ',\n')
|
|
current_EE_address_in_bytes += entry_size_in_bytes
|
|
f.write(' .EE_CRC_Address = ' +
|
|
str(current_EE_address_in_bytes) + ',\n')
|
|
current_EE_address_in_bytes += CRC_size_in_bytes
|
|
f.write(' .Value = (uint8_t *)&' +
|
|
entry_RAM_name_in_code + ',\n')
|
|
f.write(' .Default = (uint8_t *)&' +
|
|
entry_default_name_in_code + ',\n')
|
|
f.write(' .State = NVM_STATE_UNINITIALIZED\n')
|
|
f.write('};\n')
|
|
|
|
f.write('\n')
|
|
f.write('/** @} */\n')
|
|
f.write('\n')
|
|
f.write('NVM_EEPROMEntry_T * const NVM_' +
|
|
location + 'EEPROMEntries[] =\n')
|
|
f.write('{\n')
|
|
|
|
for entry in NVMEntriesByLocation[location]:
|
|
entry_name_in_code = 'NVM_' + entry.replace(' ', '_')
|
|
f.write(' &' + entry_name_in_code + ',\n')
|
|
|
|
f.write('};\n')
|
|
f.write('\n')
|
|
f.write('//! Size of the #NVM_' + location +
|
|
'EEPROMEntries array (i.e. the number of ' + location + ' EEPROM entries).\n')
|
|
f.write('const uint8_t NVM_N_' + location.upper() + '_EEPROM_ENTRIES = (uint8_t) (sizeof(NVM_' +
|
|
location + 'EEPROMEntries) / sizeof(NVM_EEPROMEntry_T *));\n')
|
|
f.write('\n')
|
|
|
|
for destination in DESTINATION_PATHS:
|
|
destination = os.path.normpath(os.path.join(SCRIPT_PATH, destination))
|
|
logger.info("Copying files to " + destination + ".")
|
|
for file in glob.glob("NVM_*.[ch]"):
|
|
shutil.copy2(file, destination)
|
|
|
|
logger.info("Cleaning up.")
|
|
for file in glob.glob("NVM_*.[ch]"):
|
|
path = pathlib.Path(file)
|
|
path.unlink()
|
|
|
|
logger.info('Nonvolatile memory autocode generation completed.')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|