Files
t-deckard/README.md
T

340 lines
16 KiB
Markdown
Raw Normal View History

2025-12-22 09:05:35 +02:00
# T-DeckARD: T-Deck Augmented Runtime Distribution
## About
T-DeckARD is a curated, interconnected and integrated collection of Python 3 modules developed for CircuitPython 10.x on LilyGO T-Deck (but also mostly compatible with MicroPython and desktop CPython) aimed at turning the stock (Circuit)Python REPL into an easy to use operating environment.
T-DeckARD modules are divided into two categories: **components**, which are the building blocks anyone can use to easily create their own applications, and ready-made **applets** which can already be used as stand-alone applications, as well as included as components of other, more complicated ones.
## Prerequisites for T-Deck (Plus)
On T-Deck (Plus), the following components must be installed:
- CircuitPython 10.x (tested on 10.0.3);
2025-12-22 17:54:40 +02:00
- Adafruit Library Bundles from the [official CircuitPython page](https://circuitpython.org/libraries).
If you have `circup` installed, you can connect the device via USB and then run `circup install -r requirements.txt` from the project root.
2025-12-22 16:49:57 +02:00
The [tdeck_repl](https://github.com/RetiredWizard/tdeck_repl) distribution is already incorporated into the repo.
2025-12-22 09:05:35 +02:00
On other generic platforms, most modules which are not T-Deck-specific are set to work on both CPython and MicroPython.
## Installation on T-Deck (Plus)
2025-12-22 16:49:57 +02:00
Current installation process doesn't involve any `.mpy` module compilation, so, after installing the prerequisites, you just need to copy the `repl`, `deck` and `app` directories, as well as the `code.py` and `tdeckboot.py` files, into the root of the `CIRCUITPY` removable volume.
2025-12-22 09:05:35 +02:00
## Components (`deck.*`)
As of now, T-DeckARD implements the following component modules.
### `deck.input`
A library for single-line and multi-line (ed-like) text input.
Exported functions:
- `input(prompt=None)`: standard single-line Python input
- `input_multi(prompt=None, terminator='.')`: multi-line input, end with the terminator string on a separate line
### `deck.pager`
A library for paged text output. In CPython, relies on the terminal dimensions provided by the host OS. In CircuitPython and MicroPython, it requires correct values to be provided inside `TERM_COLS` and `TERM_ROWS` environment variables. In case they are not provided, these values default to 53 columns and 19 rows (the actual value for T-Deck running `tdeck_repl` in CircuitPython).
Exported functions:
- `term_size()`: return the terminal dimensions tuple in the `(rows, columns)` format
- `reflow(text)`: split text into a list of lines not exceeding terminal width
2025-12-24 14:49:56 +02:00
- `print_paged(text)`: reflow and print the text in a paginated manner, prompting for Enter presses to move to the next page
2025-12-22 09:05:35 +02:00
2025-12-23 12:51:22 +02:00
### `deck.time`
A wrapper for standard Python `date`, `time` and `datetime` modules. Just run `from deck.time import *` to activate.
2025-12-22 09:05:35 +02:00
### `deck.chat`
A generic chat UI helper library. Exports a single class called `DeckChat` with the following parameters:
```python
chat_instance = DeckChat(start_state={}, chat_prefix='> ', command_prefix='/')
```
where the constructor parameters are:
- `start_state`: the state object to be modified by chat actions
- `chat_prefix`: the prefix to be displayed before each user's message input
- `command_prefix`: the prefix to be prepended to every registered chat command
Every chat instance exposes the following methods:
- `command(command_name, handler)`: register a command under the `command_name` according to the handler specification (see below); e.g. if the prefix is `/` and the command name is `list`, the chat will react to the `/list` command and pass everything after it as the argument to the handler function
- `start()`: start the chat loop (keyboard interrupts, if any are supported, exit the loop)
The handler accepts two parameters: a message and a state object, and returns the response and the new state object. If the returned state object is `None`, then the chat loop exits. Non-command handler (i.e. what should be done on any user input that doesn't start with a registered command) is registered under the `default` command.
2025-12-25 12:46:49 +02:00
### `deck.xlat`
Character translation helper library. For now, only supports base Cyrillic character set.
Exports the following functions:
- `enc(s, mapname='CYR')`: encode from the selected character set into Latin
- `dec(s, mapname='CYR')`: decode from Latin into the selected character set
2025-12-22 15:23:41 +02:00
### `deck.fs`
2025-12-22 09:05:35 +02:00
FS helper librаry.
Exports the following functions:
- `unlock_rootfs()`: make the device root filesystem read-write (does nothing on desktop)
- `lock_rootfs()`: make the device root filesystem read-only (does nothing on desktop)
2025-12-22 15:23:41 +02:00
- `stat(path)` (aka `du(path)`): alias to `os.stat` but returning the stats as a dictionary
- `statvfs(path)` (aka `df(path)`): alias to `os.statvfs` but returning the stats as a dictionary
2025-12-22 23:54:41 +02:00
- `mount_sd(path='/sd', readonly=False)`: mount the inserted microSD card (T-Deck only, must be FAT32)
- `umount_sd(path='/sd')`: unmount the inserted microSD card (T-Deck only)
2025-12-22 15:23:41 +02:00
The following `deck.fs` functions are direct aliases to the corresponding `os` call counterparts:
- `sep` = `pathsep` = `os.sep`
- `cwd` = `pwd` = `os.getcwd`
- `ls` = `listdir` = `os.listdir`
- `md` = `mkdir` = `os.mkdir`
- `cd` = `chdir` = `os.chdir`
- `rd` = `rmdir` = `os.rmdir`
- `rm` = `remove` = `os.remove`
- `mv` = `rename` = `move` = `os.rename`
- `sync` = `os.sync` (where supported)
- `env` = `getenv` = `os.getenv`
- `rnd` = `urandom` = `os.urandom`
2025-12-22 09:05:35 +02:00
2025-12-23 13:04:47 +02:00
### `deck.hwinfo`
2025-12-23 00:09:58 +02:00
2025-12-23 13:04:47 +02:00
Various hardware information. May be expanded in the future.
2025-12-23 00:09:58 +02:00
Exports the following functions:
2025-12-23 09:55:36 +02:00
- `board_id()`: get the board ID (e.g. `lilygo_tdeck` for T-Deck/T-Deck Plus), or `emulated` if it's not run on a CircuitPython device
2025-12-23 19:03:18 +02:00
- `battery_v()`: (T-Deck-specific) get the current battery voltage (or 0 if running in an emulated environment)
2025-12-23 22:02:44 +02:00
- `cpu_f()`: CPU frequency in Hertz
2025-12-23 19:03:18 +02:00
Also, this module exposes the `REAL_HW` variable to detect whether the code is running on a real hardware in the CircuitPython environment.
2025-12-23 00:09:58 +02:00
2025-12-22 09:05:35 +02:00
### `deck.net`
T-Deck/CircuitPython-specific library for Wi-Fi connection and returning `requests` and `socket` library instances.
Exported functions:
- `wifi_scan()`: scan available Wi-Fi networks and return the list of `(ssid, rssi)` tuples (sorted from the strongest signal to the weakest)
- `wifi_connect(ssid=None, password=None)`: connect to a Wi-Fi SSID without waiting (default credentials are pulled from the `/settings.toml` file)
- `wifi_connect_wait(ssid=None, password=None, tmout=15, retries=3)`: connect to a Wi-Fi SSID (with the same parameters as `wifi_connect`) and wait until the connection is established
- `my_ip()`: return the current local IPv4 (as a string)
- `ssl_context()`: get the default SSL context for wrapping in a raw socket
- `init_socket()`: return the active reference to the `socket` library
- `init_requests()`: return the active reference to the `requests` library
2025-12-23 13:04:47 +02:00
- `init_ntp(tz=0)`: return an NTP time client instance (use the `.datetime` property of the instance to access current time and the `tz` parameter to specify the timezone offset)
2025-12-22 09:05:35 +02:00
A normal flow for instantiating a Requests library looks like this:
```python
# init requests library in a platform-agnostic way
requests = None
try:
from deck import net
print('Waiting for online status...')
status, stext, myip = net.wifi_connect_wait()
if status:
print("We're online, initing requests lib...")
requests = net.init_requests()
except:
import requests
```
2025-12-23 19:03:18 +02:00
### `deck.http`
High-level HTTP client library.
Exports the following functions:
- `http_set_ua(ua_str='Mozilla/5.0')`: set a default HTTP user agent string
- `http_fetch(url, useragent=None, hdrs={})`: fetch text content from a URL into a string (returns `(status_code, content)` tuple)
2025-12-25 15:59:31 +02:00
- `auth_header(username, password)`: get a dictionary fragment with the corresponding HTTP Basic Auth header according to the username and password
2025-12-25 20:04:24 +02:00
- `url_escape(s)`: escape a string to be used as a part of URL parameters
- `form_encode(params)`: encode a form body dictionary in the `x-www-form-urlencoded` format
2025-12-23 19:03:18 +02:00
- `wget(url, filename='', useragent=None, hdrs={})`: download a file into a local path (returns `(status_code, filename)` tuple)
2025-12-25 15:51:04 +02:00
- `wput(url, file_path, field_name="file", method="POST", format="x-www-form-urlencoded", headers={}, useragent=None)`: upload a file from a local path in the `x-www-form-urlencoded` or JSON format (returns `(status_code, content)` tuple)
2025-12-23 19:03:18 +02:00
2025-12-22 09:05:35 +02:00
### `deck.llm`
A helper library for interaction with remote LLM (large language model) endpoints that expose OpenAI-compatible API. As of now, it only exports a single class, `LLMChat`, to encapsulate all the interaction.
To initialize, this class requires a provider configuration object from a JSON file that looks like this:
```json
{
"temperature": 0.7,
"system_prompt": "You are a concise, embedded assistant.",
"active_provider": "openrouter",
"providers": {
"openrouter": {
"base_url": "https://openrouter.ai/api/v1",
"api_key": "...",
"model": "xiaomi/mimo-v2-flash:free",
"extra_headers": {}
},
"pollinations": {
"base_url": "https://text.pollinations.ai/openai",
"api_key": "",
"model": "openai-fast",
"extra_headers": {}
}
}
}
```
Then, we initialize it like this:
```python
llm = LLMChat(config_file=CONFIG_FILE_PATH, message_limit=20)
```
The instance exposes the following methods (all returning a `(status, result)` tuple where applicable):
- `load_config(config_file='llmcfg.json')`: load configuration from a config file
- `save_config()`: save current configuration into the config file
- `setup_provider()`: load currently active provider settings
- `reset_context()`: clear the conversation history and re-add the system prompt
- `get_completion(prompt)`: prompt the LLM and return the response message
- `list_models()`: list available model IDs from the current active provider
- `get_last_response()`: get the most recent assistant's response as a plain string
- `add_file(fname)`: read a text file and add it to the context (within the message limit)
- `export()`: export the entire current conversation (within the message limit) as a plain string
2025-12-22 09:05:35 +02:00
## Applets (`app.*`)
As of now, T-DeckARD implements the following applets.
### `app.ed`
A simple line-oriented text editor and page-oriented text viewer for MicroPython/CircuitPython implementing a POSIX `ed` subset (feature-wise, it still is closer to EDLIN from DOS).
Exports the following functions:
2025-12-26 09:59:30 +02:00
- `edit(filename)`: start an `ed`-like text editor on a file (see below)
- `edbuf(bufstr='')`: create an empty buffer, fill it with the input string (if any), run the editor interface on it and return the result as a string (without creating any files)
- `edurl(url)`: download the URL contents via HTTP, run the editor interface on it and return the result as a string (without creating any files)
2025-12-22 11:08:13 +02:00
- `view(filename, lno=False)`: start a `more`-like pager for viewing a text file (pass an additional parameter to view it with line numbers)
2025-12-26 09:59:30 +02:00
- `viewbuf(text, lno=False)`: same as `view` but for string variables instead of files
- `viewurl(url, lno=False)`: download the URL contents via HTTP and run `viewbuf` method on the result
Note: with `edbuf` and `edurl` methods, you can always save the file locally while inside the editor interface. Use the `f` command to specify the name (see below).
2025-12-22 09:05:35 +02:00
The editor mode supports the following subset of POSIX ed commands in the standard `[range][command][param]` syntax:
- `p`: print out the range
- `n`: print out the range with line numbers
- `a`: append new text to the end of the range
- `i`: insert new text to the start of the range
- `c`: replace the range with new text
- `d`: delete the range
- `s/old/new`: replace text in the range (doesn't support regex syntax from POSIX ed)
- `f`: set the current filename to write to
- `w`: save changes to the current filename
- `q`: quit (`q!` force-quits without prompting for saving changes)
### `app.llmchat`
A simple interactive chat with remote LLMs. Configured via `llmcfg.json` (see the `deck.llm` module reference) placed into the storage root. Run with the single `llmchat()` method it exports.
2025-12-22 09:05:35 +02:00
Supported chat commands:
- `/help`: display a brief help
- `/clear`: clear the conversation context
- `/system`: display or set the system prompt
- `/temp`: display or set the model temperature
- `/prov`: display or set the current provider selection
- `/model`: display or set the current model selection
- `/provlist`: list provider IDs in the config
- `/modellist`: list model IDs available for current provider
- `/add`: add a (text) file to the context
- `/saveconv`: save the entire conversation log (within the current limits) into a file
- `/savelast`: save the most recent message into a file
- `/savecode`: extract code blocks from the most recent message and save them into a file
- `/edcode`: extract code blocks from the most recent message and open `app.ed.edbuf()` on the contents
2025-12-22 09:05:35 +02:00
- `/exit` or `/quit`: exit the chat applet
2025-12-25 20:04:24 +02:00
### `app.blog`
A simple interface to any POST-based blog API that supports HTTP Basic Auth.
Requires the following environment variables to be configured (for T-Deck, use the `/settings.toml` file):
- `BLOG_POST_ENDPOINT`: the full URL to send POST requests to
- `BLOG_POST_USER`: the username to send the requests as
- `BLOG_POST_PASS`: the password for this username
Exports the following functions:
- `blogpost()`: wait for a (multiline) input and send the post
- `bloged()`: same as `blogpost()` but with a full-featured `app.ed` interface
- `blogpost_str(content)`: post the content in a non-interactive manner
2025-12-25 20:04:24 +02:00
2025-12-22 09:05:35 +02:00
## FAQ
### Is this an OS shell?
No, but it provides comparable functionality to the on-device Python REPL. You can easily build a shell application from the T-DeckARD components.
### Why doesn't it include a shell by default?
It may at some point, but the entire idea is to give you the foundation to easily create your own custom shells for any special purposes. The project carries the spirit of early personal computing days when the programming language REPL (BASIC, Forth, Lisp etc) **was** your primary operating environment. In this case, the on-device Python REPL is such an environment.
2025-12-22 16:49:57 +02:00
To get the hang of what the shell **might** look like, you can already access some of the common methods from the main core modules via T-DeckARD REPL. See the [tdeckboot.py](./tdeckboot.py) file for details.
2025-12-22 16:06:01 +02:00
2025-12-22 09:05:35 +02:00
### Which platforms is T-DeckARD being tested on?
CircuitPython 10.x on a T-Deck Plus and MicroPython for modern x86_64 Linux. That's it.
### So, can I use T-DeckARD outside CircuitPython or even MicroPython?
Yes, but don't expect some modules (like `deck.net`) to work at all. Wrap your code inside try-blocks to achieve maximum compatibility.
2025-12-22 17:32:27 +02:00
### The `virtcode.py` could be used with bare `tdeck_repl` but can't be used here. Why so?
It has been renamed. Place your startup code into `autoexec.py` instead.
You can start it with `from tdeckboot import *` to give it the same extended capabilities as the interactive REPL.
2025-12-25 15:58:37 +02:00
### So, what's the difference between `tdeckboot.py` and `autoexec.py`?
Everything that gets imported inside `tdeckboot.py` becomes automatically available in the T-DeckARD interactive REPL. This file is solely designed for passthrough import of modules into the REPL and nothing more.
Everything that gets imported inside `autoexec.py` stays in `autoexec.py`. This is just your usual startup script.
You can use `tdeckboot.py` for autoexecing but that's not recommended as this carries a heavy risk of REPL namespace pollution.
2025-12-22 09:05:35 +02:00
### Is there any feature roadmap for core components?
The foundational module set is going to at least include high-level capabilities for:
- exposing low-level socket and HTTP APIs (done),
2025-12-25 22:29:14 +02:00
- high-level HTTP APIs (done),
2025-12-22 09:05:35 +02:00
- high-level LLM APIs (partially done),
2025-12-23 12:51:22 +02:00
- date and time informaion (done),
2025-12-22 15:27:24 +02:00
- file system manipulation (done),
2025-12-22 09:05:35 +02:00
- text viewing and editing (done),
2025-12-23 13:04:47 +02:00
- device information collection (done),
2025-12-22 09:05:35 +02:00
- chat UI creation (done).
After all this is implemented, future phases may also include device-specific features like GPS, graphics, sound and touchscreen input.
### Is there any feature roadmap for applets?
No. All applets are created ad-hoc when the author needs them.
## Credits
Created by Luxferre in 2025-2026, released into public domain with no warranties.
Special thanks to RetiredWizard for providing a wonderful tdeck_repl wrapper.