This file provides guidance to coding agents when working with code in this repository.
- Code style: Outside of
chips/, comments/docstrings only on public APIs, 2 line maximum (a ceiling not a quota). Don't paper over bad code with a comment: Precise and accurate naming avoids the need for comments (modules, classes, methods and variables). - Naming modules/classes/methods/variables: "Intention-revealing, exact names" applies everywhere. Examples:
arrayofdevicesinstead of apoolofcards,deviceinstead ofradio,sinkoraggregatorinstead ofpictureorairspace. - Human-facing docs are written by humans. Any non-chipset
.mdfiles. - Planning docs (NOT auto-loaded, open as needed):
docs/planning/FEATURES.md,docs/planning/BUGS.md,docs/SUPPORTED-HARDWARE.md(+ the grading process:docs/GRADING.md).
- Porting code style: When porting or working within
src/wifit3/chips/**/*:docs/porting/CODE-STYLE.md. - Porting / bringing up a chip? Playbook:
docs/porting/METHODOLOGY.md(or run/port <chip>in.claude/skills/port). - Per-chipset port-reference docs: each chip dir has a
<CHIP>.md. Template + rules indocs/porting/CHIP-DOC.md. - Within
chips/, don't re-use code from another driver. Why: a shared core meant a fix for one device forced re-testing every device and risked regressing the others. - Register READs can mutate device state: never assume two reads commute, never reorder them vs the capture.
- Device gets borked? User replugs. That resets cold-boot state.
This repo uses uv for env management. Always run Python via uv run (or .venv\Scripts\python.exe).
# Install (editable, with dev deps)
uv sync --group dev # preferred; or: pip install -e ".[dev]"
# Run
uv run wifit3 # or: uv run python -m wifit3
# Tests
uv run pytest # all tests
uv run pytest tests/chips/ar9271_v2/ # single module
uv run pytest tests/wlan/test_parser.py::test_wlan_frame_parser_extracts_ssid
# Lint (lint only: NEVER format)
uv run ruff check src/
# Textual live dev (hot-reload)
uv run textual run --dev src/wifit3/ui/app.pyTests require no hardware: all USB interactions are mocked via pytest-mock. asyncio_mode = "auto" is set globally, so async tests require no decorator.
Never run ruff format.
Wifit3 is a userland 802.11 auditing tool. It communicates directly with USB wireless devices via PyUSB. The TUI is built on Textual.
Not a clean top-to-bottom stack (the real flow is more tangled), but the points of interest:
ui/ Textual screens (Splash → Scanner → Focus); WifiteApp holds the DeviceManager + active interface
device/ DeviceManager + the VID:PID map (USB scan, VID:PID → driver, read WITHOUT importing the
driver), DeviceWatch (plug-in / un-plug callbacks)
wlan/ WlanInterface (802.11 abstraction: channel hopping, AP/Client registry),
WlanFrameParser (the Python 802.11 frame parser)
models/ project-wide dataclasses: AccessPoint, Client, DeviceID
campaigns/ AutoDeauth, PMKID harvest, WPS PIN, WEP/Replay, EvilTwin
chips/ driver.py is the Driver ABC; one dir per chip subclasses it
A chip dir (chips/<chipset>/) is typically shaped:
__init__.py SUPPORTED_IDS + import_driver (the VID:PID list, read without importing driver.py)
driver.py subclasses the Driver ABC; declares SUPPORTED_CHANNELS
transport.py raw USB read/write (control transfers + bulk I/O)
firmware.py firmware upload
constants.py register addresses, command IDs, magic bytes
mac.py / phy.py MAC / BB / RF / EFUSE port from kernel C
chan.py / fifo.py channel tune, set_channel, FIFO partitioning
rx.py / tx.py RX descriptor decode + frame iter / TX descriptor build + bulk-OUT
Not every chip uses every module; add modules as the chip's protocol needs them.
Discovery is a pkgutil walk over chips/* that reads each package's light __init__ (its VID:PID list) WITHOUT importing the driver; the matched driver is imported only on a hit. There is no manual registry to edit. To add a chip:
- Create
src/wifit3/chips/<name>/with at minimum__init__.py,driver.py,transport.py,constants.py(+firmware.pyif the chip needs a FW upload). chips/<name>/__init__.pydeclares the hardware, and must NOT importdriver.pyat module top:SUPPORTED_IDS: list[DeviceID](from wifit3.models import DeviceID): every VID:PID this driver claims, with a human-readable description and any chip-id discriminator inextras={}.def import_driver(): the one heavy import, lazy (from .driver import <Class>; return <Class>).
driver.pymust subclass theDriverABC (wifit3.chips.driver); Python enforces the surface at instantiation:- Class attr
SUPPORTED_CHANNELS: list[int]: every channel the driver can tune to (consumed byWlanInterface.start_hopping).SUPPORTED_IDSlives in__init__.py, not on the class. - Classmethod
from_usb_device(cls, dev, id_entry) -> Driver: driver-side construction (transport wrapping, chip_id reads fromextras). - Runtime methods:
connect(),set_channel(),inject_frame(),close(), plus theregister_rx_callback()hook.
- Class attr
- Only if the chip's setup key must differ from its dir name, or two packages claim the same VID:PID (the Realtek mainline/DKMS pairs): add a row to
_FAMILIESindevice/manager.py. Otherwise there is nothing else to register. - Drop a
<CHIP>.mdport-reference doc next to the driver (skeleton + rules indocs/porting/CHIP-DOC.md).
The cold-vs-warm distinction is a per-driver concern: if a previous session left the chip running, connect() should detect that and skip the bring-up. See chips/rtl8821au/mac.py:is_chip_warm() + driver.py:_warm_reattach for the pattern.
- SplashView — USB device discovery, driver progress, interface selection
- ScannerView — Live AP table; triggers channel hopping; leads to FocusViewV2
- FocusViewV2 — Single-AP focus view