Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

46 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸŽ›οΈ C Menu Generator for Embedded LCD1602

πŸ‡¬πŸ‡§ English Β· πŸ‡·πŸ‡Ί Русский

Generate C source code for an embedded LCD1602 menu system from a declarative configuration. You describe the menu tree in YAML/JSON β€” the generator produces a complete set of C files (data tables, navigation, drawing, editing, callbacks) ready to be compiled into your firmware.


πŸ“– Table of contents


Why this project

Writing an LCD menu system in C by hand is repetitive and error-prone: data tables, navigation links, value drawing and editing callbacks, cyclic/limited navigation rules β€” every device needs a slightly different version of the same boilerplate.

This project treats the menu as data. Instead of writing C, you write a small declarative configuration:

  • types β†’ C types (byte, uword, string, …)
  • roles β†’ how a value behaves (simple, factor, fixed, callback)
  • controls β†’ which encoder actions are supported (click, position)
  • navigation β†’ cyclic or limited (cyclic / limit)
  • callbacks β†’ your own handlers or auto-generated ones

The generator validates the configuration, flattens the tree into a navigable structure, and renders Jinja2 templates into C files that compile into your firmware.

How it works

config/config.yaml
  β†’ MenuConfig            loads all configuration files
  β†’ MenuValidator         JSON Schema + custom validation of the tree
  β†’ MenuFlattener         expands the tree into a flat list + navigation links
  β†’ FlatNode/BaseFlatNode node built from a composition of managers
  β†’ MenuCraft             coordinator, aggregates data for the templates
  β†’ MenuGenerator         renders Jinja2 templates β†’ C files

The pipeline is fully data-driven: the same generator handles any menu β€” from a single "Start" screen to a multi-level settings tree with dozens of nodes.

Repository layout

MenuCraft/
β”œβ”€β”€ generate_menu.py              # ← run this: the only entry point at the root
β”œβ”€β”€ generate_menu/                # Python package with all sources
β”‚   β”œβ”€β”€ cli.py                    # command-line interface (argparse)
β”‚   β”œβ”€β”€ i18n.py                   # gettext helper
β”‚   β”œβ”€β”€ common.py                 # JSON/YAML loaders and helpers
β”‚   β”œβ”€β”€ menu_config.py            # loads & validates all config files
β”‚   β”œβ”€β”€ menu_data.py              # type/role/control/navigation rules
β”‚   β”œβ”€β”€ menu_validator.py         # schema + custom validation
β”‚   β”œβ”€β”€ menu_flattener.py         # tree β†’ flat list, navigation links
β”‚   β”œβ”€β”€ base_flat_node.py         # base node (manager composition)
β”‚   β”œβ”€β”€ flat_node.py              # final node class
β”‚   β”œβ”€β”€ menucraft.py              # coordinator (delegates to the aggregator)
β”‚   β”œβ”€β”€ menu_data_aggregator.py   # cached aggregations
β”‚   β”œβ”€β”€ menu_generator.py         # Jinja2 rendering β†’ C files
β”‚   β”œβ”€β”€ locale/                   # gettext catalogs (messages.pot, ru/...)
β”‚   └── managers/                 # per-node managers
β”œβ”€β”€ config/                       # YAML/JSON configuration files
β”œβ”€β”€ menu/                         # the menu tree (menu.yaml / menu.json)
β”œβ”€β”€ templates/                    # Jinja2 templates (*.jinja)
β”œβ”€β”€ output/                       # generated C files (include/ + sources, git-ignored)
β”œβ”€β”€ docs/                         # documentation (see below)
β”œβ”€β”€ tests/                        # unit & smoke tests (pytest)
β”œβ”€β”€ test/                         # integration tests (pytest)
β”œβ”€β”€ conftest.py                   # shared pytest fixtures
β”œβ”€β”€ pytest.ini
└── requirements.txt

Quick start

# 1. Install dependencies
pip install -r requirements.txt

# 2. Run the generator from the project root
python generate_menu.py

The generated C files appear in output/ (sources) and output/include/ (headers).

πŸ’‘ The root keeps the entry point generate_menu.py plus the asset directories (config/, menu/, templates/, output/) and the docs; all Python sources live inside the generate_menu/ package.

Configuration

The main configuration file is config/config.yaml.

Main config: config/config.yaml

# Paths are relative to this file (the config/ directory)
menu: ../menu/menu.yaml          # the menu tree
menu_schema: menu_schema.yaml    # JSON Schema for validation
menu_config: menu_data.yaml      # type/role/control/navigation rules
output_flattern: output/flatterned.json   # flattened JSON dump (debug)
generation_files: files.yaml     # which templates produce which files

Menu tree: menu/menu.yaml

config:
  version: "1.0"
  default_navigate: limit
  default_control: position
  default_branch_navigate: cyclic
  root_navigate: cyclic
  output_directory: ./output/    # where generated C files go
  include_files: [pulse_config.h]

