# Configuration Guide OWL uses two configuration files to separate detection settings from infrastructure settings. Both are standard INI files that can be edited with any text editor or adjusted through the web dashboard. ## Config File Overview | File | Location | Purpose | |------|----------|---------| | `GENERAL_CONFIG.ini` | `~/owl/config/` | Detection parameters, camera, sensitivity presets, tracking, relay mapping | | `CONTROLLER.ini` | `~/owl/config/` | MQTT, network mode, GPS, web dashboard, actuation timing | An `active_config.txt` file in the same directory points to the current detection config (defaults to `config/GENERAL_CONFIG.ini`). ```{admonition} Copy-on-write protection :class: note `GENERAL_CONFIG.ini` and `CONTROLLER.ini` are protected defaults. When changes are made through the dashboard, a timestamped copy is created automatically (e.g. `GENERAL_CONFIG_20260318_1430.ini`) so the originals are never corrupted. ``` ## Sensitivity Presets Instead of separate config files for each sensitivity level, OWL embeds sensitivity presets directly in `GENERAL_CONFIG.ini`: ```ini [Sensitivity] active = medium [Sensitivity_Low] exg_min = 25 exg_max = 200 hue_min = 41 hue_max = 80 ... [Sensitivity_Medium] exg_min = 25 exg_max = 200 hue_min = 39 hue_max = 83 ... [Sensitivity_High] exg_min = 22 exg_max = 210 hue_min = 35 hue_max = 85 ... ``` Each preset stores the 9 GreenOnBrown threshold values. The `[Sensitivity] active` key determines which preset is loaded at startup. You can switch presets from the dashboard, the wired controller sensitivity switch, or the AI assistant. Custom presets can be saved from the dashboard and are stored as additional `[Sensitivity_YourName]` sections in the config file. ## Changing Settings There are several ways to adjust OWL settings: **Dashboard sliders (recommended)** : The web dashboard provides range sliders for all detection parameters. Changes take effect immediately and are persisted to the config file. **AI assistant** : Open the Agent tab in the dashboard and describe what you want in plain language — e.g. "make detection more sensitive" or "switch to the exg algorithm". See the [AI Assistant guide](../agent/index.md). **Direct INI editing** : Edit the config file directly with a text editor: ```bash nano ~/owl/config/GENERAL_CONFIG.ini ``` Save the file and restart OWL for changes to take effect. **Command line flags** : Override the config file or enable display mode at startup: ```bash # Use a specific config file python owl.py --config config/my_custom.ini # Show display for threshold adjustment python owl.py --show-display # Camera focus mode python owl.py --focus # Process a video or image directory python owl.py --input path/to/media ``` ## Complete GENERAL_CONFIG.ini Template ```ini [System] algorithm = exhsv input_file_or_directory = relay_num = 4 actuation_duration = 0.15 delay = 0 actuation_top = 0.0 actuation_bottom = 1.0 actuation_zone = 100 on_error = off [Controller] controller_type = none status_led_pin = 40 gps_led_pin = 38 switch_purpose = recording switch_pin = 36 detection_mode_pin_up = 36 detection_mode_pin_down = 35 recording_pin = 33 sensitivity_pin = 32 [Visualisation] image_loop_time = 5 [Camera] resolution_width = 1456 resolution_height = 1088 exp_compensation = -2 awb_mode = auto awb_red_gain = 2.0 awb_blue_gain = 2.0 rotation = auto crop_left = 0.02 crop_right = 0.02 crop_top = 0.02 crop_bottom = 0.02 crop_factor_horizontal = 0.02 crop_factor_vertical = 0.02 camera_type = auto [GreenOnGreen] model_path = models confidence = 0.5 detect_classes = actuation_mode = centre min_detection_pixels = 50 inference_resolution = 320 crop_buffer_px = 20 [GreenOnBrown] exg_min = 25 exg_max = 200 hue_min = 39 hue_max = 83 saturation_min = 50 saturation_max = 220 brightness_min = 60 brightness_max = 190 min_detection_area_percent = 0.0075 invert_hue = False [DataCollection] image_sample_enable = False detection_enable = True sample_method = whole sample_frequency = 30 sample_mode = frames sample_distance_m = 1.0 sample_interval_s = 2.0 jpeg_quality = 95 record_mode = images video_max_s = 60 shutter_cap = False smear_limit_mm = 2.0 min_speed_kmh = 1.0 save_directory = /media/owl/SanDisk log_fps = False camera_name = cam1 storage_location = auto internal_save_directory = /home/owl/owl_images min_free_gb = 4 image_quota_gb = 12 [Relays] 0 = 13 1 = 15 2 = 16 3 = 18 [Tracking] tracking_enabled = False track_class_window = 5 track_crop_persist = 3 [Sensitivity] active = medium [Sensitivity_Low] exg_min = 25 exg_max = 200 hue_min = 41 hue_max = 80 saturation_min = 52 saturation_max = 218 brightness_min = 62 brightness_max = 188 min_detection_area_percent = 0.015 [Sensitivity_Medium] exg_min = 25 exg_max = 200 hue_min = 39 hue_max = 83 saturation_min = 50 saturation_max = 220 brightness_min = 60 brightness_max = 190 min_detection_area_percent = 0.0075 [Sensitivity_High] exg_min = 22 exg_max = 210 hue_min = 35 hue_max = 85 saturation_min = 40 saturation_max = 225 brightness_min = 50 brightness_max = 200 min_detection_area_percent = 0.004 ``` ## Complete CONTROLLER.ini Reference ```ini [MQTT] enable = True broker_ip = localhost broker_port = 1883 device_id = auto [WebDashboard] port = 8000 [Network] mode = networked static_ip = localhost controller_ip = localhost [GPS] # GPS data source (none / serial / tcp / gpsd) source = none # Serial device path and baud (only used when source = serial) port = /dev/ttyACM0 baudrate = 9600 # TCP listener (only when source = tcp). `enable = True` is a deprecated # back-compat alias for `source = tcp`. enable = False nmea_port = 8500 # Session / coverage tracking (applies to every non-none source) boom_width = 12.0 track_save_directory = tracks [Actuation] # Relay timing — used as fallback when no GPS speed data actuation_duration = 0.15 delay = 0.0 # Speed-adaptive actuation geometry (networked controller, or an OWL with its own GPS) actuation_length_cm = 10 offset_cm = 30 speed_avg_window = 2.0 [Security] # Per-device API token, minted automatically when the OWL is provisioned # through the phone app (v3.11+). Guards mutating dashboard routes # (network changes, passwords, shutdown/reboot, session delete) via the # X-Device-Token header. Absent on units set up before v3.11 - those # accept requests without the header (legacy behaviour). Deleting the # section restores legacy behaviour; re-running first-boot setup mints # a fresh token. device_token = ``` ## Parameter Reference — GENERAL_CONFIG.ini ### System | Parameter | Default | Description | |-----------|---------|-------------| | `algorithm` | `exhsv` | Detection algorithm: `exg`, `exhsv`, `hsv`, `lut`, `gog`, `gog-hybrid` | | `input_file_or_directory` | *(empty)* | Path to image, video, or directory for offline processing. Leave empty for live camera. | | `relay_num` | `4` | Number of relay lanes (1-4). | | `actuation_duration` | `0.15` | Relay activation time in seconds. | | `delay` | `0` | Delay between detection and actuation in seconds. | | `actuation_top` | `0.0` | Top of the **actuation band** as a fraction of the cropped height (`0.0` = top of crop). A weed fires its relay only while its centre is inside the band. Raise this to ignore weeds too far ahead. | | `actuation_bottom` | `1.0` | Bottom of the actuation band as a fraction of the cropped height (`1.0` = bottom of crop). Lower this to ignore weeds too close (where the solenoid can't react in time). | | `actuation_zone` | `100` | **Legacy** bottom-anchored zone (% from bottom). Used only if `actuation_top`/`actuation_bottom` are absent (then `actuation_top = 1 - actuation_zone/100`, `actuation_bottom = 1.0`). Prefer the band keys above. | | `on_error` | `off` | What the nozzles do on a spraying fault: the camera stops sending frames, dashboard and switch control stops updating, or a relay is stuck on. `off` turns detection off and all nozzles off. `on` blanket sprays while spraying is switched on, if detection can no longer run (camera lost); otherwise spot spraying continues. Either way the fault shows on the dashboard, the phone app and the status LED. | **Status LED fault codes.** The status LED flashes a code, repeated, while a fault or error is active: | Flashes | Meaning | |---------|---------| | 1 | Camera or software dependency failed at start | | 2 | Camera not found at start | | 3 | Camera lost while running (spraying fault) | | 4 | Dashboard and switch control stopped updating (spraying fault) | | 5 | Storage full or storage error | | 6 | No USB drive for recording, or the drive was removed | | 7 | A relay is stuck on (spraying fault) | ### Controller | Parameter | Default | Description | |-----------|---------|-------------| | `controller_type` | `none` | Wired controller type: `none`, `ute`, or `advanced`. | | `status_led_pin` | `40` | BOARD pin for status indicator LED (shared by Ute and Advanced). | | `gps_led_pin` | `38` | BOARD pin for GPS status LED (shared by Ute and Advanced). | | `switch_purpose` | `recording` | Ute controller switch function: `recording` or `detection`. | | `switch_pin` | `36` | BOARD pin for Ute controller toggle switch. Avoid BOARD 37 if using a Sixfab HAT. Pins used by on-device GPS hardware are refused (see below). | | `detection_mode_pin_up` | `36` | BOARD pin for detection mode up (Advanced controller). | | `detection_mode_pin_down` | `35` | BOARD pin for detection mode down (Advanced controller). | | `recording_pin` | `33` | BOARD pin for recording toggle (Advanced controller). | | `sensitivity_pin` | `32` | BOARD pin for sensitivity switch (Advanced controller). | ### Camera | Parameter | Default | Description | |-----------|---------|-------------| | `resolution_width` | `1456` | Camera capture width in pixels. | | `resolution_height` | `1088` | Camera capture height in pixels. | | `exp_compensation` | `-2` | Exposure compensation (-8 to 8). Negative values give faster shutter speed. Applied live (no restart) on picamera2. | | `awb_mode` | `auto` | White balance: `auto`, `daylight`, `cloudy`, `tungsten`, `fluorescent`, `indoor` or `manual`. `auto` lets libcamera balance the scene, the same as `rpicam-hello`. The presets pin the colour temperature to a narrow window of the Pi sensor tuning file (`daylight` = 5500-6500 K) and render Arducam IMX296 modules strongly red. `manual` disables auto white balance and applies the gains below. Applied live from the dashboard's Advanced Settings editor or the phone app. | | `max_gain` | `auto` | Gain limit while the shutter limit is active: `auto`, or a number such as `8`. Lower means less noise but darker images in poor light. Set from the collect screen's Camera settings. | | `awb_red_gain` | `2.0` | Red colour gain (0.1-8.0), used only when `awb_mode = manual`. Higher = redder. Filled in by **Calibrate white balance** (dashboard Config tab or phone app): point the camera at bare soil or a grey card, tap once, and OWL samples the auto gains and locks them as `manual` so colours stay fixed for detection. The dashboard also shows the live gains and estimated colour temperature the camera is using. | | `awb_blue_gain` | `2.0` | Blue colour gain (0.1-8.0), used only when `awb_mode = manual`. Set by the same calibration. | | `rotation` | `auto` | Image rotation: `auto`, `0` or `180`. `auto` is upright for the Global Shutter camera (IMX296, OWL 3.0 mount) and `180` for the classic enclosure, where the HQ/Camera Module 3 sensors mount upside down. Set `0` or `180` explicitly if your mount differs. Applied at startup - restart detection after changing it. | | `crop_left` | `0.02` | Fraction of width cropped from the **left** edge (0.0-0.49). | | `crop_right` | `0.02` | Fraction of width cropped from the **right** edge (0.0-0.49). | | `crop_top` | `0.02` | Fraction of height cropped from the **top** edge (0.0-0.49). | | `crop_bottom` | `0.02` | Fraction of height cropped from the **bottom** edge (0.0-0.49). Detection runs only inside the remaining rectangle; relay lanes are spread across the cropped width and recenter automatically. | | `crop_factor_horizontal` | `0.02` | **Legacy** symmetric horizontal crop. Used only if `crop_left`/`crop_right` are absent (then both fall back to this value). | | `crop_factor_vertical` | `0.02` | **Legacy** symmetric vertical crop. Used only if `crop_top`/`crop_bottom` are absent. | | `camera_type` | `auto` | Camera selection: `auto` (auto-detect), `rpi` (force Pi camera), `usb` (force USB webcam). | | `allow_high_resolution` | `False` | Bypass the Pi 3/4 832x640 safety clamp. Leave `False` unless you've verified your hardware handles the target resolution. | ```{admonition} Geometry lives in GEOMETRY.ini :class: note The crop edges (`crop_left/right/top/bottom`) and the actuation band (`actuation_top/bottom`) are **mount geometry** — physical to how a unit is bolted on. They live in their own `config/GEOMETRY.ini`, which OWL reads **last** so it always wins over any values in the active config. This means switching or saving a named detection config (sensitivity, algorithm, thresholds) never changes a unit's geometry, and geometry is never copied into named configs. Set it with the dashboard's **Adjust geometry** editor (which writes `GEOMETRY.ini`) or by editing `GEOMETRY.ini` by hand. The keys are documented in the `[Camera]` and `[System]` tables for reference. ``` #### Setting the resolution Resolution can be changed two ways — pick whichever fits your workflow. **Dashboard Config tab.** Open the dashboard → **CONFIG** tab → **Camera** section → **Resolution** dropdown. The dropdown offers the supported presets (1456x1088, 1280x960, 1280x720, 1024x768, 800x600, 640x480, 416x320, 320x240) and writes both `resolution_width` and `resolution_height` to the active config in a single step. Changes are persisted via the copy-on-write mechanism described above. **Direct INI editing.** Set `resolution_width` and `resolution_height` independently under `[Camera]`, then restart OWL for the change to take effect. This route is fine when you're already editing the file for other reasons, or when you need a resolution outside the dashboard preset list. ```{admonition} Pi 3 / Pi 4 safety clamp :class: warning On the Raspberry Pi 3 and Pi 4, requests above **832x640** are silently clamped to 640x480. Older Pi models are known to lock up at higher resolutions, and the clamp prevents bricked devices in the field. To override the clamp, set `[Camera] allow_high_resolution = True` — but only after verifying that the specific Pi hardware in front of you actually copes with the resolution you're requesting. The dashboard surfaces an active clamp via a banner in the Camera section. The Pi 5 is not affected by the clamp. ``` ### Visualisation | Parameter | Default | Description | |-----------|---------|-------------| | `image_loop_time` | `5` | Seconds between display frame updates when using `--show-display`. | ### GreenOnGreen | Parameter | Default | Description | |-----------|---------|-------------| | `model_path` | `models` | Directory containing YOLO model files (NCNN or PyTorch). | | `confidence` | `0.5` | Minimum confidence threshold for detections (0.0-1.0). | | `detect_classes` | *(empty)* | Comma-separated class IDs to detect. Empty means all classes. | | `actuation_mode` | `centre` | How detections map to relays: `centre` (bounding box centre) or `zone` (segmentation mask pixel count, requires seg model). | | `min_detection_pixels` | `50` | Minimum pixel count for valid segmentation detections (zone mode). | | `inference_resolution` | `320` | Input image size for YOLO inference. Smaller is faster, larger is more accurate. | | `crop_buffer_px` | `20` | Pixel buffer added around crop mask regions (gog-hybrid mode). | ### GreenOnBrown | Parameter | Default | Description | |-----------|---------|-------------| | `exg_min` | `25` | Minimum Excess Green threshold (0-255). Lower = more sensitive. | | `exg_max` | `200` | Maximum Excess Green threshold (0-255). Keep above 180. | | `hue_min` | `39` | Minimum hue threshold (0-179). Green vegetation is typically 39-83. | | `hue_max` | `83` | Maximum hue threshold (0-179). | | `saturation_min` | `50` | Minimum saturation threshold (0-255). | | `saturation_max` | `220` | Maximum saturation threshold (0-255). | | `brightness_min` | `60` | Minimum brightness threshold (0-255). | | `brightness_max` | `190` | Maximum brightness threshold (0-255). | | `min_detection_area_percent` | `0.0075` | Minimum weed size as a percentage of the detection frame area (0.0005-5). Increase to ignore small noise. Resolution-independent. | | `invert_hue` | `False` | When True, detects colours outside the hue range instead of inside. Useful for detecting non-green targets. | !!! note "Legacy `min_detection_area`" Older configs used `min_detection_area` (contour area in pixels). It is still accepted: OWL converts it to `min_detection_area_percent` at startup and removes the px key the next time the config is saved. New configs should use the percent key only. ### DataCollection | Parameter | Default | Description | |-----------|---------|-------------| | `image_sample_enable` | `False` | Enable image data collection to USB drive. | | `detection_enable` | `True` | Enable weed detection and relay actuation. Set False for data collection only. | | `sample_method` | `whole` | Image sampling method: `bbox` (bounding boxes only), `square` (square crops), `whole` (full frame). | | `sample_frequency` | `30` | Save an image every N frames (when `sample_mode = frames`). | | `sample_mode` | `frames` | Which frames are saved: `frames` (every `sample_frequency` frames), `distance` (every `sample_distance_m` of GPS travel; every `sample_interval_s` without a fresh GPS speed; nothing while parked) or `time` (every `sample_interval_s`). The data-collection screen sets this live. | | `sample_distance_m` | `1.0` | Distance mode: metres travelled between images. | | `sample_interval_s` | `2.0` | Time mode, and distance mode without GPS: seconds between images. | | `jpeg_quality` | `95` | JPEG quality of saved images (50-100). | | `record_mode` | `images` | `images`, or `video`: one `.mp4` clip per recording in the session folder, stopped at `video_max_s`. | | `video_max_s` | `60` | Video clip length limit in seconds (5-60). | | `shutter_cap` | `False` | Limit the shutter time so ground movement during one exposure (GPS speed x shutter time) stays under `smear_limit_mm`; the gain rises instead. Released whenever GPS speed is missing. Check it on your camera first with `tools/shutter_cap_probe.py`. | | `smear_limit_mm` | `2.0` | Blur limit for `shutter_cap` and the collect screen's blur warning, in mm of ground movement per image. | | `min_speed_kmh` | `1.0` | No still images while a fresh GPS speed is below this, for example parked or turning at a headland. `0` never pauses. Without a GPS speed, images keep coming. Video clips are not paused. | | `save_directory` | `/media/owl/SanDisk` | Directory for saved images. Typically a USB drive mount point. | | `log_fps` | `False` | Log frame processing times to file. | | `camera_name` | `cam1` | Camera identifier used in saved file names. | | `storage_location` | `auto` | Where recordings go: `auto` (default - USB when a drive is mounted, otherwise the Pi's internal storage with the `min_free_gb` floor protecting the system disk), `usb` (recording requires a drive mounted under `/media`), or `internal` (always the Pi's own eMMC/SD storage - sealed OWL 3.0 units). | | `internal_save_directory` | `/home/owl/owl_images` | Recording path used in internal storage mode. | | `min_free_gb` | `4` | Internal mode only: recording is refused, and stops within seconds, when free disk space falls below this floor. Recording becomes available again once sessions are deleted or downloaded. | | `image_quota_gb` | `12` | Image allowance shown on the app/dashboard storage bar. Display only — the enforced limit is `min_free_gb`. | ### Tracking | Parameter | Default | Description | |-----------|---------|-------------| | `tracking_enabled` | `False` | Enable ByteTrack multi-object tracking for persistent weed IDs across frames. | | `track_class_window` | `5` | Number of frames to smooth class predictions over. Reduces class flickering. | | `track_crop_persist` | `3` | Number of frames a crop mask region persists after the crop leaves frame (gog-hybrid mode). | ### Relays | Parameter | Description | |-----------|-------------| | `0 = 13` | Relay 0 maps to GPIO board pin 13 | | `1 = 15` | Relay 1 maps to GPIO board pin 15 | | `2 = 16` | Relay 2 maps to GPIO board pin 16 | | `3 = 18` | Relay 3 maps to GPIO board pin 18 | ### Sensitivity | Parameter | Default | Description | |-----------|---------|-------------| | `active` | `medium` | Active sensitivity preset name. Built-in presets: `low`, `medium`, `high`. Custom presets can be saved from the dashboard. | ## Parameter Reference — CONTROLLER.ini ### MQTT | Parameter | Default | Description | |-----------|---------|-------------| | `enable` | `True` | Enable MQTT communication with the dashboard. | | `broker_ip` | `localhost` | MQTT broker IP address. `localhost` for standalone, controller IP for networked. | | `broker_port` | `1883` | MQTT broker port. | | `device_id` | `auto` | Unique device identifier. `auto` generates from hostname. | ### WebDashboard | Parameter | Default | Description | |-----------|---------|-------------| | `port` | `8000` | Gunicorn listening port (behind Nginx reverse proxy). | | `ui` | `full` | In-cab controller only: the screen the kiosk opens. `full` is the full dashboard; `collect` is the data-collection screen, which holds detection off on every OWL. Both stay reachable at `/full` and `/collect`. Restart `owl-controller` after changing. | ### Collect Read by the in-cab controller's data-collection screen and, since v3.23, by the standalone dashboard (on the OWL's own `CONTROLLER.ini`). Written by the record form and the collect screen's Settings tab. Every key is optional and can be edited by hand. | Parameter | Default | Description | |-----------|---------|-------------| | `language` | `en` | Language of the record form and capture settings: `en` or `da`. | | `crops`, `seasonal_crops`, `weather`, `lighting`, `purposes` | built-in lists | Comma-separated preset slugs for the record form. Values go into image EXIF and `session_metadata.json` and are what Noktura maps onto dataset fields, so keep the slugs (for example `wheat`, `natural_sun`, `herbicide`). | | `last_crop`, `last_crop_season`, `last_weather`, `last_lighting`, `last_purpose` | empty | The choices from the last Start recording, pre-selected next time. Written automatically; notes are never saved here. | | `sample_mode`, `sample_distance_m`, `sample_interval_s`, `jpeg_quality`, `record_mode`, `video_max_s`, `shutter_cap`, `smear_limit_mm`, `max_gain`, `min_speed_kmh` | `distance`, `1.0`, `2.0`, `95`, `images`, `60`, `false`, `2.0`, `auto`, `1.0` | In-cab controller only: capture settings pushed to every OWL (see DataCollection above). A standalone OWL keeps these in its own active config (`[DataCollection]`, and `[Camera] max_gain`) instead. | | `soft_ratio`, `dark_brightness`, `dark_percent`, `clipped_percent`, `not_saving_s`, `storage_low_mb` | `0.6`, `40`, `30`, `5`, `10`, `2048` | Warning thresholds for the collect screen. | ### Network | Parameter | Default | Description | |-----------|---------|-------------| | `mode` | `networked` | Network mode: `standalone` (creates WiFi hotspot) or `networked` (joins existing network). | | `static_ip` | `localhost` | Static IP for the OWL on the network. | | `controller_ip` | `localhost` | IP address of the networked controller (networked mode only). | **Pins used by on-device GPS.** When `[GPS] source = nrf91`, BOARD 37 (GPIO 26, modem power) and BOARD 21 and 24 (the modem UART) belong to the modem. When `source = serial` with `port = /dev/ttyAMA0` or `/dev/serial0`, BOARD 8 and 10 belong to the GPS. A relay, LED or switch configured on one of these pins is disabled at startup with an error in the journal (the OWL keeps running), and the dashboards, phone app and networked controller refuse the change. Without that hardware configured, nothing is reserved. ### GPS The networked controller supports four GPS input modes, selected via the `source` key. Pick whichever matches your hardware — only the keys relevant to your chosen source are read. | Parameter | Default | Description | |-----------|---------|-------------| | `source` | `none` | GPS data source. One of: `none` (disabled), `serial` (USB/UART device on this Pi), `nrf91` (the nRF9151 modem on the production CM5 carrier, read by the OWL itself; set up by `bash owl_setup.sh --factory`), `tcp` (NMEA pushed over TCP, e.g. from a Teltonika router), or `gpsd` (read from the local `gpsd` daemon on `localhost:2947`). | | `port` | `/dev/ttyACM0` | Serial device path (used when `source = serial`, also passed to `gpsd` as its `DEVICES` hint). Ublox USB dongles enumerate as `/dev/ttyACM0`; CP2102/CH340 adapters as `/dev/ttyUSB0`; Pi primary UART is `/dev/ttyAMA0` (or `/dev/serial0` on Raspberry Pi OS). To find yours: `ls /dev/ttyACM* /dev/ttyUSB*` after plugging in the GPS. The production carrier's nRF9151 modem is `/dev/ttyAMA3`. | | `baudrate` | `9600` | Serial link speed (used when `source = serial`). Ublox factory default is 9600; Sixfab GNSS HAT and some marine modules use 115200. Wrong baud = silent failure (garbled unicode in the log). The carrier's nRF9151 modem runs at `460800`. | | `enable` | `False` | **DEPRECATED.** Kept as a back-compat alias: if `source = none` and `enable = True`, the controller behaves as if `source = tcp`. New configs should set `source` directly. | | `nmea_port` | `8500` | TCP port the controller listens on when `source = tcp`. Configure your router to forward NMEA to `:`. Port 8500 is pre-opened in the UFW firewall rules. | | `boom_width` | `12.0` | Spray boom width in metres. Used to convert distance into hectares: `area_ha = distance_km × boom_width_m / 10`. | | `track_save_directory` | `tracks` | Directory (relative to project root) where session GeoJSON track files are written as `track_YYYY-MM-DD_HHMMSS.geojson`. Files are never auto-deleted — monitor disk on long-running kiosks. | The GPS tab in the dashboard renders these breadcrumbs on a Leaflet map (OpenStreetMap tiles when online, dark grid fallback when offline) with a HDOP-coloured current-position marker and Centre/Fit-all controls. ### Actuation | Parameter | Default | Description | |-----------|---------|-------------| | `actuation_duration` | `0.15` | Relay activation time in seconds (fallback when no GPS speed data). | | `delay` | `0.0` | Travel time from the camera's view to the nozzle, in seconds, counted from frame capture. The OWL subtracts the frame's processing time before scheduling the relay, so `0` opens the nozzle as soon as the weed is detected. | | `actuation_length_cm` | `10` | Physical spray patch length in cm (speed-adaptive calculation). | | `offset_cm` | `30` | Distance from camera to nozzle in cm (speed-adaptive calculation). | | `speed_avg_window` | `2.0` | Time window in seconds for GPS speed averaging. | With GPS speed, spray duration is `actuation_length_cm / speed`, stretched to cover the ground travelled between frames (at most 3 patch lengths), and the delay is `offset_cm / speed`. An OWL with its own GPS (`[GPS] source = serial` or `nrf91`) computes this itself while it has a fresh fix, and that wins over the networked controller's broadcast; when the fix is lost it uses the controller's timing, or the fixed values above if there is no controller. Phone GPS never drives timing. When forward speed makes sprays land late or leaves gaps between frames, the dashboards and phone app show a warning with a speed to slow to. ### Security | Parameter | Default | Description | |-----------|---------|-------------| | `device_token` | *(minted at setup)* | Per-device API token (v3.11+, contract 2). `controller/shared/setup.sh` creates one on every standalone install if the key is missing, and the phone app's first-boot setup mints one too. Mutating dashboard routes (network reconfiguration, password changes, shutdown/reboot, recording-session delete) require it in the `X-Device-Token` header. Units without the key accept requests without the header (legacy behaviour). There is deliberately no localhost bypass: nginx proxies every request from 127.0.0.1, so a bypass would disable the check entirely. Note this means a plain browser cannot call these routes on a token-minted unit - use the phone app, or remove the `[Security]` section to revert to legacy behaviour. | ### Cloud Written by `owl_cloud_provision.sh` (run by hand, or by the phone app after the unit is claimed in Noktura). See the OWL app claim flow for how a shipped unit gets these values. | Parameter | Default | Description | |-----------|---------|-------------| | `enable` | `False` | Master switch for the cloud bridge. `False` = fully local unit. | | `broker_host` / `broker_port` | `mqtt.noktura.tech` / `8883` | Cloud MQTT broker (TLS). | | `device_id` | *(issued at claim)* | Site slug on the cloud side, e.g. `owl-2026-000001`. Must be lowercase and equal to `username`. | | `username` / `password_file` | *(issued at claim)* | Bridge login. The password lives only in `password_file` (mode 600). | | `ca_cert` | `/home/owl/.owl/cloud/ca.crt` | CA used to verify the cloud broker certificate. The template default is the file `owl_cloud_provision.sh --ca-file` installs. When the script runs with `--system-ca` (or without `--ca-file`), it writes the system CA path `/etc/ssl/certs` instead, which is what Noktura uses. | | `portal_url` | *(blank)* | When set, the dashboard and app show an "Open in Noktura" link to `/d/`. | | `storage_host` | *(blank)* | v3.18+. Every upload or download a cloud command asks for must be HTTPS on this host. The phone app sets it when it connects the OWL to Noktura. Blank (manual provisioning) accepts any HTTPS host. | Cloud-origin commands arrive on a separate local topic and pass an allowlist: list, upload, preview and delete recorded sessions, update the software to a release tag (`vX.Y.Z`, never a branch), download and select a detection model, restart the OWL service and reboot. Nothing else from the cloud runs: no detection toggles, no configuration edits, no branch installs. The local broker itself listens on loopback only (v3.18+): the phone app and kiosk reach the OWL through HTTPS, never MQTT. --- ## Next Steps - [Controller Setup](../../controllers/index.md) - Build a controller for managing OWLs - [Green-on-Green](../green-on-green/index.md) - In-crop weed detection using YOLO - [AI Assistant](../agent/index.md) - Control OWL through natural language - [Troubleshooting](../../troubleshooting/index.md) - Common issues and solutions