2020TPC-SW/autocode.py
Joe Kearney e8dda3f16a Pulled in the latest SystemK (with new BLE). (#1)
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
2025-06-08 22:21:17 +00:00

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()