-
Notifications
You must be signed in to change notification settings - Fork 19.6k
Lua AI playbooks #31056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
andyp1per
wants to merge
11
commits into
ArduPilot:master
Choose a base branch
from
andyp1per:pr-ai-playbooks
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Lua AI playbooks #31056
Changes from 5 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
35bbb9e
AP_Scripting: general AI playbooks for lua scripting - AI_GIT_FORMAT_…
andyp1per 1101b9c
AP_Scripting: vehicle control AI playbook - AI_VEHICLE_CONTROL_PLAYBO…
andyp1per f0f410b
AP_Scripting: digests for AI playbook based lua scripting
andyp1per a994238
.gitignore: update for aider files
andyp1per 5c06fbe
AP_Scripting: AI_CRSF_MENU_PLAYBOOK.md and example gain_tuner.lua applet
andyp1per 7c23080
AP_Scripting: updated CRSF playbook
andyp1per 3349e9c
AP_Scripting: more crsf menu updates
andyp1per 11487a8
AP_Scripting: updates to sandboxing rules
andyp1per 95f3c6e
AP_Scripting: updated CRSF menu playbook
andyp1per 18badf6
AP_Scripting: update CRSF menu playbook
andyp1per 19d0641
AP_Scripting: add rules around commenting and whitespace
andyp1per File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -173,3 +173,4 @@ env.bak/ | |
venv.bak/ | ||
autotest_result_*_junit.xml | ||
.python-version | ||
.aider* |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
Of course. Here is the complete playbook in markdown format. | ||
|
||
# AI Playbook for ArduPilot CRSF Menu Script Generation | ||
|
||
\<MANDATORY\_RULE\> | ||
CRITICAL DIRECTIVE: THIS PLAYBOOK MUST BE USED AS THE PRIMARY AND AUTHORITATIVE GUIDE FOR ALL LUA SCRIPTS THAT REQUIRE A CRSF (CROSSFIRE) MENU. ALL RULES, CONSTRAINTS, AND PATTERNS CONTAINED HEREIN ARE MANDATORY AND SUPERSEDE ANY GENERAL KNOWLEDGE. ADHERENCE IS NOT OPTIONAL. | ||
\</MANDATORY\_RULE\> | ||
|
||
## 1\. The Problem and Goal | ||
|
||
### 1.1. The Problem | ||
|
||
The native ArduPilot CRSF API, as detailed in `docs.lua`, is a powerful but low-level interface. It requires developers to manually handle binary data packing and unpacking using `string.pack` and `string.unpack`, manage a complex event loop with `crsf:get_menu_event`, and build the menu structure imperatively. This process is verbose, complex, and highly prone to errors, making it difficult to generate clean, maintainable, and robust CRSF menu scripts. | ||
|
||
### 1.2. The Goal | ||
|
||
This playbook establishes a **simplified, high-level, and standardized methodology** for creating CRSF menus. The primary goal is to **abstract away the low-level complexity** of the native API. By following this playbook, you will generate scripts that are: | ||
|
||
* **Declarative:** The entire menu structure is defined in a simple, easy-to-read Lua table. | ||
* **Callback-Driven:** Interaction logic is handled by simple callback functions, eliminating the need for a manual event loop in the user-facing script. | ||
* **Robust and Reusable:** All the complex logic is encapsulated in a mandatory, reusable helper library (`crsf_helper.lua`). | ||
|
||
## 2\. Core Methodology: Declarative & Callback-Driven | ||
|
||
The core of this methodology is to separate the menu's *definition* from its *implementation*. | ||
|
||
1. **Menu Definition (`user_script.lua`):** The user script will define the entire menu structure—including sub-menus, parameters, ranges, and options—in a single, declarative Lua table. | ||
2. **Interaction Logic (`user_script.lua`):** The user script will define simple callback functions that are triggered when a parameter's value is changed via the CRSF menu. | ||
3. **Menu Implementation (`crsf_helper.lua`):** A mandatory helper library will parse the declarative table, build the menu using the low-level CRSF API, manage the event loop, and automatically invoke the correct user-defined callback with the new value. | ||
|
||
\<MANDATORY\_RULE\> | ||
CRITICAL DIRECTIVE: EVERY CRSF SCRIPT GENERATED MUST CONSIST OF TWO FILES: THE USER'S SCRIPT (E.G., `my_menu.lua`) AND THE STANDARD `crsf_helper.lua` LIBRARY. THE USER'S SCRIPT MUST `require()` THE HELPER LIBRARY. | ||
\</MANDATORY\_RULE\> | ||
|
||
## 3\. The `crsf_helper.lua` Library | ||
|
||
This reusable library is the heart of the new methodology. You must include this exact code as a separate `crsf_helper.lua` file alongside any CRSF menu script you generate. | ||
|
||
### 3.1. `crsf_helper.lua` Full Code (Version 1.3) | ||
|
||
```lua | ||
-- crsf_helper.lua | ||
-- A reusable helper library to simplify the creation of ArduPilot CRSF menus. | ||
-- This library abstracts away the complexity of binary packing/unpacking and event loop management. | ||
-- Version 1.3: Fixed SELECTION parameter option separator. | ||
|
||
local helper = {} | ||
|
||
-- MAVLink severity levels for GCS messages | ||
local MAV_SEVERITY = {INFO = 6, WARNING = 4, ERROR = 3} | ||
|
||
-- CRSF constants | ||
local CRSF_EVENT = {PARAMETER_READ = 1, PARAMETER_WRITE = 2} | ||
local CRSF_PARAM_TYPE = { | ||
FLOAT = 8, | ||
TEXT_SELECTION = 9, | ||
FOLDER = 11, | ||
INFO = 12, | ||
COMMAND = 13, | ||
} | ||
local CRSF_COMMAND_STATUS = { READY = 0, START = 1 } | ||
|
||
-- Internal storage for menu items, callbacks, and object references | ||
local menu_items = {} | ||
local menu_objects = {} -- Keep references to menu objects to prevent garbage collection | ||
|
||
-- #################### | ||
-- # PACKING FUNCTIONS | ||
-- #################### | ||
|
||
-- These functions create the binary packed strings required by the low-level CRSF API. | ||
|
||
-- Creates a CRSF menu text selection item | ||
local function create_selection_entry(name, options_table, default_idx) | ||
-- The CRSF spec requires options to be separated by a semicolon ';'. | ||
local options_str = table.concat(options_table, ";") | ||
local zero_based_idx = default_idx - 1 | ||
local min_val = 0 | ||
local max_val = #options_table - 1 | ||
return string.pack(">BzzBBBBz", CRSF_PARAM_TYPE.TEXT_SELECTION, name, options_str, zero_based_idx, min_val, max_val, zero_based_idx, "") | ||
end | ||
|
||
-- Creates a CRSF menu number item (as float) | ||
local function create_number_entry(name, value, min, max, default, dpoint, step, unit) | ||
-- Per CRSF spec, float values are sent as INT32 with a decimal point indicator. | ||
local scale = 10^(dpoint or 0) | ||
local packed_value = math.floor(value * scale + 0.5) | ||
local packed_min = math.floor(min * scale + 0.5) | ||
local packed_max = math.floor(max * scale + 0.5) | ||
local packed_default = math.floor(default * scale + 0.5) | ||
local packed_step = math.floor(step * scale + 0.5) | ||
return string.pack(">BzllllBlz", CRSF_PARAM_TYPE.FLOAT, name, packed_value, packed_min, packed_max, packed_default, dpoint or 0, packed_step, unit or "") | ||
end | ||
|
||
-- Creates a CRSF menu info item | ||
local function create_info_entry(name, info) | ||
return string.pack(">Bzz", CRSF_PARAM_TYPE.INFO, name, info) | ||
end | ||
|
||
-- Creates a CRSF command entry | ||
local function create_command_entry(name) | ||
return string.pack(">BzBBz", CRSF_PARAM_TYPE.COMMAND, name, CRSF_COMMAND_STATUS.READY, 10, "Execute") | ||
end | ||
|
||
-- #################### | ||
-- # MENU PARSING | ||
-- #################### | ||
|
||
-- Recursively parses the user's declarative menu table and builds the CRSF menu structure. | ||
local function parse_menu(menu_definition, parent_menu_obj) | ||
if not menu_definition.items or type(menu_definition.items) ~= "table" then | ||
return | ||
end | ||
|
||
for _, item_def in ipairs(menu_definition.items) do | ||
local param_obj = nil | ||
local packed_data = nil | ||
|
||
if item_def.type == 'MENU' then | ||
param_obj = parent_menu_obj:add_menu(item_def.name) | ||
if param_obj then | ||
table.insert(menu_objects, param_obj) -- Keep a reference to the menu object | ||
parse_menu(item_def, param_obj) -- Recurse into sub-menu | ||
else | ||
gcs:send_text(MAV_SEVERITY.WARNING, "CRSF: Failed to create menu: " .. item_def.name) | ||
end | ||
|
||
elseif item_def.type == 'SELECTION' then | ||
packed_data = create_selection_entry(item_def.name, item_def.options, item_def.default) | ||
param_obj = parent_menu_obj:add_parameter(packed_data) | ||
|
||
elseif item_def.type == 'NUMBER' then | ||
packed_data = create_number_entry(item_def.name, item_def.default, item_def.min, item_def.max, item_def.default, item_def.dpoint, item_def.step, item_def.unit) | ||
param_obj = parent_menu_obj:add_parameter(packed_data) | ||
|
||
elseif item_def.type == 'COMMAND' then | ||
packed_data = create_command_entry(item_def.name) | ||
param_obj = parent_menu_obj:add_parameter(packed_data) | ||
|
||
elseif item_def.type == 'INFO' then | ||
packed_data = create_info_entry(item_def.name, item_def.info) | ||
param_obj = parent_menu_obj:add_parameter(packed_data) | ||
end | ||
|
||
if param_obj then | ||
-- Store the CRSF-assigned ID back into our definition table for easy lookup | ||
menu_items[param_obj:id()] = item_def | ||
elseif not param_obj and item_def.type ~= 'MENU' then | ||
gcs:send_text(MAV_SEVERITY.WARNING, "CRSF: Failed to create param: " .. item_def.name) | ||
end | ||
end | ||
end | ||
|
||
-- #################### | ||
-- # EVENT HANDLING | ||
-- #################### | ||
|
||
-- This function runs in the background, listens for menu events, and triggers callbacks. | ||
local function event_loop() | ||
local param_id, payload, events = crsf:get_menu_event(CRSF_EVENT.PARAMETER_WRITE) | ||
|
||
if (events and (events & CRSF_EVENT.PARAMETER_WRITE) ~= 0) then | ||
local item_def = menu_items[param_id] | ||
if not item_def or not item_def.callback then | ||
return event_loop, 100 -- No item or callback found, continue polling | ||
end | ||
|
||
local new_value = nil | ||
if item_def.type == 'SELECTION' then | ||
-- Unpack the 0-indexed selection from the payload | ||
local selected_index = string.unpack(">B", payload) | ||
new_value = item_def.options[selected_index + 1] -- Convert to 1-indexed value for Lua | ||
|
||
elseif item_def.type == 'NUMBER' then | ||
-- Unpack the integer and scale it back to a float | ||
local raw_value = string.unpack(">l", payload) | ||
local scale = 10^(item_def.dpoint or 0) | ||
new_value = raw_value / scale | ||
|
||
elseif item_def.type == 'COMMAND' then | ||
local command_action = string.unpack(">B", payload) | ||
if command_action ~= CRSF_COMMAND_STATUS.START then | ||
return event_loop, 100 -- Ignore anything other than the 'start' command | ||
end | ||
-- For commands, the value passed to the callback is simply 'true' | ||
new_value = true | ||
end | ||
|
||
-- If we have a new value, call the user's callback function | ||
if new_value ~= nil then | ||
local success, err = pcall(item_def.callback, new_value) | ||
if not success then | ||
gcs:send_text(MAV_SEVERITY.ERROR, "CRSF Callback Err: " .. tostring(err)) | ||
end | ||
end | ||
|
||
-- For commands, we must send a response to reset the UI element | ||
if item_def.type == 'COMMAND' then | ||
local packed_data = create_command_entry(item_def.name) | ||
crsf:send_write_response(packed_data) | ||
end | ||
end | ||
|
||
return event_loop, 100 -- Reschedule the event loop | ||
end | ||
|
||
|
||
-- #################### | ||
-- # PUBLIC API | ||
-- #################### | ||
|
||
-- The main entry point for the helper library. | ||
-- The user script calls this function with its menu definition table. | ||
function helper.init(menu_definition) | ||
-- Create the top-level menu | ||
local top_menu_obj = crsf:add_menu(menu_definition.name) | ||
if not top_menu_obj then | ||
gcs:send_text(MAV_SEVERITY.ERROR, "CRSF: Failed to create top-level menu.") | ||
return | ||
end | ||
table.insert(menu_objects, top_menu_obj) -- Keep a reference to the top-level menu object | ||
|
||
-- Parse the rest of the menu structure | ||
parse_menu(menu_definition, top_menu_obj) | ||
|
||
gcs:send_text(MAV_SEVERITY.INFO, "CRSF Menu '" .. menu_definition.name .. "' initialized.") | ||
|
||
-- Start the background event loop | ||
return event_loop, 1000 -- Initial delay before starting the loop | ||
end | ||
|
||
return helper | ||
``` | ||
|
||
## 4\. Declarative Menu Syntax | ||
|
||
The menu is defined as a nested Lua table. Each item in the table is a table itself, representing a menu item, sub-menu, or parameter. | ||
|
||
### 4.1. Top-Level Menu Table | ||
|
||
The root of the definition must be a table with two keys: | ||
|
||
* `name`: (string) The name of the root menu entry that appears on the transmitter. | ||
* `items`: (table) A list of tables, where each table defines a menu item. | ||
|
||
### 4.2. Menu Item Properties | ||
|
||
| Property | Type | Description | Used By | | ||
| :--- | :--- | :--- | :--- | | ||
| `type` | `string` | **Required.** The type of menu item. Must be one of: `'MENU'`, `'NUMBER'`, `'SELECTION'`, `'COMMAND'`, `'INFO'`. | All | | ||
| `name` | `string` | **Required.** The text displayed for this menu item. | All | | ||
| `callback` | `function` | **Required.** The function to call when the value is changed or the command is executed. | `NUMBER`, `SELECTION`, `COMMAND` | | ||
| `items` | `table` | A list of item definition tables for a sub-menu. | `MENU` | | ||
| `default` | `number` | The initial value of the parameter. For `SELECTION`, this is the **1-based index** of the `options` table. | `NUMBER`, `SELECTION` | | ||
| `min`, `max` | `number` | The minimum and maximum allowed values. | `NUMBER` | | ||
| `step` | `number` | *Optional.* The increment/decrement step size. Defaults to 1. | `NUMBER` | | ||
| `dpoint` | `number` | *Optional.* The number of decimal points to display. Defaults to 0. | `NUMBER` | | ||
| `unit` | `string` | *Optional.* A short string for the unit (e.g., "m", "s", "%"). | `NUMBER` | | ||
| `options` | `table` | A list of strings for the available choices. **Note:** Options must be separated by a semicolon (`;`) in the final packed data, which the helper library handles automatically. | `SELECTION` | | ||
| `info` | `string` | The read-only text to be displayed next to the name. | `INFO` | | ||
|
||
## 5\. Main Script Pattern (`user_script.lua`) | ||
|
||
This is the standard pattern you must follow for the user-facing script. | ||
|
||
1. **Require the Helper:** The first line must be `local crsf_helper = require('crsf_helper')`. | ||
2. **Define Callbacks:** Create the functions that will handle value changes. These functions will receive one argument: the new value. For `SELECTION`, this is the selected string. For `NUMBER`, it's the new number. For `COMMAND`, it's simply `true`. | ||
3. **Define the Menu Table:** Create the `menu_definition` table according to the syntax in section 4. | ||
4. **Initialize:** The script must return `crsf_helper.init(menu_definition)`. | ||
|
||
## 6\. CRSF Specification Notes | ||
|
||
* **NUMBER Type:** The CRSF protocol transmits `FLOAT` parameter types as 32-bit signed integers. The `dpoint` property tells the receiver where to place the decimal point. The helper library handles this conversion automatically. | ||
* **SELECTION Type:** The list of text options is transmitted as a single string with each option separated by a semicolon (`;`). The helper library handles this formatting. | ||
* **String Lengths:** While the protocol can handle longer strings, transmitter screens are small. It is best practice to keep `name` and `unit` strings short and descriptive. | ||
|
||
## 7\. Mandatory Rules and Checklist | ||
|
||
1. **\[ \] Use Declarative Table:** The entire menu structure **must** be defined in a single Lua table. | ||
2. **\[ \] Use `crsf_helper.lua`:** The provided `crsf_helper.lua` library **must** be included and used. Do not attempt to re-implement its logic. | ||
3. **\[ \] Use `require()`:** The main script **must** load the helper using `require('crsf_helper')`. | ||
4. **\[ \] Use Callbacks:** All menu interactions **must** be handled via callback functions assigned in the menu definition table. The main script must not contain a `crsf:get_menu_event` loop. | ||
5. **\[ \] Return `helper.init()`:** The main script **must** conclude by returning the result of the `crsf_helper.init()` function, passing its menu definition table as the argument. | ||
6. **\[ \] Follow Parameter Syntax:** All parameter types (`NUMBER`, `SELECTION`, etc.) **must** use the exact property names and data types defined in section 4.2. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# **Playbook: Generating git format-patch Diffs (v4)** | ||
|
||
## **1\. Objective** | ||
|
||
To generate a precise, machine-readable, and human-understandable diff for a file change, formatted in the style of a git format-patch output. This ensures the change is encapsulated with its metadata, the exact code modifications, and is structurally valid for use with Git tools. | ||
|
||
## **2\. Core Principles** | ||
|
||
* **Verifiability:** The original file content must be present in my context. I will **never** generate a patch based on an assumed or hallucinated file state. | ||
* **Path Integrity:** The patch must target the correct file path. I will **never** generate a patch without knowing the file path. If the path is not provided, I must ask for it. | ||
* **Hunk Integrity:** Each non-contiguous block of changes in a file **must** be represented by its own separate hunk (@@ ... @@ section). Combining separate changes into a single hunk will corrupt the patch. | ||
* **Accuracy:** The diff must represent the change perfectly. Line numbers, context lines, additions, and deletions must be exact. | ||
* **Format Purity:** The final output must be a raw text block, using Unix-style line endings (\\n), without any surrounding text or formatting. | ||
* **Structural Integrity:** The patch must be complete, including the header, body, diff stats, and the mandatory patch terminator. | ||
|
||
## **3\. Anatomy of a format-patch File** | ||
|
||
A patch file contains a header, a commit message, diff statistics, one or more hunks, and a terminator. | ||
|
||
From \<commit\_hash\> \<timestamp\> | ||
From: Gemini \<gemini@google.com\> | ||
Date: \<current\_date\> | ||
Subject: \[PATCH\] \<Short, imperative summary of the change\> | ||
|
||
\<Detailed explanation of the change.\> | ||
\--- | ||
path/to/the/file.ext | 4 \++-- | ||
1 file changed, 2 insertions(+), 2 deletions(-) | ||
|
||
diff \--git a/path/to/the/file.ext b/path/to/the/file.ext | ||
index \<hash1\>..\<hash2\> \<file\_mode\> | ||
\--- a/path/to/the/file.ext | ||
\+++ b/path/to/the/file.ext | ||
@@ \-\<hunk\_1\_old\> \+\<hunk\_1\_new\> @@ | ||
... content for the first hunk ... | ||
@@ \-\<hunk\_2\_old\> \+\<hunk\_2\_new\> @@ | ||
... content for the second hunk ... | ||
\-- | ||
2.43.0 | ||
|
||
## **4\. Step-by-Step Generation Process** | ||
|
||
### **Step 1: Gather and Verify Information** | ||
|
||
**Prerequisites:** Before proceeding, I must have: | ||
|
||
1. The complete **Original File Content**. | ||
2. The full **File Path**. | ||
3. The complete **Modified File Content**. | ||
|
||
If any information is missing, I will stop and request it. | ||
|
||
### **Step 2-4: Header, Message, and Stats** | ||
|
||
I will generate the metadata headers, the commit message, and the diff statistics as previously defined. | ||
|
||
### **Step 5: Generate the Technical Diff Hunks** | ||
|
||
This process is critical for ensuring patch integrity. | ||
|
||
1. **Write File Headers:** I will write the diff \--git, index, \--- a/..., and \+++ b/... lines. | ||
2. **Identify All Change Blocks:** I will scan both files and identify every separate, non-contiguous block of added (+) and/or removed (-) lines. | ||
3. **Iterate Through Each Block:** For each block of changes identified in the previous step, I will perform the following procedure to generate a distinct hunk: | ||
* **Establish Context:** I will select up to **3 lines** of unchanged code directly before the block and up to **3 lines** directly after it. | ||
* **Calculate Hunk Header (@@ ... @@):** I will precisely calculate the start line and line count for both the original (-old,old\_count) and new (+new,new\_count) versions of the file for this specific block. | ||
* **Assemble the Hunk:** I will print the calculated @@ ... @@ header, followed by the context and change lines for this block, each prefixed with the correct character ( , \-, or \+). | ||
4. I will repeat this process until a separate, valid hunk has been generated for every block of changes. | ||
|
||
### **Step 6: Terminate the Patch** | ||
|
||
1. After the last line of the final hunk, I will add a line containing only \-- . | ||
2. On the next line, I will add a placeholder Git version (e.g., 2.43.0). | ||
|
||
## **6\. Self-Correction Checklist** | ||
|
||
* \[ \] Do I have the full, original file content and path? | ||
* \[ \] Is the metadata and commit message correctly formatted? | ||
* \[ \] **(Crucial)** Have I identified all non-contiguous change blocks and created a separate, correctly calculated hunk for **each one**? | ||
* \[ \] Does the patch end with the correct \-- \\n\<version\> terminator? | ||
* \[ \] Is the final output a single, raw text block, ready for direct use? |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this top line has a typo because it starts with "Of course."