llm-pid-tuner: LLM-Assisted PID Tuning Over a Serial CSV Stream
基于 LLM 的极简 PID 自动调参系统 (CLI 版)
At a glance
- What is it?
- A Python CLI that reads a controller's live CSV telemetry, asks a language model for new PID gains, and falls back to a previously stable set when a round gets worse. The interesting part is the guardrail and rollback logic, not the model call.
- Who is it for?
- Adopt it if you already have a board streaming timestamp_ms,setpoint,input,pwm,error,p,i,d over serial, you want a starting point for gains rather than a certified controller, and you are comfortable with a third-party LLM endpoint receiving your process data. Do not adopt it if the loop must be certified, if your board cannot emit that CSV or a supported profile, or if you cannot tolerate a tuning session pausing to wait on an HTTP request.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap between a working loop and a tuned loop
Most people who end up here already have closed-loop control running. The loop is stable enough to be safe, but the response is sluggish, overshoots, oscillates, or sits with a visible steady-state error, and they do not have a systematic way to move the gains. Classical tuning rules assume you can characterise the plant with a step test, and relay autotuning assumes the loop can tolerate a deliberate limit cycle. Neither is convenient when the plant is a heat block or a small actuator and you would rather not inject an excitation signal you cannot fully predict. The project's own framing is modest: the README says it is not a one-click guarantee of perfect control, not a replacement for hardware protection, and not built to chase benchmark scores. It targets thermal work (heating plates, hot ends, ovens, water baths), motors and actuators, and the case where the loop runs but the numbers are poor. That last group is the real audience: people who want the search space narrowed before they start turning knobs by hand.
Two data paths, one suggestion loop
The README's system diagram shows two arrangements. In simulator mode, simulator.py runs a thermal plant model and talks to the LLM tuner over a JSON API. In hardware mode, an MCU running firmware.cpp emits CSV over serial to tuner.py or the packaged executable, which then calls the LLM. Both converge on the same shape: telemetry in, a PID suggestion out. The serial contract is explicit. The default stream is timestamp_ms,setpoint,input,pwm,error,p,i,d, and the README states that firmware.cpp already implements this protocol, so the cheapest path is to start from that file rather than write your own emitter. On each pass the tuner reads the port, evaluates response quality, asks the model for new gains, applies a fallback strategy when needed, and rolls back to earlier, steadier parameters if a later round is worse. The evaluation is threshold-based rather than model-based: when a round satisfies GOOD_ENOUGH_AVG_ERROR, GOOD_ENOUGH_STEADY_STATE_ERROR and GOOD_ENOUGH_OVERSHOOT together, the tuner holds the current gains and enters an observation phase instead of asking the LLM for another change. It stops after REQUIRED_STABLE_ROUNDS consecutive stable evaluations (the README gives 3 as the default), and if the response degrades during observation the stable counter resets to zero and normal tuning resumes. That counter reset is the detail worth noticing. It means a single bad round does not silently end the session, but it also means a plant that drifts in and out of the good-enough band can keep the tuner running longer than you expect.
Getting a session running from config.json
The README recommends the packaged Windows build first and explicitly tells newcomers not to start with Python. Download llm-pid-tuner.exe from the releases page and run it. The launcher asks for a mode: [1] real hardware tuning, [2] local simulation TUI, which is the default. On first run, if config.json is absent from the current directory, the program writes a default one. The minimum hardware configuration given in the README is SERIAL_PORT, BAUD_RATE, HARDWARE_PROFILE, LLM_API_KEY, LLM_API_BASE_URL, LLM_MODEL_NAME and LLM_PROVIDER. Typical values are SERIAL_PORT set to AUTO (the program scans and prompts you to pick) or a fixed port such as COM5, BAUD_RATE 115200, HARDWARE_PROFILE generic_serial_csv, LLM_API_BASE_URL https://api.openai.com/v1, LLM_MODEL_NAME gpt-4o and LLM_PROVIDER openai. For simulation only, you can skip SERIAL_PORT and fill in the LLM fields. Any OpenAI-compatible endpoint is configured by pointing LLM_API_BASE_URL at its /v1 address and setting LLM_PROVIDER to openai; the README names MiniMax, DeepSeek, Ollama and LM Studio as examples, and shows MiniMax-M2.5 as a model name. For Claude relay services there is a separate value, openai_claude, which the README says still speaks the OpenAI-compatible protocol and exists only as an explicit option for those relays. Native Claude goes through LLM_PROVIDER set to anthropic. In the TUI you can press t to enter a new setpoint and watch how the current gains track the change; on the generic_serial_csv and firmware.cpp path the tuner sends SETPOINT:<value> over the serial link. The README states that mspm0_datavision stays read-only and does not write PID parameters back to the hardware, so on that profile the session ends with you copying gains across by hand. The final step is always manual: write the converged gains back into your firmware.
Hardware profiles are the integration surface
HARDWARE_PROFILE is where this project either fits your rig or does not. generic_serial_csv is the default CSV format and is what firmware.cpp and the demo mode use. stm32f407_openmv targets an STM32F407 with an OpenMV aiming link and, per the README, understands Status: text lines plus T:x,y and N tokens. mspm0_datavision covers an MSPM0 DataVision sampling link and is currently telemetry-only. Three profiles is a small set, and two of them are tied to specific board pairings rather than to a general protocol. If your board emits a different column order, different delimiters, or a different set of fields, the documented route is to adapt firmware.cpp rather than to expect the tuner to parse arbitrary output. That is a real constraint, and it is the first thing to check before downloading anything: open firmware.cpp, compare its output line against what your board actually prints, and decide whether you are changing the firmware or writing a shim. The profile system also determines whether the tuner can act on the setpoint at all, since the read-only profile deliberately cannot.
What the LLM actually contributes, and where it can mislead
Strip away the model and what remains is a loop that samples a response, scores it against three thresholds, and decides whether to keep or revert. The LLM supplies the direction of the next guess. That is a reasonable division of labour for a plant you cannot easily model, and it is also the weakest link in the chain. The tuner's safety properties come from the fallback and rollback logic and from the stability counter, not from the model's judgement. A model that proposes a wild gain set will be caught by the next evaluation only if the evaluation is meaningful and the plant survives the excursion. The README is candid that this is not a substitute for hardware protection, and that framing should be taken literally: if a bad gain set can damage a heater or a motor, the tuner is not the layer that prevents it. The second limitation is latency and cost. Every tuning round is an HTTP request to whatever endpoint you configured, so a session's pace is bounded by that endpoint's response time, and a remote provider sees your error, setpoint and gain data. For an internal lab rig that is usually acceptable; for a process where the telemetry is sensitive, it is a data-handling decision you make before the first round, not after. The third is threshold sensitivity. The stopping rule depends on three numeric thresholds and a stability count that you set. Set them tighter than the plant can physically achieve and the tuner never reaches the observation phase. Set them loose and it stops early on gains you would not have chosen. The README gives the mechanism but the values are yours to get right.
Where a plain autotuner is the better answer
The obvious alternative is a classical relay autotuner or a step-response method, both of which are available in most control toolchains and neither of which needs a network call. The difference in approach is not cosmetic. A relay autotuner deliberately drives the loop into a controlled oscillation, measures the ultimate gain and period, and computes gains from those two numbers using a fixed rule. It is deterministic, it runs entirely on the device, and its behaviour is predictable enough to reason about before you run it. llm-pid-tuner does the opposite: it observes normal operation, sends the observed response to a model, and treats the returned gains as a proposal to be validated by the next round. That makes it useful when you cannot or will not excite the plant, when the plant is nonlinear enough that a single ultimate gain is misleading, or when you have no tuning experience and want the first few guesses to come from somewhere other than intuition. It makes it a poor fit when you need a repeatable procedure that a colleague can reproduce exactly, or when the plant cannot tolerate an exploratory gain set at all. The rollback logic narrows that risk but does not eliminate it, because the first bad proposal still has to run before it can be judged.
Release cadence, licence and what to verify first
The repository is MIT licensed, which permits commercial use and modification provided the copyright notice and permission notice are retained; as always, that is a summary of the licence text and not legal advice, so read LICENSE in the repository if the terms matter to your organisation. The recent release history shows a steady cadence rather than a frozen project: v2.4.5 added the good-enough observation stop logic and a Simulink packaging fix, v2.4.6 added configurable PID guardrails and hardware profiles, and v2.4.7 added runtime setpoint control. Each of those maps to a config surface described above, which means upgrading across them can change how a session behaves even when your config file does not change. The observation stop logic in particular alters when tuning ends, so a config tuned against v2.4.4 semantics may stop at a different point after the upgrade. Budget for re-running a tuning session after a version bump rather than assuming the same thresholds produce the same stopping behaviour. Two other things deserve a check before you commit a rig to it. First, the README points at a dev branch for the newest documentation and Simulink guidance while the stable main branch carries the page you are reading, so confirm which branch's config keys you are actually targeting. Second, the project's own documentation is primarily Chinese, with English README and MATLAB guide files under docs/en-US; if your team needs to read the PROJECT_DOC, check that the English version covers the sections you need before assuming parity.
Editorial conclusion
Adopt it if you already have a board streaming timestamp_ms,setpoint,input,pwm,error,p,i,d over serial, you want a starting point for gains rather than a certified controller, and you are comfortable with a third-party LLM endpoint receiving your process data. Do not adopt it if the loop must be certified, if your board cannot emit that CSV or a supported profile, or if you cannot tolerate a tuning session pausing to wait on an HTTP request. Before trusting a session, verify three things from the repository itself: that firmware.cpp's output order matches your board's, that GOOD_ENOUGH_AVG_ERROR, GOOD_ENOUGH_STEADY_STATE_ERROR and GOOD_ENOUGH_OVERSHOOT are set to values your plant can actually reach, and that the write-back path exists for your profile, since mspm0_datavision is documented as read-only telemetry.
Community notes