menu:
  - id: start                    # a node is defined by id + title
    title: Start
    type: string                 # data type (see rules below)
    role: fixed                  # role: simple / factor / fixed / callback
    values: [Start, Started]     # fixed set of values
    default_idx: 0
    navigate: cyclic             # cyclic or limit

  - id: settings
    title: Settings
    navigate: limit
    items:                       # nested items β†’ sub-menu
      - id: hi_delay
        title: Delay
        type: udword
        role: factor
        default: 10
        min: 10
        max: 10000
        factors: [1, 10, 100, 1000]   # multipliers for the factor role

Type / role / control rules: config/menu_data.yaml

types:
  ubyte: {c_type: uint8_t}
  string: {c_type: "const char*"}

roles:
  simple: [byte, ubyte, word, uword, dword, udword]
  fixed: [string, byte, ubyte, word, uword, dword, udword]
  factor: [byte, ubyte, word, uword, dword, udword]
  callback: [callback]

navigation_rules:
  position:
    allowed_navigate: [limit, cyclic]
    default: limit

Generation files: config/files.yaml

templates_path: ./templates/     # where Jinja2 templates live
files:
  handle.h.jinja: include/menu.h
  handle.c.jinja: menu.c
  context.h.jinja: include/menu_context.h
  context.c.jinja: menu_context.c
  # ... every template maps to a generated C file

Node reference

A node in the menu tree can use the following fields:

Field Meaning
id Unique identifier of the node
title Text shown on the display
type Data type: byte, ubyte, word, uword, dword, udword, string, callback
role Behavior: simple, factor, fixed, callback
min / max / step Numeric range for simple / factor
default Default numeric value
factors Multipliers for the factor role
values / default_idx Fixed value set for the fixed role
controls Encoder controls: click, position
navigate Navigation: cyclic, limit
items Child nodes (sub-menu)
click_cb, position_cb, double_click_cb, long_click_cb, event_cb, draw_value_cb Custom callbacks

If a callback is not specified, it is generated automatically (e.g. menu_draw_{type}_{role}_value_cb for drawing, {type}_{role}_{control}_{navigate}_cb for handling).

GUI

For editing the menu tree without hand-writing YAML, an optional PyQt6 GUI is available:

python gui.py

It's a thin wrapper around the same pipeline described above β€” a tree view plus a property form for the selected node (which fields appear depends on the node's type/role, following the same rules from config/menu_data.yaml), an explicit Validate button, and a Generate C files button that runs the real MenuGenerator on a background thread. A log panel at the bottom shows everything the backend already logs, with search and copy. See docs/gui.md for the full walkthrough, including how it keeps config/config.yaml untouched while generating.

Generated files

The generator produces a full C module:

  • menu_context.h/c β€” menu context and core structures
  • menu_type.h/c β€” data types and enums
  • menu_data_*.h/c β€” data configuration, context, value and name tables
  • menu_tree.h/c β€” menu tree with navigation links
  • menu_value.h/c β€” value access functions
  • menu_navigate.h/c β€” navigation functions
  • menu_edit.h/c β€” editing functions
  • menu_draw.h/c β€” drawing functions
  • menu_name.h/c β€” node-name lookup
  • menu.h/c β€” top-level entry

Internationalization (i18n / gettext)

All user-facing messages use gettext (Babel); the primary language is English.

Choose the language with the MENU_PROCESSOR_LANG environment variable:

# English (default)
python -X utf8 generate_menu.py

# Russian (uses locale/ru catalog)
set MENU_PROCESSOR_LANG=ru
python -X utf8 generate_menu.py

If a catalog is missing or the language is unknown, English is used as fallback.

Tests

Run the whole suite (unit + integration) from the project root:

python -m pytest -q

Documentation

Document Language
docs/architect.md πŸ‡¬πŸ‡§ Architecture overview & recommendations
docs/architect_ru.md πŸ‡·πŸ‡Ί ΠžΠ±Π·ΠΎΡ€ Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρ‹ ΠΈ Ρ€Π΅ΠΊΠΎΠΌΠ΅Π½Π΄Π°Ρ†ΠΈΠΈ
docs/gui.md πŸ‡¬πŸ‡§ GUI (PyQt6): layout, actions, design notes
docs/gui_ru.md πŸ‡·πŸ‡Ί GUI (PyQt6): раскладка, дСйствия, особСнности Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ
docs/jinja_templates.md πŸ‡¬πŸ‡§ Generated C code: architecture & integration
docs/jinja_templates_ru.md πŸ‡·πŸ‡Ί Π“Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΠ΅ΠΌΡ‹ΠΉ C-ΠΊΠΎΠ΄: Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Π° ΠΈ встраиваниС
docs/tests.md πŸ‡¬πŸ‡§ Unit & smoke tests (tests/)
docs/tests_ru.md πŸ‡·πŸ‡Ί ΠœΠΎΠ΄ΡƒΠ»ΡŒΠ½Ρ‹Π΅ ΠΈ smoke-тСсты (tests/)
docs/test.md πŸ‡¬πŸ‡§ Integration tests (test/)
docs/test_ru.md πŸ‡·πŸ‡Ί Π˜Π½Ρ‚Π΅Π³Ρ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Π΅ тСсты (test/)
docs/changes.md πŸ‡¬πŸ‡§ Changelog
docs/changes_ru.md πŸ‡·πŸ‡Ί Π–ΡƒΡ€Π½Π°Π» ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ

Releases

Packages

Contributors

Languages