- Represent a device action as a small structured message.
- Validate model output independently.
- Limit a demonstration to a harmless low-voltage endpoint.
- Test ambiguous requests and unsupported actions.
Before you begin
You can run the LED bridge and understand that generated language may be wrong.
A language model proposes an action
‘Make it comfortable’ is a plausible household request but an unclear engineering instruction. Does it mean dim a light, change a fan or lower a temperature? A useful controller needs a small vocabulary, a known device and an explicit definition of success.
Our prototype controls only the Arduino's built-in LED. A locally running language model translates a typed request into on, off or none. Your Python program checks that output and converts it into a preapproved serial command. The model cannot write arbitrary firmware, run shell commands or choose other devices.
This is an architecture for learning, not instructions for modifying household wiring. The LED is the whole device in this build. Moving to commercial appliances later requires their documented low-risk interfaces, authentication and separate design work. Do not connect mains relays to extend this article.
Typed request → local model → proposed JSON
→ exact validation → one LED command → UnoPrepare the computer and the LED bridge
The reference board is an Arduino Uno R3 connected by USB. Start with its built-in LED, so this output needs no external circuit. A laptop performs inference; the Arduino accepts a tiny set of commands. A Raspberry Pi can replace the laptop after its Python setup works. No cloud service is required.
Download bridge.py beside your Python script and the LED sketch. Save the sketch in a folder named led_bridge, open it in Arduino IDE, select Uno and your port, then upload. Close Serial Monitor before opening the port from Python. The kit guide supplies Windows and Raspberry Pi setup alternatives.
On macOS or Linux, create an environment with python3 -m venv .venv, activate it with source .venv/bin/activate, and install the serial library using python -m pip install pyserial. Find your port with python -m serial.tools.list_ports; replace the sample port in the code. Never install a package called bridge: that module is the downloaded file.
Commands are newline-terminated text at 115200 baud. LED_ON means illuminate the indicator; LED_OFF and STOP turn it off. The firmware acknowledges valid messages and turns the LED off after 600 milliseconds without a refreshed light command. Bridge.hold refreshes a requested action for a bounded interval and then sends STOP. An acknowledgement proves a message was parsed; looking at the board checks the physical output.
| Part | Purpose |
|---|---|
| Uno R3 and data-capable USB cable | Low-voltage output and communication |
| Computer with Python 3 | Inference and command validation |
| Built-in LED | Safe visible actuator substitute |
Prepare a local model with a narrow task
Use an existing working Ollama installation on the computer. Follow its official platform installation instructions, select a model that fits your machine and download it through its documented CLI. Run ollama list and copy an installed model name exactly. There is no universal model size that runs acceptably on every laptop or Pi, so test latency before integrating hardware.
The Ollama generate API accepts a model, prompt and structured-output format at the local service. We set stream false so one response can be parsed as JSON. Keep the service bound to its default local interface; this example needs no internet-exposed endpoint.
A JSON schema constrains output shape, but it is not permission enforcement. A perfectly shaped on request may still misinterpret the user's meaning. The independent allowlist, one-second duration and single harmless device are the enforceable limits. Keep the user's request clearly labeled as data and ask the model to choose none when ambiguous.
Validate the proposal before opening the board
This script uses Python's built-in HTTP library and the downloaded bridge. Replace the model name with an exact entry from your local installation. The model runs before the serial connection opens, so waiting for a response does not leave a hardware action active.
import json
from urllib.request import Request, urlopen
from bridge import Bridge
request_text = input('LED request: ')
schema = {'type': 'object', 'properties': {
'action': {'type': 'string', 'enum': ['on', 'off', 'none']}},
'required': ['action'], 'additionalProperties': False}
payload = {'model': 'REPLACE_WITH_INSTALLED_MODEL', 'stream': False,
'format': schema, 'system': 'Control only a demo LED. Choose none for unclear or unsupported requests.',
'prompt': 'User request: ' + request_text}
request = Request('http://localhost:11434/api/generate',
data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json'})
with urlopen(request, timeout=60) as response:
proposal = json.loads(json.loads(response.read())['response'])
if not isinstance(proposal, dict) or set(proposal) != {'action'}:
raise ValueError('Invalid action message')
actions = {'on': 'LED_ON', 'off': 'LED_OFF'}
if proposal['action'] in actions:
with Bridge('/dev/ttyACM0') as board:
board.hold(actions[proposal['action']], 1.0)
else:
print('No supported action.')The schema asks for a single enumerated action. Independent Python checks reject unexpected message keys, and the actions dictionary translates only known values to firmware commands. HTTP, JSON or schema failures stop the script before the board opens. No model-generated text is evaluated as Python.
Expected result: A supported request produces a bounded LED response; unclear requests should produce no action. A missing model or unavailable local service raises an error. Test interpretation with hardware disconnected before relying on its meaning.
Test intent, shape and authority separately
Try ‘turn on the LED,’ ‘turn it off,’ ‘open the front door’ and ‘do something nice.’ Record the intended result before each test. Add a request that tries to change the rules. Even when a model follows that instruction and returns on, the application still cannot perform anything outside its tiny action dictionary.
Test invalid outputs by replacing the proposal in a local copy with an unexpected key, an unsupported string and a non-object value. A robust controller rejects them rather than guessing. If you extend the schema, update the validator and tests together.
A useful next version asks for human confirmation when the intended device or action is uncertain. Confirmation should show the actual proposed operation, not a vague ‘proceed?’ question. Keep logs of decisions and failures without collecting unnecessary household conversations.
Important terms
- Structured output
- A response constrained to a defined data format.
- Schema
- Rules describing permitted fields and values.
- Intent
- The action a user's request is trying to express.
- Authorization
- Permission to perform a specific action.
- Local endpoint
- A service address on the current computer.
Mini project: Build a request acceptance table
- Write ten requests with expected on, off or none outcomes.
- Run model interpretation without connecting the Arduino.
- Inject malformed proposals to test the validator independently.
- Connect the LED only after you can explain every accepted action.
Common mistakes and debugging
- Treating valid JSON as proof of correct intent: shape and meaning differ.
- Sending free-form text directly to a device: translate through a fixed command map.
- Exposing the local model server to the network for this demo: no remote access is needed.
Independent challenge
Add a human review step that displays the proposed action and requires the exact word confirm before the LED command. Ensure cancellation produces no output.
Check your understanding: 10 questions
Does a schema authorize an action?
Why run inference before opening the board?
Can this build control a real household lamp?
What happens to an unsupported action?
Why test malformed proposals manually?
In your own words, what does “Structured output” mean?
In your own words, what does “Schema” mean?
In your own words, what does “Intent” mean?
In your own words, what does “Authorization” mean?
In your own words, what does “Local endpoint” mean?
Quiz answers
Reveal all 10 answers after your attempt
- No. It constrains data shape; application policy decides what can execute.
- Model latency or failure then cannot prolong an active hardware command.
- No; its only endpoint is the built-in low-voltage indicator.
- It is absent from the fixed mapping, so no supported hardware command is sent.
- To check enforcement independently of the model's usual behavior.
- A response constrained to a defined data format.
- Rules describing permitted fields and values.
- The action a user's request is trying to express.
- Permission to perform a specific action.
- A service address on the current computer.
Summary
A language interface should propose a small, inspectable action. Deterministic validation and bounded device commands turn that proposal into a controlled demonstration. Better wording alone is never a substitute for an enforceable output boundary.
Continue learning
The series capstone combines these boundaries with data collection, perception and repeatable system tests.
- Build a Complete Computer-to-Arduino AI System
- Experiment With Local Language Models on Raspberry Pi
- Advanced Prompting and Multi-Step AI Workflows
Sources and further reading
Prepared 2026-09-18. Editorial draft. Primary documentation checked; hardware, camera and audio behavior still require a physical test.
Extra reading & source documents
Optional reading alongside the lessons. These sources do not add to your course lesson count.
- ESP8266: chip, module and development boardWikipedia reading