ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A Windows desktop application that acts as a personal AI tutor for Python students. A single window combines:
• an embedded Python editor with a Run button (no separate Python install needed — the runtime is bundled),
• a chat panel where an OpenAI-backed tutor asks the student questions, gives hints, marks answers, and adapts the difficulty,
• a teacher side where you author the goals (a tree of learning objectives) and the instructions (the prompts that shape the tutor's behaviour) without touching any code,
• per-student progress tracking, status reports, and history charts.README 전문 보기 ↗A Windows desktop application that acts as a personal AI tutor for Python students. A single window combines:
The tutor follows a teacher-defined goal tree, picks the type of exercise (multiple choice, explain-this-code, complete-the-code, write-code, Socratic question, …), and only marks a sub-goal as mastered once the student answers correctly across several different question types. UI is in Dutch.
The published binaries are hard-wired to our school's Microsoft 365 (Entra ID) tenant — only accounts in that tenant can sign in, and they are the only accounts the configured Azure Cosmos DB will accept. If you download a release from this repository and run it, you will get stuck on the sign-in screen.
To use this project in your own classroom you have to build your own copy with your own credentials. The good news: you do not need to touch any of the Dart code. You only need to:
.env file.The rest of this README walks through that. No prior Flutter/Dart experience is required, but you should be comfortable opening the Azure portal, copying GUIDs, and running PowerShell commands.
A python_teacher_install.exe (Inno Setup installer, ~150 MB) that you can hand to students. After install they sign in once with their school Microsoft account and the app remembers them. There is also a built-in updater: publish a GitHub release with the installer attached and every running copy will offer to update itself.
You need these tools installed once. All free.
flutter doctor and fix any red items, especially the Visual Studio "Desktop development with C++" workload (required for Windows builds). This is the exact version CI installs (FLUTTER_VERSION in .github/workflows/build.yml). An older SDK now fails flutter pub get outright, because pubspec.yaml requires sdk: ^3.13.1 (the Dart version bundled with Flutter 3.47.1). Building works on newer ones, but dart format output differs between Dart versions, so only this one is guaranteed to agree with CI's format gate..exe installer.sk-...). You will paste it as OPEN_AI_API_KEY later.The default model is gpt-4o, configurable later from inside the app (Teacher → settings).
This is what lets your students sign in with their school Microsoft account.
AI Tutor Python (anything you like).ENTRA_CLIENT_ID.ENTRA_TENANT_ID.http://localhost (no port, no trailing slash). Save.openid, profile, email, offline_access. Add any that are missing, then click Grant admin consent for <your tenant>.TeacherTeacher (must be exactly this string — the code checks for it)Teacher role. Students do not need any role assignment; they just need to exist in your tenant.Why no client secret? This is a desktop app, so authentication uses PKCE + a loopback redirect — no secret to leak.
This stores accounts, goals, progress, instructions, and status reports.
In the Azure portal, Create a resource → Azure Cosmos DB → Azure Cosmos DB for NoSQL.
Pick a subscription and resource group, give it an account name (e.g. mypython-tutor), pick a region close to you, Capacity mode: Serverless (cheapest for classroom use). Click Review + create.
Once deployed, open the account → Data Explorer → New Database named exactly python-tutor.
Inside that database, create the containers below. The partition key column matters — get it right.
| Container name | Partition key |
|---|---|
accounts | /uid |
progress | /uid |
progress_history | /uid |
status_reports | /uid |
playground_files | /uid |
lo_beliefs | /uid |
turn_history | /uid |
grade_proposals | /uid |
reports | /uid |
goals | /type |
instructions | /type |
config | /type |
content | /type |
modules | /type |
milestones | /type |
questions | /subgoalId |
translations | /language |
turn_content | /uid |
Cosmos serverless billing is roughly per-request. With one classroom polling every 5 seconds you should land well under €5/month, but check your own subscription's cost alerts.
.envgit clone https://github.com/yvanvds/AI-tutor-Python.git
cd AI-tutor-Python
Create a file named .env in the repo root (the same folder as pubspec.yaml) with exactly these six lines, replacing the placeholders with the values you collected in steps 1–3:
OPEN_AI_API_KEY=sk-your-openai-key-here
COSMOS_ENDPOINT=https://yourname.documents.azure.com:443/
COSMOS_KEY=your-cosmos-primary-key-here
ENTRA_TENANT_ID=00000000-0000-0000-0000-000000000000
ENTRA_CLIENT_ID=00000000-0000-0000-0000-000000000000
ENTRA_REDIRECT_URI=http://localhost
A few things to know:
OPEN_AI_API_KEY and COSMOS_KEY are obfuscated into the compiled binary at build time via the envied package. They are not shipped in plaintext, but they are shipped — anyone determined enough can extract them from the binary. Treat the build as roughly as secret as the keys themselves.ENTRA_REDIRECT_URI must be exactly http://localhost to match step 2.4.GITHUB_OAUTH_CLIENT_ID — for in-app bug reports. Leave it out and everything else works; the Options panel simply says it cannot sign in to GitHub. Step 8 sets it up.From the repo root, in PowerShell:
# Download Dart/Flutter dependencies
flutter pub get
# Generate the obfuscated env file from your .env (re-run this if you change .env)
dart run build_runner build --delete-conflicting-outputs
# Build the embedded Python interpreter students' code runs on (needs internet;
# takes a few minutes the first time, then it is cached)
pwsh tooling/python/build_bundle.ps1
# Quick sanity check: open the app on your machine
flutter run -d windows
About that Python bundle. The app runs student code in an interpreter it ships itself, which the installer places next to the executable. A flutter run build has no installer, so it looks for a development bundle instead: if build_bundle.ps1 has been run, flutter run -d windows finds build/python_bundle/ on its own and the Run button works. Skip the bundle step and the app still starts, but pressing Run shows a red "no Python environment found" message that tells you exactly this and names the script to run. (Release builds never do this lookup — they use only what the installer put in place.)
To point a dev build at some other interpreter, set both PY_RUNNER_PYTHON and PY_RUNNER_HOST_SCRIPT (an interpreter and a path to packages/py_runner/python/host.py) before launching; setting only one is ignored. The checked-in VS Code launch configuration already does this.
If flutter run opens a window with a sign-in screen and you can sign in with your school account, your config is correct. Close it and build the installer:
# Build a release executable
flutter build windows --release
# Package it as an Inno Setup installer
flutter pub global activate flutter_distributor # one-time
flutter_distributor release --name=windows
The installer ends up in dist/<version>/python_teacher_install.exe. That is the file you give to your students.
When you start the app for the first time:
Sign in with your own (Teacher-role) Microsoft account. The app creates an accounts/{your-uid} document automatically.
Because you have the Teacher role, you will see extra navigation items: Goals, Instructions, Accounts.
Go to Instructions and create the prompt documents. Each document's id must match a request type the tutor uses. The minimum set to get a working tutor:
alwaysInclude — global instructions injected on every call (tone, language, etc.).socraticQuestion, multipleChoiceQuestion, explainCode, completeCode, writeCodeQuestion, guidingQuestion — one per exercise type.submitCode, submitMcqAnswer, submitExplain, submitSocratic, submitGuiding — feedback prompts.statusSummary — periodic per-subgoal report.You can use {goal}, {subgoal}, {suggestions}, and {known concepts} as placeholders in any section. They get filled in at runtime from the active goal.
Go to Goals and build your goal tree. Roots are top-level themes ("Variables", "Loops", …); children are concrete subgoals. The tutor walks the tree in order.
Optional: edit config/global from the Cosmos Data Explorer if you want to switch to a different OpenAI model (e.g. gpt-4o-mini for cheaper runs). The field is Model. MinimumVersion (#165) is optional: set it (for example 2.6.0) to keep every older build out — such a build shows an update screen instead of the app until it is updated, so a device that keeps declining the update cannot go on writing documents in an outdated shape. Leave it out, or blank, to allow every build. QuestionBankMinimum and QuestionBankShare (#186) are optional too: once the question bank holds at least QuestionBankMinimum questions that fit what the tutor is about to ask (default 8) and that the student has not had, the question comes from the bank instead of being generated with a chance of QuestionBankShare (0 to 1, default 0.5; 0 switches serving from the bank off). See docs/CONDUCTOR_POLICY.md §2.7. TurnContentKeepUntil (#228) is optional as well: the day, , until which the question, answer and feedback of each oefening are kept in (midnight Belgian time; default , the end of the school year). See §8.4.
Once that is done, hand the installer to a student. They sign in with their school account, the app creates their profile automatically, and the tutor starts at the first incomplete subgoal.
On launch a release build asks GitHub for this repository's latest release
(https://api.github.com/repos/yvanvds/AI-tutor-Python/releases/latest,
unauthenticated) and offers to download the new installer if the tag is
higher than the running version. A release has to carry two assets:
python_teacher_install.exe — the installer, found by that name (a lone
.exe under another name is accepted too).python_teacher_install.exe.sha256 — its checksum, in sha256sum shape.
Without it the download cannot be verified and the release is not offered.tooling/build_release.ps1 builds, tags and publishes both. Drafts and
pre-releases are skipped by GitHub's /releases/latest itself, so a
pre-release tag is never pushed at a student.
To point the app at your own fork, change kReleaseOwner / kReleaseRepo in
lib/core/github_release.dart. If you do not
need auto-updates, you can ignore this entirely; the app still works.
Options → Bug reports lets a student file a GitHub issue from inside the
app, with the debug payload of a recent tutor turn attached. Signing in uses
GitHub's OAuth device flow: the app shows an eight-character code, the
student approves it at https://github.com/login/device in a browser they are
already signed into, and the app receives a token. No personal access token,
no scope picking, nothing to paste.
That needs one OAuth app registered under the account that owns the issue tracker. It is the only part of this feature that cannot come from the code. Without it the app still runs; the Bug reports card just says the build was compiled without a client id and offers no sign-in.
yvanvds/AI-tutor-Python, or your fork).AI Tutor Python (students see this on the
approval screen, so name it something they will recognise).https://github.com/yvanvds/AI-tutor-Python (your
fork's URL if you forked).device_flow_disabled. If the checkbox is not on the registration form
in your account, click Register application first, then open the new
app's settings page, tick Enable Device Flow, and click Update
application.refresh_token to exchange for a new one — which this app does
not do: it stores only the access token, so the next bug report after
eight hours fails with GitHub's raw Bad credentials and no prompt to
sign in again. Since bug reports are occasional, that is very nearly
every report. Unticked, the token is long-lived, scoped to
public_repo, kept per device, and revocable at any time from the app's
page or the student's own settings. If you would rather keep expiry on,
the app needs refresh-token support first.Ov23li…). It is
public — it ships inside the installer by design..envAdd a seventh line to the .env from Step 4:
GITHUB_OAUTH_CLIENT_ID=Ov23liYourClientIdHere
Then regenerate and rebuild:
dart run build_runner build
flutter build windows --release
The app requests exactly one scope: public_repo.
repo, which most examples use, is deliberately not requested: it would
also give the app write access to every private repository the student
can see.public_repo is not repository-scoped: it grants write access
to the student's public repositories too. That is a limitation of OAuth
apps, not of this code, and it is what the GitHub approval screen tells the
student before they approve.The scope is a single constant, kGitHubOAuthScope in
lib/services/github/github_device_flow.dart;
the repository issues go to is kBugReportRepo in
lib/services/github/github_issue_service.dart.
The token that comes back is stored per device in SharedPreferences, next to
the student's own OpenAI key — the same place the pasted token used to live.
A device that already has a token from the old paste-a-token flow keeps
working; the card offers Disconnect to clear it.
Two layers, both without an Entra tenant or a live Cosmos DB:
# Unit + widget tests (test/): real widgets and services over in-memory fakes
flutter test
# End-to-end flows (integration_test/): the real app on the Windows desktop,
# with Cosmos swapped for an in-memory database and sign-in bypassed
flutter test integration_test -d windows
# One flow on its own
flutter test integration_test/flows/lesson_flow.dart -d windows
The integration harness lives in integration_test/harness/. It boots the real
root widget (GoalsApp) with an in-memory CosmosClient, a signed-in
AuthService, an in-memory SharedPreferences, a temp playground directory,
the update check off and the LLM blocked, so a flow drives the real shell,
pages and services end to end. The bypass exists only under integration_test/
— it is never compiled into flutter build windows. Every flow is registered in
integration_test/app_test.dart and runs in one app process: on Windows,
Flutter can only launch the app for the first test file of a
flutter test integration_test invocation, so the flows under flows/
deliberately carry no _test suffix.
Both layers run in CI (.github/workflows/build.yml):
the unit tests in the SonarQube job, the flows in a separate integration
job that builds the desktop runner and launches the real app on a Windows
runner.
For a deeper architectural tour — services, data model, how the tutor decides what to ask next — read PROJECT_OVERVIEW.md. It is the source of truth for the codebase shape and stays current as the code evolves.
This is a personal classroom project, not a product. Issues and PRs are welcome but expect slow turnaround. If you fork it for your own school, you are encouraged to do so — that is exactly what the build instructions above are for.
Licensed under the GNU General Public License v3.0 — see LICENSE.md for the full text.
In short: you are free to use, study, modify, and redistribute this code, including for your own classroom. If you distribute a modified version, you must do so under the same license and make your source available. This means the project (and any forks) cannot be turned into a closed-source commercial product.
The list the app actually uses is lib/core/cosmos_paths.dart — every
container it can open is declared there, with its partition key in the doc
comment. This table is checked against that file by
test/core/cosmos_paths_readme_parity_test.dart, so the two cannot drift
apart unnoticed again.
Updating an existing deployment: a new app version can add a container
(reports arrived with report publishing, #150), and nothing creates it
in your account for you. After an update, compare this table with the
containers in your database. A container that is still missing shows up
in the app as Container "<name>" does not exist in Cosmos database "python-tutor" (#170) — create it with the partition key listed above.
questions (the question bank, #185) is the one container the app can
run without: until it exists, students practise as before and nothing is
stored or served from it (#186), and the teacher's Questions page
says the container is missing. Its partition key is /subgoalId, not
/uid or /type.
translations (translated lessons and goal texts, #206) can be missing
too: until it exists, every student sees the lessons and goal texts in
Dutch, whatever language they picked in Options. Its partition key is
/language. From the Azure CLI:
az cosmosdb sql container create --account-name <account> --resource-group <group> --database-name python-tutor --name translations --partition-key-path /language --partition-key-version 2
turn_content (the question, answer and feedback of each oefening,
#228) can be missing too: until it exists, nothing of it is stored and
the students practise as before. Create it with Time to Live on, no
default (defaultTtl: -1; in the Data Explorer: Time to Live → On
(no default)): every doc sets its own ttl and expires at the end of
the school year, but only in a container with TTL on — without it the
docs are kept forever. From the Azure CLI:
az cosmosdb sql container create --account-name <account> --resource-group <group> --database-name python-tutor --name turn_content --partition-key-path /uid --partition-key-version 2 --ttl -1
An existing deployment may still have a period_start_snapshots
container: nothing writes or reads it since formula v1.0.16 (#191), and
a fresh deployment does not need it.
Go to Settings → Keys. Copy:
COSMOS_ENDPOINT (looks like https://yourname.documents.azure.com:443/).COSMOS_KEY. Treat it like a password.MM-DDturn_content07-01docs/CONDUCTOR_POLICY.md