Full Code of prusa3d/Prusa-Link for AI

master 85746ebf2fab cached
142 files
1.1 MB
308.8k tokens
1540 symbols
1 requests
Download .txt
Showing preview only (1,232K chars total). Download the full file or copy to clipboard to get everything.
Repository: prusa3d/Prusa-Link
Branch: master
Commit: 85746ebf2fab
Files: 142
Total size: 1.1 MB

Directory structure:
gitextract_3glbe_4j/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── pages.yml
│       └── python-tests.yml
├── .gitignore
├── .gitmodules
├── .pre-commit-config.yaml
├── .pylintrc
├── CONTRIBUTION.md
├── ChangeLog
├── MANIFEST.in
├── MULTIINSTANCE.md
├── README.md
├── config.custom.js
├── docs/
│   ├── Makefile
│   ├── prusalink_states.txt
│   └── wizard.txt
├── image_builder/
│   ├── __init__.py
│   └── image_builder.py
├── prusa/
│   └── link/
│       ├── __init__.py
│       ├── __main__.py
│       ├── camera_governor.py
│       ├── cameras/
│       │   ├── __init__.py
│       │   ├── encoders.py
│       │   ├── picamera_driver.py
│       │   ├── v4l2.py
│       │   └── v4l2_driver.py
│       ├── conditions.py
│       ├── config.py
│       ├── const.py
│       ├── daemon.py
│       ├── data/
│       │   ├── image_builder/
│       │   │   ├── boot-message.service
│       │   │   ├── first-boot.sh
│       │   │   ├── manager-start-script.sh
│       │   │   └── prusalink-start-script.sh
│       │   └── prusalink.ini
│       ├── interesting_logger.py
│       ├── multi_instance/
│       │   ├── __init__.py
│       │   ├── __main__.py
│       │   ├── config_component.py
│       │   ├── const.py
│       │   ├── controller.py
│       │   ├── ipc_queue_adapter.py
│       │   ├── runner_component.py
│       │   └── web.py
│       ├── printer_adapter/
│       │   ├── __init__.py
│       │   ├── auto_telemetry.py
│       │   ├── command.py
│       │   ├── command_handlers.py
│       │   ├── command_queue.py
│       │   ├── file_printer.py
│       │   ├── filesystem/
│       │   │   ├── __init__.py
│       │   │   ├── sd_card.py
│       │   │   ├── storage.py
│       │   │   └── storage_controller.py
│       │   ├── ip_updater.py
│       │   ├── job.py
│       │   ├── keepalive.py
│       │   ├── lcd_printer.py
│       │   ├── mmu_observer.py
│       │   ├── model.py
│       │   ├── print_stat_doubler.py
│       │   ├── print_stats.py
│       │   ├── printer_polling.py
│       │   ├── prusa_link.py
│       │   ├── py.typed
│       │   ├── special_commands.py
│       │   ├── state_manager.py
│       │   ├── structures/
│       │   │   ├── __init__.py
│       │   │   ├── carousel.py
│       │   │   ├── heap.py
│       │   │   ├── item_updater.py
│       │   │   ├── mc_singleton.py
│       │   │   ├── model_classes.py
│       │   │   ├── module_data_classes.py
│       │   │   └── regular_expressions.py
│       │   ├── telemetry_passer.py
│       │   └── updatable.py
│       ├── sdk_augmentation/
│       │   ├── __init__.py
│       │   ├── command_handler.py
│       │   ├── file.py
│       │   └── printer.py
│       ├── serial/
│       │   ├── __init__.py
│       │   ├── helpers.py
│       │   ├── instruction.py
│       │   ├── is_planner_fed.py
│       │   ├── serial.py
│       │   ├── serial_adapter.py
│       │   ├── serial_parser.py
│       │   └── serial_queue.py
│       ├── service_discovery.py
│       ├── static/
│       │   ├── css/
│       │   │   ├── bootstrap.connect.css
│       │   │   └── bootstrap.prusa-link.css
│       │   ├── index.html
│       │   ├── main.9b8dc0068f6e6508dfd4.js
│       │   └── main.b3e029296dd89863b3f2.css
│       ├── templates/
│       │   ├── _footer.html
│       │   ├── _header.html
│       │   ├── _wizard.html
│       │   ├── error-gone.html
│       │   ├── error-internal-server-error.html
│       │   ├── error.html
│       │   ├── index.html
│       │   ├── link_info.html
│       │   ├── multi-instance.html
│       │   ├── wizard.html
│       │   ├── wizard_credentials.html
│       │   ├── wizard_finish.html
│       │   ├── wizard_printer.html
│       │   ├── wizard_restore.html
│       │   └── wizard_serial.html
│       ├── util.py
│       └── web/
│           ├── __init__.py
│           ├── cameras.py
│           ├── connection.py
│           ├── controls.py
│           ├── errors.py
│           ├── files.py
│           ├── files_legacy.py
│           ├── lib/
│           │   ├── __init__.py
│           │   ├── auth.py
│           │   ├── classes.py
│           │   ├── core.py
│           │   ├── files.py
│           │   ├── view.py
│           │   └── wizard.py
│           ├── link_info.py
│           ├── main.py
│           ├── settings.py
│           └── wizard.py
├── prusalink-boot
├── public/
│   └── prusalink.json
├── requirements-multi.txt
├── requirements-pi.txt
├── requirements.txt
├── ruff.toml
├── setup.py
└── tests/
    ├── __init__.py
    ├── test_carousel.py
    ├── test_ipc_queue.py
    ├── test_item_updater.py
    ├── test_serial_parser.py
    └── util.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig <https://EditorConfig.org>
root = true

# elementary defaults
[*]
charset = utf-8
end_of_line = lf
indent_size = tab
indent_style = space
insert_final_newline = true
max_line_length = 80
tab_width = 4

# Markup files
[{*.html,*.xml,*.yaml,*.yml}]
tab_width = 2

================================================
FILE: .github/workflows/pages.yml
================================================
name: Deploy to GitHub Pages

on:
  push:
    branches: [master]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: pages
  cancel-in-progress: true

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/configure-pages@v4
      - uses: actions/upload-pages-artifact@v3
        with:
          path: public
      - id: deployment
        uses: actions/deploy-pages@v4


================================================
FILE: .github/workflows/python-tests.yml
================================================
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python tests

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version:
          - "3.9"
          - "3.11"
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install system dependencies
      run: |
        sudo apt-get update && sudo apt-get install -y libcap-dev libturbojpeg
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -U -r requirements.txt
        pip install -U -r requirements-multi.txt
        pip install pylint~=2.17.7
        pip install -U ruff pytest pytest-doctestplus pytest-pylint pytest-mypy mock
        pip install --force-reinstall git+https://github.com/prusa3d/prusa-connect-sdk-printer.git
        pip install --force-reinstall git+https://github.com/prusa3d/gcode-metadata.git

    - name: Lint with ruff
      run: |
        ruff check .
    - name: Lint with pylit
      run: |
        PYTHONPATH=`pwd` pytest -v --mypy --pylint --doctest-plus --doctest-rst prusa/link
    - name: Tests
      run: |
        PYTHONPATH=`pwd` pytest -v --mypy --pylint --doctest-plus --doctest-rst tests


================================================
FILE: .gitignore
================================================
venv
.idea
.env
__pycache__
*.pyc
build
dist
*.egg-info
*.orig
.hypothesis
*.coverage
htmlcov
docs/*.png


================================================
FILE: .gitmodules
================================================
[submodule "prusa-link-web"]
	path = Prusa-Link-Web
	url = https://github.com/prusa3d/Prusa-Link-Web.git
[submodule "PiShrink"]
	path = PiShrink
	url = https://github.com/Drewsif/PiShrink


================================================
FILE: .pre-commit-config.yaml
================================================
---
repos:
  # - repo: https://github.com/pre-commit/mirrors-yapf
  #   rev: 'v0.32.0'  # Use the sha / tag you want to point at
  #   hooks:
  #     - id: yapf
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.6.0
    hooks:
      - id: check-yaml
      - id: end-of-file-fixer
      - id: trailing-whitespace
  # - repo: https://github.com/pre-commit/mirrors-pylint
  #  rev: v2.4.4
  #  hooks:
  #    - id: pylint
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: 'v0.3.5'
    hooks:
    - id: ruff
      args: [--fix]
  - repo: https://github.com/pycqa/flake8
    rev: 7.0.0
    hooks:
      - id: flake8


================================================
FILE: .pylintrc
================================================
[BASIC]

good-names=i,l,f,sn,ip

[TYPECHECK]

generated-members=prctl

[MASTER]
extension-pkg-whitelist=pydantic
ignore-patterns=.*/v4l2.py

[MESSAGES CONTROL]
disable=
    fixme,
    unsubscriptable-object,
    too-few-public-methods,
    too-many-instance-attributes,
    too-many-public-methods,
    wrong-import-order


================================================
FILE: CONTRIBUTION.md
================================================
# Contribution

## Developement

**On Rasbian**:

Running on foreground without install:

```sh
python3 -m prusa.link -f
```

**From desktop**:

`/dev/ttyACM0` can be USB <-> UART printer port

```sh
python3 -m prusa.link -f -s /dev/ttyACM0
```

When you install `socat` tool to RaspberryPi Zero, you can create use
virtual TCP <-> UART port.

```
socat PTY,link=$HOME/ttyAMA0,raw,wait-slave EXEC:'"ssh pi@prusa-link socat - /dev/ttyAMA0,nonblock,raw"'
# in another terminal
python3 -m prusa.link -f -s $HOME/ttyAMA0
```

**Own static files**:

```sh
PRUSA_LINK_STATIC=./my_static python3 -m prusa.link -f
```

**Communication debug**:
prusalink -f -I -i -l urllib3.connectionpool=DEBUG -l connect-printer=DEBUG


================================================
FILE: ChangeLog
================================================
# ChangeLog

0.8.2 (2024-12-18)
    * Fix crc issue

0.8.1 (2024-06-28)
    * Add a v4l2 workaround so broken camera handles are disqualified from scan

0.8.0 (2024-06-27)
    * Rpi5 first boot fix
    * Fix power panic stuck at Resend
    * Fix startup issues on RPi 5
    * Wait a bit for printer to finish moves after waking up from power panic
    * Sync files upon Upload finish
    * Support newer "//action" without the space
    * Fix the new image missing the libcamera dependency
    * Improve message shown when preparing a power panic recovery
    * Fix print stats not ignoring the skipped part of gcode after PP
    * Ensure power panic info is written to persistent storage
    * Fix active tool not re-setting to None when told to
    * Use printer mid-movement power panic recovery trick
    * Fix the multi-instance proxy. Again
    * Stop showing the tune menu after connecting to the printer
    * Use the 2023-10-10 Raspberry Pi OS base image for the image builder
    * Decrease the severity of web server access log messages
    * Add a way to download logs even on systemd journal based systems
    * Bump the API version so it is the same as the xBuddy reported one
    * Support the new multi-level telemetry data structure
    * Don't send over serial when temperature calibration is running
    * Periodically send a keepalive gcode to keep the printer in PrusaLink mode
    * Support the set ready and cancel ready LCD menu toggle
    * Add gcodes that flag the state of a usb print for the printer statistics to get saved
    * Handle the new re-print LCD menu item
    * Add the initial support for the MMU
    * Add Power Panic support
    * Make ->Ready to recover dissapear sooner from the LCD
    * The minimum firmwre version has been increased to 3.14.0
    * Fix the multi-instance proxy

0.7.2 (2023-10-11)
    * Add an automatic PrusaLink image builder script
    * Add multi-instance documentation
    * Telemetry improvement
    * Attempt to turn the RPi wifi power management off in the images

0.7.1rc1 (2023-08-10)
    * Fixed HTTP response status on HEAD request for non-existing files
    * Attribute ro renamed to read_only
    * Fix printer returning to READY instead of IDLE
    * Respect the X-Forwarded-Prefix header in Wizard
    * Add focus support for the v4l2 cameras
    * Add a multi-instance web waypoint, use a reverse proxy to navigate to the correct instances
    * Automatically redirect from the multi-instance waypoint if exactly one printer is configured
    * Fix missing error detail pages
    * Fix an error when dosconnecting from a cammera that failed to connect
    * Fix an intermittent error that was killing instances for like two years
    * Differentiate between multi instance printers by showing the printer number on the LCD screen
    * Static Web update
        - Fix behavior on SD card ejection
        - Add simple camera focus control if supported
        - Add default value for connect hostname
        - Update translations
        - Add tooltips for error status texts in telemetry
        - Add confirmation modal to overwrite a file by upload
        - Fix undefined printer states displaying
        - Hide zero temperatures in telemetry
        - Add support to be running behind the proxy
        - Fix folder deletion button
        - Fix prusa connect port handling in URL builder
    * Hold the STOPPED and FINISHED state for at least 11s
    * Fix MK2(.5)S SN being broken on multi-instance images
    * Implemented UPGRADE high lvl Connect command

0.7.0 (2023-05-19)
    * Fixed printer sends info about api key change to Connect after change
    * Added the network error beep setting to the web API
    * Support renaming gcodes directory (cfg and API)
    * Added a multi-instace auto config and starter utility
    * Disable error beeps during prints
    * Static Web update
        - Using v1 endpoints for job and transfer
        - Creating and Deleting folders
        - Translation update
        - Network error chime control
        - Default storage names
        - Upgrade procedure rework

0.7.0rc3 (2023-03-09)
    * Added v1 endpoints for flat filesystem structure, old struct is moved to
      files_legacy file
    * Added api/v1/update/<env> GET endpoint
    * Printer name and location are added to register url as query parameters,
      if available
    * Static Web update
        - Apply UI/UX refactoring
        - Add support for max-age cache control
        - Add control of last-modified header for snapshots
        - Add drop zone to files storage
        - Remove manual camera connection dialog
        - Migrate to files api v1
        - PrusaLink update
    * Added Force header to api/v1/files/<storage>/<path> DELETE endpoint for
      deletion of non-empty folder
    * Changed Print-After-Upload header value check for PUT
    * Added endpoint to start printing file
    * Removed the original picamera driver
    * Raspberry Pi Camera support utilizing libcamera directly
    * Hardware encoding support. (Pi Zero W manages FullHD snapshots without issues)
    * Fix "unicam" appearing when a Raspberry Pi camera is connected
    * Fix not following the configured resolution
    * Added api/v1/status endpoint
    * Added new endpoint for updating prusalink python package
    * Added api/v1/transfer endpoint

0.7.0rc2 (2022-12-09)
    * Support thermal model errors (FW 3.12)
    * USB Camera
    * SD Card fixes
    * Fix MBL data for MK2.5(S)
    * Wizard refactoring
    * Static web update
        - Cameras
        - File sorting
        - Stop dialog fixed
        - Connect status
    * API Settings moved from Wizard to Settings
    * Raspberry Pi Camera support
    * Added cache control headers for cameras snap endpoints
    * Fixed PUT upload when folders within the path does not exist
    * Cameras! Support for:
        - V4L2 cameras (webcams - MJPEG and YUYV formats supported)
        - picamera2 (libcamera stack) (slow)
        - Changing the resolution
        - Camera auto-detection
        - Triggering on layer change (PrusaSlicer sliced files only!)
    * Fix files with uppercase extensions not showing up locally
    * Support "hotend fan" = "extruder fan"
    * Re-send the complete telemetry every five minutes
    * Fix stats missing for prints of gcodes without M73
    * Fix pause being able to double print time reported
    * Fixed error when trying to get space info of SD Card


0.7.0rc1 (2022-09-13)
    * Work around a bug: printer in serial print mode while wizard is shown
    * READY state changed to IDLE, PREPARED state changed to READY
    * New status display
       - notifies about setup wizard,
       - shows upload progress
       - shows the name of a file being printed
       - notifies about errors
       - shows an idle screen with the IP address after 30min
       - add idle screen and show transfers during print pauses
    * Name and location of printer value validation
    * Fix negative timeout being possible in serial read
    * Additional Connect (un)registration support
    * File and Directory name validation refactoring
    * Fixed transfer and print in ATTENTION error
    * New Connect API support
    * Fix PrusaLink IP not getting reset from printer on shutdown
    * Fix the serial_number step in wizard
    * Fix unicode characters in file names breaking lcd printer
    * Make RESET_PRINTER clear the command queue and have priority that way
    * Made the app stop itself faster
    * Use M400 instead of G4 for printer queue syncing
    * Reworked validation of correct S/N write
    * Modified username length and password length and format validation
    * Use "Sync|->:" and "Sync->|:" to signify which way is the current transfer going
    * Add DNS service discovery compatible with PrusaSlicer
    * Support file upload cancels from PrusaSlicer
    * Static web update:
       - Fix big log files displaying
       - Decrease display log file size limit to 1M
       - Change temperature controls widget number format to display integers
       - Add stop/resume print button
       - Add protection from steppers disabling when printing
       - Fix sidebar width
       - Replace PNG icons with SVG
       - Fix router, telemetry graph dinmensions and page layout
       - Update error handling to avoid duplicates of popups
       - Add support for file extensions provided by printer profiles from API
       - Fix display names of origins
       - New application design
       - New field to rename project file uploaded by URL
       - New widget displaying used/free size (not-connected to printer yet)
       - New Rename and Copy actions (hidden)
       - New tool to unify icons colors
       - Updated free space logic
       - Fixed storage tabs behavior
       - Avoid unnecessary requests to BE for file metadata
       - Hardcode storages list to printers
       - Removed page `Temperatures`
       - Fix formatting of percentages
       - Project preview is now not dependent on `/api/job` endpoint
       - Confirm dialog after uploade via drag zone
       - Nozzle diameter
       - Offline mode
       - Connect Like icons
       - Translaction fallbacks
     * Differentiate between FW and ID errors in the wizard, update texts
     * Fixed download ref, added total storage space info
     * Added storage space info to api/printer
     * Added function for save file with custom name
     * Add dynamic download throttling when printing
     * Added caching for thumbnail images
     * Send printer info on printer reset / info invalidation event
     * Fixed error handling for PrusaLink Web
     * Reset print stats after a print ends
     * Fix print fail from a unchecked print buffer underflow
     * Report mesh bed levelling data
     * Use the print mode to report the right print stats row to connect
     * Make sure fan errors send reason, improve their behavior a little
     * Fix SD Card module race conditions
     * Make it possible to hide certain loggers from interesting log
     * Filter telemetry, send only what's "significantly" changed
     * Fixed maximum temperature check for nozzle and heatbed
     * Api-Key is implicitly None, can be set in wizard or using endpoint
     * Start PrusaLink even without a connection to the printer
     * Start sending telemetry slowly after a period of inactivity
     * Files can be printed without selecting first, fixed job printTime info in api/job
     * Don't wait for a printer to boot when running through the EINSY pins
     * Added api/v1/info endpoint
     * Add printer statistics tracking
     * Add time to filament change tracking
     * Add sheet settings tracking
     * Return a better reason when print of a non existent file is requested
     * Make printer settings reflect the actual printer type
     * Fixed doubled gcode extensions when custom name is used
     * Added nozzle diameter info to api/v1/info
     * TLS is changed from int to bool
     * Added endpoint for capture an image from a camera
     * Fixed check for negative temperatures of nozzle and bed
     * Add a special SD menu to set the printer to READY from the LCD
     * Add boot partition config copy script (for RPis)
     * Added endpoint api/v1/storage with storage info
     * Round auto guessed preheat temps to the nearest five
     * Remove any irrelevant telemetry right on state change
     * Added endpoint api/v1/<storage>/<path>
     * Add automatic serial port scan
     * Use USB S/N if available (fixes MK2.5 SN issues)
     * Added endpoint with a list of available ports
     * Added capabilities flag to api/version
     * Added min extrusion temp to api/v1/info endpoint, fixed value
     * Added optional ro parameter to api/files and api/v1/{storage}/{path} endpoints
     * Added link_state parameter to api/printer endpoint
     * Fixed item updater allowing invalidation of a disabled item
     * Fixed upload PUT Print-After-Upload if already printing error
     * Added api/v1/<storage>/<path> delete endpoint
     * Fixed a semicolon in a filename being printed breaking everything
     * Fixed a bronken RESET_PRINTER for raspis connected through USB
     * API key option removed from wizard
     * Added endpoint for deletion of API key

0.6.0 (2021-12-17)
    * Added endpoint for control of extruder
    * Added endpoint for heatbed temperature control
    * Static web update
      - Add debug outputs to investigate project picture collision
      - Removed unnecessary colon after hostname in Dashboard
      - Switched from data.link to data.printer for settings end point
      - Add advanced upload widget
      - Printer Control Page
      - Add target temperatures to the left sidebar
      - Add possibility to send control values by Enter key press
      - Add serial number setting
      - Prevent api polling when previous requests were not handled
      - Prevent error messages flood in case of a connection problem
      - Optimize application loop
      - Add serial, CONNECT and communication state to the left side bar
    * Added size and date attributes to api/logs GET endpoint
    * Removed m_time file attribute
    * Added restriction for forbidden characters in uploaded file name
    * Added download and basic upload info to link-info page
    * Added and implemented JSON file with HW limits
    * Added api/printer/printhead GET endpoint
    * Changed variable firmware_version to firmware
    * Added LOAD/UNLOAD filament commands
    * Added disable_steppers command to api/printer/printhead
    * Implementation of farm_mode into api/settings endpoints
    * New Upload errors
      - check Content-Length header
      - check if file is uploaded complete
      - check storage free space first
      - errors refactoring
      - simple html errors
    * Changed args to kwargs for high level commands
    * HTTP Request handling improvement
    * own Serial class implementation (speed improvement)
    * Changed args to kwargs for execute_gcode command
    * log thread stack on interesting events
    * move job_id into the EEPROM
    * make STOP_PRINT wait for any of the READY, STOPPED or FINISHED states
    * Implementation of new Transfer object from SDK
    * Make a centralised wizard activation condition
    * Download finished callback implementation
    * Fix SD initialising always as PRESENT even when ABSENT

0.5.1 (2021-07-16)
    * Implementation of print after upload endpoint
    * Minimal suported firmware is 3.10.0
    * Sort files directory first, newest first
    * Faster checking and processing when uploading gcode
    * Static web update
      - Upload gcode fixes
      - File browser is available when printer printes
      - Files are sort by printers API
      - Printed file widget rework
      - Fixed progress bar behaviour when printing is finished
      - Fixed error handling for periodic requests
      - Page heading is sticky now
      - Telemetry sidebar is sticky now
      - Added frontend version to the Settings page
      - Fixed 'undefined' error pop up heading in some cases
      - Log viewer
      - Login and password can be changed in settings
      - All not available project properties are hidden
      - Toaster messages are now sticky to window bottom
      - Fixed printing time estimations missmatching
      - Printer name and location can be changed in settings
      - Files with size above 100MB won't be loaded into textarea
    * React to thermal runaway by going into the error state
    * Use daemon type WSGI threads
    * Removed temporary gcode copy for printing
    * Support the new M20 attributes and their order
    * Fix progress equal to -1 not being supported
    * Fixed upload from local web
    * SEND_INFO hostname fixed
    * Fix SD Card file selection
    * Log HTTP requests and errors over Python Logger
    * Improve FW error message support
    * Work around print head returning to the print after Stop print
    * Added endpoint for download file from url
    * Password in plain text form is not stored in memory
    * Added endpoint for gettting info about the file currently being downloaded
    * Added endpoint for abort current download process
    * Require user attention after each print, even failed ones (if enabled)
    * Added checked and finished flags to api/printer
    * Added states structure to api/connection endpoint
    * Added Connect configuration info to api/connection endpoint
    * Added connection.py with api/connection GET, POST endpoints
    * Added api/settings GET endpoint
    * Added m_timestamp to SDCard files properties
    * Added api/settings POST endpoint, fix settings.py name
    * Fixed /api/printer flags
    * Implementation of gcode download endpoint
    * Added api/logs endpoint
    * Added api/logs/<log_file> endpoint
    * Added wizard/serial endpoints and page for setup S/N of the printer
    * Updated metadata for selected file
    * Require two "Not SD printing" to work around a SD printing bug
    * Added username and password change functionality to api/settings POST, fixed ChangeLog
    * Fixed SD Card metadata read
    * Go into the ERROR state when the printer stops responding for aprox. one minute
    * Added endpoint for regenerate api-key
    * Added api/settings/sn endpoint for setup S/N of the printer
    * Wizard is locked after successful configuration
    * Added endpoint for control of printhead movement
    * Added endpoint GET api/settings/sn

0.4.0 (2021-04-13)
    * getting IP refactoring
    * fix firmware version reading
    * working download gcode endpoint
    * command argument for profiling application
    * connection over VPN fix
    * Added additional network info

0.3.0 (2021-03-30)

    * Fixed broken command resends
    * Fixed state changed handler
    * Added new endpoint /api/connections with JSON response
    * Skipped pidfile when process is not alive
    * Added new endpoint /api/printer with JSON response
    * Fixed complaint in wizard about `api-key` when `username` was too short
    * Fix printer.sn being unset in the wizard by waiting for it
    * Fixed some telemetry being sent basically at random
    * Enabled the RESET_PRINTER command
    * Fixed printer resetting multiple times when it gets reset mid-print
    * Fixed accidentally hogging CPU when displaying LCD messages
    * Set log levels by module name in config or as command arguments
    * Report whether the current job is from the SD or not
    * Support long file names in the upcoming 3.10 release (file explorer only)
    * Added new endpoint /api/files with JSON response
    * Added new endpoint /api/job with JSON response
    * Added support for the new C parameter in M155
    * Modification of /api/connection, files, printer and job endpoints
    * All files in data_dir (user's home by default)
    * Parse print info from the file name (for SD files)
    * Introduce ErrorState(s) from SDK
    * Modify `LCDPrinter` to show IP and status based on SDK error states
    * Support the nem M27 P
    * Fix not being able to print from root of SD when in a folder in LCD menu
    * Send 0% when a new print start is observed
    * Fix no progress being sent when SD print has no stats in its gcode
    * Support fan errors. Send reason for ATTENTION state in state change data
    * Support showing the IP address in the support menu using "M552 P<IP>"
    * /link-info debug page
    * SN is obtained always through the FW and isn't stored in a file anymore
    * Ensure M20 won't be sent during print. Ever
    * Start faster when already printing from SD
    * Don't store password in plaintext but use digest
    * Stop Wizard on printer errors.
    * Support the new STOPPED state
    * Use X-Api-Key or HTTP Digest for /api endpoints
    * hostname in /api/version
    * Fix /api/endpoints
    * Fix /api/files and /api/job endpoints
    * Nicer messages for Wlan errors; LCDPrinter now accesses the model for IP
    * Statics generated from submodule
    * Support pausing, resuming and stopping of serial prints from the LCD
    * Implementation of metadata into /api/files endpoint
    * Process all commands in a single thread -> racing avoided
    * Uploading from local web
    * Prusa Link version in INFO event
    * G-code preview and download endpoints
    * Thread names via prctl - can be show in htop
    * Shutdown fix
    * Report build number alongside firmware version
    * Added api commands for pause, stop and resume print job
    * If-Modified-Since and If-None-Match headers support for /api/files
    * Additional version info
    * Files in hidden folder are ignored
    * Report file names of SD prints better
    * Added endpoint for start print
    * LCD message modification (GO: <IP>)
    * Fix connection errors causing the printer to report being in ERROR state
    * Add the possibility to log at debug level around interesting events
    * Distinguish wifi from lan
    * Implementation of select/print file functions from local web
    * File resource endpoint
    * working `start / stop / pause / resume` job
    * job info refactoring
    * endpoint for deleting file
    * fix job get / set

0.2.0 (2020-12-14)

    * JOB_INFO fix
    * Service must be start using daemon script prusa-link
    * Implicit config path is /etc/Prusa-Link/prusa-link.ini
    * Implicit settings path is {HOME}/prusa_printer_settings.ini
    * Wizard - part II
    * Api-Key in INFO event
    * Wizard redesign


0.1.3 (2020-12-01)

    * Report at least a file name for SD prints
    * Wizard - part I
    * Fix command handling and re-undo fw double-ok workarounds
      (FW commit gd167b3bd or newer is required)

0.1.2 (2020-11-23)

    * New FW (3.9.2.3566) required
    * local web service on http://IP-address:8080
    * file upload from Prusa Slicer (use `PrusaSlicer` Api-Key)


================================================
FILE: MANIFEST.in
================================================
include *.py
include MANIFEST.in
include requirements.txt
include requirements-pi.txt
graft prusa/link/data
graft prusa/link/templates
graft prusa/link/static
graft image_builder
global-exclude *~
global-exclude *.swp
prune venv


================================================
FILE: MULTIINSTANCE.md
================================================
# PrusaLink multi instance
In this mode, an instance of PrusaLink is created for each new printer
detected on any of the USB ports of the host system, allowing the user
to connect multiple printers using a single Raspberry Pi.

## Setup
The multi instance image requires the same setup as the regular one,
but there are some differences

1) The multi-instance manager does not connect to printers on the GPIO
pins as the device udev auto-detection in Linux does not work on those
2) Cameras automatically connect to the first instance only. If you wish
to use for example a camera for each printer, you'll need to manually
copy over relevant configuration
3) In this image, the manager of these PrusaLink instances is run as root.
However web interface of the instance manager is run under the user account.

### Cameras
The temporary process of connecting multiple cameras is not user friendly
and requires manual work. This will change in the future.
The process is as follows:
1) Connect all cameras you wish to use and let them connect to the first
instance
2) Open the web interface of the first instance and under cameras, save
every camera manually. This will create a configuration section for each
camera in `prusa_printer_settings.ini` of the first instance
3) Using ssh, navigate to `/etc/prusalink/prusalink1.ini` and open it
4) Turn off the camera auto-detection in the first instance by adding
this section into the file
    ```
    [cameras]
    auto_detect = False
    ```
5) Navigate to `/home/<username>/PrusaLink1` and open
`prusa_printer_settings.ini`
6) Move the section corresponding to each camera over to the instance in which
you wish to use it. The camera sections have hashes as names,
the order of which is noted in the section `[camera_order]`
7) Move the camera order entry for each camera as well.
A camera order section with a single camera in it looks like this
    ```
    [camera_order]
    1 = asdfghjkl
    ```
8) After a reboot, the cameras should be connected to the correct instances

## Running the manager
To run PrusaLink in the multi-instance mode run `prusalink-manager start`
as root. There are other options allowing you to specify which user to run the
instances and web under. The default is UID = 1000

Here's the help output of prusalink-manager

```
Multi-instance suite for PrusaLink

positional arguments:
  {start,stop,clean,rescan}
                        Available commands
    start               Start the instance managing daemon (needs root
                        privileges)
    stop                Stop any manager daemon running (needs root
                        privileges)
    clean               Danger! cleans all PrusaLink multi-instance
                        configuration
    rescan              Notify the daemon a printer has been connected

options:
  -h, --help            show this help message and exit
  -i, --info            include log messages up to the INFO level
  -d, --debug           include log messages up to the INFO level
  -u USERNAME, --username USERNAME
                        Which users to use for running and storing everything
  -p PREPEND_EXECUTABLES_WITH, --prepend-executables-with PREPEND_EXECUTABLES_WITH
                        Environment variables and path to the executables
                        directory
```


================================================
FILE: README.md
================================================
# PrusaLink

PrusaLink is a compatibility layer between 8-bit Prusa 3D printers
(MK2.5, MK2.5S, MK3, MK3S and MK3S+) and PrusaConnect, which lets you
control and monitor your 3D printer from anywhere.
Get more info at [connect.prusa3d.com](https://connect.prusa3d.com/)

PrusaLink also provides a local web interface:
[Prusa-Link-Web](https://github.com/prusa3d/Prusa-Link-Web)


## Setup
To use PrusaLink please follow our
[Setup Guide](https://help.prusa3d.com/guide/prusalink-and-prusa-connect-mk3-s-_221744)

### Login
If you wish to log into the console environment and haven't changed the
credentials, you'll need these default ones:

```
username: jo
password: raspberry
```

## Dev Setup
If using the Raspberry Pi pins, follow the guide above for the hardware
preparation. Pins can be used even on regular (non-Zero) Pis
through Dupont jumper cables. Just make sure those make proper contact
with the Einsy board. A connection over USB is also possible,
making PrusaLink compatible with pretty much any Linux system,
but since the RPi has been used as a reference, please excuse the Debian
specific instructions.

If using the Pi, create your micro SD card the usual way,
a Lite image will do nicely.
Just in case, here's a guide: https://www.youtube.com/watch?v=ntaXWS8Lk34

### UART over GPIO pins
On some RPis, the main UART is handling Bluetooth, so the printer
communication would get handled by a miniUART, which doesn't work for us.
To disable Bluetooth, add these lines into `config.txt` which is located in
the Pi's boot partition.
```ini
[all]
enable_uart=1
dtoverlay=disable-bt
```

### Installation
PrusaLink needs libpcap headers installed to name its OS threads.
Git and Pip are needed for installation, while pigpio is only needed if the
RPi GPIO pins are to be used.

```bash
sudo apt install git python3-pip pigpio libcap-dev libmagic1 libturbojpeg0 libatlas-base-dev python3-numpy libffi-dev libopenblas0

# If you are using different distro (e.g. Ubuntu), use libturbojpeg library
# instead of libturbojpeg0

# for the Raspberry Pi camera module support
# pre-installed on the newer Raspberry Pi OS images post September 2022
sudo apt install -y python3-libcamera --no-install-recommends

pip install PrusaLink

# Or install straight from GitHub
pip install git+https://github.com/prusa3d/gcode-metadata.git
pip install git+https://github.com/prusa3d/Prusa-Connect-SDK-Printer.git
pip install git+https://github.com/prusa3d/Prusa-Link.git
```

## Config
PrusaLink behavior can be altered using command arguments and configuration
files. The default configuration path is `/etc/prusalink/prusalink.ini` and
does not get created automatically. The configuration documentation can be
found under `prusa/link/data/prusalink.ini`. The executable argument
documentation is provided in the standard help text screen shown after
running `prusalink --help`

The `prusa_printer_settings.ini` file is created by the PrusaLink wizard,
and can be downloaded from the PrusaConnect settings page once you
 register your printer.

### Configuring PrusaLink on the SD card
If you need to manually configure PrusaLink on the SD created from our image,
it now comes with an auto-copy script. Put your `prusalink.ini` or
`prusa_printer_settings.ini` files into the boot portion of the SD,
*That's the only one that shows up under Windows or Mac,*
and they will get copied over to their default locations on the next boot.

### Permission denied
Make sure the user you're running PrusaLink under is a member of the group
**dialout**. To add it, run

```sudo usermod -a -G dialout <username>```

then log out and in with that user.

### Access on port 80
PrusaLink has a local web interface, to make it accessible
on the default port 80, either start it as root and configure the user to which
it should de-elevate itself after the web server is up, or start it as a normal
user on port 8080 - or any other, then redirect the port 80 to the port
PrusaLink is listening on using these commands.

### Running behind a reverse-proxy
If you got a proxy that changes the URI path, add the
X-Forwarded-Prefix header. PrusaLink will use it to construct the correct
URLs for the web interface.

```bash
# use -i to specify the interface affected
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
```
PrusaLink advertises itself on the local network. This makes it visible
in PrusaSlicer under Physical Printers -> Browse. To advertise port 80,
the instance has to be able to ping itself. This can be done by setting up a
similar redirect on the loopback interface
```bash
iptables -t nat -I OUTPUT -p tcp -o <loopback device name> -d localhost --dport 80 -j REDIRECT --to-ports 8080
```

### Multi-instance
If you want to connect multiple printers to a single pi, have a look at
[MULTIINSTANCE.md](MULTIINSTANCE.md)

## Usage
By default, the executable starts the daemon process and exits.
The executable is called `prusalink` and can be used to control the daemon,
if you want to run it in your terminal instead, use the `-f` option
To get the most recent help screen use `prusalink --help`, here's
what it says in 0.7.0
```
usage: prusalink [-h] [-f] [-c <file>] [-p <FILE>] [-a <ADDRESS>] [-t <PORT>]
                 [-I] [-s <PORT>] [-i] [-d] [-l MODULE_LOG_LEVEL] [--profile]
                 [command]

PrusaLink daemon.

positional arguments:
  command               daemon action (start|stop|restart|status) (default:
                        start)

options:
  -h, --help            show this help message and exit
  -f, --foreground      run as script on foreground
  -c <file>, --config <file>
                        path to config file (default:
                        /etc/prusalink/prusalink.ini)
  -p <FILE>, --pidfile <FILE>
                        path to pid file
  -a <ADDRESS>, --address <ADDRESS>
                        IP listening address (host or IP)
  -t <PORT>, --tcp-port <PORT>
                        TCP/IP listening port
  -I, --link-info       /link-info debug page
  -s <PORT>, --serial-port <PORT>
                        Serial (printer's) port or 'auto'
  -i, --info            more verbose logging level INFO is set
  -d, --debug           DEBUG logging level is set
  -l MODULE_LOG_LEVEL, --module-log-level MODULE_LOG_LEVEL
                        sets the log level of any submodule(s). use
                        <module_path>=<log_level>
  --profile             Use cProfile for profiling application.
```


================================================
FILE: config.custom.js
================================================
const webpackConfig = require("./webpack.config");

module.exports = (env, args) => {
    const config = {
        PRINTER_NAME: "Original Prusa i3",
        PRINTER_TYPE: "fdm",

        WITH_SETTINGS: true,
        WITH_CONTROLS: true,
        WITH_PROJECTS: true,
        WITH_LOGS: true,
        WITH_FONT: false,
        WITH_PRINT_BUTTON: true,
        WITH_V1_API: true,
        WITH_CAMERAS: true,
        WITH_DOWNLOAD_BUTTON: true,
        WITH_TELEMETRY_NOZZLE_DIAMETER: true,
        WITH_API_KEY_AUTH: false,
        WITH_API_KEY_SETTING: true,
        WITH_NAME_SORTING_ONLY: false,
        WITH_SYSTEM_UPDATES: true,

        WITH_SYSTEM_VERSION: true,
        WITH_PRINTER_SETTINGS: true,
        WITH_USER_SETTINGS: true,
        WITH_SERIAL: true,
        ...env,
    };
    return webpackConfig(config, args);
}


================================================
FILE: docs/Makefile
================================================
#
# Makefile
# Martin Užák, 2021-01-14 14:55
#

UMLFILES = prusalink_states.txt wizard.txt

uml: $(UMLFILES)
	plantuml $(UMLFILES)

clean:
	rm -fv *png


# vim:ft=make
#


================================================
FILE: docs/prusalink_states.txt
================================================
@startuml

Serial --> RPIenabled
Serial: Serial Port exists

RPIenabled --> IDPrinter
RPIenabled: RPI Port is enabled / Device on serial port

RPIenabled --> LCD
LCD: Messages on LCD

IDPrinter --> GoodFW
IDPrinter: Identify allowed Prusa Printer

GoodFW --> ReadSN
GoodFW: Firwmare is up-to-date

ReadSN --> ValidSN
ReadSN: SN can be read

ValidSN --> PrinterOk
ValidSN: Obtained SN is valid
PrinterOk: Printer detected right

Device --> Phy
Device: Ethernet or WiFi device exists

Phy --> Lan
Phy:  Eth|Wifi device connected

Lan --> Internet
Lan: Device has assigned IP

Lan --> Local
Local: Messages on printer Web

Internet --> HTTP
Internet: DNS is working.
Internet: There is no problem communicating
Internet: to other hosts in the internet.

HTTP --> Token
HTTP: HTTP traffic to Connect is OK, no 5XX statuses

Token --> API
Token: Token is set and valid

API: There are no 4XX problems while communicating to Connect

API --> Connect
Connect: Messages on Connect (with printer token)

note "Error output to: Connect, Printer Display and Printer Web" as ErrorOutput

state Internet #white
state HTTP #white
state Token #white

state "Printer OK" as PrinterOk #lightgreen
state "API OK" as API #lightgreen

state "Printer Web" as Local #lightblue
state LCD #lightblue
state Connect #lightblue


@enduml


================================================
FILE: docs/wizard.txt
================================================
@startuml


state "Add Printer" as Add
state "PrusaLink Wizard" as Wizard #lavender
state "Use config" as WConfig #lavender
state "Network settings" as WiFi #lavender
state Services #lavender
state Printer #lavender
state "Name" as PName #lavender
state "Connect" as PConnect #lavender
state Recapitulation #lavender
state "Add Printer Form" as AConnect
state "PrusaLink Web" as LBoard #grey
state "Printer Overview" as Overview
state "PrusaLink Web" as Link #gray
state Settings #gray
state Code #salmon
state "Network settings" as Network
state Overview
state Config #lightgreen

[*] -> Connect
Connect: Printers
Connect -> Add

Add: Select Printer
Add -up-> Wizard: Go to Printer
Add -> Name

Name: Name and Location
Name: Team
Name -> Network

Network: WiFi Settings
Network: IP Settings
Network: Link Username and Password
Network: SSH
Network: NTP server
Network-> Code

[*] -up-> Wizard

Wizard -up-> WiFi
Wizard -up-> WConfig
Wizard -> Printer

WConfig: Use downloaded
WConfig: prusa-printer-settings.ini
WConfig --> LBoard

WiFi: WiFi setting
WiFi: IP Settings
WiFi --> Wizard

Printer: Printer Detection
Printer: FW Check
Printer -> PName

PName -> Services
PName: Name and Location

Services: Username and Password
Services: SSH
Services: NTP server
Services -> PConnect
Services -> Recapitulation

PConnect: SN Check
PConnect: Connect Registration
PConnect -[dotted]-> Code

Code -up[dotted]-> PConnect
Code: NO UI FORM
Code: Generate Code
Code -> Overview

PConnect -> Recapitulation

Recapitulation -> LBoard
Recapitulation --> AConnect

AConnect: Name and Location
AConnect: Team
AConnect: Registration Code
AConnect -> Overview

Overview -up-> LBoard
Overview: Detect Link on LAN
Overview -> Config

Config: prusa-printer-settings.ini
Config: Download and add to medium
Config: Download and add to Wizard
Config -up-> WConfig
Config --> Settings

[*] --> Link

Link: Already configured
Link -> Settings

Settings: Additional (Re)registration
Settings: Unregistration
Settings -up[dotted]-> Code
Code -[dotted]-> Settings

legend right
    | Color | Type |
    |<#lavender>| Wizard on PrusaLink |
    |<#fefece>| Wizard on PrusaConnect |
    |<#gray>| PrusaLink |
    |<#salmon>| Hidden action on PrusaConnect |
    |<#lightgreen>| File download |
endlegend

@enduml


================================================
FILE: image_builder/__init__.py
================================================


================================================
FILE: image_builder/image_builder.py
================================================
"""Following a writeup from here:
https://blog.grandtrunk.net/2023/03/raspberry-pi-4-emulation-with-qemu/"""
import argparse
import os
import re
import shlex
import subprocess
import threading
from functools import partial
from importlib.resources import files
from os.path import join
from time import sleep
from urllib.request import urlretrieve

KERNEL_URL_REGEX = re.compile(
    r".*/(?P<file_name>linux-image-(?P<version_name>(?P<version>"
    r"\d+\.\d+\.\d+-\d+)-armmp-lpae)_\d+\.\d+\.\d+-\d+_armhf.deb)")


KERNEL_URL = ("http://security.debian.org/debian-security/pool/updates/main/l/"
              "linux/linux-image-6.1.0-21-armmp-lpae_6.1.90-1_armhf.deb")
match = KERNEL_URL_REGEX.match(KERNEL_URL)

if match is None:
    raise RuntimeError("Invalid kernel URL") from None

KERNEL_VERSION = match.group("version")
KERNEL_VERSION_NAME = match.group("version_name")
KERNEL_FILE_NAME = match.group("file_name")
INITRD_NAME = f"initrd.img-{KERNEL_VERSION_NAME}"
VMLINUZ_NAME = f"vmlinuz-{KERNEL_VERSION_NAME}"

IMAGE_URL = ("https://downloads.raspberrypi.org/raspios_lite_armhf/images/"
             "raspios_lite_armhf-2024-03-15/"
             "2024-03-15-raspios-bookworm-armhf-lite.img.xz")

DATA_FILE = "data.json"
COMPRESSED_IMAGE_NAME = "source_image.img.xz"
SOURCE_IMAGE_NAME = "source_image.img"
SACRIFICIAL_IMAGE_NAME = "sacrificial_image.img"
IMAGE_NAME = "image.img"
SHRUNK_IMAGE_NAME = "shrunk_image.img"
OUTPUT_IMAGE_PATTERN = "prusalink{mode}{version}.img"
BOOTFS_MOUNT = "image_bootfs"
ROOTFS_MOUNT = "image_rootfs"
KERNEL_NAME = "kernel8.img"
DTB_NAME = "bcm2710-rpi-3-b-plus.dtb"
EMULATOR_CONNECT_RETRIES = 200
EMULATOR_SHUTDOWN_TIMEOUT = 20

BUILDER_DATA_PATH = str(files("prusa.link") / "data" / "image_builder")

RPI_EMULATOR_COMMAND = (
    "qemu-system-aarch64 "
    "-machine raspi3b "
    "-cpu cortex-a72 "
    "-m 1G "
    "-smp 4 "
    "-serial stdio "
    f"-dtb {DTB_NAME} "
    f"-kernel {KERNEL_NAME} "
    "-drive file=./{image_name},format=raw,if=sd "
    "-append \"rw dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootdelay=1\" "
    "-netdev user,id=ulan,hostfwd=tcp::2222-:22 "
    "-device usb-net,netdev=ulan "
)

VIRT_EMULATOR_COMMAND = (
    "qemu-system-arm "
    "-nographic "
    "-machine virt "
    "-cpu cortex-a7 "
    "-m 2G "
    "-smp 4 "
    "-kernel {vmlinuz} "
    "-initrd {initrd} "
    "-drive file={image_name},format=raw,id=hd,if=none,media=disk "
    "-device virtio-scsi-device -device scsi-hd,drive=hd "
    "-append \"root=/dev/sda2 console=ttyAMA0,115200\" "
    "-netdev user,id=net0,hostfwd=tcp::2222-:22 "
    "-device virtio-net-device,netdev=net0 "
)

SSH_COMMAND = "sshpass -p raspberry ssh -o StrictHostKeyChecking=no " \
              "-o UserKnownHostsFile=/dev/null -q -p 2222 jo@127.0.0.1 "

DATA_DIRECTORY = "imager_data"
OUTPUT_DIRECTORY = "generated_images"


def reporthook(chunk_number, chunk_size, total_size):
    """A hook for urlretrieve to report progress"""
    percent = min(int(chunk_number * chunk_size * 100 / total_size), 100)
    print(f"\rDownloaded {percent}%", end="")


def ensure_directory(directory):
    """If missing, makes directories, along the supplied path"""
    if not os.path.exists(directory):
        os.makedirs(directory)


def run_emulator(command):
    """Runs a given command as if it is an emulator with expected settings"""
    emulator_thread = threading.Thread(target=run_command, args=(command,))
    emulator_thread.start()
    print("Waiting for the emulator to boot")

    success = False
    for _ in range(EMULATOR_CONNECT_RETRIES):
        try:
            run_over_ssh("echo Connected to the emulator")
        except subprocess.CalledProcessError:
            sleep(1)
            continue
        else:
            success = True
            break

    if not success:
        raise RuntimeError("The emulator did not boot in time")
    return emulator_thread


def retry(call, retries=3, sleep_time=1):
    """Retry a function call a number of times"""
    if retries < 0:
        raise ValueError("Number of retries must be higher or equal zero")
    repetitions = retries + 1
    for i in range(repetitions):
        try:
            return call()
        except Exception:  # pylint: disable=broad-except
            if i == repetitions - 1:
                raise
            sleep(sleep_time)
    return None


def run_command(command, check=True, retries=1):
    """Run command and print output"""
    to_run = partial(subprocess.run, shlex.split(command), check=check)
    retry(to_run, retries=retries)


def run_over_ssh(command, check=True, retries=1):
    """Runs a command over ssh, checks for errors and retries once"""
    run_command(SSH_COMMAND + command, check=check, retries=retries)


def check_binary(binary_name):
    """Checks if a binary is installed"""
    print(f"Checking if {binary_name} is installed")
    try:
        subprocess.run(shlex.split(f"which {binary_name}"), check=True)
    except subprocess.CalledProcessError as err:
        raise RuntimeError(f"{binary_name} is not installed") from err


def insert_from_file_before_line(to_file, from_file, search=None, index=None):
    """Inserts the contents of a file into another file before a given line"""
    if search is None and index is None:
        raise RuntimeError("Either search or index must be specified")

    with open(to_file, "r", encoding="utf-8") as file:
        lines = file.readlines()

    with open(from_file, "r", encoding="utf-8") as file:
        lines_to_insert = file.readlines()

    split_on = 0
    if search is not None and index is None:
        for i, line in enumerate(lines):
            if line.strip() == search:
                split_on = i
                break
    else:
        split_on = index

    result = lines[:split_on] + lines_to_insert + lines[split_on:]

    with open(to_file, "w", encoding="utf-8") as file:
        file.writelines(result)


def mount_image(image_name, expand=False):
    """Mounts the image and returns the loop device part"""
    print(f"Creating loop device for {image_name}")
    losetup_result = subprocess.run(
        shlex.split(f"sudo losetup --partscan --find --show {image_name}"),
        check=True,
        capture_output=True)
    loop_device = losetup_result.stdout.decode("utf-8").strip()

    if expand:
        print(f"Resizing {image_name}")
        run_command(f"parted {loop_device} resizepart 2 100%")
        run_command(f"e2fsck -f {loop_device}p2")
        run_command(f"resize2fs {loop_device}p2")

    ensure_directory(BOOTFS_MOUNT)
    ensure_directory(ROOTFS_MOUNT)

    print(f"Mounting {image_name}")
    run_command(f"mount {loop_device}p1 {BOOTFS_MOUNT}")
    run_command(f"mount {loop_device}p2 {ROOTFS_MOUNT}")
    return loop_device


def unmount_image(loop_device):
    """Unmounts the image and removes the loop device"""
    print("Unmounting image")
    retry(partial(run_command, f"umount {BOOTFS_MOUNT}"))
    retry(partial(run_command, f"umount {ROOTFS_MOUNT}"))

    print(f"Removing loop device {loop_device}")
    retry(partial(run_command, f"losetup -d {loop_device}"))


def basic_image_setup():
    """Sets up the image with ssh and a user jo with password raspberry"""
    print("Write userconf.txt")
    userconf_path = join(BOOTFS_MOUNT, "userconf.txt")
    with open(userconf_path, "w", encoding="utf-8") as userconf:
        userconf.write(
            "jo:$6$Jy4tV1H40VvfLZcX$hh/728SqdBocM2FTZ3fJh9Fx1u2FIJD/"
            "8U075tyNewDDVEDS3e9.Miz213qujfnJ967Zs.43VRRhC4d/FDuKn0")

    print("Enable SSH")
    ssh_file_path = join(BOOTFS_MOUNT, "ssh")
    with open(ssh_file_path, "w", encoding="utf-8") as _:
        ...


# pylint: disable=too-many-locals, too-many-statements
def build_image():
    """Builds the requested image"""
    ensure_directory(DATA_DIRECTORY)
    ensure_directory(OUTPUT_DIRECTORY)
    os.chdir(DATA_DIRECTORY)

    if os.getuid() != 0:
        raise RuntimeError("This script must be run as root")
    check_binary("qemu-system-aarch64")
    check_binary("sshpass")
    check_binary("ssh")
    check_binary("wget")
    check_binary("parted")

    parser = argparse.ArgumentParser(
        description="PrusaLink RPi image generator")

    parser.add_argument("-d", "--dev",
                        action="store_true",
                        help="Build the image from master (for development)")

    parser.add_argument("-r", "--refresh",
                        action="store_true",
                        help="Re-do everything from scratch")

    parser.add_argument("-m", "--multi-instance",
                        action="store_true",
                        help="Build the multi-instance image")

    parser.add_argument("-b", "--branch-or-hash",
                        help="Specify a commit branch name or a hash of "
                             "PrusaLink to get")

    args = parser.parse_args()

    try:
        check_binary("pishrink.sh")
    except Exception:  # pylint: disable=broad-except
        print("pishrink is not installed, downloading")
        run_command("wget https://raw.githubusercontent.com/"
                    "Drewsif/PiShrink/master/pishrink.sh")
        run_command("chmod +x pishrink.sh")

    # --- Get source image ---
    if not os.path.exists(SOURCE_IMAGE_NAME) or args.refresh:
        print("Cleaning up old image files")
        run_command(f"rm {COMPRESSED_IMAGE_NAME}", check=False)
        run_command(f"rm {SOURCE_IMAGE_NAME}", check=False)
        run_command(f"rm {IMAGE_NAME}", check=False)

        print(f"Downloading {IMAGE_URL}")
        urlretrieve(IMAGE_URL, COMPRESSED_IMAGE_NAME, reporthook=reporthook)
        print("")

        print("Decompressing image")
        run_command(f"xz --decompress -T0 {COMPRESSED_IMAGE_NAME}")

        print("Resize to 4GB")
        run_command(f"qemu-img resize -f raw {SOURCE_IMAGE_NAME} 4G")

    # --- Get kernel ---
    regenerate_initramfs = False
    if args.refresh:
        regenerate_initramfs = True
    if not os.path.exists(KERNEL_VERSION_NAME):
        regenerate_initramfs = True
    if not os.path.exists(INITRD_NAME):
        regenerate_initramfs = True
    if not os.path.exists(VMLINUZ_NAME):
        regenerate_initramfs = True

    if regenerate_initramfs:
        print("Cleaning up old kernel files")
        run_command("rm linux-image-*", check=False)
        run_command("rm initrd.img-*", check=False)
        run_command("rm vmlinuz-*", check=False)

        print(f"Downloading {KERNEL_URL}")
        urlretrieve(KERNEL_URL, KERNEL_FILE_NAME, reporthook=reporthook)
        print("")

        print("Copying sacrificial image")
        run_command(f"cp {SOURCE_IMAGE_NAME} {SACRIFICIAL_IMAGE_NAME}")

        sacrificial_loop = mount_image(SACRIFICIAL_IMAGE_NAME, expand=True)

        print("Copying the kernel package into the image")
        run_command(f"cp {KERNEL_FILE_NAME} {ROOTFS_MOUNT}/.")

        print("Extracting kernel and dtb files")
        run_command(f"cp {BOOTFS_MOUNT}/{KERNEL_NAME} .")
        run_command(f"cp {BOOTFS_MOUNT}/{DTB_NAME} .")

        basic_image_setup()

        print("Unmounting sacrificial image")
        unmount_image(sacrificial_loop)

        emulator_command = RPI_EMULATOR_COMMAND.format(
            image_name=SACRIFICIAL_IMAGE_NAME)

        print("Run the initrd generating emulator")
        emulator_thread = run_emulator(emulator_command)
        print("Generating vmlinuz and initrd")
        run_over_ssh(f"sudo dpkg -i /{KERNEL_FILE_NAME}")
        run_over_ssh("sudo poweroff", check=False)
        print("Waiting for the initrd generating emulator to shut down")
        emulator_thread.join()

        print("Copying the generated vmlinuz and initrd")
        initrd_loop = mount_image(SACRIFICIAL_IMAGE_NAME, expand=False)

        run_command(f"cp {ROOTFS_MOUNT}/boot/{VMLINUZ_NAME} .")
        run_command(f"cp {ROOTFS_MOUNT}/boot/{INITRD_NAME} .")
        run_command(f"cp -r {ROOTFS_MOUNT}/lib/modules/"
                    f"{KERNEL_VERSION_NAME} .")

        unmount_image(initrd_loop)

        print("Cleaning up")
        run_command(f"rm {SACRIFICIAL_IMAGE_NAME}")
        run_command(f"rm {KERNEL_NAME}")
        run_command(f"rm {DTB_NAME}")

    print("Copying source image")
    run_command(f"cp {SOURCE_IMAGE_NAME} {IMAGE_NAME}")

    raw_loop = mount_image(IMAGE_NAME, expand=True)

    basic_image_setup()

    print("Write boot-message.service")
    message_service_path = join(
        ROOTFS_MOUNT, "etc/systemd/system/boot-message.service")
    boot_message_path = join(BUILDER_DATA_PATH, "boot-message.service")
    run_command(f"cp {boot_message_path} {message_service_path}")

    print("Write additional temporary modules")
    run_command(f"cp -r {KERNEL_VERSION_NAME} "
                f"{ROOTFS_MOUNT}/lib/modules/{KERNEL_VERSION_NAME}")

    config_txt_path = join(BOOTFS_MOUNT, "config.txt")
    with open(config_txt_path, "a", encoding="utf-8") as config_txt:
        config_txt.write("dtoverlay=disable-bt\n")

    unmount_image(raw_loop)

    print("Run the emulator")
    emulator_command = VIRT_EMULATOR_COMMAND.format(
        image_name=IMAGE_NAME,
        vmlinuz=VMLINUZ_NAME,
        initrd=INITRD_NAME)
    emulator_thread = run_emulator(emulator_command)

    print("Enabling boot-message.service")
    run_over_ssh("sudo systemctl enable boot-message.service")

    print("Disabling bluetooth service")
    run_over_ssh("sudo systemctl disable hciuart.service")

    print("Disabling console over serial")
    run_over_ssh("sudo raspi-config nonint do_serial_hw 0")
    run_over_ssh("sudo raspi-config nonint do_serial_cons 1")

    print("Changing hostname to prusalink")
    run_over_ssh("sudo raspi-config nonint do_hostname prusalink")

    print("Waiting for NTP to sync, TODO: make this smarter")
    sleep(20)

    print("Updating system")
    run_over_ssh("sudo apt-get update -y")
    run_over_ssh("sudo apt-get upgrade -y")

    print("Installing dependencies")
    # I guess we need this for the wi-fi setting to get applied normally
    run_over_ssh("sudo apt-get install -y uuid")
    run_over_ssh("sudo apt-get install -y git python3-pip pigpio libcap-dev "
                 "libmagic1 libturbojpeg0 libffi-dev python3-numpy "
                 "cmake iptables python3-libcamera")

    print("Installing PrusaLink")
    # Caution: not tied to requirements-pi.txt
    run_over_ssh("pip install --break-system-packages wiringpi")
    if args.multi_instance:
        run_over_ssh("pip install --break-system-packages ipcqueue")
    if args.dev or args.branch_or_hash is not None:
        hash_part = ""
        if args.branch_or_hash is not None:
            hash_part = f"@{args.branch_or_hash}"
        run_over_ssh("pip install --break-system-packages git+https://"
                     "github.com/prusa3d/gcode-metadata.git")
        run_over_ssh("pip install --break-system-packages git+https://"
                     "github.com/prusa3d/Prusa-Connect-SDK-Printer.git")
        run_over_ssh("pip install --break-system-packages git+https://"
                     f"github.com/prusa3d/Prusa-Link.git{hash_part}")
    else:
        run_over_ssh("pip install --break-system-packages prusalink")

    output = subprocess.run(
        shlex.split(SSH_COMMAND + ".local/bin/prusalink --version"),
        capture_output=True, check=False)
    version_text = output.stdout.decode("utf-8").split("\n")[0]
    prusalink_version = version_text.split(": ")[1]

    print("Removing traces of the installation")
    run_over_ssh("sudo systemctl disable ssh")
    run_over_ssh("sudo logrotate -f /etc/logrotate.conf")
    run_over_ssh("sudo rm /var/log/*.1", check=False)
    run_over_ssh("sudo rm /var/log/*.gz", check=False)
    run_over_ssh("sudo cat /dev/null | sudo tee /var/log/lastlog")
    run_over_ssh("rm ~/.bash_history", check=False)

    print("Shutting down the emulator")
    run_over_ssh("sudo poweroff", check=False)

    emulator_thread.join(timeout=EMULATOR_SHUTDOWN_TIMEOUT)

    print("Shrinking image")
    run_command(f"pishrink.sh -p {IMAGE_NAME} {SHRUNK_IMAGE_NAME} ")

    shrunk_loop = mount_image(SHRUNK_IMAGE_NAME)

    print("Adding the first boot script")
    rc_local_path = join(ROOTFS_MOUNT, "etc/rc.local")
    insert_from_file_before_line(
        to_file=rc_local_path,
        from_file=join(BUILDER_DATA_PATH, "first-boot.sh"),
        index=1)

    print("Adding the start script")
    if args.multi_instance:
        rc_local_bak_path = join(ROOTFS_MOUNT, "etc/rc.local.bak")
        insert_from_file_before_line(
            to_file=rc_local_bak_path,
            from_file=join(BUILDER_DATA_PATH, "manager-start-script.sh"),
            search="exit 0")
    else:
        rc_local_bak_path = join(ROOTFS_MOUNT, "etc/rc.local.bak")
        insert_from_file_before_line(
            to_file=rc_local_bak_path,
            from_file=join(BUILDER_DATA_PATH, "prusalink-start-script.sh"),
            search="exit 0")

    print("Removing modules needed for virtio")
    run_command(f"rm -r {ROOTFS_MOUNT}/lib/modules/"
                f"{KERNEL_VERSION_NAME}")

    run_command(f"rm -r {ROOTFS_MOUNT}/var/cache/*", check=False)
    run_command(f"rm -r {ROOTFS_MOUNT}/home/jo/.cache/*", check=False)

    unmount_image(shrunk_loop)

    output_image_name = OUTPUT_IMAGE_PATTERN.format(
        mode="-multi-instance" if args.multi_instance else "",
        version=f"-{prusalink_version}")

    run_command(f"mv {SHRUNK_IMAGE_NAME} {output_image_name}")

    print("Removing old compressed image")
    run_command(f"rm {output_image_name}.xz", check=False)

    print("Compressing image")
    run_command(f"xz --compress --keep -6 -T0 {output_image_name}")

    print("Cleaning up")
    run_command(f"rm {IMAGE_NAME}")

    run_command(f"mv {output_image_name}.xz ../{OUTPUT_DIRECTORY}/")
    run_command(f"mv {output_image_name} ../{OUTPUT_DIRECTORY}/")

    os.chdir("..")

    print("Done")


def main():
    """Main function, if the build fails, tries to kill the emulator"""
    try:
        build_image()
    except Exception:  # pylint: disable=broad-except
        run_command("killall qemu-system-aarch64", check=False)
        run_command("killall qemu-system-arm", check=False)
        raise


if __name__ == '__main__':
    main()


================================================
FILE: prusa/link/__init__.py
================================================
"""Original PrusaLink printer adapter.

    Copyright (C) 2024 PrusaResearch
"""
__application__ = "PrusaLink"
__vendor__ = "Prusa Research"

__version__ = "0.8.2"
__date__ = "18 Dec 2024"
__copyright__ = "(c) 2024 Prusa 3D"
__author_name__ = "PrusaLink Developers"
__author_email__ = "link@prusa3d.cz"
__author__ = f"{__author_name__} <{__author_email__}>"
__description__ = f"{__application__} for MK3 host software"

__credits__ = "Tomáš Jozífek, Ondřej Tůma, Michal Zoubek"
__url__ = "https://github.com/prusa3d/Prusa-Link"


================================================
FILE: prusa/link/__main__.py
================================================
"""main() command line function."""

import logging
import sys
import threading
from argparse import ArgumentParser, ArgumentTypeError
from cProfile import Profile
from grp import getgrnam
from os import chmod, geteuid, kill, mkdir, path
from pwd import getpwnam
from signal import SIGKILL, SIGTERM
from time import sleep

from daemon import DaemonContext  # type: ignore
from lockfile.pidlockfile import PIDLockFile  # type: ignore
from prusa.connect.printer import __version__ as sdk_version

from . import __version__ as link_version
from .config import Config
from .const import EXIT_TIMEOUT
from .interesting_logger import InterestingLogger, InterestingLogRotator
from .printer_adapter.updatable import Thread

# pylint: disable=wrong-import-position, wrong-import-order
# Pop this singleton into existence before importing prusalink
InterestingLogRotator()
logging.setLoggerClass(InterestingLogger)

from .daemon import Daemon  # noqa: E402

log = logging.getLogger(__name__)

# pylint: disable=too-many-return-statements
# pylint: disable=too-many-statements
CONFIG_FILE = '/etc/prusalink/prusalink.ini'


def excepthook(exception_arguments, args, argv):
    """If running as a daemon, restarts the app on unhandled exceptions"""
    assert exception_arguments is not None
    InterestingLogRotator.trigger("exception in a thread")
    log.exception("Caught an exception at top level!")
    if args is None:
        log.fatal("Exception during startup, cannot restart")
    if args.foreground:
        log.fatal("This instance is now broken. Will not restart "
                  "because we're running in the foreground mode")
    else:
        log.warning("Caught unhandled exception, restarting PrusaLink")
        Daemon.restart(argv)
    # excepthook has the global exception set, besides even if we failed
    # here, it will literally affect nothing
    # pylint: disable=misplaced-bare-raise
    # ruff: noqa: PLE0704
    raise


def set_log_levels(config: Config):
    """Set log level for each defined module."""
    for module, level in config.log_settings.items():
        logging.getLogger(module).setLevel(level)


class LogLevel(str):
    """Log level type with __call__ checker method."""

    def __new__(cls, level):
        if len(level.split("=")) != 2:
            raise ArgumentTypeError("log level needs to be specified in format"
                                    "<module_path>=<log_level>")
        return super().__new__(cls, level)


def check_process(pid):
    """Check if process with pid is alive."""
    try:
        kill(pid, 0)
        return True
    except OSError:
        return False


def wait_process(pid, timeout=1):
    """Wait for process with timeout. Return True if process was terminated."""
    sleep_amount = 0.1
    for _ in range(int(timeout / sleep_amount)):
        if not check_process(pid):
            return True
        sleep(sleep_amount)
    return False


def stop(pid):
    """Tries to stop PrusaLink nicely, if it times out, uses SIGKILL"""
    kill(pid, SIGTERM)
    if wait_process(pid, EXIT_TIMEOUT):
        return

    log.warning("Failed to stop - SIGKIL will be used!")
    try:
        kill(pid, SIGKILL)
    except ProcessLookupError:
        log.warning("Could not find a process with pid %s to kill", pid)
    wait_process(pid, EXIT_TIMEOUT)


def main():
    """Standard main function."""
    # pylint: disable=too-many-branches
    parser = ArgumentParser(prog="prusalink", description="PrusaLink daemon.")
    parser.add_argument(
        "command",
        nargs='?',
        default="start",
        type=str,
        help="daemon action (start|stop|restart|status) (default: start)")
    parser.add_argument("-f",
                        "--foreground",
                        action="store_true",
                        help="run as script on foreground")
    parser.add_argument("-c",
                        "--config",
                        default=CONFIG_FILE,
                        type=str,
                        help=f"path to config file (default: {CONFIG_FILE})",
                        metavar="<file>")
    parser.add_argument("-p",
                        "--pidfile",
                        type=str,
                        help="path to pid file",
                        metavar="<FILE>")
    parser.add_argument("-a",
                        "--address",
                        type=str,
                        help="IP listening address (host or IP)",
                        metavar="<ADDRESS>")
    parser.add_argument("-t",
                        "--tcp-port",
                        type=int,
                        help="TCP/IP listening port",
                        metavar="<PORT>")
    parser.add_argument("-I",
                        "--link-info",
                        action="store_true",
                        help="/link-info debug page")
    parser.add_argument("-s",
                        "--serial-port",
                        type=str,
                        help="Serial (printer's) port or 'auto'",
                        metavar="<PORT>")
    parser.add_argument("-n",
                        "--printer-number",
                        type=int,
                        help="Multi-instance printer number to show in wizard")
    parser.add_argument("-i",
                        "--info",
                        action="store_true",
                        help="more verbose logging level INFO is set")
    parser.add_argument("-d",
                        "--debug",
                        action="store_true",
                        help="DEBUG logging level is set")
    parser.add_argument("-l",
                        "--module-log-level",
                        action="append",
                        help="sets the log level of any submodule(s). "
                        "use <module_path>=<log_level>",
                        type=LogLevel)
    parser.add_argument("--profile",
                        action="store_true",
                        help="Use cProfile for profiling application.")
    parser.add_argument("--version",
                        action="store_true",
                        help="Print out version info and exit")

    argv = list(arg for arg in sys.argv[1:] if arg not in ('start', 'restart'))
    args = parser.parse_args()

    if args.version:
        print("PrusaLink version:", link_version)
        print("PrusaConnect-SDK version:", sdk_version)
        return 0

    profile = None
    if args.profile:
        profile = Profile()
        profile.enable()
        Thread.enable_profiling()

    # Restart on thread exceptions
    threading.excepthook = lambda exc_args: excepthook(exc_args, args, argv)

    try:
        config = Config(args)

        set_log_levels(config)

        pid_file = PIDLockFile(config.daemon.pid_file)
        pid = pid_file.read_pid() if pid_file.is_locked() else None

        if args.command == "stop":
            if pid and check_process(pid):
                print("Stopping service with pid", pid)
                stop(pid)
            else:
                print("Service not running")
            return 0

        if args.command == "status":
            if pid and check_process(pid):
                print("Service running with pid", pid)
                return 0
            print("Service not running")
            return 1

        if args.command == "restart":
            if pid and check_process(pid):
                print("Restarting service with pid", pid)
                stop(pid)

        elif args.command == "start":
            pass
        elif not args.foreground:
            parser.error("Unknown command %s")
            return 1

        daemon = Daemon(config, argv)
        if args.foreground:
            log.info("Starting service on foreground.")
            return daemon.run(False)

        if pid:
            if not check_process(pid):
                pid_file.break_lock()
            else:
                print("Service is already running")
                return 1

        files_preserve = []
        for handler in logging.root.handlers:
            if hasattr(handler, "socket"):
                files_preserve.append(handler.socket.fileno())
        context = DaemonContext(pidfile=pid_file,
                                files_preserve=files_preserve,
                                signal_map={SIGTERM: daemon.sigterm})

        pid_dir = path.dirname(config.daemon.pid_file)
        if pid_dir == '/var/run/prusalink' and not path.exists(pid_dir):
            mkdir(pid_dir)
            chmod(pid_dir, 0o777)

        if geteuid() == 0:
            context.initgroups = True  # need only for RPi, don't know why
            context.uid = getpwnam(config.daemon.user).pw_uid
            context.gid = getgrnam(config.daemon.group).gr_gid

        with context:
            log.info("Starting service with pid %d", pid_file.read_pid())
            retval = daemon.run()
            log.info("Shutdown")
            return retval

    except Exception as exc:  # pylint: disable=broad-except
        log.info("%s", args)
        log.exception("Unhandled exception reached the top level")
        parser.error(f"{exc}")
        return 1

    finally:
        if profile:
            profile.disable()
            profile.dump_stats("prusalink-__main__.profile")


if __name__ == "__main__":
    sys.exit(main())


================================================
FILE: prusa/link/camera_governor.py
================================================
"""Implements a simple loop for getting cameras unstuck
and for auto adding them"""
import logging
from functools import partial
from threading import Event, Thread
from typing import Optional

from prusa.connect.printer.camera_configurator import CameraConfigurator
from prusa.connect.printer.camera_controller import CameraController

from .const import CAMERA_SCAN_INTERVAL
from .interesting_logger import InterestingLogRotator
from .util import loop_until

log = logging.getLogger("my_camera_configurator")


class CameraGovernor:
    """A module for continually refreshing and adding cameras"""

    def __init__(self, camera_configurator: CameraConfigurator,
                 camera_controller: CameraController) -> None:
        self.camera_configurator = camera_configurator
        self.camera_controller = camera_controller

        self._governance_quit_event = Event()
        self._governance_thread: Optional[Thread] = None

    def _govern(self) -> None:
        """Monitors the cameras re-starts failed ones,
        optionally scans for newly connected ones"""
        log.debug("Running the camera governance routine")
        if self.camera_controller.disconnect_stuck_cameras():
            InterestingLogRotator.trigger("a stuck camera")
        self.camera_configurator.load_cameras()

    def start(self) -> None:
        """Starts the camera governing loop"""
        self._governance_quit_event.clear()
        target = partial(
            loop_until,
            loop_evt=self._governance_quit_event,
            run_every_sec=lambda: CAMERA_SCAN_INTERVAL,
            to_run=self._govern)

        self._governance_thread = Thread(
            target=target,
            name="camera_governance",
            daemon=True,
        )
        self._governance_thread.start()

    def stop(self) -> None:
        """Stops the auto-add loop"""
        self._governance_quit_event.set()

    def wait_stopped(self) -> None:
        """Waits util the component's thread stops"""
        if self._governance_thread is None:
            return
        if self._governance_thread.is_alive():
            self._governance_thread.join()


================================================
FILE: prusa/link/cameras/__init__.py
================================================


================================================
FILE: prusa/link/cameras/encoders.py
================================================
"""This file contains encoders for the camera drivers
Especially the hardware conversion needs a lot of prep work"""

import abc
import ctypes
import fcntl
import functools
import mmap
import os
import select
from enum import Enum
from math import sqrt
from types import MappingProxyType

import numpy as np
from turbojpeg import TJSAMP_422, TurboJPEG  # type: ignore

from . import v4l2

jpeg = TurboJPEG()


def fopen(path, write=False):
    """Opens a specified video device file"""
    return open(path, "rb+" if write else "rb", buffering=0, opener=opener)


def opener(path, flags):
    """Adds flags for the open function"""
    return os.open(path, flags | os.O_NONBLOCK)


class Quality(Enum):
    """A simple enum that can be easily interpreted by encoders"""
    VERY_LOW = "Very low"
    LOW = "Low"
    MEDIUM = "Medium"
    HIGH = "High"
    VERY_HIGH = "Very high"


class BufferDetails:
    """A structure to encapsulate buffer info needed for encoding"""

    def __init__(self, file_descriptor, length, offset):
        self.file_descriptor = file_descriptor
        self.length = length
        self.offset = offset
        self.mmap = mmap.mmap(fileno=self.file_descriptor,
                              length=self.length,
                              offset=self.offset)

    def __del__(self):
        try:
            self.mmap.close()
        except AttributeError:
            pass


def get_appropriate_encoder(resolution, pixel_format, use_mmap=False):
    """Returns the appropriate encoder based on stream parameters"""
    max_resolution = max(resolution.width, resolution.height)
    if pixel_format == v4l2.V4L2_PIX_FMT_MJPEG:
        return PassthroughEncoder()
    if not MJPEGEncoder.is_available():
        return JPEGEncoder()
    if max_resolution > MJPEGEncoder.WIDTH_LIMIT:
        return JPEGEncoder()
    encoder = MJPEGEncoder()
    if use_mmap:
        # Switch to a type that copies data instead of trying to use
        # a foreign buffer
        encoder.ingest_buffer_memory = v4l2.V4L2_MEMORY_MMAP
    return encoder


class Encoder:
    """A base class for encoders"""

    def __init__(self):
        """Set all parameters encoder needs after calling init"""
        self.width = 0
        self.height = 0
        self.stride = 0
        self.fps = 30
        self._quality = Quality.HIGH

        # Information about the buffer from which to read
        self.source_details = None

    def start(self):
        """Initializes the encoder"""

    def stop(self):
        """Stops the encoder"""

    @property
    def quality(self):
        """Gets the quality"""
        return self._quality

    @quality.setter
    def quality(self, quality=Quality.HIGH):
        """An entry point for other parameters dependant on quality"""
        self._quality = quality

    @abc.abstractmethod
    def encode(self, bytes_used: int) -> bytes:
        """Encode here, return bytes"""


class MJPEGEncoder(Encoder):
    """Encoder using the MJPEG Encoder on the Raspberry Pi through V4L2

    Glossary:
    SOURCE means foreign object like a buffer we copy data from
    INGEST means our own data structure with raw data (V4L2 name: Output)
    CODED means the structure with compressed data (V4L2 name: Capture)
    """
    WIDTH_LIMIT = 1920
    DEVICE_PATH = "/dev/video11"

    # These are suggested bitrates for 1080p30 in Mbps
    BITRATE_TABLE = MappingProxyType({
        Quality.VERY_LOW: 6,
        Quality.LOW: 12,
        Quality.MEDIUM: 18,
        Quality.HIGH: 27,
        Quality.VERY_HIGH: 45,
    })

    # Use only one buffer, so no indexes need to exist
    BUFFER_INDEX = 0

    @classmethod
    @functools.cache
    def is_available(cls):
        """Figures whether we can do hardware decode or not"""
        if not os.path.exists(cls.DEVICE_PATH):
            return False

        with open(cls.DEVICE_PATH, 'rb+', buffering=0) as file_descriptor:
            coded_format = v4l2.v4l2_format()
            coded_format.type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
            coded_format.fmt.pix_mp.pixelformat = v4l2.V4L2_PIX_FMT_MJPEG
            if fcntl.ioctl(file_descriptor, v4l2.VIDIOC_S_FMT, coded_format):
                return False

            ingest_format = v4l2.v4l2_format()
            ingest_format.type = v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
            ingest_format.fmt.pix_mp.pixelformat = v4l2.V4L2_PIX_FMT_YUYV
            return not fcntl.ioctl(file_descriptor, v4l2.VIDIOC_S_FMT,
                                   ingest_format)

    def __init__(self):
        """Initialise V4L2 encoder"""
        super().__init__()

        self._bitrate = None  # set by setting quality
        self.coded_buffer = None
        self.coded_mmap = None
        self.ingest_buffer = None
        self.ingest_mmap = None

        self.controls = []
        self.file_object = None

        # This is important, it tells us if we can use the buffer given to
        # encode as is, or if we are to copy the data (MMAP = copy)
        self.ingest_buffer_memory = v4l2.V4L2_MEMORY_DMABUF

    def _pre_fill_format(self, format_type, pixel_format):
        format_ = v4l2.v4l2_format()
        format_.type = format_type
        format_.fmt.pix_mp.width = self.width
        format_.fmt.pix_mp.height = self.height
        format_.fmt.pix_mp.pixelformat = pixel_format
        format_.fmt.pix_mp.plane_fmt[0].bytesperline = self.stride
        format_.fmt.pix_mp.field = v4l2.V4L2_FIELD_ANY
        format_.fmt.pix_mp.colorspace = v4l2.V4L2_COLORSPACE_JPEG
        format_.fmt.pix_mp.num_planes = 1
        return format_

    def _request_buffers(self, buffer_type, memory, count=1):
        buffer_request = v4l2.v4l2_requestbuffers()
        buffer_request.count = count
        buffer_request.type = buffer_type
        buffer_request.memory = memory
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_REQBUFS, buffer_request)

    def _get_buffer(self, buffer_type, memory):
        # This is a definition of a ctype array
        plane_proto = v4l2.v4l2_plane * 1
        buffer = v4l2.v4l2_buffer()
        ctypes.memset(ctypes.byref(buffer), 0, ctypes.sizeof(buffer))
        buffer.type = buffer_type
        buffer.memory = memory
        buffer.index = 0
        buffer.length = 1
        buffer.m.planes = plane_proto()
        return buffer

    def _stream_on(self, buffer_type):
        typev = v4l2.v4l2_buf_type(buffer_type)
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_STREAMON, typev)

    def _stream_off(self, buffer_type):
        typev = v4l2.v4l2_buf_type(buffer_type)
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_STREAMOFF, typev)

    def start(self):
        # Removed framerate calculation, we don't do those
        reference_complexity = 1920 * 1080
        actual_complexity = self.width * self.height
        reference_bitrate = self.BITRATE_TABLE[self.quality] * 1000000
        self._bitrate = int(reference_bitrate *
                            sqrt(actual_complexity / reference_complexity))

        # pylint: disable=consider-using-with
        self.file_object = open(self.DEVICE_PATH, 'rb+', buffering=0)

        capability = v4l2.v4l2_capability()
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_QUERYCAP, capability)

        control = v4l2.v4l2_control()
        control.id = v4l2.V4L2_CID_MPEG_VIDEO_BITRATE
        control.value = self._bitrate
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_S_CTRL, control)

        ingest_format = self._pre_fill_format(
            format_type=v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
            pixel_format=v4l2.V4L2_PIX_FMT_YUYV,
        )
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_S_FMT, ingest_format)

        coded_format = self._pre_fill_format(
            format_type=v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
            pixel_format=v4l2.V4L2_PIX_FMT_MJPEG,
        )
        coded_format.fmt.pix_mp.plane_fmt[0].bytesperline = 0
        coded_format.fmt.pix_mp.plane_fmt[0].sizeimage = 512 << 10
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_S_FMT, coded_format)

        self._request_buffers(
            buffer_type=v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
            memory=self.ingest_buffer_memory)

        self._request_buffers(
            buffer_type=v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
            memory=v4l2.V4L2_MEMORY_MMAP)

        # Prepare the buffer for encoded data
        # The raw data buffer will get re-used from libcamera in this case
        self.coded_buffer = self._get_buffer(
            v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
            v4l2.V4L2_MEMORY_MMAP,
        )
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_QUERYBUF, self.coded_buffer)
        plane = self.coded_buffer.m.planes[0]
        self.coded_mmap = mmap.mmap(
            fileno=self.file_object.fileno(),
            length=plane.length,
            offset=plane.m.mem_offset,
            prot=mmap.PROT_READ | mmap.PROT_WRITE,
            flags=mmap.MAP_SHARED,
        )

        self.ingest_buffer = self._get_buffer(
            v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
            self.ingest_buffer_memory,
        )
        fcntl.ioctl(self.file_object, v4l2.VIDIOC_QUERYBUF, self.ingest_buffer)
        if self.ingest_buffer_memory == v4l2.V4L2_MEMORY_MMAP:
            plane = self.ingest_buffer.m.planes[0]
            self.ingest_mmap = mmap.mmap(
                fileno=self.file_object.fileno(),
                length=plane.length,
                offset=plane.m.mem_offset,
                prot=mmap.PROT_READ | mmap.PROT_WRITE,
                flags=mmap.MAP_SHARED,
            )

        fcntl.ioctl(self.file_object, v4l2.VIDIOC_QBUF, self.coded_buffer)

        self._stream_on(v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
        self._stream_on(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)

    def stop(self):
        """Prepares the encoder for encoding"""
        self._stream_off(v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
        self._stream_off(v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)

        self._request_buffers(
            buffer_type=v4l2.V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
            memory=self.ingest_buffer_memory,
            count=0)

        self.coded_mmap.close()
        self.coded_mmap = None

        if self.ingest_mmap is not None:
            self.ingest_mmap.close()
            self.ingest_mmap = None

        self._request_buffers(
            buffer_type=v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
            memory=v4l2.V4L2_MEMORY_MMAP,
            count=0)

        self.file_object.close()
        self.ingest_buffer = None
        self.coded_buffer = None

    def encode(self, bytes_used):
        """Encodes a frame"""

        if self.file_object is None or self.file_object.closed:
            raise RuntimeError("Cannot encode with a stopped encoder")

        if self.ingest_buffer_memory == v4l2.V4L2_MEMORY_DMABUF:
            ingest_plane = self.ingest_buffer.m.planes[0]
            ingest_plane.m.fd = self.source_details.file_descriptor
            ingest_plane.length = self.source_details.length
            ingest_plane.bytesused = bytes_used

        elif self.ingest_buffer_memory == v4l2.V4L2_MEMORY_MMAP:
            self.ingest_mmap.write(self.source_details.mmap.read(bytes_used))
            self.ingest_mmap.seek(self.ingest_buffer.m.planes[0].m.mem_offset)
            self.source_details.mmap.seek(self.source_details.offset)

        fcntl.ioctl(self.file_object, v4l2.VIDIOC_QBUF, self.ingest_buffer)

        select.select((self.file_object, ), (), ())

        if fcntl.ioctl(self.file_object, v4l2.VIDIOC_DQBUF,
                       self.ingest_buffer):
            raise RuntimeError(
                "Encoding failed - dequeueing the ingest buffer")

        if fcntl.ioctl(self.file_object, v4l2.VIDIOC_DQBUF, self.coded_buffer):
            raise RuntimeError(
                "Encoding failed - de-queueing the coded buffer")

        output = self.coded_mmap.read(self.coded_buffer.m.planes[0].bytesused)
        self.coded_mmap.seek(0)

        if fcntl.ioctl(self.file_object, v4l2.VIDIOC_QBUF, self.coded_buffer):
            raise RuntimeError(
                "Encoding failed - re-queueing the coded buffer")

        return output


class JPEGEncoder(Encoder):
    """Encoder using the TurboJPEG library (CPU encoding)"""
    QUALITY_TABLE = MappingProxyType({
        Quality.VERY_LOW: 25,
        Quality.LOW: 50,
        Quality.MEDIUM: 70,
        Quality.HIGH: 85,
        Quality.VERY_HIGH: 95,
    })

    def __init__(self):
        super().__init__()
        self.quality_percent = None

    def start(self):
        """Prepares the encoder for encoding"""
        self.quality_percent = self.QUALITY_TABLE[self.quality]

    def encode(self, bytes_used):
        """Extracts Y, U and V, then puts them one after another instead of
        interweaving"""
        array_data = np.array(self.source_details.mmap, dtype=np.uint8)

        size = bytes_used
        yuv_array = np.empty((size, ), dtype=np.uint8)
        yuv_array[:size // 2] = array_data[0::2]
        yuv_array[size // 2:size // 4 * 3] = array_data[1::4]
        yuv_array[size // 4 * 3:] = array_data[3::4]
        return jpeg.encode_from_yuv(yuv_array,
                                    self.height,
                                    self.width,
                                    quality=self.quality_percent,
                                    jpeg_subsample=TJSAMP_422)


class PassthroughEncoder(Encoder):
    """An encoder, that just transforms the data from the format accepted by
    encode to the format returned by encoders without touching the data"""

    def encode(self, bytes_used: int) -> bytes:
        """Reads the source data and outputs as bytes"""
        return self.source_details.mmap[:bytes_used]


================================================
FILE: prusa/link/cameras/picamera_driver.py
================================================
"""Contains implementation of a driver for Rpi Cameras"""
import gc
import logging
import select
from time import time
from types import MappingProxyType
from typing import Any, Callable, Dict, Optional

from prusa.connect.printer.camera import Resolution
from prusa.connect.printer.camera_driver import CameraDriver
from prusa.connect.printer.const import (
    CAMERA_WAIT_TIMEOUT,
    CapabilityType,
    NotSupported,
)

from ..util import is_potato_cpu, prctl_name
from . import v4l2
from .encoders import BufferDetails, MJPEGEncoder, get_appropriate_encoder

log = logging.getLogger(__name__)

PICAMERA_SUPPORTED = False
try:
    from libcamera import (  # type: ignore
        Camera,
        CameraManager,
        ControlId,
        FrameBufferAllocator,
        PixelFormat,
        Rectangle,
        Request,
        Size,
        Stream,
        StreamConfiguration,
        StreamFormats,
        StreamRole,
        controls,
    )
except ImportError:
    CameraManager = Camera = StreamConfiguration = Stream = StreamFormats = \
        StreamRole = PixelFormat = Request = Size = FrameBufferAllocator = \
        controls = Rectangle = ControlId = None
else:
    PICAMERA_SUPPORTED = True


PICAMERA_MODELS = {
    "imx219",
    "imx296_mono",
    "imx477_v1",
    "ov5647_noir",
    "imx219_noir",
    "imx378",
    "imx519",
    "ov9281_mono",
    "imx290",
    "imx477",
    "se327m12",
    "imx296",
    "imx477_noir",
    "ov5647",
    "imx708",
    "imx708_noir",
    "imx708_wide",
    "imx708_wide_noir",
}

SUPPORTED_PIXEL_FORMAT = "YUYV"


def param_change(func):
    """Wraps any settings change with a stop and start of the video
    stream, so the camera driver does not return it's busy"""

    def inner(self, new_param):
        # pylint: disable=protected-access
        self.camera.stop()
        self.encoder.stop()
        func(self, new_param)
        self._start()

    return inner


class PiCameraDriver(CameraDriver):
    """A camera driver for RaspberryPi cameras"""

    name = "PiCamera"
    supported = PICAMERA_SUPPORTED
    REQUIRES_SETTINGS: MappingProxyType[str, str] = MappingProxyType({})

    @staticmethod
    def _scan():
        """Scan for Pi Cameras"""
        available = {}

        camera_manager = CameraManager.singleton()
        for camera in camera_manager.cameras:
            model = "unknown"
            for name, value in camera.properties.items():
                if str(name) == "Model":
                    model = value
                    break
            log.debug("picamera found model: %s", model)
            if model in PICAMERA_MODELS:
                available[camera.id] = {
                    "id_string": camera.id,
                    "name": f"RaspberryPi Camera: {model}"}

        return available

    def __init__(self, camera_id: str, config: Dict[str, str],
                 disconnected_cb: Callable[["CameraDriver"], None]) -> None:
        # pylint: disable=duplicate-code
        super().__init__(camera_id, config, disconnected_cb)

        self.camera_manager: CameraManager = CameraManager.singleton()
        self.camera: Optional[Camera] = None
        self.resolution: Optional[Resolution] = None
        self.raw_resolution = None
        self.stream: Optional[Stream] = None
        self.request: Optional[Request] = None
        self.allocator: Optional[FrameBufferAllocator] = None
        self.frame_number = 0
        self.scaler_crop = Rectangle(Size(3200, 2400))

        self.encoder = None

        self.controls_to_set: Dict[ControlId, Any] = {}

    @staticmethod
    def get_resolutions(camera: Camera, stream_role: StreamRole,
                        wanted_pixel_format: Optional[str] = None):
        """Gets the formats and their resolutions for any given camera"""
        resolutions = set()
        camera_config = camera.generate_configuration(
            [stream_role])
        stream_config = camera_config.at(0)
        stream_formats: StreamFormats = stream_config.formats

        for pixel_format in stream_formats.pixel_formats:
            if wanted_pixel_format is not None:
                if str(pixel_format) != wanted_pixel_format:
                    continue
            for resolution in stream_formats.sizes(pixel_format):
                # Ignore resolutions that would need more post-processing
                # as a result of padding to 64 bytes. Docs say 32,
                # but that does not seem to be right. 32 here, means 64 bytes.
                # One for brightness and one for color, two per pixel
                if stream_role != StreamRole.Raw:
                    if resolution.width % 32:
                        continue
                    # Cannot HW encode these, and we don't have the CPU
                    # for it either
                    if is_potato_cpu() and \
                            resolution.width > MJPEGEncoder.WIDTH_LIMIT:
                        continue
                resolutions.add(Resolution(
                    resolution.width, resolution.height))
        return resolutions

    @staticmethod
    def make_camera_configuration(camera, still_resolution: Resolution,
                                  raw_resolution: Resolution,
                                  pixel_format: str):
        """Creates a camera configuration for our specific use case

        Sets the raw sensor resolution, the scaled down output resolution
        and the pixel format for a specified camera

        The buffer counts are hardcoded, getting more of them would
        incentivize the camera stack to pre-fill them which would mean
        we'd get old data from the first couple of them
        """
        camera_configuration = camera.generate_configuration(
            [StreamRole.Raw, StreamRole.StillCapture])

        raw_configuration: StreamConfiguration = camera_configuration.at(0)
        raw_configuration.size = Size(raw_resolution.width,
                                      raw_resolution.height)
        raw_configuration.buffer_count = 0

        still_configuration: StreamConfiguration = camera_configuration.at(1)
        still_configuration.size = Size(still_resolution.width,
                                        still_resolution.height)
        still_configuration.pixel_format = PixelFormat(pixel_format)
        still_configuration.buffer_count = 1

        return camera_configuration

    def _connect(self):
        """Connects to the picamera"""
        for camera in self.camera_manager.cameras:
            if camera.id == self.config["id_string"]:
                self.camera = camera
                break
        if self.camera is None:
            raise RuntimeError("Couldn't find a configured pi camera"
                               f" {self.config['name']} in the connected ones")
        self._capabilities = ({
            CapabilityType.TRIGGER_SCHEME,
            CapabilityType.IMAGING,
            CapabilityType.RESOLUTION,
        })

        if controls.LensPosition in self.camera.controls:
            self._capabilities.add(CapabilityType.FOCUS)
            # Defaults to infinity
            self._config["focus"] = self._config.get("focus", str(0.0))
            self.set_focus(float(self._config["focus"]))

        sensor_resolutions = self.get_resolutions(
            self.camera, StreamRole.Raw)
        self._available_resolutions = self.get_resolutions(
            self.camera, StreamRole.StillCapture, SUPPORTED_PIXEL_FORMAT)

        if not self.available_resolutions or not sensor_resolutions:
            raise NotSupported(
                "Sorry, PrusaLink PiCamera module supports only YUYV 4:2:2. "
                "This camera does not support either that, or something else "
                "is broken")
        self.raw_resolution = sorted(sensor_resolutions)[-1]

        self.camera.acquire()
        self.allocator = FrameBufferAllocator(self.camera)

        initial_resolution = self._get_initial_resolution(
            self._available_resolutions, self._config)
        self._set_resolution(initial_resolution)
        self._config["resolution"] = str(initial_resolution)

        self._start()

    def _start(self):
        """A method to start the camera and the encoder after connecting
        or parameter change"""
        # set controls again
        if controls.AfMode in self.camera.controls:
            self.controls_to_set[controls.AfMode] = \
                controls.AfModeEnum.Manual
        self.controls_to_set[controls.ScalerCrop] = self.scaler_crop

        self.encoder.start()
        self.camera.start()

    @staticmethod
    def _get_scalar_crop(raw_resolution, target_resolution):
        """Figures out how to crop the raw sensor to get the resulting scaled
        image in the correct aspect ratio"""
        raw_aspect_ratio = (raw_resolution.width /
                            raw_resolution.height)
        still_aspect_ratio = (target_resolution.width /
                              target_resolution.height)
        if raw_aspect_ratio > still_aspect_ratio:
            width = int(raw_resolution.height * still_aspect_ratio)
            width_offset = int((raw_resolution.width - width) / 2)

            cropped_size = Size(width, raw_resolution.height)
            scaler_crop = Rectangle(width_offset, 0, cropped_size)

        elif raw_aspect_ratio < still_aspect_ratio:
            height = int(raw_resolution.width / still_aspect_ratio)
            height_offset = int((raw_resolution.height - height) / 2)

            cropped_size = Size(raw_resolution.width, height)
            scaler_crop = Rectangle(0, height_offset, cropped_size)
        else:
            cropped_size = Size(raw_resolution.width, raw_resolution.height)
            scaler_crop = Rectangle(0, 0, cropped_size)
        return scaler_crop

    @param_change
    def set_resolution(self, resolution):
        """Sets the camera resolution"""
        self._set_resolution(resolution)

    def _set_resolution(self, resolution):
        """A way to set the resolution without @param_change"""
        self.allocator.buffers(self.stream).clear()
        self.allocator = None
        self.request = None
        self.stream = None
        gc.collect()

        camera_configuration = self.make_camera_configuration(
            self.camera, resolution, self.raw_resolution,
            SUPPORTED_PIXEL_FORMAT)
        camera_configuration.validate()

        self.scaler_crop = self._get_scalar_crop(
            raw_resolution=self.raw_resolution,
            target_resolution=resolution)

        self.camera.configure(camera_configuration)

        self.stream = camera_configuration.at(1).stream

        # A lot of this can fail, that would hopefully result in another
        # attempt to connect. To see what result codes to expect and stuff,
        # look at picamera2 on github, they do it the more proper way
        gc.collect()
        self.allocator = FrameBufferAllocator(self.camera)
        self.allocator.allocate(self.stream)

        buffer = self.allocator.buffers(self.stream)[0]
        self.request = self.camera.create_request()
        self.request.add_buffer(self.stream, buffer)

        self.encoder = get_appropriate_encoder(
            resolution, v4l2.v4l2_fourcc(*SUPPORTED_PIXEL_FORMAT))

        plane = buffer.planes[0]
        self.encoder.source_details = BufferDetails(
            file_descriptor=plane.fd,
            length=self.stream.configuration.frame_size,
            offset=plane.offset)

        self.encoder.width = resolution.width
        self.encoder.height = resolution.height
        self.encoder.stride = self.stream.configuration.stride

    def _focus_transform(self, value):
        """Transforms the focus value from 0 - 1 to the range
        supported by the camera"""
        min_position = self.camera.controls[controls.LensPosition].min
        max_position = self.camera.controls[controls.LensPosition].max
        position_range = max_position - min_position
        return value * position_range - min_position

    def set_focus(self, focus):
        """Sets the camera resolution"""
        self.controls_to_set[controls.LensPosition] = \
            self._focus_transform(focus)

    def take_a_photo(self):
        """Asks for eight photos but is only interested in the last one"""
        prctl_name()
        log.debug("Taking a photo!")

        self.request.reuse()

        for control_id, value in self.controls_to_set.items():
            self.request.set_control(control_id, value)
        self.controls_to_set.clear()

        self.camera.queue_request(self.request)

        started_at = time()
        while True:
            remaining = started_at + CAMERA_WAIT_TIMEOUT - time()
            if self.request.status == Request.Status.Complete:
                break
            if remaining <= 0:
                raise TimeoutError("Taking a photo timed out")

            # Cannot use returned events for breaking this loop because
            # we would need to handle a negative time remaining as well
            select.select((self.camera_manager.event_fd,), (), (), remaining)

        log.debug("Converting a photo")
        data = self.encoder.encode(self.stream.configuration.frame_size)
        log.debug("Done converting a photo")
        return data

    def _disconnect(self):
        """Disconnects from the camera"""
        if self.camera is None:
            return
        self.camera.stop()
        self.camera.release()


================================================
FILE: prusa/link/cameras/v4l2.py
================================================
# Python bindings for the v4l2 userspace api

# Copyright (C) 1999-2009 the contributors

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# Alternatively you can redistribute this file under the terms of the
# BSD license as stated below:

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 3. The names of its contributors may not be used to endorse or promote
#    products derived from this software without specific prior written
#    permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
Python bindings for the v4l2 userspace api in Linux 2.6.34
"""

# see linux/videodev2.h

# flake8: noqa

import ctypes


_IOC_NRBITS = 8
_IOC_TYPEBITS = 8
_IOC_SIZEBITS = 14
_IOC_DIRBITS = 2

_IOC_NRSHIFT = 0
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS

_IOC_NONE = 0
_IOC_WRITE = 1
_IOC_READ  = 2


def _IOC(dir_, type_, nr, size):
    return (
        ctypes.c_int32(dir_ << _IOC_DIRSHIFT).value |
        ctypes.c_int32(ord(type_) << _IOC_TYPESHIFT).value |
        ctypes.c_int32(nr << _IOC_NRSHIFT).value |
        ctypes.c_int32(size << _IOC_SIZESHIFT).value)


def _IOC_TYPECHECK(t):
    return ctypes.sizeof(t)


def _IO(type_, nr):
    return _IOC(_IOC_NONE, type_, nr, 0)


def _IOW(type_, nr, size):
    return _IOC(_IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))


def _IOR(type_, nr, size):
    return _IOC(_IOC_READ, type_, nr, _IOC_TYPECHECK(size))


def _IOWR(type_, nr, size):
    return _IOC(_IOC_READ | _IOC_WRITE, type_, nr, _IOC_TYPECHECK(size))


#
# type alias
#

enum = ctypes.c_uint
c_int = ctypes.c_int


#
# time
#

class timeval(ctypes.Structure):
    _fields_ = [
        ('secs', ctypes.c_long),
        ('usecs', ctypes.c_long),
    ]


#
# v4l2
#


VIDEO_MAX_FRAME = 32
VIDEO_MAX_PLANES = 8

VID_TYPE_CAPTURE = 1
VID_TYPE_TUNER = 2
VID_TYPE_TELETEXT = 4
VID_TYPE_OVERLAY = 8
VID_TYPE_CHROMAKEY = 16
VID_TYPE_CLIPPING = 32
VID_TYPE_FRAMERAM = 64
VID_TYPE_SCALES	= 128
VID_TYPE_MONOCHROME = 256
VID_TYPE_SUBCAPTURE = 512
VID_TYPE_MPEG_DECODER = 1024
VID_TYPE_MPEG_ENCODER = 2048
VID_TYPE_MJPEG_DECODER = 4096
VID_TYPE_MJPEG_ENCODER = 8192


def v4l2_fourcc(a, b, c, d):
    return ord(a) | (ord(b) << 8) | (ord(c) << 16) | (ord(d) << 24)


def v4l2_fourcc2str(fourcc):
    a = chr(fourcc & 0xFF)
    b = chr((fourcc >> 8) & 0xFF)
    c = chr((fourcc >> 16) & 0xFF)
    d = chr((fourcc >> 24) & 0xFF)
    return ''.join([a, b, c, d])


v4l2_field = enum
(
    V4L2_FIELD_ANY,
    V4L2_FIELD_NONE,
    V4L2_FIELD_TOP,
    V4L2_FIELD_BOTTOM,
    V4L2_FIELD_INTERLACED,
    V4L2_FIELD_SEQ_TB,
    V4L2_FIELD_SEQ_BT,
    V4L2_FIELD_ALTERNATE,
    V4L2_FIELD_INTERLACED_TB,
    V4L2_FIELD_INTERLACED_BT,
) = range(10)


def V4L2_FIELD_HAS_TOP(field):
    return (
	field == V4L2_FIELD_TOP or
	field == V4L2_FIELD_INTERLACED or
	field == V4L2_FIELD_INTERLACED_TB or
	field == V4L2_FIELD_INTERLACED_BT or
	field == V4L2_FIELD_SEQ_TB or
	field == V4L2_FIELD_SEQ_BT)


def V4L2_FIELD_HAS_BOTTOM(field):
    return (
        field == V4L2_FIELD_BOTTOM or
        field == V4L2_FIELD_INTERLACED or
        field == V4L2_FIELD_INTERLACED_TB or
        field == V4L2_FIELD_INTERLACED_BT or
        field == V4L2_FIELD_SEQ_TB or
        field == V4L2_FIELD_SEQ_BT)


def V4L2_FIELD_HAS_BOTH(field):
    return (
        field == V4L2_FIELD_INTERLACED or
        field == V4L2_FIELD_INTERLACED_TB or
        field == V4L2_FIELD_INTERLACED_BT or
        field == V4L2_FIELD_SEQ_TB or
        field == V4L2_FIELD_SEQ_BT)


v4l2_buf_type = enum
(	V4L2_BUF_TYPE_VIDEO_CAPTURE,
	V4L2_BUF_TYPE_VIDEO_OUTPUT,
	V4L2_BUF_TYPE_VIDEO_OVERLAY,
	V4L2_BUF_TYPE_VBI_CAPTURE,
	V4L2_BUF_TYPE_VBI_OUTPUT,
	V4L2_BUF_TYPE_SLICED_VBI_CAPTURE,
	V4L2_BUF_TYPE_SLICED_VBI_OUTPUT,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY,
	V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
	V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
	V4L2_BUF_TYPE_SDR_CAPTURE,
	V4L2_BUF_TYPE_SDR_OUTPUT,
	V4L2_BUF_TYPE_META_CAPTURE,
	V4L2_BUF_TYPE_PRIVATE, # Deprecated, do not use.
) = list(range(1, 14)) + [0x80]


v4l2_ctrl_type = enum
(
    V4L2_CTRL_TYPE_INTEGER,
    V4L2_CTRL_TYPE_BOOLEAN,
    V4L2_CTRL_TYPE_MENU,
    V4L2_CTRL_TYPE_BUTTON,
    V4L2_CTRL_TYPE_INTEGER64,
    V4L2_CTRL_TYPE_CTRL_CLASS,
    V4L2_CTRL_TYPE_STRING,
    V4L2_CTRL_TYPE_BITMASK,
	V4L2_CTRL_TYPE_INTEGER_MENU,
) = range(1, 10)


# Compound types are >= 0x0100
V4L2_CTRL_COMPOUND_TYPES = 0x0100
V4L2_CTRL_TYPE_U8	     = 0x0100
V4L2_CTRL_TYPE_U16	     = 0x0101
V4L2_CTRL_TYPE_U32	     = 0x0102


v4l2_tuner_type = enum
(
    V4L2_TUNER_RADIO,
    V4L2_TUNER_ANALOG_TV,
    V4L2_TUNER_DIGITAL_TV,
) = range(1, 4)


v4l2_memory = enum
(
    V4L2_MEMORY_MMAP,
    V4L2_MEMORY_USERPTR,
    V4L2_MEMORY_OVERLAY,
    V4L2_MEMORY_DMABUF,
) = range(1, 5)


v4l2_colorspace = enum
(
    #Default colorspace, i.e. let the driver figure it out.
    #Can only be used with video capture.
	V4L2_COLORSPACE_DEFAULT,
	# SMPTE 170M: used for broadcast NTSC/PAL SDTV
	V4L2_COLORSPACE_SMPTE170M,
	# Obsolete pre-1998 SMPTE 240M HDTV standard, superseded by Rec 709
	V4L2_COLORSPACE_SMPTE240M,
	# Rec.709: used for HDTV
	V4L2_COLORSPACE_REC709,
    #Deprecated, do not use. No driver will ever return this. This was
    #based on a misunderstanding of the bt878 datasheet.
	V4L2_COLORSPACE_BT878,
    #NTSC 1953 colorspace. This only makes sense when dealing with
    #really, really old NTSC recordings. Superseded by SMPTE 170M.
	V4L2_COLORSPACE_470_SYSTEM_M,
    #EBU Tech 3213 PAL/SECAM colorspace. This only makes sense when
    #dealing with really old PAL/SECAM recordings. Superseded by
    #SMPTE 170M.
	V4L2_COLORSPACE_470_SYSTEM_BG,
    #Effectively shorthand for V4L2_COLORSPACE_SRGB, V4L2_YCBCR_ENC_601
    #and V4L2_QUANTIZATION_FULL_RANGE. To be used for (Motion-)JPEG.
	V4L2_COLORSPACE_JPEG,
	# For RGB colorspaces such as produces by most webcams.
	V4L2_COLORSPACE_SRGB,
	# AdobeRGB colorspace
	V4L2_COLORSPACE_ADOBERGB,
	# BT.2020 colorspace, used for UHDTV.
	V4L2_COLORSPACE_BT2020,
	# Raw colorspace: for RAW unprocessed images
	V4L2_COLORSPACE_RAW,
	# DCI-P3 colorspace, used by cinema projectors
	V4L2_COLORSPACE_DCI_P3,
) = range(0, 13)


v4l2_priority = enum
(
    V4L2_PRIORITY_UNSET,
    V4L2_PRIORITY_BACKGROUND,
    V4L2_PRIORITY_INTERACTIVE,
    V4L2_PRIORITY_RECORD,
    V4L2_PRIORITY_DEFAULT,
) = list(range(0, 4)) + [2]


class v4l2_rect(ctypes.Structure):
    _fields_ = [
        ('left', ctypes.c_int32),
        ('top', ctypes.c_int32),
        ('width', ctypes.c_int32),
        ('height', ctypes.c_int32),
    ]


class v4l2_fract(ctypes.Structure):
    _fields_ = [
        ('numerator', ctypes.c_uint32),
        ('denominator', ctypes.c_uint32),
    ]


#
# Driver capabilities
#

class v4l2_capability(ctypes.Structure):
    _fields_ = [
        ('driver', ctypes.c_char * 16),
        ('card', ctypes.c_char * 32),
        ('bus_info', ctypes.c_char * 32),
        ('version', ctypes.c_uint32),
        ('capabilities', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


#
# Values for 'capabilities' field
#

V4L2_CAP_VIDEO_CAPTURE        = 0x00000001  # Is a video capture device
V4L2_CAP_VIDEO_OUTPUT         = 0x00000002  # Is a video output device
V4L2_CAP_VIDEO_OVERLAY        = 0x00000004  # Can do video overlay
V4L2_CAP_VBI_CAPTURE          = 0x00000010  # Is a raw VBI capture device
V4L2_CAP_VBI_OUTPUT           = 0x00000020  # Is a raw VBI output device
V4L2_CAP_SLICED_VBI_CAPTURE   = 0x00000040  # Is a sliced VBI capture device
V4L2_CAP_SLICED_VBI_OUTPUT    = 0x00000080  # Is a sliced VBI output device
V4L2_CAP_RDS_CAPTURE          = 0x00000100  # RDS data capture
V4L2_CAP_VIDEO_OUTPUT_OVERLAY = 0x00000200  # Can do video output overlay
V4L2_CAP_HW_FREQ_SEEK         = 0x00000400  # Can do hardware frequency seek
V4L2_CAP_RDS_OUTPUT           = 0x00000800  # Is an RDS encoder
V4L2_CAP_VIDEO_CAPTURE_MPLANE =	0x00001000  # Is a video capture device that supports multiplanar formats
V4L2_CAP_VIDEO_OUTPUT_MPLANE  = 0x00002000  # Is a video output device that supports multiplanar formats
V4L2_CAP_VIDEO_M2M_MPLANE     = 0x00004000  # Is a video mem-to-mem device that supports multiplanar formats
V4L2_CAP_VIDEO_M2M            = 0x00008000  # Is a video mem-to-mem device
V4L2_CAP_TUNER                = 0x00010000  # has a tuner
V4L2_CAP_AUDIO                = 0x00020000  # has audio support
V4L2_CAP_RADIO                = 0x00040000  # is a radio device
V4L2_CAP_MODULATOR            = 0x00080000  # has a modulator
V4L2_CAP_SDR_CAPTURE		  = 0x00100000  # Is a SDR capture device
V4L2_CAP_EXT_PIX_FORMAT		  = 0x00200000  # Supports the extended pixel format
V4L2_CAP_SDR_OUTPUT		      = 0x00400000  # Is a SDR output device
V4L2_CAP_META_CAPTURE		  = 0x00800000  # Is a metadata capture device
V4L2_CAP_READWRITE            = 0x01000000  # read/write systemcalls
V4L2_CAP_ASYNCIO              = 0x02000000  # async I/O
V4L2_CAP_STREAMING            = 0x04000000  # streaming I/O ioctls
V4L2_CAP_TOUCH                = 0x10000000  # Is a touch device
V4L2_CAP_DEVICE_CAPS          = 0x80000000  # sets device capabilities field


#
# Video image format
#

class v4l2_pix_format(ctypes.Structure):
    _fields_ = [
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
        ('pixelformat', ctypes.c_uint32),
        ('field', v4l2_field),
        ('bytesperline', ctypes.c_uint32),
        ('sizeimage', ctypes.c_uint32),
        ('colorspace', v4l2_colorspace),
        ('priv', ctypes.c_uint32),
    ]

# RGB formats
V4L2_PIX_FMT_RGB332 = v4l2_fourcc('R', 'G', 'B', '1')
V4L2_PIX_FMT_RGB444 = v4l2_fourcc('R', '4', '4', '4')
V4L2_PIX_FMT_RGB555 = v4l2_fourcc('R', 'G', 'B', 'O')
V4L2_PIX_FMT_RGB565 = v4l2_fourcc('R', 'G', 'B', 'P')
V4L2_PIX_FMT_RGB555X = v4l2_fourcc('R', 'G', 'B', 'Q')
V4L2_PIX_FMT_RGB565X = v4l2_fourcc('R', 'G', 'B', 'R')
V4L2_PIX_FMT_BGR24 = v4l2_fourcc('B', 'G', 'R', '3')
V4L2_PIX_FMT_RGB24 = v4l2_fourcc('R', 'G', 'B', '3')
V4L2_PIX_FMT_BGR32 = v4l2_fourcc('B', 'G', 'R', '4')
V4L2_PIX_FMT_RGB32 = v4l2_fourcc('R', 'G', 'B', '4')
V4L2_PIX_FMT_RGBX32 = v4l2_fourcc('X', 'B', '2', '4')
V4L2_PIX_FMT_XRGB32 = v4l2_fourcc('B', 'X', '2', '4')
V4L2_PIX_FMT_RGBA32 = v4l2_fourcc('A', 'B', '2', '4')

# Grey formats
V4L2_PIX_FMT_GREY = v4l2_fourcc('G', 'R', 'E', 'Y')
V4L2_PIX_FMT_Y10 =  v4l2_fourcc('Y', '1', '0', ' ')
V4L2_PIX_FMT_Y16 = v4l2_fourcc('Y', '1', '6', ' ')

# Palette formats
V4L2_PIX_FMT_PAL8 = v4l2_fourcc('P', 'A', 'L', '8')

# Luminance+Chrominance formats
V4L2_PIX_FMT_YVU410 = v4l2_fourcc('Y', 'V', 'U', '9')
V4L2_PIX_FMT_YVU420 = v4l2_fourcc('Y', 'V', '1', '2')
V4L2_PIX_FMT_YUYV = v4l2_fourcc('Y', 'U', 'Y', 'V')
V4L2_PIX_FMT_YYUV = v4l2_fourcc('Y', 'Y', 'U', 'V')
V4L2_PIX_FMT_YVYU = v4l2_fourcc('Y', 'V', 'Y', 'U')
V4L2_PIX_FMT_UYVY = v4l2_fourcc('U', 'Y', 'V', 'Y')
V4L2_PIX_FMT_VYUY = v4l2_fourcc('V', 'Y', 'U', 'Y')
V4L2_PIX_FMT_YUV422P = v4l2_fourcc('4', '2', '2', 'P')
V4L2_PIX_FMT_YUV411P = v4l2_fourcc('4', '1', '1', 'P')
V4L2_PIX_FMT_Y41P = v4l2_fourcc('Y', '4', '1', 'P')
V4L2_PIX_FMT_YUV444 = v4l2_fourcc('Y', '4', '4', '4')
V4L2_PIX_FMT_YUV555 = v4l2_fourcc('Y', 'U', 'V', 'O')
V4L2_PIX_FMT_YUV565 = v4l2_fourcc('Y', 'U', 'V', 'P')
V4L2_PIX_FMT_YUV32 = v4l2_fourcc('Y', 'U', 'V', '4')
V4L2_PIX_FMT_YUV410 = v4l2_fourcc('Y', 'U', 'V', '9')
V4L2_PIX_FMT_YUV420 = v4l2_fourcc('Y', 'U', '1', '2')
V4L2_PIX_FMT_HI240 = v4l2_fourcc('H', 'I', '2', '4')
V4L2_PIX_FMT_HM12 = v4l2_fourcc('H', 'M', '1', '2')

# two planes -- one Y, one Cr + Cb interleaved
V4L2_PIX_FMT_NV12 = v4l2_fourcc('N', 'V', '1', '2')
V4L2_PIX_FMT_NV21 = v4l2_fourcc('N', 'V', '2', '1')
V4L2_PIX_FMT_NV16 = v4l2_fourcc('N', 'V', '1', '6')
V4L2_PIX_FMT_NV61 = v4l2_fourcc('N', 'V', '6', '1')

# Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm
V4L2_PIX_FMT_SBGGR8 = v4l2_fourcc('B', 'A', '8', '1')
V4L2_PIX_FMT_SGBRG8 = v4l2_fourcc('G', 'B', 'R', 'G')
V4L2_PIX_FMT_SGRBG8 = v4l2_fourcc('G', 'R', 'B', 'G')
V4L2_PIX_FMT_SRGGB8 = v4l2_fourcc('R', 'G', 'G', 'B')
V4L2_PIX_FMT_SBGGR10 = v4l2_fourcc('B', 'G', '1', '0')
V4L2_PIX_FMT_SGBRG10 = v4l2_fourcc('G', 'B', '1', '0')
V4L2_PIX_FMT_SGRBG10 = v4l2_fourcc('B', 'A', '1', '0')
V4L2_PIX_FMT_SRGGB10 = v4l2_fourcc('R', 'G', '1', '0')
V4L2_PIX_FMT_SGRBG10DPCM8 = v4l2_fourcc('B', 'D', '1', '0')
V4L2_PIX_FMT_SBGGR16 = v4l2_fourcc('B', 'Y', 'R', '2')

# compressed formats
V4L2_PIX_FMT_MJPEG = v4l2_fourcc('M', 'J', 'P', 'G')
V4L2_PIX_FMT_JPEG = v4l2_fourcc('J', 'P', 'E', 'G')
V4L2_PIX_FMT_DV = v4l2_fourcc('d', 'v', 's', 'd')
V4L2_PIX_FMT_MPEG = v4l2_fourcc('M', 'P', 'E', 'G')
V4L2_PIX_FMT_H264 = v4l2_fourcc('H', '2', '6', '4')

# Vendor-specific formats
V4L2_PIX_FMT_CPIA1 = v4l2_fourcc('C', 'P', 'I', 'A')
V4L2_PIX_FMT_WNVA = v4l2_fourcc('W', 'N', 'V', 'A')
V4L2_PIX_FMT_SN9C10X = v4l2_fourcc('S', '9', '1', '0')
V4L2_PIX_FMT_SN9C20X_I420 = v4l2_fourcc('S', '9', '2', '0')
V4L2_PIX_FMT_PWC1 = v4l2_fourcc('P', 'W', 'C', '1')
V4L2_PIX_FMT_PWC2 = v4l2_fourcc('P', 'W', 'C', '2')
V4L2_PIX_FMT_ET61X251 = v4l2_fourcc('E', '6', '2', '5')
V4L2_PIX_FMT_SPCA501 = v4l2_fourcc('S', '5', '0', '1')
V4L2_PIX_FMT_SPCA505 = v4l2_fourcc('S', '5', '0', '5')
V4L2_PIX_FMT_SPCA508 = v4l2_fourcc('S', '5', '0', '8')
V4L2_PIX_FMT_SPCA561 = v4l2_fourcc('S', '5', '6', '1')
V4L2_PIX_FMT_PAC207 = v4l2_fourcc('P', '2', '0', '7')
V4L2_PIX_FMT_MR97310A = v4l2_fourcc('M', '3', '1', '0')
V4L2_PIX_FMT_SN9C2028 = v4l2_fourcc('S', 'O', 'N', 'X')
V4L2_PIX_FMT_SQ905C = v4l2_fourcc('9', '0', '5', 'C')
V4L2_PIX_FMT_PJPG = v4l2_fourcc('P', 'J', 'P', 'G')
V4L2_PIX_FMT_OV511 = v4l2_fourcc('O', '5', '1', '1')
V4L2_PIX_FMT_OV518 = v4l2_fourcc('O', '5', '1', '8')
V4L2_PIX_FMT_STV0680 = v4l2_fourcc('S', '6', '8', '0')


#
# Format enumeration
#

class v4l2_fmtdesc(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('type', ctypes.c_int),
        ('flags', ctypes.c_uint32),
        ('description', ctypes.c_char * 32),
        ('pixelformat', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]

V4L2_FMT_FLAG_COMPRESSED = 0x0001
V4L2_FMT_FLAG_EMULATED = 0x0002


#
# Experimental frame size and frame rate enumeration
#

v4l2_frmsizetypes = enum
(
    V4L2_FRMSIZE_TYPE_DISCRETE,
    V4L2_FRMSIZE_TYPE_CONTINUOUS,
    V4L2_FRMSIZE_TYPE_STEPWISE,
) = range(1, 4)


class v4l2_frmsize_discrete(ctypes.Structure):
    _fields_ = [
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
    ]


class v4l2_frmsize_stepwise(ctypes.Structure):
    _fields_ = [
        ('min_width', ctypes.c_uint32),
        ('min_height', ctypes.c_uint32),
        ('step_width', ctypes.c_uint32),
        ('min_height', ctypes.c_uint32),
        ('max_height', ctypes.c_uint32),
        ('step_height', ctypes.c_uint32),
    ]


class v4l2_frmsizeenum(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('discrete', v4l2_frmsize_discrete),
            ('stepwise', v4l2_frmsize_stepwise),
        ]

    _fields_ = [
        ('index', ctypes.c_uint32),
        ('pixel_format', ctypes.c_uint32),
        ('type', ctypes.c_uint32),
        ('_u', _u),
        ('reserved', ctypes.c_uint32 * 2)
    ]

    _anonymous_ = ('_u',)


#
# Frame rate enumeration
#

v4l2_frmivaltypes = enum
(
    V4L2_FRMIVAL_TYPE_DISCRETE,
    V4L2_FRMIVAL_TYPE_CONTINUOUS,
    V4L2_FRMIVAL_TYPE_STEPWISE,
) = range(1, 4)


class v4l2_frmival_stepwise(ctypes.Structure):
    _fields_ = [
        ('min', v4l2_fract),
        ('max', v4l2_fract),
        ('step', v4l2_fract),
    ]


class v4l2_frmivalenum(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('discrete', v4l2_fract),
            ('stepwise', v4l2_frmival_stepwise),
        ]

    _fields_ = [
        ('index', ctypes.c_uint32),
        ('pixel_format', ctypes.c_uint32),
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
        ('type', ctypes.c_uint32),
        ('_u', _u),
        ('reserved', ctypes.c_uint32 * 2),
    ]

    _anonymous_ = ('_u',)


#
# Timecode
#

class v4l2_timecode(ctypes.Structure):
    _fields_ = [
        ('type', ctypes.c_uint32),
        ('flags', ctypes.c_uint32),
        ('frames', ctypes.c_uint8),
        ('seconds', ctypes.c_uint8),
        ('minutes', ctypes.c_uint8),
        ('hours', ctypes.c_uint8),
        ('userbits', ctypes.c_uint8 * 4),
    ]


V4L2_TC_TYPE_24FPS = 1
V4L2_TC_TYPE_25FPS = 2
V4L2_TC_TYPE_30FPS = 3
V4L2_TC_TYPE_50FPS = 4
V4L2_TC_TYPE_60FPS = 5

V4L2_TC_FLAG_DROPFRAME = 0x0001
V4L2_TC_FLAG_COLORFRAME = 0x0002
V4L2_TC_USERBITS_field = 0x000C
V4L2_TC_USERBITS_USERDEFINED = 0x0000
V4L2_TC_USERBITS_8BITCHARS = 0x0008


class v4l2_jpegcompression(ctypes.Structure):
    _fields_ = [
        ('quality', ctypes.c_int),
        ('APPn', ctypes.c_int),
        ('APP_len', ctypes.c_int),
        ('APP_data', ctypes.c_char * 60),
        ('COM_len', ctypes.c_int),
        ('COM_data', ctypes.c_char * 60),
        ('jpeg_markers', ctypes.c_uint32),
    ]


V4L2_JPEG_MARKER_DHT = 1 << 3
V4L2_JPEG_MARKER_DQT = 1 << 4
V4L2_JPEG_MARKER_DRI = 1 << 5
V4L2_JPEG_MARKER_COM = 1 << 6
V4L2_JPEG_MARKER_APP = 1 << 7


#
# Memory-mapping buffers
#

# https://www.kernel.org/doc/html/v5.10/userspace-api/media/v4l/buffer.html#struct-v4l2-plane
class v4l2_plane(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [("mem_offset", ctypes.c_uint32),
                    ("userptr", ctypes.c_ulong),
                    ("fd", ctypes.c_int32)]
    _fields_ = [
                ('bytesused', ctypes.c_uint32),
                ('length', ctypes.c_uint32),
                ('m', _u),
                ('data_offset',  ctypes.c_uint32),
                ('reserved', ctypes.c_uint32 * 11)
            ]

class v4l2_requestbuffers(ctypes.Structure):
    _fields_ = [
        ('count', ctypes.c_uint32),
        ('type', v4l2_buf_type),
        ('memory', v4l2_memory),
        ('reserved', ctypes.c_uint32 * 2),
    ]


class v4l2_buffer(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('offset', ctypes.c_uint32),
            ('userptr', ctypes.c_ulong),
            ('planes', ctypes.POINTER(v4l2_plane)),
            ('fd', ctypes.c_int32)
        ]

    _fields_ = [
        ('index', ctypes.c_uint32),
        ('type', v4l2_buf_type),
        ('bytesused', ctypes.c_uint32),
        ('flags', ctypes.c_uint32),
        ('field', v4l2_field),
        ('timestamp', timeval),
        ('timecode', v4l2_timecode),
        ('sequence', ctypes.c_uint32),
        ('memory', v4l2_memory),
        ('m', _u),
        ('length', ctypes.c_uint32),
        ('input', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32),
    ]


V4L2_BUF_FLAG_MAPPED = 0x0001
V4L2_BUF_FLAG_QUEUED = 0x0002
V4L2_BUF_FLAG_DONE = 0x0004
V4L2_BUF_FLAG_KEYFRAME = 0x0008
V4L2_BUF_FLAG_PFRAME = 0x0010
V4L2_BUF_FLAG_BFRAME = 0x0020
V4L2_BUF_FLAG_TIMECODE = 0x0100
V4L2_BUF_FLAG_INPUT = 0x0200


#
# Overlay preview
#

class v4l2_framebuffer(ctypes.Structure):
    _fields_ = [
        ('capability', ctypes.c_uint32),
        ('flags', ctypes.c_uint32),
        ('base', ctypes.c_void_p),
        ('fmt', v4l2_pix_format),
    ]

V4L2_FBUF_CAP_EXTERNOVERLAY = 0x0001
V4L2_FBUF_CAP_CHROMAKEY	= 0x0002
V4L2_FBUF_CAP_LIST_CLIPPING = 0x0004
V4L2_FBUF_CAP_BITMAP_CLIPPING = 0x0008
V4L2_FBUF_CAP_LOCAL_ALPHA = 0x0010
V4L2_FBUF_CAP_GLOBAL_ALPHA = 0x0020
V4L2_FBUF_CAP_LOCAL_INV_ALPHA = 0x0040
V4L2_FBUF_CAP_SRC_CHROMAKEY = 0x0080

V4L2_FBUF_FLAG_PRIMARY = 0x0001
V4L2_FBUF_FLAG_OVERLAY = 0x0002
V4L2_FBUF_FLAG_CHROMAKEY = 0x0004
V4L2_FBUF_FLAG_LOCAL_ALPHA = 0x0008
V4L2_FBUF_FLAG_GLOBAL_ALPHA = 0x0010
V4L2_FBUF_FLAG_LOCAL_INV_ALPHA = 0x0020
V4L2_FBUF_FLAG_SRC_CHROMAKEY = 0x0040


class v4l2_clip(ctypes.Structure):
    pass
v4l2_clip._fields_ = [
    ('c', v4l2_rect),
    ('next', ctypes.POINTER(v4l2_clip)),
]


class v4l2_window(ctypes.Structure):
    _fields_ = [
        ('w', v4l2_rect),
        ('field', v4l2_field),
        ('chromakey', ctypes.c_uint32),
        ('clips', ctypes.POINTER(v4l2_clip)),
        ('clipcount', ctypes.c_uint32),
        ('bitmap', ctypes.c_void_p),
        ('global_alpha', ctypes.c_uint8),
    ]


#
# Capture parameters
#

class v4l2_captureparm(ctypes.Structure):
    _fields_ = [
        ('capability', ctypes.c_uint32),
        ('capturemode', ctypes.c_uint32),
        ('timeperframe', v4l2_fract),
        ('extendedmode', ctypes.c_uint32),
        ('readbuffers', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


V4L2_MODE_HIGHQUALITY = 0x0001
V4L2_CAP_TIMEPERFRAME = 0x1000


class v4l2_outputparm(ctypes.Structure):
    _fields_ = [
        ('capability', ctypes.c_uint32),
        ('outputmode', ctypes.c_uint32),
        ('timeperframe', v4l2_fract),
        ('extendedmode', ctypes.c_uint32),
        ('writebuffers', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


#
# Input image cropping
#

class v4l2_cropcap(ctypes.Structure):
    _fields_ = [
        ('type', v4l2_buf_type),
        ('bounds', v4l2_rect),
        ('defrect', v4l2_rect),
        ('pixelaspect', v4l2_fract),
    ]


class v4l2_crop(ctypes.Structure):
    _fields_ = [
        ('type', ctypes.c_int),
        ('c', v4l2_rect),
    ]


#
# Analog video standard
#

v4l2_std_id = ctypes.c_uint64


V4L2_STD_PAL_B = 0x00000001
V4L2_STD_PAL_B1 = 0x00000002
V4L2_STD_PAL_G = 0x00000004
V4L2_STD_PAL_H = 0x00000008
V4L2_STD_PAL_I = 0x00000010
V4L2_STD_PAL_D = 0x00000020
V4L2_STD_PAL_D1 = 0x00000040
V4L2_STD_PAL_K = 0x00000080

V4L2_STD_PAL_M = 0x00000100
V4L2_STD_PAL_N = 0x00000200
V4L2_STD_PAL_Nc = 0x00000400
V4L2_STD_PAL_60 = 0x00000800

V4L2_STD_NTSC_M = 0x00001000
V4L2_STD_NTSC_M_JP = 0x00002000
V4L2_STD_NTSC_443 = 0x00004000
V4L2_STD_NTSC_M_KR = 0x00008000

V4L2_STD_SECAM_B = 0x00010000
V4L2_STD_SECAM_D = 0x00020000
V4L2_STD_SECAM_G = 0x00040000
V4L2_STD_SECAM_H = 0x00080000
V4L2_STD_SECAM_K = 0x00100000
V4L2_STD_SECAM_K1 = 0x00200000
V4L2_STD_SECAM_L = 0x00400000
V4L2_STD_SECAM_LC = 0x00800000

V4L2_STD_ATSC_8_VSB = 0x01000000
V4L2_STD_ATSC_16_VSB = 0x02000000


# some common needed stuff
V4L2_STD_PAL_BG = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_PAL_G)
V4L2_STD_PAL_DK = (V4L2_STD_PAL_D | V4L2_STD_PAL_D1 | V4L2_STD_PAL_K)
V4L2_STD_PAL = (V4L2_STD_PAL_BG | V4L2_STD_PAL_DK | V4L2_STD_PAL_H | V4L2_STD_PAL_I)
V4L2_STD_NTSC = (V4L2_STD_NTSC_M | V4L2_STD_NTSC_M_JP | V4L2_STD_NTSC_M_KR)
V4L2_STD_SECAM_DK = (V4L2_STD_SECAM_D | V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1)
V4L2_STD_SECAM = (V4L2_STD_SECAM_B | V4L2_STD_SECAM_G | V4L2_STD_SECAM_H | V4L2_STD_SECAM_DK | V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)

V4L2_STD_525_60 = (V4L2_STD_PAL_M | V4L2_STD_PAL_60 | V4L2_STD_NTSC | V4L2_STD_NTSC_443)
V4L2_STD_625_50 = (V4L2_STD_PAL | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_SECAM)
V4L2_STD_ATSC = (V4L2_STD_ATSC_8_VSB | V4L2_STD_ATSC_16_VSB)

V4L2_STD_UNKNOWN = 0
V4L2_STD_ALL = (V4L2_STD_525_60 | V4L2_STD_625_50)

# some merged standards
V4L2_STD_MN = (V4L2_STD_PAL_M | V4L2_STD_PAL_N | V4L2_STD_PAL_Nc | V4L2_STD_NTSC)
V4L2_STD_B = (V4L2_STD_PAL_B | V4L2_STD_PAL_B1 | V4L2_STD_SECAM_B)
V4L2_STD_GH = (V4L2_STD_PAL_G | V4L2_STD_PAL_H|V4L2_STD_SECAM_G | V4L2_STD_SECAM_H)
V4L2_STD_DK = (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK)


class v4l2_standard(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('id', v4l2_std_id),
        ('name', ctypes.c_char * 24),
        ('frameperiod', v4l2_fract),
        ('framelines', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


#
# Video timings dv preset
#

class v4l2_dv_preset(ctypes.Structure):
    _fields_ = [
        ('preset', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4)
    ]


#
# DV preset enumeration
#

class v4l2_dv_enum_preset(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('preset', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]

#
# DV preset values
#

V4L2_DV_INVALID = 0
V4L2_DV_480P59_94 = 1
V4L2_DV_576P50 = 2
V4L2_DV_720P24 = 3
V4L2_DV_720P25 = 4
V4L2_DV_720P30 = 5
V4L2_DV_720P50 = 6
V4L2_DV_720P59_94 = 7
V4L2_DV_720P60 = 8
V4L2_DV_1080I29_97 = 9
V4L2_DV_1080I30	= 10
V4L2_DV_1080I25	= 11
V4L2_DV_1080I50	= 12
V4L2_DV_1080I60	= 13
V4L2_DV_1080P24	= 14
V4L2_DV_1080P25	= 15
V4L2_DV_1080P30	= 16
V4L2_DV_1080P50	= 17
V4L2_DV_1080P60	= 18


#
# DV BT timings
#

class v4l2_bt_timings(ctypes.Structure):
    _fields_ = [
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
        ('interlaced', ctypes.c_uint32),
        ('polarities', ctypes.c_uint32),
        ('pixelclock', ctypes.c_uint64),
        ('hfrontporch', ctypes.c_uint32),
        ('hsync', ctypes.c_uint32),
        ('hbackporch', ctypes.c_uint32),
        ('vfrontporch', ctypes.c_uint32),
        ('vsync', ctypes.c_uint32),
        ('vbackporch', ctypes.c_uint32),
        ('il_vfrontporch', ctypes.c_uint32),
        ('il_vsync', ctypes.c_uint32),
        ('il_vbackporch', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 16),
    ]

    _pack_ = True

# Interlaced or progressive format
V4L2_DV_PROGRESSIVE = 0
V4L2_DV_INTERLACED = 1

# Polarities. If bit is not set, it is assumed to be negative polarity
V4L2_DV_VSYNC_POS_POL = 0x00000001
V4L2_DV_HSYNC_POS_POL = 0x00000002


class v4l2_dv_timings(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('bt', v4l2_bt_timings),
            ('reserved', ctypes.c_uint32 * 32),
        ]

    _fields_ = [
        ('type', ctypes.c_uint32),
        ('_u', _u),
    ]

    _anonymous_ = ('_u',)
    _pack_ = True


# Values for the type field
V4L2_DV_BT_656_1120 = 0


#
# Video inputs
#

class v4l2_input(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('type', ctypes.c_uint32),
        ('audioset', ctypes.c_uint32),
        ('tuner', ctypes.c_uint32),
        ('std', v4l2_std_id),
        ('status', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


V4L2_INPUT_TYPE_TUNER = 1
V4L2_INPUT_TYPE_CAMERA = 2

V4L2_IN_ST_NO_POWER = 0x00000001
V4L2_IN_ST_NO_SIGNAL = 0x00000002
V4L2_IN_ST_NO_COLOR = 0x00000004

V4L2_IN_ST_HFLIP = 0x00000010
V4L2_IN_ST_VFLIP = 0x00000020

V4L2_IN_ST_NO_H_LOCK = 0x00000100
V4L2_IN_ST_COLOR_KILL = 0x00000200

V4L2_IN_ST_NO_SYNC = 0x00010000
V4L2_IN_ST_NO_EQU = 0x00020000
V4L2_IN_ST_NO_CARRIER = 0x00040000

V4L2_IN_ST_MACROVISION = 0x01000000
V4L2_IN_ST_NO_ACCESS = 0x02000000
V4L2_IN_ST_VTR = 0x04000000

V4L2_IN_CAP_PRESETS = 0x00000001
V4L2_IN_CAP_CUSTOM_TIMINGS = 0x00000002
V4L2_IN_CAP_STD = 0x00000004

#
# Video outputs
#

class v4l2_output(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('type', ctypes.c_uint32),
        ('audioset', ctypes.c_uint32),
        ('modulator', ctypes.c_uint32),
        ('std', v4l2_std_id),
        ('reserved', ctypes.c_uint32 * 4),
    ]


V4L2_OUTPUT_TYPE_MODULATOR = 1
V4L2_OUTPUT_TYPE_ANALOG	= 2
V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY = 3

V4L2_OUT_CAP_PRESETS = 0x00000001
V4L2_OUT_CAP_CUSTOM_TIMINGS = 0x00000002
V4L2_OUT_CAP_STD = 0x00000004

#
# Controls
#

class v4l2_control(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint32),
        ('value', ctypes.c_int32),
    ]


class v4l2_ext_control(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('value', ctypes.c_int32),
            ('value64', ctypes.c_int64),
            ('reserved', ctypes.c_void_p),
        ]

    _fields_ = [
        ('id', ctypes.c_uint32),
        ('reserved2', ctypes.c_uint32 * 2),
        ('_u', _u)
    ]

    _anonymous_ = ('_u',)
    _pack_ = True


class v4l2_ext_controls(ctypes.Structure):
    _fields_ = [
        ('ctrl_class', ctypes.c_uint32),
        ('count', ctypes.c_uint32),
        ('error_idx', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
        ('controls', ctypes.POINTER(v4l2_ext_control)),
    ]


V4L2_CTRL_CLASS_USER = 0x00980000
V4L2_CTRL_CLASS_MPEG = 0x00990000
V4L2_CTRL_CLASS_CAMERA = 0x009a0000
V4L2_CTRL_CLASS_FM_TX = 0x009b0000


def V4L2_CTRL_ID_MASK():
    return 0x0fffffff


def V4L2_CTRL_ID2CLASS(id_):
    return id_ & 0x0fff0000 # unsigned long


def V4L2_CTRL_DRIVER_PRIV(id_):
    return (id_ & 0xffff) >= 0x1000


class v4l2_queryctrl(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint32),
        ('type', v4l2_ctrl_type),
        ('name', ctypes.c_char * 32),
        ('minimum', ctypes.c_int32),
        ('maximum', ctypes.c_int32),
        ('step', ctypes.c_int32),
        ('default_value', ctypes.c_int32),
        ('flags', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


class v4l2_querymenu(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint32),
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('reserved', ctypes.c_uint32),
    ]


NONE = 0x0000
V4L2_CTRL_FLAG_DISABLED = 0x0001
V4L2_CTRL_FLAG_GRABBED = 0x0002
V4L2_CTRL_FLAG_READ_ONLY = 0x0004
V4L2_CTRL_FLAG_UPDATE = 0x0008
V4L2_CTRL_FLAG_INACTIVE = 0x0010
V4L2_CTRL_FLAG_SLIDER = 0x0020
V4L2_CTRL_FLAG_WRITE_ONLY = 0x0040
V4L2_CTRL_FLAG_VOLATILE = 0x0080
V4L2_CTRL_FLAG_HAS_PAYLOAD = 0x0100
V4L2_CTRL_FLAG_EXECUTE_ON_WRITE = 0x0200
V4L2_CTRL_FLAG_MODIFY_LAYOUT = 0x0400
V4L2_CTRL_FLAG_NEXT_CTRL = 0x80000000
V4L2_CTRL_FLAG_NEXT_COMPOUND = 0x40000000

V4L2_CID_BASE = V4L2_CTRL_CLASS_USER | 0x900
V4L2_CID_USER_BASE = V4L2_CID_BASE
V4L2_CID_PRIVATE_BASE = 0x08000000

V4L2_CID_USER_CLASS = V4L2_CTRL_CLASS_USER | 1
V4L2_CID_BRIGHTNESS = V4L2_CID_BASE + 0
V4L2_CID_CONTRAST = V4L2_CID_BASE + 1
V4L2_CID_SATURATION = V4L2_CID_BASE + 2
V4L2_CID_HUE = V4L2_CID_BASE + 3
V4L2_CID_AUDIO_VOLUME = V4L2_CID_BASE + 5
V4L2_CID_AUDIO_BALANCE = V4L2_CID_BASE + 6
V4L2_CID_AUDIO_BASS = V4L2_CID_BASE + 7
V4L2_CID_AUDIO_TREBLE = V4L2_CID_BASE + 8
V4L2_CID_AUDIO_MUTE = V4L2_CID_BASE + 9
V4L2_CID_AUDIO_LOUDNESS = V4L2_CID_BASE + 10
V4L2_CID_BLACK_LEVEL = V4L2_CID_BASE + 11 # Deprecated
V4L2_CID_AUTO_WHITE_BALANCE = V4L2_CID_BASE + 12
V4L2_CID_DO_WHITE_BALANCE = V4L2_CID_BASE + 13
V4L2_CID_RED_BALANCE = V4L2_CID_BASE + 14
V4L2_CID_BLUE_BALANCE = V4L2_CID_BASE + 15
V4L2_CID_GAMMA = V4L2_CID_BASE + 16
V4L2_CID_WHITENESS = V4L2_CID_GAMMA # Deprecated
V4L2_CID_EXPOSURE = V4L2_CID_BASE + 17
V4L2_CID_AUTOGAIN = V4L2_CID_BASE + 18
V4L2_CID_GAIN = V4L2_CID_BASE + 19
V4L2_CID_HFLIP = V4L2_CID_BASE + 20
V4L2_CID_VFLIP = V4L2_CID_BASE + 21

# Deprecated; use V4L2_CID_PAN_RESET and V4L2_CID_TILT_RESET
V4L2_CID_HCENTER = V4L2_CID_BASE + 22
V4L2_CID_VCENTER = V4L2_CID_BASE + 23

V4L2_CID_POWER_LINE_FREQUENCY = V4L2_CID_BASE + 24

v4l2_power_line_frequency = enum
(
    V4L2_CID_POWER_LINE_FREQUENCY_DISABLED,
    V4L2_CID_POWER_LINE_FREQUENCY_50HZ,
    V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
) = range(3)

V4L2_CID_HUE_AUTO = V4L2_CID_BASE + 25
V4L2_CID_WHITE_BALANCE_TEMPERATURE = V4L2_CID_BASE + 26
V4L2_CID_SHARPNESS = V4L2_CID_BASE + 27
V4L2_CID_BACKLIGHT_COMPENSATION = V4L2_CID_BASE + 28
V4L2_CID_CHROMA_AGC = V4L2_CID_BASE + 29
V4L2_CID_COLOR_KILLER = V4L2_CID_BASE + 30
V4L2_CID_COLORFX = V4L2_CID_BASE + 31

v4l2_colorfx = enum
(
    V4L2_COLORFX_NONE,
    V4L2_COLORFX_BW,
    V4L2_COLORFX_SEPIA,
) = range(3)

V4L2_CID_AUTOBRIGHTNESS = V4L2_CID_BASE + 32
V4L2_CID_BAND_STOP_FILTER = V4L2_CID_BASE + 33

V4L2_CID_ROTATE = V4L2_CID_BASE + 34
V4L2_CID_BG_COLOR = V4L2_CID_BASE + 35
V4L2_CID_LASTP1 = V4L2_CID_BASE + 36

V4L2_CID_MPEG_BASE = V4L2_CTRL_CLASS_MPEG | 0x900
V4L2_CID_MPEG_CLASS = V4L2_CTRL_CLASS_MPEG | 1

# MPEG streams
V4L2_CID_MPEG_STREAM_TYPE = V4L2_CID_MPEG_BASE + 0

v4l2_mpeg_stream_type = enum
(
    V4L2_MPEG_STREAM_TYPE_MPEG2_PS,
    V4L2_MPEG_STREAM_TYPE_MPEG2_TS,
    V4L2_MPEG_STREAM_TYPE_MPEG1_SS,
    V4L2_MPEG_STREAM_TYPE_MPEG2_DVD,
    V4L2_MPEG_STREAM_TYPE_MPEG1_VCD,
    V4L2_MPEG_STREAM_TYPE_MPEG2_SVCD,
) = range(6)

V4L2_CID_MPEG_STREAM_PID_PMT = V4L2_CID_MPEG_BASE + 1
V4L2_CID_MPEG_STREAM_PID_AUDIO = V4L2_CID_MPEG_BASE + 2
V4L2_CID_MPEG_STREAM_PID_VIDEO = V4L2_CID_MPEG_BASE + 3
V4L2_CID_MPEG_STREAM_PID_PCR = V4L2_CID_MPEG_BASE + 4
V4L2_CID_MPEG_STREAM_PES_ID_AUDIO = V4L2_CID_MPEG_BASE + 5
V4L2_CID_MPEG_STREAM_PES_ID_VIDEO = V4L2_CID_MPEG_BASE + 6
V4L2_CID_MPEG_STREAM_VBI_FMT = V4L2_CID_MPEG_BASE + 7

v4l2_mpeg_stream_vbi_fmt = enum
(
    V4L2_MPEG_STREAM_VBI_FMT_NONE,
    V4L2_MPEG_STREAM_VBI_FMT_IVTV,
) = range(2)

V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ = V4L2_CID_MPEG_BASE + 100

v4l2_mpeg_audio_sampling_freq = enum
(
    V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100,
    V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000,
    V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000,
) = range(3)

V4L2_CID_MPEG_AUDIO_ENCODING = V4L2_CID_MPEG_BASE + 101

v4l2_mpeg_audio_encoding = enum
(
    V4L2_MPEG_AUDIO_ENCODING_LAYER_1,
    V4L2_MPEG_AUDIO_ENCODING_LAYER_2,
    V4L2_MPEG_AUDIO_ENCODING_LAYER_3,
    V4L2_MPEG_AUDIO_ENCODING_AAC,
    V4L2_MPEG_AUDIO_ENCODING_AC3,
) = range(5)

V4L2_CID_MPEG_AUDIO_L1_BITRATE = V4L2_CID_MPEG_BASE + 102

v4l2_mpeg_audio_l1_bitrate = enum
(
    V4L2_MPEG_AUDIO_L1_BITRATE_32K,
    V4L2_MPEG_AUDIO_L1_BITRATE_64K,
    V4L2_MPEG_AUDIO_L1_BITRATE_96K,
    V4L2_MPEG_AUDIO_L1_BITRATE_128K,
    V4L2_MPEG_AUDIO_L1_BITRATE_160K,
    V4L2_MPEG_AUDIO_L1_BITRATE_192K,
    V4L2_MPEG_AUDIO_L1_BITRATE_224K,
    V4L2_MPEG_AUDIO_L1_BITRATE_256K,
    V4L2_MPEG_AUDIO_L1_BITRATE_288K,
    V4L2_MPEG_AUDIO_L1_BITRATE_320K,
    V4L2_MPEG_AUDIO_L1_BITRATE_352K,
    V4L2_MPEG_AUDIO_L1_BITRATE_384K,
    V4L2_MPEG_AUDIO_L1_BITRATE_416K,
    V4L2_MPEG_AUDIO_L1_BITRATE_448K,
) = range(14)

V4L2_CID_MPEG_AUDIO_L2_BITRATE = V4L2_CID_MPEG_BASE + 103

v4l2_mpeg_audio_l2_bitrate = enum
(
    V4L2_MPEG_AUDIO_L2_BITRATE_32K,
    V4L2_MPEG_AUDIO_L2_BITRATE_48K,
    V4L2_MPEG_AUDIO_L2_BITRATE_56K,
    V4L2_MPEG_AUDIO_L2_BITRATE_64K,
    V4L2_MPEG_AUDIO_L2_BITRATE_80K,
    V4L2_MPEG_AUDIO_L2_BITRATE_96K,
    V4L2_MPEG_AUDIO_L2_BITRATE_112K,
    V4L2_MPEG_AUDIO_L2_BITRATE_128K,
    V4L2_MPEG_AUDIO_L2_BITRATE_160K,
    V4L2_MPEG_AUDIO_L2_BITRATE_192K,
    V4L2_MPEG_AUDIO_L2_BITRATE_224K,
    V4L2_MPEG_AUDIO_L2_BITRATE_256K,
    V4L2_MPEG_AUDIO_L2_BITRATE_320K,
    V4L2_MPEG_AUDIO_L2_BITRATE_384K,
) = range(14)

V4L2_CID_MPEG_AUDIO_L3_BITRATE = V4L2_CID_MPEG_BASE + 104

v4l2_mpeg_audio_l3_bitrate = enum
(
    V4L2_MPEG_AUDIO_L3_BITRATE_32K,
    V4L2_MPEG_AUDIO_L3_BITRATE_40K,
    V4L2_MPEG_AUDIO_L3_BITRATE_48K,
    V4L2_MPEG_AUDIO_L3_BITRATE_56K,
    V4L2_MPEG_AUDIO_L3_BITRATE_64K,
    V4L2_MPEG_AUDIO_L3_BITRATE_80K,
    V4L2_MPEG_AUDIO_L3_BITRATE_96K,
    V4L2_MPEG_AUDIO_L3_BITRATE_112K,
    V4L2_MPEG_AUDIO_L3_BITRATE_128K,
    V4L2_MPEG_AUDIO_L3_BITRATE_160K,
    V4L2_MPEG_AUDIO_L3_BITRATE_192K,
    V4L2_MPEG_AUDIO_L3_BITRATE_224K,
    V4L2_MPEG_AUDIO_L3_BITRATE_256K,
    V4L2_MPEG_AUDIO_L3_BITRATE_320K,
) = range(14)

V4L2_CID_MPEG_AUDIO_MODE = V4L2_CID_MPEG_BASE + 105

v4l2_mpeg_audio_mode = enum
(
    V4L2_MPEG_AUDIO_MODE_STEREO,
    V4L2_MPEG_AUDIO_MODE_JOINT_STEREO,
    V4L2_MPEG_AUDIO_MODE_DUAL,
    V4L2_MPEG_AUDIO_MODE_MONO,
) = range(4)

V4L2_CID_MPEG_AUDIO_MODE_EXTENSION = V4L2_CID_MPEG_BASE + 106

v4l2_mpeg_audio_mode_extension = enum
(
    V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_4,
    V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_8,
    V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_12,
    V4L2_MPEG_AUDIO_MODE_EXTENSION_BOUND_16,
) = range(4)

V4L2_CID_MPEG_AUDIO_EMPHASIS = V4L2_CID_MPEG_BASE + 107

v4l2_mpeg_audio_emphasis = enum
(
    V4L2_MPEG_AUDIO_EMPHASIS_NONE,
    V4L2_MPEG_AUDIO_EMPHASIS_50_DIV_15_uS,
    V4L2_MPEG_AUDIO_EMPHASIS_CCITT_J17,
) = range(3)

V4L2_CID_MPEG_AUDIO_CRC = V4L2_CID_MPEG_BASE + 108

v4l2_mpeg_audio_crc = enum
(
    V4L2_MPEG_AUDIO_CRC_NONE,
    V4L2_MPEG_AUDIO_CRC_CRC16,
) = range(2)

V4L2_CID_MPEG_AUDIO_MUTE = V4L2_CID_MPEG_BASE + 109
V4L2_CID_MPEG_AUDIO_AAC_BITRATE = V4L2_CID_MPEG_BASE + 110
V4L2_CID_MPEG_AUDIO_AC3_BITRATE	= V4L2_CID_MPEG_BASE + 111

v4l2_mpeg_audio_ac3_bitrate = enum
(
    V4L2_MPEG_AUDIO_AC3_BITRATE_32K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_40K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_48K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_56K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_64K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_80K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_96K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_112K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_128K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_160K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_192K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_224K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_256K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_320K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_384K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_448K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_512K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_576K,
    V4L2_MPEG_AUDIO_AC3_BITRATE_640K,
) = range(19)

V4L2_CID_MPEG_VIDEO_ENCODING = V4L2_CID_MPEG_BASE + 200

v4l2_mpeg_video_encoding = enum
(
    V4L2_MPEG_VIDEO_ENCODING_MPEG_1,
    V4L2_MPEG_VIDEO_ENCODING_MPEG_2,
    V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC,
) = range(3)

V4L2_CID_MPEG_VIDEO_ASPECT = V4L2_CID_MPEG_BASE + 201

v4l2_mpeg_video_aspect = enum
(
    V4L2_MPEG_VIDEO_ASPECT_1x1,
    V4L2_MPEG_VIDEO_ASPECT_4x3,
    V4L2_MPEG_VIDEO_ASPECT_16x9,
    V4L2_MPEG_VIDEO_ASPECT_221x100,
) = range(4)

V4L2_CID_MPEG_VIDEO_B_FRAMES = V4L2_CID_MPEG_BASE + 202
V4L2_CID_MPEG_VIDEO_GOP_SIZE = V4L2_CID_MPEG_BASE + 203
V4L2_CID_MPEG_VIDEO_GOP_CLOSURE = V4L2_CID_MPEG_BASE + 204
V4L2_CID_MPEG_VIDEO_PULLDOWN = V4L2_CID_MPEG_BASE + 205
V4L2_CID_MPEG_VIDEO_BITRATE_MODE = V4L2_CID_MPEG_BASE + 206

v4l2_mpeg_video_bitrate_mode = enum
(
    V4L2_MPEG_VIDEO_BITRATE_MODE_VBR,
    V4L2_MPEG_VIDEO_BITRATE_MODE_CBR,
) = range(2)

V4L2_CID_MPEG_VIDEO_BITRATE = V4L2_CID_MPEG_BASE + 207
V4L2_CID_MPEG_VIDEO_BITRATE_PEAK = V4L2_CID_MPEG_BASE + 208
V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION = V4L2_CID_MPEG_BASE + 209
V4L2_CID_MPEG_VIDEO_MUTE = V4L2_CID_MPEG_BASE + 210
V4L2_CID_MPEG_VIDEO_MUTE_YUV = V4L2_CID_MPEG_BASE + 211

V4L2_CID_MPEG_VIDEO_VBV_SIZE = V4L2_CID_MPEG_BASE + 222
V4L2_CID_MPEG_VIDEO_DEC_PTS	= V4L2_CID_MPEG_BASE + 223
V4L2_CID_MPEG_VIDEO_DEC_FRAME = V4L2_CID_MPEG_BASE + 224
V4L2_CID_MPEG_VIDEO_VBV_DELAY = V4L2_CID_MPEG_BASE + 225
V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER = V4L2_CID_MPEG_BASE + 226
V4L2_CID_MPEG_VIDEO_MV_H_SEARCH_RANGE = V4L2_CID_MPEG_BASE + 227
V4L2_CID_MPEG_VIDEO_MV_V_SEARCH_RANGE = V4L2_CID_MPEG_BASE + 228
V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME = V4L2_CID_MPEG_BASE + 229

V4L2_CID_MPEG_VIDEO_H264_I_PERIOD = V4L2_CID_MPEG_BASE + 358
V4L2_CID_MPEG_VIDEO_H264_LEVEL = V4L2_CID_MPEG_BASE + 359

V4L2_CID_MPEG_CX2341X_BASE = V4L2_CTRL_CLASS_MPEG | 0x1000
V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 0

v4l2_mpeg_cx2341x_video_spatial_filter_mode = enum
(
    V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_MANUAL,
    V4L2_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE_AUTO,
) = range(2)

V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 1
V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 2

v4l2_mpeg_cx2341x_video_luma_spatial_filter_type = enum
(
    V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_OFF,
    V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_HOR,
    V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_1D_VERT,
    V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_HV_SEPARABLE,
    V4L2_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE_2D_SYM_NON_SEPARABLE,
) = range(5)

V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 3

v4l2_mpeg_cx2341x_video_chroma_spatial_filter_type = enum
(
    V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_OFF,
    V4L2_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE_1D_HOR,
) = range(2)

V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE = V4L2_CID_MPEG_CX2341X_BASE + 4

v4l2_mpeg_cx2341x_video_temporal_filter_mode = enum
(
    V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_MANUAL,
    V4L2_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE_AUTO,
) = range(2)

V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER = V4L2_CID_MPEG_CX2341X_BASE + 5
V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE = V4L2_CID_MPEG_CX2341X_BASE + 6

v4l2_mpeg_cx2341x_video_median_filter_type = enum
(
    V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_OFF,
    V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR,
    V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_VERT,
    V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_HOR_VERT,
    V4L2_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE_DIAG,
) = range(5)

V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 7
V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 8
V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM = V4L2_CID_MPEG_CX2341X_BASE + 9
V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP = V4L2_CID_MPEG_CX2341X_BASE + 10
V4L2_CID_MPEG_CX2341X_STREAM_INSERT_NAV_PACKETS = V4L2_CID_MPEG_CX2341X_BASE + 11

V4L2_CID_CAMERA_CLASS_BASE = V4L2_CTRL_CLASS_CAMERA | 0x900
V4L2_CID_CAMERA_CLASS = V4L2_CTRL_CLASS_CAMERA | 1

V4L2_CID_EXPOSURE_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 1

v4l2_exposure_auto_type = enum
(
    V4L2_EXPOSURE_AUTO,
    V4L2_EXPOSURE_MANUAL,
    V4L2_EXPOSURE_SHUTTER_PRIORITY,
    V4L2_EXPOSURE_APERTURE_PRIORITY,
) = range(4)

V4L2_CID_EXPOSURE_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 2
V4L2_CID_EXPOSURE_AUTO_PRIORITY = V4L2_CID_CAMERA_CLASS_BASE + 3

V4L2_CID_PAN_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 4
V4L2_CID_TILT_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 5
V4L2_CID_PAN_RESET = V4L2_CID_CAMERA_CLASS_BASE + 6
V4L2_CID_TILT_RESET = V4L2_CID_CAMERA_CLASS_BASE + 7

V4L2_CID_PAN_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 8
V4L2_CID_TILT_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 9

V4L2_CID_FOCUS_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 10
V4L2_CID_FOCUS_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 11
V4L2_CID_FOCUS_AUTO = V4L2_CID_CAMERA_CLASS_BASE + 12

V4L2_CID_ZOOM_ABSOLUTE = V4L2_CID_CAMERA_CLASS_BASE + 13
V4L2_CID_ZOOM_RELATIVE = V4L2_CID_CAMERA_CLASS_BASE + 14
V4L2_CID_ZOOM_CONTINUOUS = V4L2_CID_CAMERA_CLASS_BASE + 15

V4L2_CID_PRIVACY = V4L2_CID_CAMERA_CLASS_BASE + 16

V4L2_CID_FM_TX_CLASS_BASE = V4L2_CTRL_CLASS_FM_TX | 0x900
V4L2_CID_FM_TX_CLASS = V4L2_CTRL_CLASS_FM_TX | 1

V4L2_CID_RDS_TX_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 1
V4L2_CID_RDS_TX_PI = V4L2_CID_FM_TX_CLASS_BASE + 2
V4L2_CID_RDS_TX_PTY = V4L2_CID_FM_TX_CLASS_BASE + 3
V4L2_CID_RDS_TX_PS_NAME = V4L2_CID_FM_TX_CLASS_BASE + 5
V4L2_CID_RDS_TX_RADIO_TEXT = V4L2_CID_FM_TX_CLASS_BASE + 6

V4L2_CID_AUDIO_LIMITER_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 64
V4L2_CID_AUDIO_LIMITER_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 65
V4L2_CID_AUDIO_LIMITER_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 66

V4L2_CID_AUDIO_COMPRESSION_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 80
V4L2_CID_AUDIO_COMPRESSION_GAIN = V4L2_CID_FM_TX_CLASS_BASE + 81
V4L2_CID_AUDIO_COMPRESSION_THRESHOLD = V4L2_CID_FM_TX_CLASS_BASE + 82
V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME = V4L2_CID_FM_TX_CLASS_BASE + 83
V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME = V4L2_CID_FM_TX_CLASS_BASE + 84

V4L2_CID_PILOT_TONE_ENABLED = V4L2_CID_FM_TX_CLASS_BASE + 96
V4L2_CID_PILOT_TONE_DEVIATION = V4L2_CID_FM_TX_CLASS_BASE + 97
V4L2_CID_PILOT_TONE_FREQUENCY = V4L2_CID_FM_TX_CLASS_BASE + 98

V4L2_CID_TUNE_PREEMPHASIS = V4L2_CID_FM_TX_CLASS_BASE + 112

v4l2_preemphasis = enum
(
    V4L2_PREEMPHASIS_DISABLED,
    V4L2_PREEMPHASIS_50_uS,
    V4L2_PREEMPHASIS_75_uS,
) = range(3)

V4L2_CID_TUNE_POWER_LEVEL = V4L2_CID_FM_TX_CLASS_BASE + 113
V4L2_CID_TUNE_ANTENNA_CAPACITOR = V4L2_CID_FM_TX_CLASS_BASE + 114


#
# Tuning
#

class v4l2_tuner(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('type', v4l2_tuner_type),
        ('capability', ctypes.c_uint32),
        ('rangelow', ctypes.c_uint32),
        ('rangehigh', ctypes.c_uint32),
        ('rxsubchans', ctypes.c_uint32),
        ('audmode', ctypes.c_uint32),
        ('signal', ctypes.c_int32),
        ('afc', ctypes.c_int32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


class v4l2_modulator(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('capability', ctypes.c_uint32),
        ('rangelow', ctypes.c_uint32),
        ('rangehigh', ctypes.c_uint32),
        ('txsubchans', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
    ]


V4L2_TUNER_CAP_LOW = 0x0001
V4L2_TUNER_CAP_NORM = 0x0002
V4L2_TUNER_CAP_STEREO = 0x0010
V4L2_TUNER_CAP_LANG2 = 0x0020
V4L2_TUNER_CAP_SAP = 0x0020
V4L2_TUNER_CAP_LANG1 = 0x0040
V4L2_TUNER_CAP_RDS = 0x0080

V4L2_TUNER_SUB_MONO = 0x0001
V4L2_TUNER_SUB_STEREO = 0x0002
V4L2_TUNER_SUB_LANG2 = 0x0004
V4L2_TUNER_SUB_SAP = 0x0004
V4L2_TUNER_SUB_LANG1 = 0x0008
V4L2_TUNER_SUB_RDS = 0x0010

V4L2_TUNER_MODE_MONO = 0x0000
V4L2_TUNER_MODE_STEREO = 0x0001
V4L2_TUNER_MODE_LANG2 = 0x0002
V4L2_TUNER_MODE_SAP = 0x0002
V4L2_TUNER_MODE_LANG1 = 0x0003
V4L2_TUNER_MODE_LANG1_LANG2 = 0x0004


class v4l2_frequency(ctypes.Structure):
    _fields_ = [
        ('tuner', ctypes.c_uint32),
        ('type', v4l2_tuner_type),
        ('frequency', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 8),
    ]


class v4l2_hw_freq_seek(ctypes.Structure):
    _fields_ = [
        ('tuner', ctypes.c_uint32),
        ('type', v4l2_tuner_type),
        ('seek_upward', ctypes.c_uint32),
        ('wrap_around', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 8),
    ]


#
# RDS
#

class v4l2_rds_data(ctypes.Structure):
    _fields_ = [
        ('lsb', ctypes.c_char),
        ('msb', ctypes.c_char),
        ('block', ctypes.c_char),
    ]

    _pack_ = True


V4L2_RDS_BLOCK_MSK =  0x7
V4L2_RDS_BLOCK_A = 0
V4L2_RDS_BLOCK_B = 1
V4L2_RDS_BLOCK_C = 2
V4L2_RDS_BLOCK_D = 3
V4L2_RDS_BLOCK_C_ALT = 4
V4L2_RDS_BLOCK_INVALID = 7

V4L2_RDS_BLOCK_CORRECTED = 0x40
V4L2_RDS_BLOCK_ERROR = 0x80


#
# Audio
#

class v4l2_audio(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('capability', ctypes.c_uint32),
        ('mode', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


V4L2_AUDCAP_STEREO = 0x00001
V4L2_AUDCAP_AVL = 0x00002

V4L2_AUDMODE_AVL = 0x00001


class v4l2_audioout(ctypes.Structure):
    _fields_ = [
        ('index', ctypes.c_uint32),
        ('name', ctypes.c_char * 32),
        ('capability', ctypes.c_uint32),
        ('mode', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


#
# Mpeg services (experimental)
#

V4L2_ENC_IDX_FRAME_I = 0
V4L2_ENC_IDX_FRAME_P = 1
V4L2_ENC_IDX_FRAME_B = 2
V4L2_ENC_IDX_FRAME_MASK = 0xf


class v4l2_enc_idx_entry(ctypes.Structure):
    _fields_ = [
        ('offset', ctypes.c_uint64),
        ('pts', ctypes.c_uint64),
        ('length', ctypes.c_uint32),
        ('flags', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


V4L2_ENC_IDX_ENTRIES = 64


class v4l2_enc_idx(ctypes.Structure):
    _fields_ = [
        ('entries', ctypes.c_uint32),
        ('entries_cap', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 4),
        ('entry', v4l2_enc_idx_entry * V4L2_ENC_IDX_ENTRIES),
    ]


V4L2_ENC_CMD_START = 0
V4L2_ENC_CMD_STOP = 1
V4L2_ENC_CMD_PAUSE = 2
V4L2_ENC_CMD_RESUME = 3

V4L2_ENC_CMD_STOP_AT_GOP_END = 1 << 0


class v4l2_encoder_cmd(ctypes.Structure):
    class _u(ctypes.Union):
        class _s(ctypes.Structure):
            _fields_ = [
                ('data', ctypes.c_uint32 * 8),
            ]

        _fields_ = [
            ('raw', _s),
        ]

    _fields_ = [
        ('cmd', ctypes.c_uint32),
        ('flags', ctypes.c_uint32),
        ('_u', _u),
    ]

    _anonymous_ = ('_u',)


#
# Data services (VBI)
#

class v4l2_vbi_format(ctypes.Structure):
    _fields_ = [
        ('sampling_rate', ctypes.c_uint32),
        ('offset', ctypes.c_uint32),
        ('samples_per_line', ctypes.c_uint32),
        ('sample_format', ctypes.c_uint32),
        ('start', ctypes.c_int32 * 2),
        ('count', ctypes.c_uint32 * 2),
        ('flags', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


V4L2_VBI_UNSYNC = 1 << 0
V4L2_VBI_INTERLACED = 1 << 1


class v4l2_sliced_vbi_format(ctypes.Structure):
    _fields_ = [
        ('service_set', ctypes.c_uint16),
        ('service_lines', ctypes.c_uint16 * 2 * 24),
        ('io_size', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32 * 2),
    ]


V4L2_SLICED_TELETEXT_B = 0x0001
V4L2_SLICED_VPS = 0x0400
V4L2_SLICED_CAPTION_525 = 0x1000
V4L2_SLICED_WSS_625 = 0x4000
V4L2_SLICED_VBI_525 = V4L2_SLICED_CAPTION_525
V4L2_SLICED_VBI_625 = (
    V4L2_SLICED_TELETEXT_B | V4L2_SLICED_VPS | V4L2_SLICED_WSS_625)


class v4l2_sliced_vbi_cap(ctypes.Structure):
    _fields_ = [
        ('service_set', ctypes.c_uint16),
        ('service_lines', ctypes.c_uint16 * 2 * 24),
        ('type', v4l2_buf_type),
        ('reserved', ctypes.c_uint32 * 3),
    ]


class v4l2_sliced_vbi_data(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint32),
        ('field', ctypes.c_uint32),
        ('line', ctypes.c_uint32),
        ('reserved', ctypes.c_uint32),
        ('data', ctypes.c_char * 48),
    ]


#
# Sliced VBI data inserted into MPEG Streams
#


V4L2_MPEG_VBI_IVTV_TELETEXT_B = 1
V4L2_MPEG_VBI_IVTV_CAPTION_525 = 4
V4L2_MPEG_VBI_IVTV_WSS_625 = 5
V4L2_MPEG_VBI_IVTV_VPS = 7


class v4l2_mpeg_vbi_itv0_line(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_char),
        ('data', ctypes.c_char * 42),
    ]

    _pack_ = True


class v4l2_mpeg_vbi_itv0(ctypes.Structure):
    _fields_ = [
        ('linemask', ctypes.c_uint32 * 2), # how to define __le32 in ctypes?
        ('line', v4l2_mpeg_vbi_itv0_line * 35),
    ]

    _pack_ = True


class v4l2_mpeg_vbi_ITV0(ctypes.Structure):
    _fields_ = [
        ('line', v4l2_mpeg_vbi_itv0_line * 36),
    ]

    _pack_ = True


V4L2_MPEG_VBI_IVTV_MAGIC0 = "itv0"
V4L2_MPEG_VBI_IVTV_MAGIC1 = "ITV0"


class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('itv0', v4l2_mpeg_vbi_itv0),
            ('ITV0', v4l2_mpeg_vbi_ITV0),
        ]

    _fields_ = [
        ('magic', ctypes.c_char * 4),
        ('_u', _u)
    ]

    _anonymous_ = ('_u',)
    _pack_ = True


#
# Aggregate structures
#

class v4l2_plane_pix_format(ctypes.Structure):
    _fields_ = [
        ('sizeimage', ctypes.c_uint32),
        ('bytesperline', ctypes.c_uint32),
        ('reserved', ctypes.c_uint16 * 6)
    ]

class v4l2_sdr_format(ctypes.Structure):
    _fields_ = [
        ('pixelformat', ctypes.c_uint32),
        ('buffersize', ctypes.c_uint32),
        ('reserved', ctypes.c_uint8 * 24)
    ]

class v4l2_meta_format(ctypes.Structure):
    _fields_ = [
        ('dataformat', ctypes.c_uint32),
        ('buffersize', ctypes.c_uint32)
    ]

class v4l2_pix_format_mplane(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('ycbcr_enc', ctypes.c_uint8),
            ('hsv_enc', ctypes.c_uint8)
        ]

    _fields_ = [
        ('width', ctypes.c_uint32),
        ('height', ctypes.c_uint32),
        ('pixelformat', ctypes.c_uint32),
        ('field', ctypes.c_uint32),
        ('colorspace', ctypes.c_uint32),
        ('plane_fmt', v4l2_plane_pix_format * VIDEO_MAX_PLANES),
        ('num_planes', ctypes.c_uint8),
        ('flags', ctypes.c_uint8),
        ('_u', _u),
        ('quantization', ctypes.c_uint8),
        ('xfer_func', ctypes.c_uint8),
        ('reserved', ctypes.c_uint8 * 7)
    ]

class v4l2_format(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('pix', v4l2_pix_format),
            ('pix_mp', v4l2_pix_format_mplane),
            ('win', v4l2_window),
            ('vbi', v4l2_vbi_format),
            ('sliced', v4l2_sliced_vbi_format),
            ('sdr', v4l2_sdr_format),
            ('meta', v4l2_meta_format),
            ('raw_data', ctypes.c_char * 200)
        ]

    _fields_ = [
        ('type', v4l2_buf_type),
        ('fmt', _u)
    ]


class v4l2_streamparm(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('capture', v4l2_captureparm),
            ('output', v4l2_outputparm),
            ('raw_data', ctypes.c_char * 200),
        ]

    _fields_ = [
        ('type', v4l2_buf_type),
        ('parm', _u)
    ]


#
# Advanced debugging
#

V4L2_CHIP_MATCH_HOST = 0
V4L2_CHIP_MATCH_I2C_DRIVER = 1
V4L2_CHIP_MATCH_I2C_ADDR = 2
V4L2_CHIP_MATCH_AC97 = 3


class v4l2_dbg_match(ctypes.Structure):
    class _u(ctypes.Union):
        _fields_ = [
            ('addr', ctypes.c_uint32),
            ('name', ctypes.c_char * 32),
        ]

    _fields_ = [
        ('type', ctypes.c_uint32),
        ('_u', _u),
    ]

    _anonymous_ = ('_u',)
    _pack_ = True


class v4l2_dbg_register(ctypes.Structure):
    _fields_ = [
        ('match', v4l2_dbg_match),
        ('size', ctypes.c_uint32),
        ('reg', ctypes.c_uint64),
        ('val', ctypes.c_uint64),
    ]

    _pack_ = True


class v4l2_dbg_chip_ident(ctypes.Structure):
    _fields_ = [
        ('match', v4l2_dbg_match),
        ('ident', ctypes.c_uint32),
        ('revision', ctypes.c_uint32),
    ]

    _pack_ = True


#
# ioctl codes for video devices
#

VIDIOC_QUERYCAP = _IOR('V', 0, v4l2_capability)
VIDIOC_RESERVED = _IO('V', 1)
VIDIOC_ENUM_FMT = _IOWR('V', 2, v4l2_fmtdesc)
VIDIOC_G_FMT = _IOWR('V', 4, v4l2_format)
VIDIOC_S_FMT = _IOWR('V', 5, v4l2_format)
VIDIOC_REQBUFS = _IOWR('V', 8, v4l2_requestbuffers)
VIDIOC_QUERYBUF	= _IOWR('V', 9, v4l2_buffer)
VIDIOC_G_FBUF = _IOR('V', 10, v4l2_framebuffer)
VIDIOC_S_FBUF = _IOW('V', 11, v4l2_framebuffer)
VIDIOC_OVERLAY = _IOW('V', 14, ctypes.c_int)
VIDIOC_QBUF = _IOWR('V', 15, v4l2_buffer)
VIDIOC_DQBUF = _IOWR('V', 17, v4l2_buffer)
VIDIOC_STREAMON = _IOW('V', 18, ctypes.c_int)
VIDIOC_STREAMOFF = _IOW('V', 19, ctypes.c_int)
VIDIOC_G_PARM = _IOWR('V', 21, v4l2_streamparm)
VIDIOC_S_PARM = _IOWR('V', 22, v4l2_streamparm)
VIDIOC_G_STD = _IOR('V', 23, v4l2_std_id)
VIDIOC_S_STD = _IOW('V', 24, v4l2_std_id)
VIDIOC_ENUMSTD = _IOWR('V', 25, v4l2_standard)
VIDIOC_ENUMINPUT = _IOWR('V', 26, v4l2_input)
VIDIOC_G_CTRL = _IOWR('V', 27, v4l2_control)
VIDIOC_S_CTRL = _IOWR('V', 28, v4l2_control)
VIDIOC_G_TUNER = _IOWR('V', 29, v4l2_tuner)
VIDIOC_S_TUNER = _IOW('V', 30, v4l2_tuner)
VIDIOC_G_AUDIO = _IOR('V', 33, v4l2_audio)
VIDIOC_S_AUDIO = _IOW('V', 34, v4l2_audio)
VIDIOC_QUERYCTRL = _IOWR('V', 36, v4l2_queryctrl)
VIDIOC_QUERYMENU = _IOWR('V', 37, v4l2_querymenu)
VIDIOC_G_INPUT = _IOR('V', 38, ctypes.c_int)
VIDIOC_S_INPUT = _IOWR('V', 39, ctypes.c_int)
VIDIOC_G_OUTPUT = _IOR('V', 46, ctypes.c_int)
VIDIOC_S_OUTPUT = _IOWR('V', 47, ctypes.c_int)
VIDIOC_ENUMOUTPUT = _IOWR('V', 48, v4l2_output)
VIDIOC_G_AUDOUT = _IOR('V', 49, v4l2_audioout)
VIDIOC_S_AUDOUT	= _IOW('V', 50, v4l2_audioout)
VIDIOC_G_MODULATOR = _IOWR('V', 54, v4l2_modulator)
VIDIOC_S_MODULATOR = _IOW('V', 55, v4l2_modulator)
VIDIOC_G_FREQUENCY = _IOWR('V', 56, v4l2_frequency)
VIDIOC_S_FREQUENCY = _IOW('V', 57, v4l2_frequency)
VIDIOC_CROPCAP = _IOWR('V', 58, v4l2_cropcap)
VIDIOC_G_CROP = _IOWR('V', 59, v4l2_crop)
VIDIOC_S_CROP = _IOW('V', 60, v4l2_crop)
VIDIOC_G_JPEGCOMP = _IOR('V', 61, v4l2_jpegcompression)
VIDIOC_S_JPEGCOMP = _IOW('V', 62, v4l2_jpegcompression)
VIDIOC_QUERYSTD = _IOR('V', 63, v4l2_std_id)
VIDIOC_TRY_FMT = _IOWR('V', 64, v4l2_format)
VIDIOC_ENUMAUDIO = _IOWR('V', 65, v4l2_audio)
VIDIOC_ENUMAUDOUT = _IOWR('V', 66, v4l2_audioout)
VIDIOC_G_PRIORITY = _IOR('V', 67, v4l2_priority)
VIDIOC_S_PRIORITY = _IOW('V', 68, v4l2_priority)
VIDIOC_G_SLICED_VBI_CAP = _IOWR('V', 69, v4l2_sliced_vbi_cap)
VIDIOC_LOG_STATUS = _IO('V', 70)
VIDIOC_G_EXT_CTRLS = _IOWR('V', 71, v4l2_ext_controls)
VIDIOC_S_EXT_CTRLS = _IOWR('V', 72, v4l2_ext_controls)
VIDIOC_TRY_EXT_CTRLS = _IOWR('V', 73, v4l2_ext_controls)

VIDIOC_ENUM_FRAMESIZES = _IOWR('V', 74, v4l2_frmsizeenum)
VIDIOC_ENUM_FRAMEINTERVALS = _IOWR('V', 75, v4l2_frmivalenum)
VIDIOC_G_ENC_INDEX = _IOR('V', 76, v4l2_enc_idx)
VIDIOC_ENCODER_CMD = _IOWR('V', 77, v4l2_encoder_cmd)
VIDIOC_TRY_ENCODER_CMD = _IOWR('V', 78, v4l2_encoder_cmd)

VIDIOC_DBG_S_REGISTER = _IOW('V', 79, v4l2_dbg_register)
VIDIOC_DBG_G_REGISTER = _IOWR('V', 80, v4l2_dbg_register)

VIDIOC_DBG_G_CHIP_IDENT = _IOWR('V', 81, v4l2_dbg_chip_ident)

VIDIOC_S_HW_FREQ_SEEK = _IOW('V', 82, v4l2_hw_freq_seek)
VIDIOC_ENUM_DV_PRESETS = _IOWR('V', 83, v4l2_dv_enum_preset)
VIDIOC_S_DV_PRESET = _IOWR('V', 84, v4l2_dv_preset)
VIDIOC_G_DV_PRESET = _IOWR('V', 85, v4l2_dv_preset)
VIDIOC_QUERY_DV_PRESET = _IOR('V', 86, v4l2_dv_preset)
VIDIOC_S_DV_TIMINGS = _IOWR('V', 87, v4l2_dv_timings)
VIDIOC_G_DV_TIMINGS = _IOWR('V', 88, v4l2_dv_timings)

VIDIOC_OVERLAY_OLD = _IOWR('V', 14, ctypes.c_int)
VIDIOC_S_PARM_OLD = _IOW('V', 22, v4l2_streamparm)
VIDIOC_S_CTRL_OLD = _IOW('V', 28, v4l2_control)
VIDIOC_G_AUDIO_OLD = _IOWR('V', 33, v4l2_audio)
VIDIOC_G_AUDOUT_OLD = _IOWR('V', 49, v4l2_audioout)
VIDIOC_CROPCAP_OLD = _IOR('V', 58, v4l2_cropcap)

BASE_VIDIOC_PRIVATE = 192




v4l2_colorspace_dict = {0:'DEFAULT',
                        1:'SMPTE170M',
                        2:'SMPTE240M',
                        3:'REC709',
                        4:'BT878',
                        5:'470_SYSTEM_M',
                        6:'470_SYSTEM_BG',
                        7:'JPEG',
                        8:'SRGB',
                        9:'ADOBERGB',
                        10:'BT2020',
                        11:'RAW',
                        12:'DCI_P3'}



v4l2_field_dict = {0:'ANY',
                   1:'NONE',
                   2:'TOP',
                   3:'BOTTOM',
                   4:'INTERLACED',
                   5:'SEQ_TB',
                   6:'SEQ_BT',
                   7:'ALTERNATE',
                   8:'INTERLACED_TB',
                   9:'INTERLACED_BT'}



v4l2_CID_dict = {V4L2_CID_BRIGHTNESS:'V4L2_CID_BRIGHTNESS',
                       V4L2_CID_CONTRAST:'V4L2_CID_CONTRAST',
                       V4L2_CID_SATURATION:'V4L2_CID_SATURATION',
                       V4L2_CID_HUE:'V4L2_CID_HUE',
                       V4L2_CID_AUTO_WHITE_BALANCE:'V4L2_CID_AUTO_WHITE_BALANCE',
                       V4L2_CID_GAMMA:'V4L2_CID_GAMMA',
                       V4L2_CID_GAIN:'V4L2_CID_GAIN',
                       V4L2_CID_POWER_LINE_FREQUENCY:'V4L2_CID_POWER_LINE_FREQUENCY',
                       V4L2_CID_WHITE_BALANCE_TEMPERATURE:'V4L2_CID_WHITE_BALANCE_TEMPERATURE',
                       V4L2_CID_SHARPNESS:'V4L2_CID_SHARPNESS',
                       V4L2_CID_BACKLIGHT_COMPENSATION:'V4L2_CID_BACKLIGHT_COMPENSATION',
                       V4L2_CID_EXPOSURE_AUTO:'V4L2_CID_EXPOSURE_AUTO',
                       V4L2_CID_EXPOSURE_ABSOLUTE:'V4L2_CID_EXPOSURE_ABSOLUTE',
                       V4L2_CID_EXPOSURE_AUTO_PRIORITY:'V4L2_CID_EXPOSURE_AUTO_PRIORITY'}



v4l2_CTRL_FLAG_dict = {NONE:'NONE',
                       V4L2_CTRL_FLAG_DISABLED:'V4L2_CTRL_FLAG_DISABLED',
                       V4L2_CTRL_FLAG_GRABBED:'V4L2_CTRL_FLAG_GRABBED',
                       V4L2_CTRL_FLAG_READ_ONLY:'V4L2_CTRL_FLAG_READ_ONLY',
                       V4L2_CTRL_FLAG_UPDATE:'V4L2_CTRL_FLAG_UPDATE',
                       V4L2_CTRL_FLAG_INACTIVE:'V4L2_CTRL_FLAG_INACTIVE',
                       V4L2_CTRL_FLAG_SLIDER:'V4L2_CTRL_FLAG_SLIDER',
                       V4L2_CTRL_FLAG_WRITE_ONLY:'V4L2_CTRL_FLAG_WRITE_ONLY',
                       V4L2_CTRL_FLAG_VOLATILE:'V4L2_CTRL_FLAG_VOLATILE',
                       V4L2_CTRL_FLAG_HAS_PAYLOAD:'V4L2_CTRL_FLAG_HAS_PAYLOAD',
                       V4L2_CTRL_FLAG_EXECUTE_ON_WRITE:'V4L2_CTRL_FLAG_EXECUTE_ON_WRITE',
                       V4L2_CTRL_FLAG_MODIFY_LAYOUT:'V4L2_CTRL_FLAG_MODIFY_LAYOUT',
                       V4L2_CTRL_FLAG_NEXT_CTRL:'V4L2_CTRL_FLAG_NEXT_CTRL',
                       V4L2_CTRL_FLAG_NEXT_COMPOUND:'V4L2_CTRL_FLAG_NEXT_COMPOUND'}



v4l2_ctrl_type_dict = {V4L2_CTRL_TYPE_INTEGER:'V4L2_CTRL_TYPE_INTEGER',
                       V4L2_CTRL_TYPE_BOOLEAN:'V4L2_CTRL_TYPE_BOOLEAN',
                       V4L2_CTRL_TYPE_MENU:'V4L2_CTRL_TYPE_MENU',
                       V4L2_CTRL_TYPE_BUTTON:'V4L2_CTRL_TYPE_BUTTON',
                       V4L2_CTRL_TYPE_INTEGER64:'V4L2_CTRL_TYPE_INTEGER64',
                       V4L2_CTRL_TYPE_CTRL_CLASS:'V4L2_CTRL_TYPE_CTRL_CLASS',
                       V4L2_CTRL_TYPE_STRING:'V4L2_CTRL_TYPE_STRING',
                       V4L2_CTRL_TYPE_BITMASK:'V4L2_CTRL_TYPE_BITMASK',
                       V4L2_CTRL_TYPE_INTEGER_MENU:'V4L2_CTRL_TYPE_INTEGER_MENU',
                       V4L2_CTRL_COMPOUND_TYPES:'V4L2_CTRL_COMPOUND_TYPES',
                       V4L2_CTRL_TYPE_U8:'V4L2_CTRL_TYPE_U8',
                       V4L2_CTRL_TYPE_U16:'V4L2_CTRL_TYPE_U16',
                       V4L2_CTRL_TYPE_U32:'V4L2_CTRL_TYPE_U32'}



v4l2_capabilities_dict = {V4L2_CAP_VIDEO_CAPTURE:'V4L2_CAP_VIDEO_CAPTURE',
                          V4L2_CAP_VIDEO_OUTPUT:'V4L2_CAP_VIDEO_OUTPUT',
                          V4L2_CAP_VIDEO_OVERLAY:'V4L2_CAP_VIDEO_OVERLAY',
                          V4L2_CAP_VBI_CAPTURE:'V4L2_CAP_VBI_CAPTURE',
                          V4L2_CAP_VBI_OUTPUT:'V4L2_CAP_VBI_OUTPUT',
                          V4L2_CAP_SLICED_VBI_CAPTURE:'V4L2_CAP_SLICED_VBI_CAPTURE',
                          V4L2_CAP_SLICED_VBI_OUTPUT:'V4L2_CAP_SLICED_VBI_OUTPUT',
                          V4L2_CAP_RDS_CAPTURE:'V4L2_CAP_RDS_CAPTURE',
                          V4L2_CAP_VIDEO_OUTPUT_OVERLAY:'V4L2_CAP_VIDEO_OUTPUT_OVERLAY',
                          V4L2_CAP_HW_FREQ_SEEK:'V4L2_CAP_HW_FREQ_SEEK',
                          V4L2_CAP_RDS_OUTPUT:'V4L2_CAP_RDS_OUTPUT',
                          V4L2_CAP_VIDEO_CAPTURE_MPLANE:'V4L2_CAP_VIDEO_CAPTURE_MPLANE',
                          V4L2_CAP_VIDEO_OUTPUT_MPLANE:'V4L2_CAP_VIDEO_OUTPUT_MPLANE',
                          V4L2_CAP_VIDEO_M2M_MPLANE:'V4L2_CAP_VIDEO_M2M_MPLANE',
                          V4L2_CAP_VIDEO_M2M:'V4L2_CAP_VIDEO_M2M',
                          V4L2_CAP_TUNER:'V4L2_CAP_TUNER',
                          V4L2_CAP_AUDIO:'V4L2_CAP_AUDIO',
                          V4L2_CAP_RADIO:'V4L2_CAP_RADIO',
                          V4L2_CAP_MODULATOR:'V4L2_CAP_MODULATOR',
                          V4L2_CAP_SDR_CAPTURE:'V4L2_CAP_SDR_CAPTURE',
                          V4L2_CAP_EXT_PIX_FORMAT:'V4L2_CAP_EXT_PIX_FORMAT',
                          V4L2_CAP_SDR_OUTPUT:'V4L2_CAP_SDR_OUTPUT',
                          V4L2_CAP_META_CAPTURE:'V4L2_CAP_META_CAPTURE',
                          V4L2_CAP_READWRITE:'V4L2_CAP_READWRITE',
                          V4L2_CAP_ASYNCIO:'V4L2_CAP_ASYNCIO',
                          V4L2_CAP_STREAMING:'V4L2_CAP_STREAMING',
                          V4L2_CAP_TOUCH:'V4L2_CAP_TOUCH',
                          V4L2_CAP_DEVICE_CAPS:'V4L2_CAP_DEVICE_CAPS'}


v4l2_BUF_TYPE_dict = {V4L2_BUF_TYPE_VIDEO_CAPTURE:'V4L2_BUF_TYPE_VIDEO_CAPTURE',
                      V4L2_BUF_TYPE_VIDEO_OUTPUT:'V4L2_BUF_TYPE_VIDEO_OUTPUT',
                      V4L2_BUF_TYPE_VIDEO_OVERLAY:'V4L2_BUF_TYPE_VIDEO_OVERLAY',
                      V4L2_BUF_TYPE_VBI_CAPTURE:'V4L2_BUF_TYPE_VBI_CAPTURE',
                      V4L2_BUF_TYPE_VBI_OUTPUT:'V4L2_BUF_TYPE_VBI_OUTPUT',
                      V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:'V4L2_BUF_TYPE_SLICED_VBI_CAPTURE',
                      V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:'V4L2_BUF_TYPE_SLICED_VBI_OUTPUT',
                      V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:'V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY',
                      V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:'V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE',
                      V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:'V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE',
                      V4L2_BUF_TYPE_SDR_CAPTURE:'V4L2_BUF_TYPE_SDR_CAPTURE',
                      V4L2_BUF_TYPE_SDR_OUTPUT:'V4L2_BUF_TYPE_SDR_OUTPUT',
                      V4L2_BUF_TYPE_META_CAPTURE:'V4L2_BUF_TYPE_META_CAPTURE',
                      V4L2_BUF_TYPE_PRIVATE:'V4L2_BUF_TYPE_PRIVATE'}


v4l2_MEMORY_dict = {V4L2_MEMORY_MMAP:'V4L2_MEMORY_MMAP',
                    V4L2_MEMORY_USERPTR:'V4L2_MEMORY_USERPTR',
                    V4L2_MEMORY_OVERLAY:'V4L2_MEMORY_OVERLAY',
                    V4L2_MEMORY_DMABUF:'V4L2_MEMORY_DMABUF'}


================================================
FILE: prusa/link/cameras/v4l2_driver.py
================================================
"""Contains implementation of a camera driver utilizing the V4L2 API"""
import ctypes
import errno
import fcntl
import fractions
import logging
import os
import pathlib
import re
import select
from glob import glob
from types import MappingProxyType
from typing import Any, NamedTuple

from prusa.connect.printer.camera import Resolution
from prusa.connect.printer.camera_driver import CameraDriver
from prusa.connect.printer.const import (
    CAMERA_WAIT_TIMEOUT,
    CapabilityType,
    NotSupported,
)

from ..util import is_potato_cpu, prctl_name
from . import v4l2
from .encoders import BufferDetails, MJPEGEncoder, get_appropriate_encoder
from .v4l2 import (
    V4L2_CID_FOCUS_ABSOLUTE,
    V4L2_CID_FOCUS_AUTO,
    VIDIOC_QUERYCTRL,
    VIDIOC_S_CTRL,
    v4l2_control,
    v4l2_queryctrl,
)

log = logging.getLogger(__name__)


# --- code taken from v4l2py, unused features cut

class Info(NamedTuple):
    """Contains information about the device"""
    driver: Any
    card: Any
    bus_info: Any
    version: Any
    physical_capabilities: Any
    capabilities: Any
    formats: Any
    frame_sizes: Any
    focus_info: Any


class ImageFormat(NamedTuple):
    """Contains information about a specific image format"""
    type: Any
    description: Any
    flags: Any
    pixel_format: Any


class FrameType(NamedTuple):
    """Contains information about a specific frame type"""
    pixel_format: Any
    width: Any
    height: Any


class FocusInfo(NamedTuple):
    """Contains information about the focus capabilities of the device"""
    available: Any
    min: Any
    max: Any
    step: Any


STREAM_TYPE = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
IGNORED_BUS_INFO_REGEX = re.compile(
    r"(platform:[0-9a-fA-F]+\.csi)|(platform:bcm2835-isp)")


def frame_sizes(file_descriptor, pixel_formats):
    """Gets a list of frame sizes for a specified pixel format"""
    size = v4l2.v4l2_frmsizeenum()
    sizes = []
    for pixel_format in pixel_formats:
        size.pixel_format = pixel_format
        size.index = 0
        while True:
            try:
                fcntl.ioctl(
                    file_descriptor, v4l2.VIDIOC_ENUM_FRAMESIZES, size)
            except OSError:
                break
            if size.type == v4l2.V4L2_FRMSIZE_TYPE_DISCRETE:
                sizes.append(FrameType(
                    pixel_format=pixel_format,
                    width=size.discrete.width,
                    height=size.discrete.height,
                ))
            size.index += 1
    return sizes


def read_capabilities(file_descriptor):
    """Reads device capabilities in the raw flag format"""
    caps = v4l2.v4l2_capability()
    fcntl.ioctl(file_descriptor, v4l2.VIDIOC_QUERYCAP, caps)
    return caps


def read_info(filename):
    """Reads device specific info needed for device initialization"""
    with fopen(filename) as file_descriptor:
        caps = read_capabilities(file_descriptor)
        version_tuple = (
            (caps.version & 0xFF0000) >> 16,
            (caps.version & 0x00FF00) >> 8,
            (caps.version & 0x0000FF),
        )
        version_str = ".".join(map(str, version_tuple))
        device_capabilities = caps.capabilities

        formats = []
        pixel_formats = set()

        fmt = v4l2.v4l2_fmtdesc()
        fmt.type = STREAM_TYPE
        for index in range(128):
            fmt.index = index
            try:
                fcntl.ioctl(file_descriptor, v4l2.VIDIOC_ENUM_FMT, fmt)
            except OSError as error:
                if error.errno == errno.EINVAL:
                    break
                raise
            try:
                pixel_format = fmt.pixelformat
            except ValueError:
                continue
            formats.append(
                ImageFormat(
                    type=STREAM_TYPE,
                    flags=fmt.flags,
                    description=fmt.description.decode(),
                    pixel_format=pixel_format,
                ),
            )
            pixel_formats.add(pixel_format)

        focus_info = None

        focus_auto = v4l2_queryctrl()
        focus_auto.id = V4L2_CID_FOCUS_AUTO

        focus_absolute = v4l2_queryctrl()
        focus_absolute.id = V4L2_CID_FOCUS_ABSOLUTE

        try:
            if fcntl.ioctl(file_descriptor, VIDIOC_QUERYCTRL, focus_auto) != 0:
                raise RuntimeError("Unable to get focus auto")
            if fcntl.ioctl(
                    file_descriptor, VIDIOC_QUERYCTRL, focus_absolute) != 0:
                raise RuntimeError("Unable to get focus absolute")
        except (OSError, RuntimeError):
            focus_info = FocusInfo(
                available=False,
                min=None,
                max=None,
                step=None,
            )
        else:
            focus_info = FocusInfo(
                available=True,
                min=focus_absolute.minimum,
                max=focus_absolute.maximum,
                step=focus_absolute.step,
            )

        return Info(
            driver=caps.driver.decode(),
            card=caps.card.decode(),
            bus_info=caps.bus_info.decode(),
            version=version_str,
            physical_capabilities=caps.capabilities,
            capabilities=device_capabilities,
            formats=formats,
            frame_sizes=frame_sizes(file_descriptor, pixel_formats),
            focus_info=focus_info,
        )


def fopen(path, write=False):
    """Opens a specified video device file"""
    return open(path, "rb+" if write else "rb", buffering=0, opener=opener)


def opener(path, flags):
    """Adds flags for the open function"""
    return os.open(path, flags | os.O_NONBLOCK)


def iter_video_files(path="/dev"):
    """Iterates over the linux detected video files under /dev"""
    path = pathlib.Path(path)
    return path.glob("video*")


def iter_devices(path="/dev"):
    """Returns a tuple of all detected video devices as an objects"""
    return (V4L2Camera(name) for name in iter_video_files(path=path))


def iter_video_capture_devices(path="/dev"):
    """Returns all video devices that report the ability to capture video"""
    def filt(filename):
        with fopen(filename) as fobj:
            caps = read_capabilities(fobj.fileno())
            return v4l2.V4L2_CAP_VIDEO_CAPTURE & caps.capabilities

    return (V4L2Camera(name) for name in filter(filt, iter_video_files(path)))


# --- Video device

class MediaDeviceInfo(ctypes.Structure):
    """A data structure for getting media device info"""
    _fields_ = (
        ("driver", ctypes.c_char * 16),
        ("model", ctypes.c_char * 32),
        ("serial", ctypes.c_char * 40),
        ("bus_info", ctypes.c_char * 32),
        ("media_version", ctypes.c_uint32),
        ("hw_revision", ctypes.c_uint32),
        ("driver_version", ctypes.c_uint32),
        ("reserved", ctypes.c_uint32 * 31),
    )


SUPPORTED_PIXEL_FORMATS = {v4l2.V4L2_PIX_FMT_MJPEG, v4l2.V4L2_PIX_FMT_YUYV}
BYTES_PER_PIXEL = {v4l2.V4L2_PIX_FMT_YUYV: 2}


# pylint: disable=protected-access
MEDIA_IOC_DEVICE_INFO = v4l2._IOWR('|', 0x00, MediaDeviceInfo)


def read_media_device_info(path):
    """Given a media device path, reads its associated info
    :raises PermissionError"""
    info = MediaDeviceInfo()
    # pylint: disable=unspecified-encoding
    with open(path, "r") as file:
        file_descriptor = file.fileno()
        if fcntl.ioctl(file_descriptor, MEDIA_IOC_DEVICE_INFO, info):
            raise RuntimeError("Failed getting media device info "
                               f"for device {path}")
    return info


class V4L2Camera:
    """An object allowing us to easily control a camera"""

    buffer_type = v4l2.V4L2_BUF_TYPE_VIDEO_CAPTURE
    # To support more, more coding is needed
    buffer_size = 1

    def __init__(self, path):
        self.path = pathlib.Path(path)

        self.width = None
        self.height = None
        self.pixel_format = v4l2.V4L2_PIX_FMT_MJPEG
        self.fps = None

        self.info = read_info(self.path)
        self.buffer_details = None
        self._file_object = None

        if not v4l2.V4L2_CAP_VIDEO_CAPTURE & self.info.capabilities:
            raise RuntimeError("This device cannot capture video")

    def _ioctl(self, request, arg: Any = 0):
        """A helper method to call a linux kernel function"""
        return fcntl.ioctl(self._file_object, request, arg)

    @property
    def is_stopped(self):
        """Is the driver currently operating or not"""
        return self._file_object is None

    def _set_format(self):
        """Uses the V4L2 api to set the stream format"""
        f = v4l2.v4l2_format()
        f.type = self.buffer_type
        if self.width is None or self.height is None:
            self._ioctl(v4l2.VIDIOC_G_FMT, f)
            self.width = f.fmt.pix.width
            self.height = f.fmt.pix.height
            self.pixel_format = f.fmt.pix.pixelformat
        else:
            f.fmt.pix.pixelformat = self.pixel_format
            f.fmt.pix.field = v4l2.V4L2_FIELD_ANY
            f.fmt.pix.width = self.width
            f.fmt.pix.height = self.height
            f.fmt.pix.bytesperline = 0
        return self._ioctl(v4l2.VIDIOC_S_FMT, f)

    def _set_fps(self):
        """Uses the V4L2 API to set the fps, leaves the default
         if None is provided"""
        stream_params = v4l2.v4l2_streamparm()
        stream_params.type = self.buffer_type
        if self.fps is None:
            self._ioctl(v4l2.VIDIOC_G_PARM, stream_params)
            self.fps = (stream_params.parm.capture.timeperframe.numerator /
                        stream_params.parm.capture.timeperframe.denominator)
        else:
            fps = fractions.Fraction(self.fps)
            stream_params.parm.capture.timeperframe.numerator = fps.denominator
            stream_params.parm.capture.timeperframe.denominator = fps.numerator
        return self._ioctl(v4l2.VIDIOC_S_PARM, stream_params)

    def _buffer_request(self, count=1):
        """Requests either zero or one buffer to be prepared

        the zero is to de-allocate existing ones"""
        if count > 1:
            raise RuntimeError("We don't support more buffers")
        buffer_request = v4l2.v4l2_requestbuffers()
        buffer_request.count = self.buffer_size  # only one is supported
        buffer_request.type = self.buffer_type
        buffer_request.memory = v4l2.V4L2_MEMORY_MMAP
        self._ioctl(v4l2.VIDIOC_REQBUFS, buffer_request)

        if not buffer_request.count:
            raise IOError("Not enough buffer memory")

    def _v4l2_buffer(self):
        """Pre-fills a new buffer structure with the correct buffer type"""
        buff = v4l2.v4l2_buffer()
        buff.index = 0
        buff.type = self.buffer_type
        buff.memory = v4l2.V4L2_MEMORY_MMAP
        return buff

    def start(self):
        """Sets up and starts the V4L2 capture, so we can request frames"""
        if not self.is_stopped:
            raise RuntimeError("Already running")
        self._file_object = fopen(self.path, write=True)

        # Set up the device parameters
        self._set_format()
        self._set_fps()

        # Ask for one buffer from the device (can't do more)
        self._buffer_request(count=1)

        # Query what the buffer looks like and map the memory, so we can look
        # at its data
        buffer = self._v4l2_buffer()
        self._ioctl(v4l2.VIDIOC_QUERYBUF, buffer)
        self.buffer_details = BufferDetails(self._file_object.fileno(),
                                            length=buffer.length,
                                            offset=buffer.m.offset)

        # Turn on the stream
        btype = v4l2.v4l2_buf_type(self.buffer_type)
        try:
            self._ioctl(v4l2.VIDIOC_STREAMON, btype)
        except OSError as exception:
            if exception.args[0] == 28:
                log.error(
                    "You have probably plugged too many cameras into a "
                    "Single-TT USB3 (or higher) or a USB2 (or lower) USB hub. "
                    "This guy explains it quite well https://www.amazon.com/"
                    "review/R12F7RYUKPCQX7/?ie=UTF8 ")
            raise

        if self.info.focus_info.available:
            # Set the focus to absolute
            self._ioctl(v4l2.VIDIOC_S_CTRL,
                        v4l2.v4l2_control(id=V4L2_CID_FOCUS_AUTO,
                                          value=0),
                        )

    def stop(self):
        """Stops all V4L2 capturing activity and frees everything"""
        if self.is_stopped:
            raise RuntimeError("Already stopped")

        btype = v4l2.v4l2_buf_type(self.buffer_type)
        self._ioctl(v4l2.VIDIOC_STREAMOFF, btype)

        # Request there be 0 buffers ready - deallocate them
        self._buffer_request(count=0)
        if self.buffer_details is not None:
            self.buffer_details.mmap.close()
        if self._file_object is not None:
            self._file_object.close()
            self._file_object = None

    def next_frame(self):
        """Asks for the next frame, leaves the buffer memory accessible
        from the outside, returns the buffer details"""
        buffer = self._v4l2_buffer()
        self._ioctl(v4l2.VIDIOC_QBUF, buffer)

        # The same piece of code in picamera driver broke,
        # this one seems to work fine
        events, *_ = select.select((self._file_object,),
                                   (), (), CAMERA_WAIT_TIMEOUT)
        if not events:
            raise TimeoutError("Getting the next frame timed out")
        self._ioctl(v4l2.VIDIOC_DQBUF, buffer)
        return buffer

    def set_focus(self, value):
        """Sets absolute focus - source value from 0 to 1"""
        value_range = self.info.focus_info.max - self.info.focus_info.min
        scaled_value = value * value_range
        value_in_step = (scaled_value
                         - (scaled_value % self.info.focus_info.step))
        final_value = int(value_in_step + self.info.focus_info.min)

        # Create a v4l2_control structure with the control ID and value
        control = v4l2_control()
        control.id = V4L2_CID_FOCUS_ABSOLUTE
        control.value = final_value

        # Use the ioctl call to set the control value
        if self._ioctl(VIDIOC_S_CTRL, control) != 0:
            raise RuntimeError("Unable to set control value")


def get_media_device_path(device: V4L2Camera):
    """Gets the media device path for a video device

    Pairs /dev/video* to /dev/media*"""
    bus_info = device.info.bus_info
    paths = glob("/dev/media*")
    for path in paths:
        try:
            info = read_media_device_info(path)
        except PermissionError:
            log.exception("Failed getting a media device for %s. "
                          "This is commonly caused by the linux user "
                          "not being a member of the 'video' group",
                          device.path)
        else:
            if bus_info == info.bus_info.decode("UTF-8"):
                return path
    return None


def param_change(func):
    """Wraps any settings change with a stop and start of the video
    stream, so the camera driver does not return it's busy"""

    def inner(self, new_param):
        # pylint: disable=protected-access
        self.device.stop()
        self.encoder.stop()
        func(self, new_param)
        self.device.start()
        self.encoder.source_details = self.device.buffer_details
        self.encoder.start()

    return inner


class V4L2Driver(CameraDriver):
    """Linux V4L2 USB webcam driver"""

    name = "V4L2"
    REQUIRES_SETTINGS = MappingProxyType({
        "path": "Path to the V4L2 device like '/dev/video1'",
    })

    @staticmethod
    def _scan():
        """Implements the mandated scan method, returns available USB
        cameras"""
        available = {}
        devices = iter_video_capture_devices()
        for device in devices:
            # Ignore picameras as they are handled by their own driver
            if IGNORED_BUS_INFO_REGEX.match(device.info.bus_info) is not None:
                continue

            if not device.info.formats:
                continue

            media_device_path = get_media_device_path(device)
            if media_device_path is None:
                continue

            path = str(device.path)
            name = device.info.card
            try:
                info = read_media_device_info(media_device_path)
                serial = info.serial.decode("ascii")
            except (OSError, PermissionError):
                log.exception("Getting camera sn failed for camera %s at %s",
                              name, path)
                continue
            else:
                camera_id = " ".join((name, serial))
                log.debug("Camera id is %s", camera_id)
                available[camera_id] = {
                    "path": path,
                    "name": name,
                }
        return available

    def __init__(self, camera_id, config, unavailable_cb):
        # pylint: disable=duplicate-code
        super().__init__(camera_id, config, unavailable_cb)

        self._resolution_to_format = {}
        self.device = None
        self.stream = None
        self.encoder = None

    def _connect(self):
        """Connects to the V4L2 camera"""
        path = self.config["path"]

        self._capabilities = ({
            CapabilityType.TRIGGER_SCHEME,
            CapabilityType.IMAGING,
            CapabilityType.RESOLUTION,
        })

        extra_unsupported_formats = set()
        self.device = V4L2Camera(path)
        if self.device.info.focus_info.available:
            self._capabilities.add(CapabilityType.FOCUS)
            self._config["focus"] = self._config.get("focus", str(0.0))

        self._available_resolutions = set()
        for frame_type in self.device.info.frame_sizes:
            resolution = Resolution(width=frame_type.width,
                                    height=frame_type.height)

            # Prefer MJPEG to others
            if resolution in self._resolution_to_format:
                pixel_format = self._resolution_to_format[resolution]
                if pixel_format == v4l2.V4L2_PIX_FMT_MJPEG:
                    continue

            pixel_format = frame_type.pixel_format
            if pixel_format not in SUPPORTED_PIXEL_FORMATS:
                if pixel_format not in extra_unsupported_formats:
                    log.debug("Pixel format %s not supported",
                              pixel_format)
                extra_unsupported_formats.add(pixel_format)
                continue

            max_resolution = max(resolution.width, resolution.height)
            if (pixel_format != v4l2.V4L2_PIX_FMT_MJPEG
                    and is_potato_cpu()
                    and max_resolution > MJPEGEncoder.WIDTH_LIMIT):
                # The format needs to be encoded, but we cannot encode this
                # using the HW encoder, and our CPU is not good either
                continue

            self._available_resolutions.add(resolution)
            self._resolution_to_format[resolution] = pixel_format

        if not self.available_resolutions:
            raise NotSupported(
                "Sorry, PrusaLink supports only YUYV 4:2:2 and MJPEG. "
                f"Camera {self.camera_id} supports only these formats: "
                f"{extra_unsupported_formats}")

        initial_resolution = self._get_initial_resolution(
            self._available_resolutions, self._config)
        self._set_resolution(initial_resolution)
        self._config["resolution"] = str(initial_resolution)

        self.device.start()
        self.encoder.start()
        if CapabilityType.FOCUS in self.capabilities:
            self.set_focus(float(self._config["focus"]))

    @param_change
    def set_resolution(self, resolution):
        """Sets the camera resolution"""
        self._set_resolution(resolution)

    def _set_resolution(self, resolution):
        """Sets the camera resolution"""
        pixel_format = self._resolution_to_format[resolution]

        self.device.width = resolution.width
        self.device.height = resolution.height
        self.device.pixel_format = pixel_format

        self.encoder = get_appropriate_encoder(
            resolution, pixel_format, use_mmap=True)
        self.encoder.width = resolution.width
        self.encoder.height = resolution.height
        self.encoder.stride = (resolution.width
                               * BYTES_PER_PIXEL.get(pixel_format, 0))

    def set_focus(self, focus):
        """Sets the camera focus"""
        self.device.set_focus(focus)

    def take_a_photo(self):
        """Takes a photo, blocking while doing it"""
        prctl_name()
        v4l2_source_buffer = self.device.next_frame()
        return self.encoder.encode(v4l2_source_buffer.bytesused)

    def _disconnect(self):
        """Disconnects from the camera"""
        if self.device is None:
            return
        try:
            self.device.stop()
        except OSError:
            log.exception("Camera %s could not be closed",
                          self.camera_id)
        except Exception:  # pylint: disable=broad-except
            log.exception("Camera %s could not be closed - unknown error",
                          self.camera_id)

        try:
            self.encoder.stop()
        except OSError:
            log.exception("Encoder for %s could not be closed",
                          self.camera_id)
        except Exception:  # pylint: disable=broad-except
            log.exception("Encoder for %s could not be closed - unknown error",
                          self.camera_id)


================================================
FILE: prusa/link/conditions.py
================================================
"""PrusaLink error states.html

For more information see prusalink_states.txt.
"""

from typing import Optional

from poorwsgi import state
from poorwsgi.response import JSONResponse, TextResponse
from prusa.connect.printer.conditions import (
    COND_TRACKER,
    HTTP,
    INTERNET,
    TOKEN,
    Condition,
    ConditionTracker,
)

from .config import Settings

assert HTTP is not None
assert TOKEN is not None

OK_MSG = {"ok": True, "message": "OK"}

ROOT_COND = Condition("Root", "The root of everything, it's almost always OK")

DEVICE = Condition("Device",
                   "Eth|WLAN device does not exist",
                   short_msg="No WLAN device",
                   parent=ROOT_COND,
                   priority=1020)
PHY = Condition("Phy",
                "Eth|WLAN device is not connected",
                parent=DEVICE,
                short_msg="No WLAN conn",
                priority=1010)
LAN = Condition("Lan",
                "Eth|WLAN has no IP address",
                parent=PHY,
                short_msg="No WLAN IP addr",
                priority=1000)

INTERNET.set_parent(LAN)

SERIAL = Condition("Port",
                   "Serial device does not exist",
                   parent=ROOT_COND,
                   priority=570)
RPI_ENABLED = Condition("RPIenabled",
                        "RPi port is not enabled",
                        parent=SERIAL,
                        priority=560)
ID = Condition("ID",
               "Device is not supported",
               parent=RPI_ENABLED,
               priority=550)
UPGRADED = Condition("Upgraded",
                     "Printer upgraded, re-register it",
                     parent=ID,
                     priority=500)
FW = Condition("Firmware",
               "Firmware is not up-to-date",
               parent=RPI_ENABLED,
               priority=540)
SN = Condition("SN",
               "Serial number cannot be obtained",
               parent=RPI_ENABLED,
               priority=530)
JOB_ID = Condition("JobID",
                   "Job ID cannot be obtained",
                   parent=RPI_ENABLED,
                   priority=520)
HW = Condition("HW",
               "Firmware detected a hardware issue",
               parent=RPI_ENABLED,
               priority=510)

COND_TRACKER.add_tracked_condition_tree(ROOT_COND)

NET_TRACKER = ConditionTracker()
NET_TRACKER.add_tracked_condition_tree(DEVICE)

PRINTER_TRACKER = ConditionTracker()
PRINTER_TRACKER.add_tracked_condition_tree(SERIAL)


def use_connect_errors(use_connect):
    """Set whether to use Connect related errors or not"""
    if use_connect:
        COND_TRACKER.add_tracked_condition_tree(INTERNET)
        NET_TRACKER.add_tracked_condition_tree(INTERNET)
    else:
        COND_TRACKER.remove_tracked_condition_tree(INTERNET)
        NET_TRACKER.remove_tracked_condition_tree(INTERNET)


def status():
    """Return a dict with representation of all current conditions"""
    result = {}
    for condition in reversed(list(ROOT_COND)):
        result[condition.name] = (condition.state.name, condition.long_msg)
    return result


def printer_status():
    """Returns a representation of the currently broken printer condition"""
    worst = PRINTER_TRACKER.get_worst()
    if worst is None:
        return OK_MSG
    return {"ok": False, "message": worst.long_msg}


def connect_status():
    """Returns a representation of the currently broken Connect condition"""
    worst = NET_TRACKER.get_worst()
    if worst is None:
        if not Settings.instance.use_connect():
            return {"ok": True, "message": "Connect isn't configured"}
        return OK_MSG
    return {"ok": False, "message": worst.long_msg}


class LinkError(RuntimeError):
    """Link error structure."""
    title: str
    text: str
    id: Optional[str] = None
    status_code: int
    path: Optional[str] = None
    details: Optional[str] = None
    url: str = ''
    use_basic_template = True

    def __init__(self, details: str = ""):
        if details:
            self.details = details
        if self.id:
            self.path = '/error/' + self.id
        # pylint: disable=consider-using-f-string
        if self.use_basic_template:
            self.template = "error.html"
        else:
            self.template = 'error-%s.html' % self.id
        super().__init__(self.text)

    def set_url(self, req):
        """Set url from request and self.path."""
        self.url = req.construct_url(self.path) if self.path else ''

    def gen_headers(self):
        """Return headers with Content-Location if id was set."""
        return {'Content-Location': self.url} if self.url else {}

    def json_response(self):
        """Return JSONResponse for error."""
        kwargs = {
            "title": self.title,
            "message": self.text,
        }
        if self.url:
            kwargs['url'] = self.url
        return JSONResponse(status_code=self.status_code,
                            headers=self.gen_headers(),
                            **kwargs)

    def text_response(self):
        """Return TextResponse for error."""
        url = "\n\nSee: " + self.url if self.url else ''
        # pylint: disable=consider-using-f-string
        text_response = "%s\n%s\n%s%s" % \
                        (self.title, self.text,
                         self.details if self.details else "", url)
        return TextResponse(text_response,
                            status_code=self.status_code,
                            headers=self.gen_headers())


class BadRequestError(LinkError):
    """400 Bad Request error"""
    status_code = state.HTTP_BAD_REQUEST


class TemperatureTooLow(BadRequestError):
    """400 Temperature is too low"""
    title = "Temperature too low"
    text = "Desired temperature is too low"
    id = "temperature-too-low"


class TemperatureTooHigh(BadRequestError):
    """400 Temperature is too high"""
    title = "Temperature too high"
    text = "Desired temperature is too high"
    id = "temperature-too-high"


class ValueTooLow(BadRequestError):
    """400 Generic value is too low"""
    title = "Value too low"
    text = "Desired value is too low"
    id = "value-too-low"


class ValueTooHigh(BadRequestError):
    """400 Generic value is too high"""
    title = "Value too high"
    text = "Desired value is too high"
    id = "value-too-high"


class CantMoveAxis(BadRequestError):
    """400 Can't Move Axis"""
    title = "Can't move axis"
    text = "Can't move axis in current state"
    id = "cant-move-axis"


class CantMoveAxisZ(BadRequestError):
    """400 Can't move axis in current state"""
    title = "Can't Move Axis Z in current state"
    text = "Axis Z can't be moved in current state"
    id = "cant-move-axis-z"


class DestinationSameAsSource(BadRequestError):
    """400 Destination is same as source"""
    title = "Destination same as source"
    text = "Destination to move file is same as the source of the file"
    id = "destination-same-as-source"


class NoFileInRequest(BadRequestError):
    """400 File not found in request payload."""
    title = "Missing file in payload."
    text = "File is not send in request payload or it hasn't right name."
    id = "no-file-in-request"


class FileSizeMismatch(BadRequestError):
    """400 File size mismatch."""
    title = "File Size Mismatch"
    text = "You sent more or less data than is in Content-Length header."
    id = "file-size-mismatch"


class InvalidIniFileFormat(BadRequestError):
    """400 Invalid ini file format."""
    title = "Invalid ini File Format"
    text = "Format or the structure of your ini file is invalid."
    id = "invalid-ini-file-format"


class InvalidBooleanHeader(BadRequestError):
    """400 Invalid Boolean Header"""
    title = "Invalid Boolean Header"
    text = "Invalid Boolean Header according to RFC8941 / 3.3.6"
    id = "invalid-boolean-header"


class ForbiddenCharacters(BadRequestError):
    """400 Forbidden Characters."""
    title = "Forbidden Characters"
    text = "Forbidden characters in file or folder name."
    id = "forbidden-characters"


class FilenameTooLong(BadRequestError):
    """400 Filename Too Long"""
    title = "Filename Too Long"
    text = "File name length is too long"
    id = "filename-too-long"


class FoldernameTooLong(BadRequestError):
    """400 Foldername Too Long"""
    title = "Foldername Too Long"
    text = "Folder name length is too long"
    id = "foldername-too-long"


class FileUploadFailed(BadRequestError):
    """400 File Upload Failed"""
    title = "File Upload Failed"
    text = "File upload has failed"
    id = "file-upload-failed"


class CantConnect(BadRequestError):
    """400 Can't connect to PrusaConnect"""
    title = "Can't Connect"
    text = "Can't connect to PrusaConnect"
    id = "cant-connect"


class CantResolveHostname(BadRequestError):
    """400 Can't resolve PrusaConnect hostname"""
    title = "Can't resolve hostname"
    text = "Can't resolve PrusaConnect hostname"
    id = "cant-resolve-hostname"


class NotSupportedFileType(LinkError):
    """415 Not supported file"""
    title = "Not Supported File Type"
    text = "Uploaded file type is not supported."
    id = "not-supported-file-type"
    status_code = state.HTTP_UNSUPPORTED_MEDIA_TYPE


class ForbiddenError(LinkError):
    """403 Forbidden"""
    title = "Forbidden"
    text = "You don not have permission to access this."
    status_code = state.HTTP_FORBIDDEN
    id = "forbidden"


class NotFoundError(LinkError):
    """404 Not Found error"""
    title = "Not Found"
    text = "Resource you want not found."
    status_code = state.HTTP_NOT_FOUND
    id = "not-found"


class NotCurrentJob(NotFoundError):
    """404 Not current job"""
    title = "Not Current Job"
    text = "Given job id does not belong to current job"


class GoneError(LinkError):
    """410 Gone"""
    title = "Target Resource Unavailable"
    text = "Target resource is unavailable."
    status_code = state.HTTP_GONE
    id = "file-gone"


class ThumbnailUnavailable(GoneError):
    """410 Thumbnail Unavailable"""
    title = "Thumbnail Unavailable"
    text = "Thumbnail is unavailable."


class FileNotFound(NotFoundError):
    """404 File Not Found"""
    title = "File Not Found"
    text = "File you want was not found."


class FolderNotFound(NotFoundError):
    """404 Folder Not Found"""
    title = "Folder Not Found"
    text = "Folder you want was not found."


class LocationNotFound(NotFoundError):
    """404 Location from url not found."
Download .txt
gitextract_3glbe_4j/

├── .editorconfig
├── .github/
│   └── workflows/
│       ├── pages.yml
│       └── python-tests.yml
├── .gitignore
├── .gitmodules
├── .pre-commit-config.yaml
├── .pylintrc
├── CONTRIBUTION.md
├── ChangeLog
├── MANIFEST.in
├── MULTIINSTANCE.md
├── README.md
├── config.custom.js
├── docs/
│   ├── Makefile
│   ├── prusalink_states.txt
│   └── wizard.txt
├── image_builder/
│   ├── __init__.py
│   └── image_builder.py
├── prusa/
│   └── link/
│       ├── __init__.py
│       ├── __main__.py
│       ├── camera_governor.py
│       ├── cameras/
│       │   ├── __init__.py
│       │   ├── encoders.py
│       │   ├── picamera_driver.py
│       │   ├── v4l2.py
│       │   └── v4l2_driver.py
│       ├── conditions.py
│       ├── config.py
│       ├── const.py
│       ├── daemon.py
│       ├── data/
│       │   ├── image_builder/
│       │   │   ├── boot-message.service
│       │   │   ├── first-boot.sh
│       │   │   ├── manager-start-script.sh
│       │   │   └── prusalink-start-script.sh
│       │   └── prusalink.ini
│       ├── interesting_logger.py
│       ├── multi_instance/
│       │   ├── __init__.py
│       │   ├── __main__.py
│       │   ├── config_component.py
│       │   ├── const.py
│       │   ├── controller.py
│       │   ├── ipc_queue_adapter.py
│       │   ├── runner_component.py
│       │   └── web.py
│       ├── printer_adapter/
│       │   ├── __init__.py
│       │   ├── auto_telemetry.py
│       │   ├── command.py
│       │   ├── command_handlers.py
│       │   ├── command_queue.py
│       │   ├── file_printer.py
│       │   ├── filesystem/
│       │   │   ├── __init__.py
│       │   │   ├── sd_card.py
│       │   │   ├── storage.py
│       │   │   └── storage_controller.py
│       │   ├── ip_updater.py
│       │   ├── job.py
│       │   ├── keepalive.py
│       │   ├── lcd_printer.py
│       │   ├── mmu_observer.py
│       │   ├── model.py
│       │   ├── print_stat_doubler.py
│       │   ├── print_stats.py
│       │   ├── printer_polling.py
│       │   ├── prusa_link.py
│       │   ├── py.typed
│       │   ├── special_commands.py
│       │   ├── state_manager.py
│       │   ├── structures/
│       │   │   ├── __init__.py
│       │   │   ├── carousel.py
│       │   │   ├── heap.py
│       │   │   ├── item_updater.py
│       │   │   ├── mc_singleton.py
│       │   │   ├── model_classes.py
│       │   │   ├── module_data_classes.py
│       │   │   └── regular_expressions.py
│       │   ├── telemetry_passer.py
│       │   └── updatable.py
│       ├── sdk_augmentation/
│       │   ├── __init__.py
│       │   ├── command_handler.py
│       │   ├── file.py
│       │   └── printer.py
│       ├── serial/
│       │   ├── __init__.py
│       │   ├── helpers.py
│       │   ├── instruction.py
│       │   ├── is_planner_fed.py
│       │   ├── serial.py
│       │   ├── serial_adapter.py
│       │   ├── serial_parser.py
│       │   └── serial_queue.py
│       ├── service_discovery.py
│       ├── static/
│       │   ├── css/
│       │   │   ├── bootstrap.connect.css
│       │   │   └── bootstrap.prusa-link.css
│       │   ├── index.html
│       │   ├── main.9b8dc0068f6e6508dfd4.js
│       │   └── main.b3e029296dd89863b3f2.css
│       ├── templates/
│       │   ├── _footer.html
│       │   ├── _header.html
│       │   ├── _wizard.html
│       │   ├── error-gone.html
│       │   ├── error-internal-server-error.html
│       │   ├── error.html
│       │   ├── index.html
│       │   ├── link_info.html
│       │   ├── multi-instance.html
│       │   ├── wizard.html
│       │   ├── wizard_credentials.html
│       │   ├── wizard_finish.html
│       │   ├── wizard_printer.html
│       │   ├── wizard_restore.html
│       │   └── wizard_serial.html
│       ├── util.py
│       └── web/
│           ├── __init__.py
│           ├── cameras.py
│           ├── connection.py
│           ├── controls.py
│           ├── errors.py
│           ├── files.py
│           ├── files_legacy.py
│           ├── lib/
│           │   ├── __init__.py
│           │   ├── auth.py
│           │   ├── classes.py
│           │   ├── core.py
│           │   ├── files.py
│           │   ├── view.py
│           │   └── wizard.py
│           ├── link_info.py
│           ├── main.py
│           ├── settings.py
│           └── wizard.py
├── prusalink-boot
├── public/
│   └── prusalink.json
├── requirements-multi.txt
├── requirements-pi.txt
├── requirements.txt
├── ruff.toml
├── setup.py
└── tests/
    ├── __init__.py
    ├── test_carousel.py
    ├── test_ipc_queue.py
    ├── test_item_updater.py
    ├── test_serial_parser.py
    └── util.py
Download .txt
SYMBOL INDEX (1540 symbols across 83 files)

FILE: image_builder/image_builder.py
  function reporthook (line 91) | def reporthook(chunk_number, chunk_size, total_size):
  function ensure_directory (line 97) | def ensure_directory(directory):
  function run_emulator (line 103) | def run_emulator(command):
  function retry (line 125) | def retry(call, retries=3, sleep_time=1):
  function run_command (line 140) | def run_command(command, check=True, retries=1):
  function run_over_ssh (line 146) | def run_over_ssh(command, check=True, retries=1):
  function check_binary (line 151) | def check_binary(binary_name):
  function insert_from_file_before_line (line 160) | def insert_from_file_before_line(to_file, from_file, search=None, index=...
  function mount_image (line 186) | def mount_image(image_name, expand=False):
  function unmount_image (line 210) | def unmount_image(loop_device):
  function basic_image_setup (line 220) | def basic_image_setup():
  function build_image (line 236) | def build_image():
  function main (line 512) | def main():

FILE: prusa/link/__main__.py
  function excepthook (line 38) | def excepthook(exception_arguments, args, argv):
  function set_log_levels (line 58) | def set_log_levels(config: Config):
  class LogLevel (line 64) | class LogLevel(str):
    method __new__ (line 67) | def __new__(cls, level):
  function check_process (line 74) | def check_process(pid):
  function wait_process (line 83) | def wait_process(pid, timeout=1):
  function stop (line 93) | def stop(pid):
  function main (line 107) | def main():

FILE: prusa/link/camera_governor.py
  class CameraGovernor (line 18) | class CameraGovernor:
    method __init__ (line 21) | def __init__(self, camera_configurator: CameraConfigurator,
    method _govern (line 29) | def _govern(self) -> None:
    method start (line 37) | def start(self) -> None:
    method stop (line 53) | def stop(self) -> None:
    method wait_stopped (line 57) | def wait_stopped(self) -> None:

FILE: prusa/link/cameras/encoders.py
  function fopen (line 23) | def fopen(path, write=False):
  function opener (line 28) | def opener(path, flags):
  class Quality (line 33) | class Quality(Enum):
  class BufferDetails (line 42) | class BufferDetails:
    method __init__ (line 45) | def __init__(self, file_descriptor, length, offset):
    method __del__ (line 53) | def __del__(self):
  function get_appropriate_encoder (line 60) | def get_appropriate_encoder(resolution, pixel_format, use_mmap=False):
  class Encoder (line 77) | class Encoder:
    method __init__ (line 80) | def __init__(self):
    method start (line 91) | def start(self):
    method stop (line 94) | def stop(self):
    method quality (line 98) | def quality(self):
    method quality (line 103) | def quality(self, quality=Quality.HIGH):
    method encode (line 108) | def encode(self, bytes_used: int) -> bytes:
  class MJPEGEncoder (line 112) | class MJPEGEncoder(Encoder):
    method is_available (line 137) | def is_available(cls):
    method __init__ (line 155) | def __init__(self):
    method _pre_fill_format (line 172) | def _pre_fill_format(self, format_type, pixel_format):
    method _request_buffers (line 184) | def _request_buffers(self, buffer_type, memory, count=1):
    method _get_buffer (line 191) | def _get_buffer(self, buffer_type, memory):
    method _stream_on (line 203) | def _stream_on(self, buffer_type):
    method _stream_off (line 207) | def _stream_off(self, buffer_type):
    method start (line 211) | def start(self):
    method stop (line 288) | def stop(self):
    method encode (line 314) | def encode(self, bytes_used):
  class JPEGEncoder (line 354) | class JPEGEncoder(Encoder):
    method __init__ (line 364) | def __init__(self):
    method start (line 368) | def start(self):
    method encode (line 372) | def encode(self, bytes_used):
  class PassthroughEncoder (line 389) | class PassthroughEncoder(Encoder):
    method encode (line 393) | def encode(self, bytes_used: int) -> bytes:

FILE: prusa/link/cameras/picamera_driver.py
  function param_change (line 72) | def param_change(func):
  class PiCameraDriver (line 86) | class PiCameraDriver(CameraDriver):
    method _scan (line 94) | def _scan():
    method __init__ (line 113) | def __init__(self, camera_id: str, config: Dict[str, str],
    method get_resolutions (line 133) | def get_resolutions(camera: Camera, stream_role: StreamRole,
    method make_camera_configuration (line 164) | def make_camera_configuration(camera, still_resolution: Resolution,
    method _connect (line 192) | def _connect(self):
    method _start (line 235) | def _start(self):
    method _get_scalar_crop (line 248) | def _get_scalar_crop(raw_resolution, target_resolution):
    method set_resolution (line 274) | def set_resolution(self, resolution):
    method _set_resolution (line 278) | def _set_resolution(self, resolution):
    method _focus_transform (line 323) | def _focus_transform(self, value):
    method set_focus (line 331) | def set_focus(self, focus):
    method take_a_photo (line 336) | def take_a_photo(self):
    method _disconnect (line 366) | def _disconnect(self):

FILE: prusa/link/cameras/v4l2.py
  function _IOC (line 69) | def _IOC(dir_, type_, nr, size):
  function _IOC_TYPECHECK (line 77) | def _IOC_TYPECHECK(t):
  function _IO (line 81) | def _IO(type_, nr):
  function _IOW (line 85) | def _IOW(type_, nr, size):
  function _IOR (line 89) | def _IOR(type_, nr, size):
  function _IOWR (line 93) | def _IOWR(type_, nr, size):
  class timeval (line 109) | class timeval(ctypes.Structure):
  function v4l2_fourcc (line 140) | def v4l2_fourcc(a, b, c, d):
  function v4l2_fourcc2str (line 144) | def v4l2_fourcc2str(fourcc):
  function V4L2_FIELD_HAS_TOP (line 167) | def V4L2_FIELD_HAS_TOP(field):
  function V4L2_FIELD_HAS_BOTTOM (line 177) | def V4L2_FIELD_HAS_BOTTOM(field):
  function V4L2_FIELD_HAS_BOTH (line 187) | def V4L2_FIELD_HAS_BOTH(field):
  class v4l2_rect (line 299) | class v4l2_rect(ctypes.Structure):
  class v4l2_fract (line 308) | class v4l2_fract(ctypes.Structure):
  class v4l2_capability (line 319) | class v4l2_capability(ctypes.Structure):
  class v4l2_pix_format (line 368) | class v4l2_pix_format(ctypes.Structure):
  class v4l2_fmtdesc (line 474) | class v4l2_fmtdesc(ctypes.Structure):
  class v4l2_frmsize_discrete (line 500) | class v4l2_frmsize_discrete(ctypes.Structure):
  class v4l2_frmsize_stepwise (line 507) | class v4l2_frmsize_stepwise(ctypes.Structure):
  class v4l2_frmsizeenum (line 518) | class v4l2_frmsizeenum(ctypes.Structure):
    class _u (line 519) | class _u(ctypes.Union):
  class v4l2_frmival_stepwise (line 548) | class v4l2_frmival_stepwise(ctypes.Structure):
  class v4l2_frmivalenum (line 556) | class v4l2_frmivalenum(ctypes.Structure):
    class _u (line 557) | class _u(ctypes.Union):
  class v4l2_timecode (line 580) | class v4l2_timecode(ctypes.Structure):
  class v4l2_jpegcompression (line 605) | class v4l2_jpegcompression(ctypes.Structure):
  class v4l2_plane (line 629) | class v4l2_plane(ctypes.Structure):
    class _u (line 630) | class _u(ctypes.Union):
  class v4l2_requestbuffers (line 642) | class v4l2_requestbuffers(ctypes.Structure):
  class v4l2_buffer (line 651) | class v4l2_buffer(ctypes.Structure):
    class _u (line 652) | class _u(ctypes.Union):
  class v4l2_framebuffer (line 691) | class v4l2_framebuffer(ctypes.Structure):
  class v4l2_clip (line 717) | class v4l2_clip(ctypes.Structure):
  class v4l2_window (line 725) | class v4l2_window(ctypes.Structure):
  class v4l2_captureparm (line 741) | class v4l2_captureparm(ctypes.Structure):
  class v4l2_outputparm (line 756) | class v4l2_outputparm(ctypes.Structure):
  class v4l2_cropcap (line 771) | class v4l2_cropcap(ctypes.Structure):
  class v4l2_crop (line 780) | class v4l2_crop(ctypes.Structure):
  class v4l2_standard (line 848) | class v4l2_standard(ctypes.Structure):
  class v4l2_dv_preset (line 863) | class v4l2_dv_preset(ctypes.Structure):
  class v4l2_dv_enum_preset (line 874) | class v4l2_dv_enum_preset(ctypes.Structure):
  class v4l2_bt_timings (line 913) | class v4l2_bt_timings(ctypes.Structure):
  class v4l2_dv_timings (line 943) | class v4l2_dv_timings(ctypes.Structure):
    class _u (line 944) | class _u(ctypes.Union):
  class v4l2_input (line 967) | class v4l2_input(ctypes.Structure):
  class v4l2_output (line 1009) | class v4l2_output(ctypes.Structure):
  class v4l2_control (line 1033) | class v4l2_control(ctypes.Structure):
  class v4l2_ext_control (line 1040) | class v4l2_ext_control(ctypes.Structure):
    class _u (line 1041) | class _u(ctypes.Union):
  class v4l2_ext_controls (line 1058) | class v4l2_ext_controls(ctypes.Structure):
  function V4L2_CTRL_ID_MASK (line 1074) | def V4L2_CTRL_ID_MASK():
  function V4L2_CTRL_ID2CLASS (line 1078) | def V4L2_CTRL_ID2CLASS(id_):
  function V4L2_CTRL_DRIVER_PRIV (line 1082) | def V4L2_CTRL_DRIVER_PRIV(id_):
  class v4l2_queryctrl (line 1086) | class v4l2_queryctrl(ctypes.Structure):
  class v4l2_querymenu (line 1100) | class v4l2_querymenu(ctypes.Structure):
  class v4l2_tuner (line 1539) | class v4l2_tuner(ctypes.Structure):
  class v4l2_modulator (line 1555) | class v4l2_modulator(ctypes.Structure):
  class v4l2_frequency (line 1590) | class v4l2_frequency(ctypes.Structure):
  class v4l2_hw_freq_seek (line 1599) | class v4l2_hw_freq_seek(ctypes.Structure):
  class v4l2_rds_data (line 1613) | class v4l2_rds_data(ctypes.Structure):
  class v4l2_audio (line 1639) | class v4l2_audio(ctypes.Structure):
  class v4l2_audioout (line 1655) | class v4l2_audioout(ctypes.Structure):
  class v4l2_enc_idx_entry (line 1675) | class v4l2_enc_idx_entry(ctypes.Structure):
  class v4l2_enc_idx (line 1688) | class v4l2_enc_idx(ctypes.Structure):
  class v4l2_encoder_cmd (line 1705) | class v4l2_encoder_cmd(ctypes.Structure):
    class _u (line 1706) | class _u(ctypes.Union):
      class _s (line 1707) | class _s(ctypes.Structure):
  class v4l2_vbi_format (line 1729) | class v4l2_vbi_format(ctypes.Structure):
  class v4l2_sliced_vbi_format (line 1746) | class v4l2_sliced_vbi_format(ctypes.Structure):
  class v4l2_sliced_vbi_cap (line 1764) | class v4l2_sliced_vbi_cap(ctypes.Structure):
  class v4l2_sliced_vbi_data (line 1773) | class v4l2_sliced_vbi_data(ctypes.Structure):
  class v4l2_mpeg_vbi_itv0_line (line 1794) | class v4l2_mpeg_vbi_itv0_line(ctypes.Structure):
  class v4l2_mpeg_vbi_itv0 (line 1803) | class v4l2_mpeg_vbi_itv0(ctypes.Structure):
  class v4l2_mpeg_vbi_ITV0 (line 1812) | class v4l2_mpeg_vbi_ITV0(ctypes.Structure):
  class v4l2_mpeg_vbi_fmt_ivtv (line 1824) | class v4l2_mpeg_vbi_fmt_ivtv(ctypes.Structure):
    class _u (line 1825) | class _u(ctypes.Union):
  class v4l2_plane_pix_format (line 1844) | class v4l2_plane_pix_format(ctypes.Structure):
  class v4l2_sdr_format (line 1851) | class v4l2_sdr_format(ctypes.Structure):
  class v4l2_meta_format (line 1858) | class v4l2_meta_format(ctypes.Structure):
  class v4l2_pix_format_mplane (line 1864) | class v4l2_pix_format_mplane(ctypes.Structure):
    class _u (line 1865) | class _u(ctypes.Union):
  class v4l2_format (line 1886) | class v4l2_format(ctypes.Structure):
    class _u (line 1887) | class _u(ctypes.Union):
  class v4l2_streamparm (line 1905) | class v4l2_streamparm(ctypes.Structure):
    class _u (line 1906) | class _u(ctypes.Union):
  class v4l2_dbg_match (line 1929) | class v4l2_dbg_match(ctypes.Structure):
    class _u (line 1930) | class _u(ctypes.Union):
  class v4l2_dbg_register (line 1945) | class v4l2_dbg_register(ctypes.Structure):
  class v4l2_dbg_chip_ident (line 1956) | class v4l2_dbg_chip_ident(ctypes.Structure):

FILE: prusa/link/cameras/v4l2_driver.py
  class Info (line 40) | class Info(NamedTuple):
  class ImageFormat (line 53) | class ImageFormat(NamedTuple):
  class FrameType (line 61) | class FrameType(NamedTuple):
  class FocusInfo (line 68) | class FocusInfo(NamedTuple):
  function frame_sizes (line 81) | def frame_sizes(file_descriptor, pixel_formats):
  function read_capabilities (line 104) | def read_capabilities(file_descriptor):
  function read_info (line 111) | def read_info(filename):
  function fopen (line 192) | def fopen(path, write=False):
  function opener (line 197) | def opener(path, flags):
  function iter_video_files (line 202) | def iter_video_files(path="/dev"):
  function iter_devices (line 208) | def iter_devices(path="/dev"):
  function iter_video_capture_devices (line 213) | def iter_video_capture_devices(path="/dev"):
  class MediaDeviceInfo (line 225) | class MediaDeviceInfo(ctypes.Structure):
  function read_media_device_info (line 247) | def read_media_device_info(path):
  class V4L2Camera (line 260) | class V4L2Camera:
    method __init__ (line 267) | def __init__(self, path):
    method _ioctl (line 282) | def _ioctl(self, request, arg: Any = 0):
    method is_stopped (line 287) | def is_stopped(self):
    method _set_format (line 291) | def _set_format(self):
    method _set_fps (line 308) | def _set_fps(self):
    method _buffer_request (line 323) | def _buffer_request(self, count=1):
    method _v4l2_buffer (line 338) | def _v4l2_buffer(self):
    method start (line 346) | def start(self):
    method stop (line 387) | def stop(self):
    method next_frame (line 403) | def next_frame(self):
    method set_focus (line 418) | def set_focus(self, value):
  function get_media_device_path (line 436) | def get_media_device_path(device: V4L2Camera):
  function param_change (line 456) | def param_change(func):
  class V4L2Driver (line 472) | class V4L2Driver(CameraDriver):
    method _scan (line 481) | def _scan():
    method __init__ (line 516) | def __init__(self, camera_id, config, unavailable_cb):
    method _connect (line 525) | def _connect(self):
    method set_resolution (line 588) | def set_resolution(self, resolution):
    method _set_resolution (line 592) | def _set_resolution(self, resolution):
    method set_focus (line 607) | def set_focus(self, focus):
    method take_a_photo (line 611) | def take_a_photo(self):
    method _disconnect (line 617) | def _disconnect(self):

FILE: prusa/link/conditions.py
  function use_connect_errors (line 88) | def use_connect_errors(use_connect):
  function status (line 98) | def status():
  function printer_status (line 106) | def printer_status():
  function connect_status (line 114) | def connect_status():
  class LinkError (line 124) | class LinkError(RuntimeError):
    method __init__ (line 135) | def __init__(self, details: str = ""):
    method set_url (line 147) | def set_url(self, req):
    method gen_headers (line 151) | def gen_headers(self):
    method json_response (line 155) | def json_response(self):
    method text_response (line 167) | def text_response(self):
  class BadRequestError (line 179) | class BadRequestError(LinkError):
  class TemperatureTooLow (line 184) | class TemperatureTooLow(BadRequestError):
  class TemperatureTooHigh (line 191) | class TemperatureTooHigh(BadRequestError):
  class ValueTooLow (line 198) | class ValueTooLow(BadRequestError):
  class ValueTooHigh (line 205) | class ValueTooHigh(BadRequestError):
  class CantMoveAxis (line 212) | class CantMoveAxis(BadRequestError):
  class CantMoveAxisZ (line 219) | class CantMoveAxisZ(BadRequestError):
  class DestinationSameAsSource (line 226) | class DestinationSameAsSource(BadRequestError):
  class NoFileInRequest (line 233) | class NoFileInRequest(BadRequestError):
  class FileSizeMismatch (line 240) | class FileSizeMismatch(BadRequestError):
  class InvalidIniFileFormat (line 247) | class InvalidIniFileFormat(BadRequestError):
  class InvalidBooleanHeader (line 254) | class InvalidBooleanHeader(BadRequestError):
  class ForbiddenCharacters (line 261) | class ForbiddenCharacters(BadRequestError):
  class FilenameTooLong (line 268) | class FilenameTooLong(BadRequestError):
  class FoldernameTooLong (line 275) | class FoldernameTooLong(BadRequestError):
  class FileUploadFailed (line 282) | class FileUploadFailed(BadRequestError):
  class CantConnect (line 289) | class CantConnect(BadRequestError):
  class CantResolveHostname (line 296) | class CantResolveHostname(BadRequestError):
  class NotSupportedFileType (line 303) | class NotSupportedFileType(LinkError):
  class ForbiddenError (line 311) | class ForbiddenError(LinkError):
  class NotFoundError (line 319) | class NotFoundError(LinkError):
  class NotCurrentJob (line 327) | class NotCurrentJob(NotFoundError):
  class GoneError (line 333) | class GoneError(LinkError):
  class ThumbnailUnavailable (line 341) | class ThumbnailUnavailable(GoneError):
  class FileNotFound (line 347) | class FileNotFound(NotFoundError):
  class FolderNotFound (line 353) | class FolderNotFound(NotFoundError):
  class LocationNotFound (line 359) | class LocationNotFound(NotFoundError):
  class ConflictError (line 367) | class ConflictError(LinkError):
  class DirectoryNotEmpty (line 372) | class DirectoryNotEmpty(ConflictError):
  class CurrentlyPrinting (line 379) | class CurrentlyPrinting(ConflictError):
  class NotStateToPrint (line 385) | class NotStateToPrint(ConflictError):
  class NotPrinting (line 392) | class NotPrinting(ConflictError):
  class NotPaused (line 398) | class NotPaused(ConflictError):
  class FileCurrentlyPrinted (line 404) | class FileCurrentlyPrinted(ConflictError):
  class TransferConflict (line 412) | class TransferConflict(ConflictError):
  class TransferStopped (line 420) | class TransferStopped(ConflictError):
  class UnavailableUpdate (line 427) | class UnavailableUpdate(ConflictError):
  class UnableToUpdate (line 435) | class UnableToUpdate(ConflictError):
  class LengthRequired (line 443) | class LengthRequired(LinkError):
  class EntityTooLarge (line 451) | class EntityTooLarge(LinkError):
  class FileAlreadyExists (line 459) | class FileAlreadyExists(LinkError):
  class FolderAlreadyExists (line 467) | class FolderAlreadyExists(LinkError):
  class StorageNotExist (line 475) | class StorageNotExist(LinkError):
  class SDCardReadOnly (line 483) | class SDCardReadOnly(LinkError):
  class SDCardNotSupported (line 491) | class SDCardNotSupported(LinkError):
  class UnsupportedMediaError (line 499) | class UnsupportedMediaError(LinkError):
  class InternalServerError (line 507) | class InternalServerError(LinkError):
  class ResponseTimeout (line 516) | class ResponseTimeout(InternalServerError):
  class PrinterUnavailable (line 523) | class PrinterUnavailable(LinkError):
  class RequestTimeout (line 531) | class RequestTimeout(LinkError):

FILE: prusa/link/config.py
  function get_log_level_dict (line 27) | def get_log_level_dict(log_levels: Iterable[str]):
  function check_log_level (line 40) | def check_log_level(value):
  function check_server_type (line 46) | def check_server_type(value):
  class Model (line 52) | class Model(dict):
    method __getattr__ (line 57) | def __getattr__(self, key):
    method __setattr__ (line 63) | def __setattr__(self, key, val):
    method get (line 67) | def get(cfg, name, options):
  class FakeArgs (line 71) | class FakeArgs:
    method __init__ (line 74) | def __init__(self, path):
  class Config (line 89) | class Config(Get):
    method __init__ (line 93) | def __init__(self, args):
    method set_section (line 204) | def set_section(self, name, model):
    method update_sections (line 220) | def update_sections(self):
    method set_global_log_level (line 228) | def set_global_log_level(self, args):
    method get_log_handler (line 241) | def get_log_handler(self, args):
  class Settings (line 263) | class Settings(Get):
    method __init__ (line 271) | def __init__(self, settings_file):
    method set_section (line 321) | def set_section(self, name, model):
    method update_sections (line 328) | def update_sections(self, connect_skip=False):
    method is_wizard_needed (line 336) | def is_wizard_needed(self):
    method use_connect (line 346) | def use_connect(self):

FILE: prusa/link/const.py
  class LimitsFDM (line 157) | class LimitsFDM:
  class LimitsMK25 (line 195) | class LimitsMK25(LimitsFDM):
  class LimitsMK25S (line 204) | class LimitsMK25S(LimitsFDM):
  class LimitsMK3 (line 213) | class LimitsMK3(LimitsFDM):
  class LimitsMK3S (line 222) | class LimitsMK3S(LimitsFDM):

FILE: prusa/link/daemon.py
  class Daemon (line 18) | class Daemon:
    method __init__ (line 23) | def __init__(self, config, argv: List):
    method run (line 35) | def run(self, daemon=True):
    method restart (line 72) | def restart(argv: List):
    method sigterm (line 82) | def sigterm(self, *_):

FILE: prusa/link/interesting_logger.py
  class DecoySrcfile (line 17) | class DecoySrcfile:
    method __init__ (line 35) | def __init__(self):
    method __eq__ (line 38) | def __eq__(self, other):
    method __hash__ (line 41) | def __hash__(self):
  class InterestingLogRotator (line 49) | class InterestingLogRotator(metaclass=MCSingleton):
    method __init__ (line 55) | def __init__(self):
    method skip_logger (line 61) | def skip_logger(self, logger_to_skip):
    method is_skipped (line 75) | def is_skipped(self, logger_name):
    method process_log_entry (line 79) | def process_log_entry(self, got_printed, level, msg, *args, **kwargs):
    method _log (line 94) | def _log(level, msg, *args, **kwargs):
    method trigger (line 103) | def trigger(by_what: str):
    method instance_trigger (line 111) | def instance_trigger(self, by_what: str):
  class InterestingLogger (line 147) | class InterestingLogger(Logger):
    method __init__ (line 150) | def __init__(self, name, level=NOTSET):
    method is_skipped (line 156) | def is_skipped(self):
    method debug (line 180) | def debug(self, msg, *args, **kwargs):
    method info (line 190) | def info(self, msg, *args, **kwargs):
    method warning (line 200) | def warning(self, msg, *args, **kwargs):
    method error (line 210) | def error(self, msg, *args, **kwargs):
    method critical (line 220) | def critical(self, msg, *args, **kwargs):
    method log (line 230) | def log(self, level, msg, *args, **kwargs):

FILE: prusa/link/multi_instance/__main__.py
  function main_thread_exception (line 36) | def main_thread_exception(exc_type, exc_value, exc_traceback):
  function thread_exception (line 46) | def thread_exception(_):
  function get_logger_file_descriptors (line 56) | def get_logger_file_descriptors():
  class Manager (line 67) | class Manager:
    method __init__ (line 72) | def __init__(self, user_info, prepend_executables_with):
    method _sigterm_handler (line 96) | def _sigterm_handler(self, *_):
  class Server (line 102) | class Server:
    method __init__ (line 107) | def __init__(self, user_info):
    method _sigterm_handler (line 133) | def _sigterm_handler(self, *_):
  function get_username (line 140) | def get_username(username=None):
  function start (line 158) | def start(user_info, prepend_executables_with):
  function handle_process_stop (line 168) | def handle_process_stop(pid_file, name="Process", quiet=False):
  function stop (line 182) | def stop(quiet=False):
  function clean (line 207) | def clean(user_info, prepend_executables_with):
  function rescan (line 214) | def rescan():
  function main (line 223) | def main():

FILE: prusa/link/multi_instance/config_component.py
  class MultiInstanceConfig (line 37) | class MultiInstanceConfig(Get):
    method __init__ (line 40) | def __init__(self):
    method add (line 64) | def add(self, printer_number, serial_number, config_path):
    method add_from_section (line 81) | def add_from_section(self, section_name: str):
    method save (line 102) | def save(self):
  class ConfigComponent (line 130) | class ConfigComponent:
    method __init__ (line 133) | def __init__(self, multi_instance_config, user_info,
    method configure_instance (line 144) | def configure_instance(self, printer: PrinterDevice, printer_number):
    method remove_all_printers (line 178) | def remove_all_printers(self):
    method is_configured (line 184) | def is_configured(self, serial_number):
    method _get_highest_printer_number (line 192) | def _get_highest_printer_number(self):
    method configure_new (line 199) | def configure_new(self):
    method setup_connected_trigger (line 228) | def setup_connected_trigger(self):
    method teardown_connected_trigger (line 251) | def teardown_connected_trigger(self):
    method _create_udev_rule (line 258) | def _create_udev_rule(self, printer: PrinterDevice, printer_number):
    method _create_printer_config (line 291) | def _create_printer_config(self,
    method remove_printers (line 333) | def remove_printers(self, numbers_to_remove: List[int]):
    method refresh_udev_rules (line 399) | def refresh_udev_rules():
    method delete_file (line 405) | def delete_file(path):
    method delete_folder (line 414) | def delete_folder(path):
    method wait_for_symlink (line 423) | def wait_for_symlink(symlink_path):

FILE: prusa/link/multi_instance/controller.py
  class Controller (line 14) | class Controller:
    method __init__ (line 17) | def __init__(self, user_info, prepend_executables_with):
    method run (line 39) | def run(self):
    method rescan (line 49) | def rescan(self):
    method stop (line 59) | def stop(self):
    method remove_all_printers (line 64) | def remove_all_printers(self):
    method config_changed (line 68) | def config_changed(self, *_):

FILE: prusa/link/multi_instance/ipc_queue_adapter.py
  function get_queue_path (line 16) | def get_queue_path(queue_name):
  class IPCConsumer (line 24) | class IPCConsumer:
    method __init__ (line 27) | def __init__(self,
    method add_handler (line 46) | def add_handler(self, command: str, handler: Callable[[], None]):
    method start (line 51) | def start(self):
    method stop (line 57) | def stop(self):
    method _setup_queue (line 63) | def _setup_queue(self):
    method _read_commands (line 76) | def _read_commands(self):
  class IPCSender (line 107) | class IPCSender:
    method send_and_close (line 111) | def send_and_close(queue_name, command, *args, **kwargs):
    method __init__ (line 118) | def __init__(self, queue_name):
    method send (line 127) | def send(self, command, *args, **kwargs):
    method close (line 143) | def close(self):
    method __del__ (line 147) | def __del__(self):

FILE: prusa/link/multi_instance/runner_component.py
  class LoadedInstance (line 16) | class LoadedInstance:
    method __init__ (line 19) | def __init__(self, config: Config, config_path: str):
  class RunnerComponent (line 24) | class RunnerComponent:
    method __init__ (line 27) | def __init__(self, multi_instance_config, user_info,
    method start_configured (line 34) | def start_configured(self):
    method load_instance (line 51) | def load_instance(self, config_path: str):

FILE: prusa/link/multi_instance/web.py
  class InfoKeeper (line 28) | class InfoKeeper:
    class PrinterInfo (line 30) | class PrinterInfo:
      method __init__ (line 32) | def __init__(self, number, name, port):
    method __init__ (line 37) | def __init__(self):
    method refresh (line 45) | def refresh(self):
    method printer_info (line 50) | def printer_info(self):
  class MultInstanceApp (line 70) | class MultInstanceApp(Application):
  function single_instance_redirect (line 86) | def single_instance_redirect(func):
  function get_web_server (line 103) | def get_web_server(port):
  function index (line 113) | def index(req):
  function list_printers (line 122) | def list_printers(req):
  function get_content_length (line 138) | def get_content_length(headers):
  function file_data_generator (line 146) | def file_data_generator(file_like, length):
  function proxy (line 162) | def proxy(req, printer_number, path="/", use_proxy_headers=True):
  function fallback (line 209) | def fallback(req):

FILE: prusa/link/printer_adapter/auto_telemetry.py
  class AutoTelemetry (line 22) | class AutoTelemetry(ThreadedUpdatable):
    method __init__ (line 30) | def __init__(self, serial_parser: ThreadedSerialParser,
    method temps_recorded (line 52) | def temps_recorded(self, sender, match: Match):
    method positions_recorded (line 69) | def positions_recorded(self, sender, match: Match):
    method fans_recorded (line 83) | def fans_recorded(self, sender, match: Match):
    method update (line 100) | def update(self):
    method turn_reporting_on (line 112) | def turn_reporting_on(self):
    method proper_stop (line 122) | def proper_stop(self):
    method _reset_last_seen (line 132) | def _reset_last_seen(self):

FILE: prusa/link/printer_adapter/command.py
  class CommandFailed (line 27) | class CommandFailed(Exception):
  class NotStateToPrint (line 31) | class NotStateToPrint(CommandFailed):
  class FileNotFound (line 35) | class FileNotFound(CommandFailed):
  class Command (line 40) | class Command:
    method __init__ (line 47) | def __init__(self, command_id=None, source=Source.CONNECT) -> None:
    method wait_while_running (line 64) | def wait_while_running(self, instruction):
    method do_instruction (line 68) | def do_instruction(self, message):
    method do_matchable (line 77) | def do_matchable(self, message, regexp: re.Pattern):
    method wait_for_instruction (line 87) | def wait_for_instruction(self, instruction):
    method run_command (line 94) | def run_command(self) -> Dict[str, Any]:
    method _run_command (line 104) | def _run_command(self):
    method stop (line 107) | def stop(self):

FILE: prusa/link/printer_adapter/command_handlers.py
  function check_update_prusalink (line 52) | def check_update_prusalink():
  function update_prusalink (line 59) | def update_prusalink():
  function change_reset_mode (line 67) | def change_reset_mode(model, serial_adapter, serial_parser, quit_evt,
  class TryUntilState (line 115) | class TryUntilState(Command):
    method __init__ (line 119) | def __init__(self, command_id=None, source=Source.CONNECT):
    method _try_until_state (line 128) | def _try_until_state(self, gcode: str, desired_states: Set[State]):
    method _run_command (line 185) | def _run_command(self):
  class StopPrint (line 189) | class StopPrint(TryUntilState):
    method _run_command (line 193) | def _run_command(self):
  class PausePrint (line 209) | class PausePrint(TryUntilState):
    method _run_command (line 213) | def _run_command(self):
  class ResumePrint (line 227) | class ResumePrint(TryUntilState):
    method _run_command (line 231) | def _run_command(self):
  class StartPrint (line 248) | class StartPrint(Command):
    method __init__ (line 252) | def __init__(self, path: str, **kwargs):
    method _run_command (line 256) | def _run_command(self):
    method _start_file_print (line 304) | def _start_file_print(self, path):
    method _load_file (line 312) | def _load_file(self, raw_sd_path: str) -> None:
    method _start_print (line 326) | def _start_print(self):
  class ExecuteGcode (line 331) | class ExecuteGcode(Command):
    method __init__ (line 335) | def __init__(self, gcode, force=False, **kwargs):
    method _run_command (line 345) | def _run_command(self):
    method _get_state_change (line 396) | def _get_state_change(default_source):
  class FilamentCommand (line 400) | class FilamentCommand(Command):
    method __init__ (line 403) | def __init__(self, parameters: Optional[Dict], **kwargs):
    method prepare_for_load_unload (line 407) | def prepare_for_load_unload(self):
    method _run_command (line 443) | def _run_command(self):
  class LoadFilament (line 447) | class LoadFilament(FilamentCommand):
    method _run_command (line 452) | def _run_command(self):
  class UnloadFilament (line 464) | class UnloadFilament(FilamentCommand):
    method _run_command (line 469) | def _run_command(self):
  class ResetPrinter (line 476) | class ResetPrinter(Command):
    method _run_command (line 485) | def _run_command(self):
  class UpgradeLink (line 537) | class UpgradeLink(Command):
    method _run_command (line 541) | def _run_command(self):
  class JobInfo (line 559) | class JobInfo(Command):
    method _run_command (line 563) | def _run_command(self):
  class SetReady (line 595) | class SetReady(Command):
    method _run_command (line 599) | def _run_command(self):
  class CancelReady (line 612) | class CancelReady(Command):
    method _run_command (line 616) | def _run_command(self):
  class RePrint (line 630) | class RePrint(StartPrint):
    method __init__ (line 634) | def __init__(self, **kwargs):
    method _run_command (line 642) | def _run_command(self):
  class DisableResets (line 654) | class DisableResets(Command):
    method _run_command (line 659) | def _run_command(self):
  class EnableResets (line 665) | class EnableResets(Command):
    method _run_command (line 670) | def _run_command(self):
  class PPRecovery (line 676) | class PPRecovery(Command):
    method _run_command (line 680) | def _run_command(self):

FILE: prusa/link/printer_adapter/command_queue.py
  class CommandAdapter (line 23) | class CommandAdapter:
    method __init__ (line 27) | def __init__(self, command) -> None:
  class CommandQueue (line 34) | class CommandQueue:
    method __init__ (line 40) | def __init__(self) -> None:
    method start (line 49) | def start(self) -> None:
    method stop (line 54) | def stop(self) -> None:
    method enqueue_command (line 59) | def enqueue_command(self, command: Command) -> CommandAdapter:
    method do_command (line 69) | def do_command(self, command: Command):
    method force_command (line 92) | def force_command(self, command: Command):
    method process_queue (line 98) | def process_queue(self) -> None:
    method _stop_current (line 119) | def _stop_current(self):
    method clear_queue (line 124) | def clear_queue(self):

FILE: prusa/link/printer_adapter/file_printer.py
  class FilePrinter (line 39) | class FilePrinter(metaclass=MCSingleton):
    method __init__ (line 47) | def __init__(self, serial_queue: SerialQueue,
    method start (line 86) | def start(self) -> None:
    method stop (line 90) | def stop(self) -> None:
    method wait_stopped (line 95) | def wait_stopped(self) -> None:
    method pp_exists (line 101) | def pp_exists(self) -> bool:
    method print (line 105) | def print(self, os_path: str, from_gcode_number=None) -> None:
    method power_panic (line 130) | def power_panic(self) -> None:
    method _print (line 138) | def _print(self, from_gcode_number=None):
    method _print_pause (line 209) | def _print_pause(self):
    method _print_end (line 230) | def _print_end(self):
    method do_instruction (line 252) | def do_instruction(self, message):
    method print_gcode (line 261) | def print_gcode(self, gcode):
    method wait_for_queue (line 284) | def wait_for_queue(self) -> None:
    method react_to_gcode (line 302) | def react_to_gcode(self, gcode):
    method send_print_stats (line 314) | def send_print_stats(self):
    method to_print_stats (line 333) | def to_print_stats(self, gcode_number):
    method pause (line 346) | def pause(self):
    method resume (line 353) | def resume(self):
    method stop_print (line 366) | def stop_print(self):
    method write_file_stats (line 375) | def write_file_stats(self, file_path, message_number, gcode_number):
    method serial_message_number_changed (line 388) | def serial_message_number_changed(self, message_number):

FILE: prusa/link/printer_adapter/filesystem/sd_card.py
  function alternative_filename (line 45) | def alternative_filename(long_filename: str,
  function get_root (line 60) | def get_root():
  class FileTreeParser (line 65) | class FileTreeParser:
    method __init__ (line 70) | def __init__(self, matches):
    method check_uniqueness (line 98) | def check_uniqueness(self, path: Path):
    method parse_file (line 105) | def parse_file(self, groups):
    method parse_dir (line 171) | def parse_dir(self, groups):
  class SDCard (line 195) | class SDCard(ThreadedUpdatable):
    method __init__ (line 219) | def __init__(self, serial_queue: SerialQueue,
    method handle_special_menu (line 251) | def handle_special_menu(self, file_tree_parser):
    method update (line 269) | def update(self):
    method _set_files (line 325) | def _set_files(self, file_tree_parser: FileTreeParser):
    method set_flash_air (line 335) | def set_flash_air(self, is_flash_air):
    method _construct_file_tree (line 342) | def _construct_file_tree(self) -> FileTreeParser:
    method sd_inserted (line 371) | def sd_inserted(self, sender, match: re.Match):
    method sd_ejected (line 387) | def sd_ejected(self, sender, match: re.Match):
    method _sd_state_changed (line 398) | def _sd_state_changed(self, new_state):
    method decide_presence (line 422) | def decide_presence(self):

FILE: prusa/link/printer_adapter/filesystem/storage.py
  class Storage (line 29) | class Storage(ThreadedUpdatable):
    method __init__ (line 37) | def __init__(self, model: Model):
    method update (line 65) | def update(self):
    method filter_blacklisted_paths (line 86) | def filter_blacklisted_paths(candidate_list, black_list):
    method filter_blacklisted_names (line 96) | def filter_blacklisted_names(candidate_list, black_list):
    method is_path_blacklisted (line 106) | def is_path_blacklisted(candidate, black_list):
    method is_name_blacklisted (line 117) | def is_name_blacklisted(candidate, black_list):
    method _get_clean_paths (line 129) | def _get_clean_paths(dirty_paths):
    method get_differences (line 135) | def get_differences(self, new_storage_set: Set[str]):
    method get_storage (line 142) | def get_storage(self) -> Set[str]:
    method get_data_object (line 149) | def get_data_object(self) -> StorageData:
  class FilesystemStorage (line 156) | class FilesystemStorage(Storage):
    method __init__ (line 164) | def __init__(self, model: Model, cfg: Config):
    method get_data_object (line 184) | def get_data_object(self) -> StorageData:
    method get_storage (line 187) | def get_storage(self) -> Set[str]:
    method storage_belongs (line 213) | def storage_belongs(self, path, fs_type):
    method stop (line 219) | def stop(self):
  class FolderStorage (line 225) | class FolderStorage(Storage):
    method __init__ (line 231) | def __init__(self, model: Model, cfg: Config):
    method get_data_object (line 248) | def get_data_object(self) -> StorageData:
    method get_storage (line 255) | def get_storage(self) -> Set[str]:
    method dir_belongs (line 273) | def dir_belongs(directory: str):

FILE: prusa/link/printer_adapter/filesystem/storage_controller.py
  class StorageController (line 23) | class StorageController:
    method __init__ (line 30) | def __init__(self, cfg, serial_queue: SerialQueue,
    method folder_attached (line 58) | def folder_attached(self, sender, path: str):
    method folder_detached (line 63) | def folder_detached(self, sender, path: str):
    method sd_attached (line 68) | def sd_attached(self, sender, files: File):
    method sd_detached (line 73) | def sd_detached(self, sender):
    method menu_found (line 78) | def menu_found(self, _, menu_sfn):
    method update (line 82) | def update(self):
    method start (line 88) | def start(self):
    method stop (line 94) | def stop(self):
    method wait_stopped (line 100) | def wait_stopped(self):

FILE: prusa/link/printer_adapter/ip_updater.py
  class IPUpdater (line 25) | class IPUpdater(ThreadedUpdatable):
    method __init__ (line 33) | def __init__(self, model: Model, serial_queue: SerialQueue):
    method get_mac (line 53) | def get_mac(card):
    method update_additional_info (line 63) | def update_additional_info(self, ip):
    method update (line 102) | def update(self):
    method update_ip (line 118) | def update_ip(self):
    method update_ip6 (line 143) | def update_ip6(self):
    method send_ip_to_printer (line 161) | def send_ip_to_printer(self,
    method proper_stop (line 190) | def proper_stop(self):

FILE: prusa/link/printer_adapter/job.py
  class Job (line 26) | class Job(metaclass=MCSingleton):
    method __init__ (line 30) | def __init__(self,
    method file_opened (line 60) | def file_opened(self, _, match: re.Match):
    method process_mixed_path (line 67) | def process_mixed_path(self, mixed_path):
    method job_started (line 85) | def job_started(self, command_id=None):
    method job_ended (line 111) | def job_ended(self):
    method state_changed (line 122) | def state_changed(self, command_id=None):
    method tick (line 135) | def tick(self):
    method change_state (line 140) | def change_state(self, state: JobState):
    method write (line 147) | def write(self):
    method set_file_path (line 158) | def set_file_path(self, path, path_incomplete, prepend_sd_storage):
    method update_last_job_path (line 202) | def update_last_job_path(self):
    method get_job_info_data (line 208) | def get_job_info_data(self, for_connect=False):
    method progress_broken (line 232) | def progress_broken(self, progress_broken):
    method file_position (line 246) | def file_position(self, current, total):
    method job_info_updated (line 260) | def job_info_updated(self):
    method select_file (line 270) | def select_file(self, path):
    method deselect_file (line 281) | def deselect_file(self):
    method job_id_from_eeprom (line 289) | def job_id_from_eeprom(self, job_id):

FILE: prusa/link/printer_adapter/keepalive.py
  class KeepaliveMode (line 12) | class KeepaliveMode(Enum):
  class Keepalive (line 18) | class Keepalive(metaclass=MCSingleton):
    method __init__ (line 21) | def __init__(self, serial_queue: SerialQueue):
    method start (line 33) | def start(self):
    method set_use_connect (line 37) | def set_use_connect(self, use_connect: bool):
    method _keepalive (line 43) | def _keepalive(self):
    method stop (line 57) | def stop(self):
    method wait_stopped (line 62) | def wait_stopped(self):

FILE: prusa/link/printer_adapter/lcd_printer.py
  function through_queue (line 100) | def through_queue(func):
  class LCDPrinter (line 112) | class LCDPrinter(metaclass=MCSingleton):
    method __init__ (line 116) | def __init__(self,
    method start (line 200) | def start(self):
    method lcd_updated (line 204) | def lcd_updated(self, sender, match):
    method _message_and_disable (line 221) | def _message_and_disable(self, screen: Screen, message):
    method whats_going_on (line 228) | def whats_going_on(self):
    method _check_printing (line 239) | def _check_printing(self):
    method _filter_http (line 261) | def _filter_http(self, error):
    method _check_errors (line 277) | def _check_errors(self):
    method _check_wizard (line 333) | def _check_wizard(self):
    method _get_progress_graphic (line 359) | def _get_progress_graphic(self, progress, sync_type: TransferType):
    method _check_upload (line 395) | def _check_upload(self):
    method _check_ready (line 424) | def _check_ready(self):
    method _check_recovery (line 441) | def _check_recovery(self):
    method _check_idle (line 448) | def _check_idle(self):
    method get_wait_interval (line 479) | def get_wait_interval(self):
    method should_advance_carousel (line 489) | def should_advance_carousel(self):
    method _lcd_printer (line 500) | def _lcd_printer(self):
    method _print (line 521) | def _print(self, line: LCDLine, to_wait=None):
    method _reset_idle (line 550) | def _reset_idle(self):
    method stop (line 554) | def stop(self, fast=False):
    method wait_stopped (line 565) | def wait_stopped(self):
    method add_event (line 569) | def add_event(self, handler):
    method notify (line 574) | def notify(self):
    method reset_error_grace (line 578) | def reset_error_grace(self):
    method print_message (line 583) | def print_message(self, line: LCDLine):

FILE: prusa/link/printer_adapter/mmu_observer.py
  class MMUObserver (line 23) | class MMUObserver:
    method __init__ (line 27) | def __init__(self,
    method _prime_q0 (line 60) | def _prime_q0(self, _, match: Match) -> None:
    method _handle_mmu_progress (line 65) | def _handle_mmu_progress(self, _, match: Match):
    method _handle_active_slot (line 76) | def _handle_active_slot(self, _, match: Match):
    method _handle_mmu_error (line 90) | def _handle_mmu_error(self, error_code):
    method _handle_mmu_no_error (line 103) | def _handle_mmu_no_error(self):
    method _handle_q0_response (line 108) | def _handle_q0_response(self, _, match: Match):

FILE: prusa/link/printer_adapter/model.py
  class Model (line 18) | class Model(metaclass=MCSingleton):
    method __init__ (line 40) | def __init__(self) -> None:

FILE: prusa/link/printer_adapter/print_stat_doubler.py
  class PrintStatDoubler (line 13) | class PrintStatDoubler:
    method __init__ (line 22) | def __init__(self, serial_parser: ThreadedSerialParser,
    method reset (line 34) | def reset(self, sender, match):
    method matched (line 40) | def matched(self, sender, match):

FILE: prusa/link/printer_adapter/print_stats.py
  class PrintStats (line 13) | class PrintStats:
    method __init__ (line 19) | def __init__(self, model: Model):
    method track_new_print (line 30) | def track_new_print(self, file_path, from_gcode_number=None):
    method reset_stats (line 53) | def reset_stats(self):
    method end_time_segment (line 59) | def end_time_segment(self):
    method start_time_segment (line 68) | def start_time_segment(self):
    method get_stats (line 74) | def get_stats(self, gcode_number):
    method get_time_printing (line 104) | def get_time_printing(self):

FILE: prusa/link/printer_adapter/printer_polling.py
  class InfoGroup (line 72) | class InfoGroup(WatchedGroup):
    method __init__ (line 75) | def __init__(self, *args, **kwargs):
    method mark_for_send (line 79) | def mark_for_send(self):
  class PrinterPolling (line 88) | class PrinterPolling:
    method __init__ (line 94) | def __init__(self, serial_queue: SerialQueue,
    method start (line 371) | def start(self):
    method stop (line 375) | def stop(self):
    method wait_stopped (line 379) | def wait_stopped(self):
    method invalidate_printer_info (line 383) | def invalidate_printer_info(self):
    method invalidate_network_info (line 392) | def invalidate_network_info(self):
    method invalidate_serial_number (line 396) | def invalidate_serial_number(self):
    method invalidate_mbl (line 400) | def invalidate_mbl(self):
    method invalidate_statistics (line 404) | def invalidate_statistics(self):
    method schedule_printer_type_invalidation (line 409) | def schedule_printer_type_invalidation(self):
    method _change_interval (line 414) | def _change_interval(self, item: WatchedItem, interval):
    method polling_not_ok (line 422) | def polling_not_ok(self):
    method polling_ok (line 433) | def polling_ok(self):
    method ensure_job_id (line 444) | def ensure_job_id(self):
    method should_wait (line 461) | def should_wait(self):
    method do_matchable (line 465) | def do_matchable(self, gcode, regex, to_front=False, has_to_match=True):
    method do_multimatch (line 479) | def do_multimatch(self, gcode, regex, to_front=False):
    method _get_network_info (line 490) | def _get_network_info(self):
    method _get_printer_type (line 520) | def _get_printer_type(self):
    method _get_firmware_version (line 530) | def _get_firmware_version(self):
    method _get_nozzle_diameter (line 535) | def _get_nozzle_diameter(self):
    method _get_serial_number (line 540) | def _get_serial_number(self):
    method _get_sheet_settings (line 556) | def _get_sheet_settings(self) -> List[Sheet]:
    method get_active_sheet (line 589) | def get_active_sheet(self):
    method _get_mmu_version (line 600) | def _get_mmu_version(self):
    method _get_job_id (line 615) | def _get_job_id(self):
    method _get_mbl (line 622) | def _get_mbl(self):
    method _get_flash_air (line 642) | def _get_flash_air(self):
    method _get_print_mode (line 648) | def _get_print_mode(self):
    method _get_speed_multiplier (line 656) | def _get_speed_multiplier(self):
    method _get_flow_multiplier (line 660) | def _get_flow_multiplier(self):
    method _get_print_info (line 664) | def _get_print_info(self):
    method _get_m27 (line 672) | def _get_m27(self):
    method _parse_print_state (line 694) | def _parse_print_state(groups):
    method _parse_mixed_path (line 702) | def _parse_mixed_path(self, groups):
    method _parse_byte_position (line 708) | def _parse_byte_position(self, groups):
    method _parse_sd_seconds_printing (line 713) | def _parse_sd_seconds_printing(self, groups):
    method _get_progress_from_byte_position (line 720) | def _get_progress_from_byte_position(self, value):
    method _guess_time_remaining (line 726) | def _guess_time_remaining(self, _):
    method print_info_handler (line 750) | def print_info_handler(self, sender, matches: List[re.Match]):
    method _speed_adjust_time_value (line 813) | def _speed_adjust_time_value(self, value):
    method _eeprom_little_endian_uint32 (line 826) | def _eeprom_little_endian_uint32(self, dcode):
    method _get_total_filament (line 834) | def _get_total_filament(self):
    method _get_total_print_time (line 840) | def _get_total_print_time(self):
    method _validate_serial_number (line 848) | def _validate_serial_number(self, value):
    method _validate_printer_type (line 860) | def _validate_printer_type(self, value):
    method _validate_fw_version (line 875) | def _validate_fw_version(value):
    method _validate_mbl (line 883) | def _validate_mbl(value):
    method _validate_percent (line 895) | def _validate_percent(value):
    method _validate_progress (line 903) | def _validate_progress(value):
    method _validate_time_till (line 911) | def _validate_time_till(value):
    method _set_network_info (line 919) | def _set_network_info(self, value):
    method _set_printer_type (line 923) | def _set_printer_type(self, value):
    method _set_firmware_version (line 929) | def _set_firmware_version(self, value):
    method _set_nozzle_diameter (line 934) | def _set_nozzle_diameter(self, value):
    method _set_serial_number (line 938) | def _set_serial_number(self, value):
    method _set_job_id (line 944) | def _set_job_id(self, value):
    method _set_flash_air (line 948) | def _set_flash_air(self, value):
    method _set_speed_multiplier (line 952) | def _set_speed_multiplier(self, value):
    method _set_flow_multiplier (line 956) | def _set_flow_multiplier(self, value):
    method _set_print_progress (line 960) | def _set_print_progress(self, value):
    method _set_time_remaining (line 964) | def _set_time_remaining(self, value):
    method _set_filament_change_in (line 968) | def _set_filament_change_in(self, value):
    method _set_sd_seconds_printing (line 973) | def _set_sd_seconds_printing(self, value):
    method _set_progress_from_bytes (line 977) | def _set_progress_from_bytes(self, value):
    method _set_time_remaining_guesstimate (line 989) | def _set_time_remaining_guesstimate(self, value):
    method _set_total_filament (line 997) | def _set_total_filament(self, value):
    method _set_total_print_time (line 1001) | def _set_total_print_time(self, value):
    method _set_inaccurate_estimates (line 1005) | def _set_inaccurate_estimates(self, value):
    method set_progress_broken (line 1012) | def set_progress_broken(self, value: bool):
    method set_time_broken (line 1016) | def set_time_broken(self, value: bool):
    method _set_sn_condition (line 1021) | def _set_sn_condition(state: CondState):
    method _set_id_condition (line 1026) | def _set_id_condition(state: CondState):
    method _set_fw_condition (line 1031) | def _set_fw_condition(state: CondState):
    method _set_job_id_condition (line 1036) | def _set_job_id_condition(state: CondState):
    method _printer_type_became_valid (line 1040) | def _printer_type_became_valid(self, _):
    method _firmware_version_became_valid (line 1046) | def _firmware_version_became_valid(self, _):
    method _mmu_connected_became_valid (line 1053) | def _mmu_connected_became_valid(self, _):
    method _printer_info_became_valid (line 1061) | def _printer_info_became_valid(self, _):
    method _send_info_if_changed (line 1078) | def _send_info_if_changed(self):
    method _infer_estimate_accuracy (line 1085) | def _infer_estimate_accuracy(self):

FILE: prusa/link/printer_adapter/prusa_link.py
  class TransferCallbackState (line 117) | class TransferCallbackState(Enum):
  class PrusaLink (line 125) | class PrusaLink:
    method __init__ (line 133) | def __init__(self, cfg: Config, settings: Settings) -> None:
    method debug_shell (line 394) | def debug_shell(self) -> None:
    method stop (line 432) | def stop(self, fast: bool = False) -> None:
    method printed_file_cb (line 502) | def printed_file_cb(self) -> Optional[str]:
    method download_finished_cb (line 509) | def download_finished_cb(self, transfer):
    method execute_gcode (line 535) | def execute_gcode(self, caller: SDKCommand) -> CommandResult:
    method start_print (line 545) | def start_print(self, caller: SDKCommand) -> CommandResult:
    method pause_print (line 554) | def pause_print(self, caller: SDKCommand) -> CommandResult:
    method resume_print (line 561) | def resume_print(self, caller: SDKCommand) -> CommandResult:
    method stop_print (line 568) | def stop_print(self, caller: SDKCommand) -> CommandResult:
    method reset_printer (line 575) | def reset_printer(self, caller: SDKCommand) -> CommandResult:
    method upgrade_link (line 582) | def upgrade_link(self, caller: SDKCommand) -> CommandResult:
    method job_info (line 589) | def job_info(self, caller: SDKCommand) -> CommandResult:
    method load_filament (line 596) | def load_filament(self, caller: SDKCommand) -> CommandResult:
    method unload_filament (line 602) | def unload_filament(self, caller: SDKCommand) -> CommandResult:
    method set_printer_ready (line 608) | def set_printer_ready(self, caller: SDKCommand) -> CommandResult:
    method cancel_printer_ready (line 613) | def cancel_printer_ready(self, caller: SDKCommand) -> CommandResult:
    method fw_pause_print (line 620) | def fw_pause_print(self) -> None:
    method fw_resume_print (line 630) | def fw_resume_print(self) -> None:
    method fw_set_ready (line 639) | def fw_set_ready(self) -> None:
    method fw_cancel_ready (line 645) | def fw_cancel_ready(self) -> None:
    method fw_reprint (line 651) | def fw_reprint(self) -> None:
    method layer_trigger (line 658) | def layer_trigger(self, _):
    method mbl_data_changed (line 662) | def mbl_data_changed(self, data) -> None:
    method sheet_settings_changed (line 669) | def sheet_settings_changed(self, printer_sheets: List[Sheet]) -> None:
    method active_sheet_changed (line 686) | def active_sheet_changed(self, active_sheet) -> None:
    method mmu_connection_changed (line 696) | def mmu_connection_changed(self, _) -> None:
    method mmu_info_changed (line 703) | def mmu_info_changed(self, _) -> None:
    method mmu_error_changed (line 733) | def mmu_error_changed(self, _) -> None:
    method job_info_updated (line 737) | def job_info_updated(self, _) -> None:
    method job_id_updated (line 748) | def job_id_updated(self, _, job_id: int) -> None:
    method printer_type_changed (line 753) | def printer_type_changed(self, item: WatchedItem) -> None:
    method print_state_changed (line 780) | def print_state_changed(self, item: WatchedItem) -> None:
    method observed_print (line 791) | def observed_print(self) -> None:
    method observed_sd_pause (line 802) | def observed_sd_pause(self) -> None:
    method observed_serial_pause (line 811) | def observed_serial_pause(self) -> None:
    method observed_no_print (line 826) | def observed_no_print(self) -> None:
    method progress_broken (line 840) | def progress_broken(self, progress_broken: bool) -> None:
    method byte_position_changed (line 847) | def byte_position_changed(self, _, current: int, total: int) -> None:
    method mixed_path_changed (line 851) | def mixed_path_changed(self, path: str) -> None:
    method _reset_print_stats (line 855) | def _reset_print_stats(self) -> None:
    method file_printer_started_printing (line 866) | def file_printer_started_printing(self, _) -> None:
    method file_printer_stopped_printing (line 870) | def file_printer_stopped_printing(self, _) -> None:
    method file_printer_finished_printing (line 874) | def file_printer_finished_printing(self, _) -> None:
    method serial_failed (line 878) | def serial_failed(self, _) -> None:
    method serial_renewed (line 883) | def serial_renewed(self, _) -> None:
    method set_sn (line 888) | def set_sn(self, _, serial_number: str) -> None:
    method printer_registered (line 900) | def printer_registered(self, token: str) -> None:
    method ip_updated (line 912) | def ip_updated(self, _) -> None:
    method folder_attach (line 916) | def folder_attach(self, _, path: str) -> None:
    method folder_detach (line 920) | def folder_detach(self, _, path: str) -> None:
    method sd_attach (line 924) | def sd_attach(self, _, files: File) -> None:
    method sd_detach (line 928) | def sd_detach(self, _) -> None:
    method instruction_confirmed (line 932) | def instruction_confirmed(self, _) -> None:
    method serial_message_number_changed (line 938) | def serial_message_number_changed(self, message_number):
    method block_serial_queue (line 943) | def block_serial_queue(self, *_, **__) -> None:
    method unblock_serial_queue (line 947) | def unblock_serial_queue(self, *_, **__) -> None:
    method power_panic_observed (line 951) | def power_panic_observed(self, *_, **__):
    method recover_from_pp (line 962) | def recover_from_pp(self, *_, **__) -> None:
    method printer_reconnected (line 966) | def printer_reconnected(self, *_, **__) -> None:
    method sd_ready (line 992) | def sd_ready(self) -> bool:
    method pre_state_change (line 996) | def pre_state_change(self, _, command_id: int):
    method post_state_change (line 1003) | def post_state_change(self, _) -> None:
    method state_changed (line 1010) | def state_changed(self,
    method time_printing_updated (line 1074) | def time_printing_updated(self, _, time_printing: int) -> None:
    method serial_queue_failed (line 1079) | def serial_queue_failed(self, _) -> None:
    method connection_renewed (line 1089) | def connection_renewed(self, *_) -> None:
    method transfer_activity_observed (line 1093) | def transfer_activity_observed(self, *_) -> None:
    method log_tm_error (line 1098) | def log_tm_error(self, _, match: re.Match) -> None:

FILE: prusa/link/printer_adapter/special_commands.py
  class SpecialCommands (line 24) | class SpecialCommands:
    method __init__ (line 28) | def __init__(self, serial_parser: ThreadedSerialParser,
    method menu_folder_found (line 48) | def menu_folder_found(self, _, menu_sfn):
    method menu_folder_gone (line 53) | def menu_folder_gone(self, _):
    method _open_is_special (line 58) | def _open_is_special(self, match):
    method handle_file (line 73) | def handle_file(self, _, match):
    method handle_start (line 84) | def handle_start(self, _, match: re.Match):
    method handle_done (line 95) | def handle_done(self, _, match: re.Match):
    method set_ready (line 105) | def set_ready(self):

FILE: prusa/link/printer_adapter/state_manager.py
  class StateChange (line 30) | class StateChange:
    method __init__ (line 37) | def __init__(self,
  function state_influencer (line 59) | def state_influencer(state_change: Optional[StateChange] = None):
  class StateManager (line 93) | class StateManager(metaclass=MCSingleton):
    method __init__ (line 102) | def __init__(self, serial_parser: ThreadedSerialParser, model: Model):
    method new_attention_timer (line 211) | def new_attention_timer(self):
    method start_attention_timer (line 220) | def start_attention_timer(self):
    method stop_attention_timer (line 227) | def stop_attention_timer(self):
    method link_error_detected (line 233) | def link_error_detected(self, condition: Condition, old_value: CondSta...
    method link_error_resolved (line 244) | def link_error_resolved(self, condition: Condition, old_value: CondSta...
    method file_printer_started_printing (line 252) | def file_printer_started_printing(self):
    method get_state (line 261) | def get_state(self):
    method expect_change (line 273) | def expect_change(self, change: StateChange):
    method stop_expecting_change (line 281) | def stop_expecting_change(self):
    method is_expected (line 286) | def is_expected(self):
    method get_expected_source (line 299) | def get_expected_source(self):
    method state_may_have_changed (line 346) | def state_may_have_changed(self):
    method fan_error (line 404) | def fan_error(self, sender, match: re.Match):
    method mmu_error_changed (line 422) | def mmu_error_changed(self):
    method fan_error_resolver (line 441) | def fan_error_resolver(self, sender, match):
    method _cancel_fan_error (line 472) | def _cancel_fan_error(self):
    method error_handler (line 478) | def error_handler(self):
    method error_reason_handler (line 489) | def error_reason_handler(self, sender, match: re.Match):
    method attention_reason_handler (line 505) | def attention_reason_handler(self, sender, match: re.Match):
    method filter_pause_events (line 532) | def filter_pause_events(self):
    method clear_tm_error (line 543) | def clear_tm_error(self, _, match: re.Match):
    method power_panic_observed (line 548) | def power_panic_observed(self):
    method reset_power_panic (line 552) | def reset_power_panic(self):
    method parse_error_reason (line 557) | def parse_error_reason(groups):
    method error_reason_waiter (line 588) | def error_reason_waiter(self):
    method stopped_or_not_printing (line 604) | def stopped_or_not_printing(self):
    method reset (line 617) | def reset(self):
    method printing (line 630) | def printing(self):
    method not_printing (line 649) | def not_printing(self):
    method finished (line 659) | def finished(self):
    method ready (line 666) | def ready(self):
    method idle (line 672) | def idle(self):
    method busy (line 678) | def busy(self):
    method paused (line 685) | def paused(self):
    method resumed (line 695) | def resumed(self):
    method stopped (line 708) | def stopped(self):
    method instruction_confirmed (line 730) | def instruction_confirmed(self):
    method _attention_timer_handler (line 753) | def _attention_timer_handler(self):
    method _clear_attention (line 762) | def _clear_attention(self):
    method clear_attention (line 776) | def clear_attention(self):
    method attention (line 781) | def attention(self):
    method error (line 797) | def error(self):
    method error_resolved (line 803) | def error_resolved(self):
    method serial_error (line 813) | def serial_error(self):
    method serial_error_resolved (line 825) | def serial_error_resolved(self):

FILE: prusa/link/printer_adapter/structures/carousel.py
  class LCDLine (line 10) | class LCDLine:
    method __init__ (line 13) | def __init__(self, text: str, delay: float = 5.0,
    method reset_end (line 24) | def reset_end(self):
  class Screen (line 30) | class Screen:
    method __init__ (line 33) | def __init__(self, resets_idle=True, chime_gcode=None, order=0):
    method __str__ (line 63) | def __str__(self):
    method lines (line 66) | def lines(self):
  class Carousel (line 84) | class Carousel:
    method __init__ (line 89) | def __init__(self, screens: List[Screen]):
    method _lines (line 102) | def _lines(self):
    method get_next (line 117) | def get_next(self):
    method _rewind (line 132) | def _rewind(self):
    method set_rewind (line 137) | def set_rewind(self):
    method add_message (line 143) | def add_message(self, line: LCDLine):
    method verify_tracked (line 148) | def verify_tracked(self, screen):
    method set_text (line 154) | def set_text(self,
    method enable (line 188) | def enable(self, screen: Screen, silent=False):
    method set_priority (line 199) | def set_priority(self, screen: Screen, priority):
    method disable (line 209) | def disable(self, screen: Screen):
    method is_enabled (line 218) | def is_enabled(self, screen: Screen):
    method get_set_to_show (line 222) | def get_set_to_show(self):
    method _react (line 231) | def _react(self):

FILE: prusa/link/printer_adapter/structures/heap.py
  class HeapItem (line 9) | class HeapItem:
    method __init__ (line 11) | def __init__(self, value):
    method __gt__ (line 16) | def __gt__(self, other):
    method __ge__ (line 21) | def __ge__(self, other):
    method __lt__ (line 26) | def __lt__(self, other):
    method __le__ (line 31) | def __le__(self, other):
    method __eq__ (line 36) | def __eq__(self, other):
    method __hash__ (line 41) | def __hash__(self):
  class MinHeap (line 45) | class MinHeap:
    method __init__ (line 47) | def __init__(self) -> None:
    method __len__ (line 50) | def __len__(self):
    method __bool__ (line 53) | def __bool__(self):
    method __getitem__ (line 56) | def __getitem__(self, key):
    method __setitem__ (line 59) | def __setitem__(self, key, value):
    method push (line 62) | def push(self, item: HeapItem):
    method _push (line 67) | def _push(self, item):
    method pop (line 79) | def pop(self, index: int = 0) -> HeapItem:
    method sift_up (line 110) | def sift_up(self, pos):
    method sift_down (line 138) | def sift_down(self, startpos, pos):
  class MaxHeap (line 163) | class MaxHeap(MinHeap):
    method push (line 168) | def push(self, item: HeapItem):

FILE: prusa/link/printer_adapter/structures/item_updater.py
  class SideEffectOnly (line 18) | class SideEffectOnly(Exception):
  class Watchable (line 24) | class Watchable:
    method __init__ (line 27) | def __init__(self):
  class WatchedItem (line 35) | class WatchedItem(Watchable):
    method __init__ (line 44) | def __init__(self,
    method __repr__ (line 104) | def __repr__(self):
    method __lt__ (line 107) | def __lt__(self, other):
    method __eq__ (line 112) | def __eq__(self, other):
    method __hash__ (line 117) | def __hash__(self):
  class WatchedGroup (line 121) | class WatchedGroup(Watchable):
    method __init__ (line 127) | def __init__(self, items: Iterable[WatchedItem]):
    method __iter__ (line 150) | def __iter__(self):
    method invalid_handler (line 153) | def invalid_handler(self, item):
    method valid_handler (line 165) | def valid_handler(self, item):
  class ItemUpdater (line 178) | class ItemUpdater:
    method __init__ (line 189) | def __init__(self, quit_interval=0.2):
    method start (line 212) | def start(self):
    method stop (line 218) | def stop(self):
    method wait_stopped (line 224) | def wait_stopped(self):
    method add_item (line 230) | def add_item(self, item: WatchedItem, start_tracking=True):
    method invalidate_group (line 244) | def invalidate_group(self, group: WatchedGroup):
    method invalidate (line 251) | def invalidate(self, item: WatchedItem):
    method disable (line 280) | def disable(self, item: WatchedItem):
    method enable (line 290) | def enable(self, item: WatchedItem):
    method set_value (line 300) | def set_value(self, item: WatchedItem, value):
    method schedule_invalidation (line 329) | def schedule_invalidation(self, item: WatchedItem, interval=None,
    method cancel_scheduled_invalidation (line 374) | def cancel_scheduled_invalidation(self, item: WatchedItem):
    method _time_out (line 393) | def _time_out(item: WatchedItem):
    method _validate_is_tracked (line 405) | def _validate_is_tracked(self, item: WatchedItem):
    method _gather (line 410) | def _gather(self, item: WatchedItem):
    method _gather_error_reschedule (line 451) | def _gather_error_reschedule(self, item):
    method _set_value (line 463) | def _set_value(self, item, value):
    method _enqueue_refresh (line 487) | def _enqueue_refresh(self, item):
    method _refresher (line 504) | def _refresher(self):
    method _process_invalidations (line 519) | def _process_invalidations(self):
    method _process_timeouts (line 547) | def _process_timeouts(self):

FILE: prusa/link/printer_adapter/structures/mc_singleton.py
  class MCSingleton (line 4) | class MCSingleton(type):
    method __init__ (line 8) | def __init__(cls, name, bases, dic):
    method __call__ (line 13) | def __call__(cls, *args, **kwargs):

FILE: prusa/link/printer_adapter/structures/model_classes.py
  class IndividualSlot (line 12) | class IndividualSlot(BaseModel):
  class Slot (line 20) | class Slot(BaseModel):
    method dict (line 30) | def dict(self, **kwargs) -> Dict:
  class Telemetry (line 39) | class Telemetry(BaseModel):
    method dict (line 74) | def dict(self, **kwargs) -> Dict:
  class NetworkInfo (line 81) | class NetworkInfo(BaseModel):
  class FileType (line 96) | class FileType(Enum):
  class JobState (line 103) | class JobState(Enum):
  class SDState (line 110) | class SDState(Enum):
  class PrintState (line 118) | class PrintState(Enum):
  class PrintMode (line 126) | class PrintMode(Enum):
  class EEPROMParams (line 133) | class EEPROMParams(Enum):
  class PPData (line 145) | class PPData(BaseModel):

FILE: prusa/link/printer_adapter/structures/module_data_classes.py
  class Port (line 16) | class Port(BaseModel):
    method __str__ (line 28) | def __str__(self):
  class SerialAdapterData (line 37) | class SerialAdapterData(BaseModel):
  class FilePrinterData (line 45) | class FilePrinterData(BaseModel):
  class StateManagerData (line 61) | class StateManagerData(BaseModel):
  class JobData (line 75) | class JobData(BaseModel):
    method get_job_id_for_api (line 93) | def get_job_id_for_api(self):
  class IPUpdaterData (line 103) | class IPUpdaterData(BaseModel):
  class SDCardData (line 116) | class SDCardData(BaseModel):
  class StorageData (line 130) | class StorageData(BaseModel):
  class MMUObserverData (line 138) | class MMUObserverData(BaseModel):
  class PrintStatsData (line 143) | class PrintStatsData(BaseModel):
  class Sheet (line 153) | class Sheet(BaseModel):

FILE: prusa/link/printer_adapter/telemetry_passer.py
  class Modifier (line 42) | class Modifier(Enum):
  class TelemetryPasser (line 84) | class TelemetryPasser(metaclass=MCSingleton):
    method __init__ (line 87) | def __init__(self, model: Model, printer: Printer):
    method start (line 109) | def start(self):
    method stop (line 113) | def stop(self):
    method wait_stopped (line 118) | def wait_stopped(self):
    method _keep_updating (line 122) | def _keep_updating(self):
    method _update (line 133) | def _update(self):
    method pass_telemetry (line 148) | def pass_telemetry(self):
    method _get_filtered_paths (line 177) | def _get_filtered_paths(self):
    method _get_modifiers (line 193) | def _get_modifiers(self, key_path):
    method set_telemetry (line 199) | def set_telemetry(self, new_telemetry: Telemetry):
    method _should_wake_up (line 250) | def _should_wake_up(self, modifiers):
    method reset_value (line 261) | def reset_value(self, key_path):
    method _set_multi (line 267) | def _set_multi(self, structure, key, value):
    method _get_multi (line 276) | def _get_multi(self, structure, key):
    method _get_correct_path (line 284) | def _get_correct_path(self, structure, key_path):
    method _update_by_path (line 292) | def _update_by_path(self, target, source, key_path,
    method _reset_by_path (line 327) | def _reset_by_path(self, target, key_path):
    method _get_by_path (line 338) | def _get_by_path(self, source, key_path):
    method _path_to_model (line 348) | def _path_to_model(self, key_path) -> tuple[Any, ...]:
    method _resend_telemetry_on_timer (line 365) | def _resend_telemetry_on_timer(self):
    method state_changed (line 371) | def state_changed(self):
    method activity_observed (line 396) | def activity_observed(self):
    method wipe_telemetry (line 403) | def wipe_telemetry(self):
    method resend_latest_telemetry (line 412) | def resend_latest_telemetry(self):

FILE: prusa/link/printer_adapter/updatable.py
  class Thread (line 15) | class Thread(_Thread):
    method profile_run (line 18) | def profile_run(self):
    method enable_profiling (line 29) | def enable_profiling():
    method disable_profiling (line 34) | def disable_profiling():
  class ThreadedUpdatable (line 39) | class ThreadedUpdatable:
    method __init__ (line 44) | def __init__(self):
    method start (line 55) | def start(self):
    method stop (line 59) | def stop(self):
    method wait_stopped (line 63) | def wait_stopped(self):
    method update (line 67) | def update(self):

FILE: prusa/link/sdk_augmentation/command_handler.py
  class CommandHandler (line 9) | class CommandHandler:
    method __init__ (line 12) | def __init__(self, sdk_command: Command):
    method handle_commands (line 23) | def handle_commands(self):
    method stop (line 36) | def stop(self):

FILE: prusa/link/sdk_augmentation/file.py
  class SDFile (line 7) | class SDFile(File):
    method add_node (line 10) | def add_node(self, is_dir, path: Path, name, sfn, **attrs):
    method add_directory (line 25) | def add_directory(self, path: Path, name, sfn, **attrs):
    method add_file (line 29) | def add_file(self, path, name, sfn, **attrs):

FILE: prusa/link/sdk_augmentation/printer.py
  class MyPrinter (line 30) | class MyPrinter(SDKPrinter, metaclass=MCSingleton):
    method __init__ (line 36) | def __init__(self, *args, **kwargs):
    method parse_command (line 52) | def parse_command(self, res):
    method get_info (line 68) | def get_info(self) -> Dict[str, Any]:
    method connection_from_settings (line 77) | def connection_from_settings(self, settings):
    method get_file_info (line 90) | def get_file_info(self, caller: Command) -> Dict[str, Any]:
    method from_path (line 109) | def from_path(self, path: Path):
    method start (line 129) | def start(self):
    method indicate_stop (line 141) | def indicate_stop(self):
    method wait_stopped (line 155) | def wait_stopped(self):
    method loop (line 167) | def loop(self):
    method inotify_loop (line 172) | def inotify_loop(self):
    method download_loop (line 182) | def download_loop(self):
    method snapshot_loop (line 187) | def snapshot_loop(self):
    method type_string (line 193) | def type_string(self):

FILE: prusa/link/serial/helpers.py
  function wait_for_instruction (line 15) | def wait_for_instruction(instruction,
  function enqueue_instruction (line 33) | def enqueue_instruction(queue: SerialQueue,
  function enqueue_matchable (line 52) | def enqueue_matchable(queue: SerialQueue,
  function enqueue_list_from_str (line 84) | def enqueue_list_from_str(queue: SerialQueue,

FILE: prusa/link/serial/instruction.py
  class Instruction (line 14) | class Instruction:
    method __init__ (line 16) | def __init__(self,
    method __str__ (line 52) | def __str__(self):
    method __repr__ (line 55) | def __repr__(self):
    method confirm (line 58) | def confirm(self, force=False) -> bool:
    method sent (line 69) | def sent(self):
    method output_captured (line 77) | def output_captured(self, sender, match):
    method wait_for_send (line 85) | def wait_for_send(self, timeout=None):
    method wait_for_confirmation (line 89) | def wait_for_confirmation(self, timeout=None):
    method is_sent (line 93) | def is_sent(self):
    method is_confirmed (line 97) | def is_confirmed(self):
    method reset (line 101) | def reset(self):
    method fill_data (line 106) | def fill_data(self, message_number: int):
    method get_checksum (line 126) | def get_checksum(data: bytes):
  class MatchableInstruction (line 139) | class MatchableInstruction(Instruction):
    method __init__ (line 143) | def __init__(self,
    method output_captured (line 155) | def output_captured(self, sender, match):
    method match (line 160) | def match(self, index=0):
    method get_matches (line 166) | def get_matches(self):
  class MandatoryMatchableInstruction (line 171) | class MandatoryMatchableInstruction(MatchableInstruction):
    method confirm (line 176) | def confirm(self, force=False) -> bool:

FILE: prusa/link/serial/is_planner_fed.py
  class HeapName (line 24) | class HeapName(Enum):
  class TimeValue (line 30) | class TimeValue(HeapItem):
    method __init__ (line 33) | def __init__(self, value: float) -> None:
  class IsPlannerFed (line 38) | class IsPlannerFed:
    method __init__ (line 66) | def __init__(self, threshold_path):
    method item_count (line 88) | def item_count(self):
    method threshold (line 93) | def threshold(self):
    method get_dynamic_threshold (line 103) | def get_dynamic_threshold(self):
    method __call__ (line 111) | def __call__(self):
    method process_value (line 117) | def process_value(self, value):
    method _remove_last (line 137) | def _remove_last(self) -> None:
    method _add (line 152) | def _add(self, value):
    method balance (line 178) | def balance(self):
    method _short_push (line 193) | def _short_push(self, item: TimeValue):
    method _long_push (line 200) | def _long_push(self, item: TimeValue):
    method save (line 207) | def save(self):

FILE: prusa/link/serial/serial.py
  class SerialException (line 16) | class SerialException(RuntimeError):
  class Serial (line 20) | class Serial:
    method __init__ (line 24) | def __init__(self, port: str, baudrate: int, timeout: int):
    method close (line 96) | def close(self):
    method __read (line 107) | def __read(self, timeout):
    method readline (line 120) | def readline(self):
    method write (line 141) | def write(self, data: bytes):
    method is_open (line 146) | def is_open(self):
    method dtr (line 151) | def dtr(self):
    method dtr (line 156) | def dtr(self, value: bool):

FILE: prusa/link/serial/serial_adapter.py
  class PortAdapter (line 44) | class PortAdapter:
    method __init__ (line 46) | def __init__(self, port: Port) -> None:
  class SerialAdapter (line 51) | class SerialAdapter(metaclass=MCSingleton):
    method is_rpi_port (line 60) | def is_rpi_port(port_path):
    method __init__ (line 80) | def __init__(self,
    method is_open (line 115) | def is_open(serial) -> bool:
    method _get_info (line 120) | def _get_info(port_adapter: PortAdapter):
    method _detect (line 159) | def _detect(port_adapter: PortAdapter):
    method _reopen (line 188) | def _reopen(self) -> bool:
    method close (line 249) | def close(self):
    method _renew_serial_connection (line 257) | def _renew_serial_connection(self, starting: bool = False):
    method _read_continually (line 297) | def _read_continually(self):
    method write (line 330) | def write(self, message: bytes):
    method disable_dtr_resets (line 359) | def disable_dtr_resets(self):
    method enable_dtr_resets (line 367) | def enable_dtr_resets(self):
    method _reset_pi (line 375) | def _reset_pi(self):
    method _blip_dtr (line 393) | def _blip_dtr(self):
    method reset_client (line 401) | def reset_client(self):
    method stop (line 412) | def stop(self):
    method wait_stopped (line 417) | def wait_stopped(self):
    method power_panic_observed (line 421) | def power_panic_observed(self):
    method power_panic_unblock (line 425) | def power_panic_unblock(self):

FILE: prusa/link/serial/serial_parser.py
  class RegexPairing (line 25) | class RegexPairing:
    method __init__ (line 31) | def __init__(self, regexp, priority=0) -> None:
    method __str__ (line 36) | def __str__(self) -> str:
    method __repr__ (line 43) | def __repr__(self) -> str:
    method fire (line 46) | def fire(self, match: Optional[Match] = None) -> None:
  class SerialParser (line 60) | class SerialParser(metaclass=MCSingleton):
    method __init__ (line 66) | def __init__(self) -> None:
    method decide (line 71) | def decide(self, line: str) -> None:
    method add_handler (line 90) | def add_handler(self,
    method remove_handler (line 126) | def remove_handler(self, regexp, handler) -> None:
  class ThreadedSerialParser (line 144) | class ThreadedSerialParser(SerialParser):
    method __init__ (line 148) | def __init__(self):
    method decoupled (line 158) | def decoupled(self, handler):
    method process (line 165) | def process(self):
    method add_decoupled_handler (line 172) | def add_decoupled_handler(self,
    method stop (line 179) | def stop(self):
    method wait_stopped (line 184) | def wait_stopped(self):

FILE: prusa/link/serial/serial_queue.py
  class SerialQueue (line 49) | class SerialQueue(metaclass=MCSingleton):
    method __init__ (line 59) | def __init__(self,
    method _keep_sending (line 130) | def _keep_sending(self):
    method block_sending (line 150) | def block_sending(self):
    method unblock_sending (line 154) | def unblock_sending(self):
    method _try_writing (line 160) | def _try_writing(self):
    method stop (line 166) | def stop(self):
    method wait_stopped (line 173) | def wait_stopped(self):
    method peek_next (line 177) | def peek_next(self):
    method _next_instruction (line 194) | def _next_instruction(self):
    method can_write (line 223) | def can_write(self):
    method is_empty (line 228) | def is_empty(self):
    method _hookup_output_capture (line 236) | def _hookup_output_capture(self):
    method _teardown_output_capture (line 247) | def _teardown_output_capture(self):
    method _send (line 256) | def _send(self):
    method set_message_number (line 309) | def set_message_number(self, number):
    method replenish_history (line 315) | def replenish_history(self, messages: List[str]):
    method _enqueue (line 325) | def _enqueue(self, instruction: Instruction, to_front=False):
    method enqueue_one (line 332) | def enqueue_one(self, instruction: Instruction, to_front=False):
    method enqueue_list (line 348) | def enqueue_list(self,
    method _confirmation_handler (line 369) | def _confirmation_handler(self, sender, match: re.Match):
    method _resend_handler (line 375) | def _resend_handler(self, sender, match: re.Match):
    method _resend (line 399) | def _resend(self, count):
    method _confirmed (line 420) | def _confirmed(self, force=False):
    method _rx_got_yeeted (line 457) | def _rx_got_yeeted(self):
    method reset_message_number (line 474) | def reset_message_number(self):
    method _reset_message_number (line 483) | def _reset_message_number(self):
    method flush_print_queue (line 488) | def flush_print_queue(self):
    method _flush_queues (line 505) | def _flush_queues(self):
    method _throw_out_current_instruction (line 524) | def _throw_out_current_instruction(self):
    method _worst_case_scenario (line 531) | def _worst_case_scenario(self):
    method printer_reconnected (line 541) | def printer_reconnected(self, was_printing, was_power_panic):
    method _printer_reconnected (line 548) | def _printer_reconnected(self, was_printing, was_power_panic):
  class MonitoredSerialQueue (line 586) | class MonitoredSerialQueue(SerialQueue):
    method __init__ (line 589) | def __init__(self,
    method get_current_delay (line 618) | def get_current_delay(self):
    method keep_monitoring (line 627) | def keep_monitoring(self):
    method check_status (line 633) | def check_status(self):
    method stop (line 652) | def stop(self):
    method wait_stopped (line 660) | def wait_stopped(self):
    method _confirmed (line 665) | def _confirmed(self, force=False):
    method _renew_timeout (line 670) | def _renew_timeout(self, unstuck=True):

FILE: prusa/link/service_discovery.py
  class ServiceDiscovery (line 22) | class ServiceDiscovery:
    method __init__ (line 28) | def __init__(self, port):
    method _get_port_part (line 44) | def _get_port_part(port):
    method is_on_port (line 48) | def is_on_port(self, port):
    method _register (line 59) | def _register(self):
    method unregister (line 90) | def unregister(self):
    method _register_service (line 94) | def _register_service(self, name, service_type, port):

FILE: prusa/link/static/main.9b8dc0068f6e6508dfd4.js
  function u (line 1) | async function u(e,t={},a,i){if("true"!=sessionStorage.getItem("auth"))t...
  function i (line 1) | function i(e,t=!0){e&&(t&&!e.hasAttribute("hidden")&&e.setAttribute("hid...
  function s (line 1) | function s(e,t=!0){i(e,!t)}
    method value (line 1) | set value(e){this._value=e,this.updateLabel()}
    method value (line 1) | get value(){return this._value}
    method constructor (line 1) | constructor(e,t,a){const i=this;this._label=t,this._ul=a,this._value=t...
    method _highlight (line 1) | _highlight(e){this._ul.childNodes.forEach((t=>{t===e?t.classList.add("...
    method destructor (line 1) | destructor(){window.removeEventListener("keypress",this._onKeyPress),w...
    method init (line 1) | static init(e,t){let a=(0,i.Z)(e);const n=document.getElementById("dro...
    method setOptions (line 1) | setOptions(e){this._options=e}
    method updateLabel (line 1) | updateLabel(){this._label.innerHTML=this._value}
    method select (line 1) | select(e){this._label.innerHTML=e}
    method open (line 1) | open(){this._ul.classList.contains("open")||(this._options.forEach((e=...
    method close (line 1) | close(){for(this._ul.classList.remove(["open"]);this._ul.firstChild;)t...
  function n (line 1) | function n(e,t=!0){e&&(t&&!e.hasAttribute("disabled")&&e.setAttribute("d...
  function r (line 1) | function r(e,t=!0){return n(e,!t)}
  function o (line 1) | function o(){s(document.querySelector("#job .loading-overlay"))}
  function l (line 1) | function l(){i(document.querySelector("#job .loading-overlay"))}
  function d (line 1) | function d(e,...t){const a=i=>{if(t)for(const e of t)if(e&&e.contains(i....
  function p (line 1) | function p(){return navigator.language||navigator.userLanguage||""}
  function m (line 1) | function m(e){const t=o.indexOf(e);return-1!==t&&(u=t,c=e,localStorage.s...
  function v (line 1) | function v(){return c}
  function g (line 1) | function g(){return[...o]}
  function h (line 1) | function h(e,t){let a=n()(l,`${e}.${u}`);if(!a)return a=n()(l,`${e}.${d}...
  function b (line 1) | function b(e,t){if(t)if(t.ref){t.ref.innerHTML=e}else if(t.query){let a=...
  function f (line 1) | function f(e){(0,i.Z)(e).querySelectorAll('[data-label]:not([data-label=...
  function p (line 1) | function p(e,t){if("move"===e){(e=>(0,r.LK)("/api/printer/printhead",{me...
  function m (line 1) | function m(e,t,a){function i(e){return Number.parseFloat(e.getAttribute(...
  function t (line 1) | function t(e,t){switch(e){case"bed":return i=t,(0,r.LK)("/api/printer/be...
  function f (line 1) | function f(e,t=!0,a=1){return e>0?t?e.toFixed(a):e:0}
  class s (line 1) | class s{set value(e){this._value=e,this.updateLabel()}get value(){return...
    method value (line 1) | set value(e){this._value=e,this.updateLabel()}
    method value (line 1) | get value(){return this._value}
    method constructor (line 1) | constructor(e,t,a){const i=this;this._label=t,this._ul=a,this._value=t...
    method _highlight (line 1) | _highlight(e){this._ul.childNodes.forEach((t=>{t===e?t.classList.add("...
    method destructor (line 1) | destructor(){window.removeEventListener("keypress",this._onKeyPress),w...
    method init (line 1) | static init(e,t){let a=(0,i.Z)(e);const n=document.getElementById("dro...
    method setOptions (line 1) | setOptions(e){this._options=e}
    method updateLabel (line 1) | updateLabel(){this._label.innerHTML=this._value}
    method select (line 1) | select(e){this._label.innerHTML=e}
    method open (line 1) | open(){this._ul.classList.contains("open")||(this._options.forEach((e=...
    method close (line 1) | close(){for(this._ul.classList.remove(["open"]);this._ul.firstChild;)t...
  function n (line 1) | function n(e,t){let a=e?.data?.title||t?.fallbackMessage?.title||"Error"...
  function i (line 1) | function i(e,t,a="right"){if(!e)return;const i=e.querySelector(".fill");...
  function h (line 1) | function h(e){const t=document.getElementById("api_key");t&&(t.innerText...
  function b (line 1) | function b(e,t){(0,s.ZP)("con-settings",e.link),function(e,t){const a=do...
  function f (line 1) | function f(e,t,a,s,n){e&&e.setAttribute("ok",Boolean(a)),t&&(t.innerHTML...
  function y (line 1) | function y(e){switch(e.toLowerCase()){case"python":return(0,i.Iu)("sys-v...
  function p (line 1) | function p(e){const t=document.querySelector("ul.logs");t&&(t.innerHTML=...
  function m (line 1) | function m(e){return`<li class="txt-md">${e}</li>`}
  function n (line 1) | function n({title:e,message:t,type:a,onClose:n,autoClose:r=!0}){const o=...
  function r (line 1) | function r(e,t,a,i=!0){n({type:"warning",title:e,message:t,onClose:a,aut...
  function o (line 1) | function o(e,t,a,i=!0){n({type:"success",title:e,message:t,onClose:a,aut...
  function l (line 1) | function l(e,t,a,i=!0){n({type:"error",title:e,message:t,onClose:a,autoC...
  function g (line 1) | function g(e){const t=p.Z.getContext(),a=u.cG.includes(e),i=document.que...
  function h (line 1) | function h(e){const t=e.data;if(![200,201,204].includes(e.code))return(0...
  function f (line 1) | function f(){y("choose"),function(e){const t=document.querySelector("#up...
  function y (line 1) | function y(e){m="uploading"==e;const t=document.getElementById("upld-rem...
  method isUploading (line 1) | get isUploading(){return m}
  function l (line 1) | function l(){const e=document.getElementById("graph");e&&(0==e.childElem...
  function d (line 1) | function d(e,t){!function(e,t){e.push(t);const a=(new Date).getTime();fo...
  function t (line 1) | function t(e){try{return JSON.parse(e)}catch{return}}
  function k (line 1) | function k(e,t,a,i,s){const n=document.querySelector(e);n&&(n.setAttribu...
  function w (line 1) | function w(){[document.querySelector('#upld-direct input[type="file"]'),...
  function x (line 1) | function x(e){f="uploading"===e;const t=document.getElementById("upld-di...
  function z (line 1) | function z(e){y=e;const t=document.getElementById("upld-progress");t&&(t...
  method isUploading (line 1) | get isUploading(){return f}
  method selected (line 1) | get selected(){return this._selected}
  method isLocked (line 1) | get isLocked(){return this._isLocked}
  method lock (line 1) | lock(){this._isLocked=!0,this._root&&this._root.querySelectorAll("[data-...
  method unlock (line 1) | unlock(){this._isLocked=!1,this._root&&this._root.querySelectorAll("[dat...
  method constructor (line 1) | constructor(){this._root=null,this._selected=null,this._isLocked=!1}
  method init (line 1) | init(e){this._root=e,this._root&&(e.querySelectorAll("[data-tab-btn]").f...
  method openTab (line 1) | openTab(e){if(this._root&&e){const t=this._root.querySelector(`[data-tab...
  method closeTab (line 1) | closeTab(){if(this._root&&this._selected){const e=this._root.querySelect...
  function N (line 1) | function N(){E.isUploading?(I.openTab("direct"),I.lock()):L?.isUploading...
  function V (line 1) | function V(e){return`inset(${100-e}% 0% 0% 0%)`}
  function te (line 1) | function te(e){e.classList.remove("open")}
  function ve (line 1) | function ve(){const e=me.origin;return me.storages[e]}
  function ge (line 1) | function ge(){return me.current_path.map((e=>e.path)).join("/")}
  function he (line 1) | function he(e){const t=ve(),a=ge();return be(t.path,a,e)}
  function be (line 1) | function be(e,t,a){const i=["/api/v1/files",e,t,a].filter((e=>!!e)).join...
  function xe (line 1) | function xe(e,t,a){const i=document.getElementById(e).content,s=document...
  function ze (line 1) | function ze(e,t){const a=e.display_name||e.name,i=e.name,s=he(i),n=xe("n...
  function Ee (line 1) | function Ee(e){const t=xe("node-file",e.display_name||e.name,(t=>Se(e)))...
  function Pe (line 1) | function Pe(e,t){const a=xe("node-file",e.display_name||e.name,(e=>{})),...
  function Le (line 1) | function Le(e){if(e in me.storages){const t=me.storages[e];me.origin=e,m...
  function Te (line 1) | function Te(e,t=!1){const a=je(e,t);Ne&&Ne.state!==e.state&&(Ne=null),a&...
  function je (line 1) | function je(e,t){const a=document.getElementById("job");if(!a)return!1;c...
  function Ce (line 1) | function Ce(e,t){const a=t?e.files.selected:e.job,s=a?.file?.resource;e....
  method constructor (line 1) | constructor(){this.state=void 0,this.printer=void 0,this.job=void 0,this...
  method updateConnection (line 1) | updateConnection(){return(0,m.LK)("/api/connection",{method:"GET"}).then...
  method update (line 1) | update({status:e,printer:t}){e?.ok&&e.payload&&this.updateStatus(e.paylo...
  method updateStatus (line 1) | updateStatus(e){this.updateTelemetry(e.printer),this.updateJob(e.job),th...
  method updatePrinter (line 1) | updatePrinter(e){this.printer={name:e.name,location:e.location,farmMode:...
  method updateTelemetry (line 1) | updateTelemetry(e){this.state=h.PT.fromApi(e.state.toUpperCase()),this.t...
  method updateJob (line 1) | updateJob(e){const t=this.job?.id||null,a=e?.id||null;if(t!==a||this.job...
  method updateJobDetails (line 1) | updateJobDetails(){return(0,m.LK)("/api/v1/job").then((e=>{const t=e.dat...
  method updateStorage (line 1) | updateStorage(e){Object.keys(e).forEach((e=>{const t={path:e.path,name:e...
  method updateTransfer (line 1) | updateTransfer(e){const t=this.transfer?.id||null,a=e?.id||null;if(t!==a...
  method updateCamera (line 1) | updateCamera(e){this.camera={id:e?.id}}
  method selectFile (line 1) | selectFile(e){if(!e)return void(this.files.selected=null);const t=e.refs...
  function o (line 1) | function o(e,t){const a=n(e),r=s.Z.routes.find((e=>e.path===a));if(!r)re...
  function a (line 1) | function a(i){var s=t[i];if(void 0!==s)return s.exports;var n=t[i]={expo...
  function n (line 1) | function n(){document.getElementById("menu").classList.remove("burger-op...
  function p (line 1) | async function p(e){const t=(new Date).getTime(),a=Object.fromEntries(Ob...
  function m (line 1) | async function m(e){let a=!1;for(;;){let i=!1;try{const t=await p(a);t.s...
  function v (line 1) | function v(a){try{t.Z.init(a),window.onpopstate=t=>t&&(0,e.g9)(t.current...
  function g (line 1) | function g(e){try{t.Z.update(e)}catch(e){h(e)}}
  function h (line 1) | function h(e){(0,o.S)(e,{fallbackMessage:{title:"Application error",mess...

FILE: prusa/link/util.py
  function prctl_name (line 37) | def prctl_name():
  function loop_until (line 44) | def loop_until(loop_evt: Event, run_every_sec: Callable[[], float], to_run,
  function get_local_ip (line 69) | def get_local_ip():
  function get_local_ip6 (line 84) | def get_local_ip6():
  function get_clean_path (line 98) | def get_clean_path(path):
  function ensure_directory (line 106) | def ensure_directory(directory, chown_username=None):
  function get_checksum (line 116) | def get_checksum(message: str):
  function persist_file (line 127) | def persist_file(file: typing.TextIO):
  function get_gcode (line 137) | def get_gcode(line):
  function file_is_on_sd (line 150) | def file_is_on_sd(path_parts):
  function make_fingerprint (line 157) | def make_fingerprint(sn):
  function fat_datetime_to_tuple (line 166) | def fat_datetime_to_tuple(fat_datetime):
  function get_print_stats_gcode (line 191) | def get_print_stats_gcode(quiet_percent=-1,
  function get_d3_code (line 202) | def get_d3_code(address: int, byte_count: int):
  function round_to_five (line 218) | def round_to_five(number: float):
  function decode_line (line 233) | def decode_line(line: bytes):
  function is_potato_cpu (line 238) | def is_potato_cpu():
  class PrinterDevice (line 243) | class PrinterDevice:
    method __init__ (line 246) | def __init__(self, vendor_id: str,
  function get_usb_printers (line 256) | def get_usb_printers():
  function walk_dict (line 293) | def walk_dict(data: dict, key_path=None):
  function slots_with_param (line 304) | def slots_with_param(model, key, default, value):
  function _parse_little_endian_uint32 (line 323) | def _parse_little_endian_uint32(match):
  function power_panic_delay (line 330) | def power_panic_delay(cfg):

FILE: prusa/link/web/__init__.py
  function init_web_app (line 27) | def init_web_app(daemon):
  class WebServer (line 58) | class WebServer:
    method __init__ (line 61) | def __init__(self, application, address, port, exit_on_error=False):
    method start (line 71) | def start(self):
    method run (line 77) | def run(self):
    method stop (line 105) | def stop(self):

FILE: prusa/link/web/cameras.py
  function format_header (line 29) | def format_header(header):
  function photo_by_camera_id (line 34) | def photo_by_camera_id(camera_id, req):
  function default_camera_snap (line 84) | def default_camera_snap(req):
  function list_cameras (line 98) | def list_cameras(_):
  function set_order (line 135) | def set_order(req):
  function get_photo_by_camera_id (line 145) | def get_photo_by_camera_id(req, camera_id):
  function take_photo_by_camera_id (line 152) | def take_photo_by_camera_id(_, camera_id):
  function camera_config (line 176) | def camera_config(_, camera_id):
  function add_camera (line 203) | def add_camera(req, camera_id):
  function delete_camera (line 233) | def delete_camera(_, camera_id):
  function set_settings (line 249) | def set_settings(req, camera_id):
  function reset_settings (line 270) | def reset_settings(_, camera_id):
  function register_camera (line 283) | def register_camera(_, camera_id):
  function unregister_camera (line 310) | def unregister_camera(_, camera_id):

FILE: prusa/link/web/connection.py
  function compose_register_url (line 18) | def compose_register_url(printer, connect_url, name, location):
  function api_connection (line 39) | def api_connection(req):
  function api_connection_set (line 91) | def api_connection_set(req):
  function api_connection_delete (line 138) | def api_connection_delete(req):

FILE: prusa/link/web/controls.py
  function check_temperature_limits (line 22) | def check_temperature_limits(temperature, min_temperature, max_temperatu...
  function check_value_limits (line 30) | def check_value_limits(value, min_value, max_value):
  function jog (line 38) | def jog(req, serial_queue):
  function home (line 91) | def home(req, serial_queue):
  function set_speed (line 102) | def set_speed(req, serial_queue):
  function disable_steppers (line 112) | def disable_steppers(serial_queue):
  function extrude (line 118) | def extrude(req, serial_queue):
  function api_printhead (line 138) | def api_printhead(req):
  function api_tool (line 177) | def api_tool(req):
  function api_bed (line 220) | def api_bed(req):

FILE: prusa/link/web/errors.py
  function response_error (line 16) | def response_error(req, error: conditions.LinkError):
  function internal_server_error (line 33) | def internal_server_error(req):
  function forbidden (line 64) | def forbidden(req):
  function not_found (line 71) | def not_found(req):
  function no_file_in_request (line 77) | def no_file_in_request(req):
  function file_size_mismatch (line 83) | def file_size_mismatch(req):
  function forbidden_characters (line 89) | def forbidden_characters(req):
  function filename_too_long (line 95) | def filename_too_long(req):
  function foldername_too_long (line 101) | def foldername_too_long(req):
  function sdcard_not_supported (line 107) | def sdcard_not_supported(req):
  function location_not_found (line 113) | def location_not_found(req):
  function file_currently_printed (line 119) | def file_currently_printed(req):
  function transfer_conflict (line 125) | def transfer_conflict(req):
  function unavailable_update (line 131) | def unavailable_update(req):
  function unable_to_update (line 137) | def unable_to_update(req):
  function entity_too_large (line 143) | def entity_too_large(req):
  function unsupported_media_type (line 149) | def unsupported_media_type(req):
  function response_timeout (line 155) | def response_timeout(req):
  function cant_connect (line 161) | def cant_connect(req):
  function cant_move_axis (line 167) | def cant_move_axis(req):
  function cant_move_axis_z (line 173) | def cant_move_axis_z(req):
  function cant_resolve_hostname (line 179) | def cant_resolve_hostname(req):
  function destination_same_as_source (line 185) | def destination_same_as_source(req):
  function directory_not_empty (line 191) | def directory_not_empty(req):
  function file_already_exists (line 197) | def file_already_exists(req):
  function file_upload_failed (line 203) | def file_upload_failed(req):
  function folder_already_exists (line 209) | def folder_already_exists(req):
  function invalid_boolean_header (line 215) | def invalid_boolean_header(req):
  function length_required (line 221) | def length_required(req):
  function not_state_to_print (line 227) | def not_state_to_print(req):
  function storage_not_exist (line 233) | def storage_not_exist(req):
  function temperature_too_high (line 239) | def temperature_too_high(req):
  function temperature_too_low (line 245) | def temperature_too_low(req):
  function transfer_stopped (line 251) | def transfer_stopped(req):
  function value_too_high (line 257) | def value_too_high(req):
  function value_too_low (line 263) | def value_too_low(req):
  function gone (line 269) | def gone(req):
  function service_unavailable (line 283) | def service_unavailable(req):
  function link_error_handler (line 293) | def link_error_handler(req, error):

FILE: prusa/link/web/files.py
  function storage_info (line 51) | def storage_info(req):
  function head_file_info (line 93) | def head_file_info(req, storage, path=None):
  function file_info (line 117) | def file_info(req, storage, path=None):
  function file_upload (line 191) | def file_upload(req, storage, path):
  function file_delete (line 308) | def file_delete(req, storage, path):
  function file_start_print (line 334) | def file_start_print(req, storage, path):
  function transfer_info (line 351) | def transfer_info(req):
  function transfer_abort (line 375) | def transfer_abort(req):

FILE: prusa/link/web/files_legacy.py
  function api_files (line 64) | def api_files(req, path=''):
  function api_upload (line 136) | def api_upload(req, storage):
  function api_start_print (line 220) | def api_start_print(req, storage, path):
  function api_downloads (line 258) | def api_downloads(req, storage, path):
  function api_file_info (line 271) | def api_file_info(req, storage, path):
  function api_delete (line 326) | def api_delete(req, storage, path):
  function api_transfer_info (line 340) | def api_transfer_info(req):
  function api_download (line 367) | def api_download(req, storage):
  function api_create_folder (line 418) | def api_create_folder(req, storage, path):
  function api_delete_folder (line 435) | def api_delete_folder(req, storage, path):
  function api_modify (line 452) | def api_modify(req, storage):
  function api_download_abort (line 487) | def api_download_abort(req):
  function api_thumbnails (line 497) | def api_thumbnails(req, path, wanted_format):

FILE: prusa/link/web/lib/__init__.py
  function try_int (line 4) | def try_int(value):

FILE: prusa/link/web/lib/auth.py
  function check_digest (line 31) | def check_digest(req):
  function check_api_digest (line 54) | def check_api_digest(func):
  function check_config (line 78) | def check_config(func):
  function set_digest (line 93) | def set_digest(username, password):
  function valid_credentials (line 98) | def valid_credentials(username, new_password, new_repassword, errors):
  function valid_digests (line 117) | def valid_digests(digest, old_digest, new_digest, errors):

FILE: prusa/link/web/lib/classes.py
  class ThreadingServer (line 15) | class ThreadingServer(ThreadingMixIn, WSGIServer):
    method handle_error (line 23) | def handle_error(self, request, client_address):
  class LinkHandler (line 27) | class LinkHandler(ServerHandler):
    method log_exception (line 33) | def log_exception(self, exc_info):
  class RequestHandler (line 38) | class RequestHandler(WSGIRequestHandler):
    method log_message (line 43) | def log_message(self, format, *args):
    method log_error (line 47) | def log_error(self, *args):
    method handle (line 51) | def handle(self):

FILE: prusa/link/web/lib/core.py
  class LinkWebApp (line 16) | class LinkWebApp(Application):

FILE: prusa/link/web/lib/files.py
  function get_os_path (line 40) | def get_os_path(abs_path):
  function local_simple_refs (line 61) | def local_simple_refs(path: str):
  function sdcard_simple_refs (line 68) | def sdcard_simple_refs():
  function local_refs (line 75) | def local_refs(path: str, meta: FDMMetaData):
  function sdcard_refs (line 91) | def sdcard_refs():
  function gcode_analysis (line 100) | def gcode_analysis(meta):
  function fill_file_data (line 115) | def fill_file_data(path: str, storage: str):
  function fill_printfile_data (line 126) | def fill_printfile_data(path: str, os_path: str, storage: str,
  function file_to_api (line 166) | def file_to_api(node, origin: str = 'local', path: str = '/',
  function sort_files (line 276) | def sort_files(files, sort_by='folder,date'):
  function check_filename (line 296) | def check_filename(filename: str):
  function check_foldername (line 311) | def check_foldername(foldername: str):
  function check_os_path (line 323) | def check_os_path(os_path: str):
  function check_storage (line 330) | def check_storage(func):
  function check_read_only (line 340) | def check_read_only(func):
  function check_job (line 350) | def check_job(job: Job, path: str):
  function storage_display_name (line 358) | def storage_display_name(storage: str):
  function storage_display_path (line 368) | def storage_display_path(storage: str, path: str):
  function partfilepath (line 376) | def partfilepath(filename):
  function get_local_free_space (line 382) | def get_local_free_space(path: str):
  function get_files_size (line 391) | def get_files_size(files: dict, file_type: str):
  class GCodeFile (line 400) | class GCodeFile(FileIO):
    method __init__ (line 403) | def __init__(self, filepath: str, transfer: Transfer):
    method uploaded (line 415) | def uploaded(self):
    method write (line 419) | def write(self, data):
    method close (line 435) | def close(self):
  function callback_factory (line 447) | def callback_factory(req: Request):
  function make_headers (line 487) | def make_headers(storage: str, path: str) -> dict:
  function get_last_modified (line 497) | def get_last_modified(file_system: Filesystem) -> datetime:
  function generate_etag (line 506) | def generate_etag(last_modified_str: str) -> str:
  function make_cache_headers (line 512) | def make_cache_headers(last_modified: datetime) -> dict:
  function check_cache_headers (line 526) | def check_cache_headers(req_headers: Headers, headers: dict,
  function get_boolean_header (line 542) | def get_boolean_header(headers, variable):

FILE: prusa/link/web/lib/view.py
  function printer_type (line 18) | def printer_type(type_):
  function add_prefix (line 32) | def add_prefix(prefix, uri):
  function prefix_filter (line 43) | def prefix_filter(context: Context, uri):
  function redirect_with_proxy (line 49) | def redirect_with_proxy(req, uri):
  function package_to_api (line 69) | def package_to_api(pkg):
  function generate_page (line 78) | def generate_page(request, template, **kwargs):

FILE: prusa/link/web/lib/wizard.py
  function valid_sn_format (line 28) | def valid_sn_format(serial):
  function new_sn_format (line 33) | def new_sn_format(serial):
  function sn_write_success (line 38) | def sn_write_success() -> bool:
  function execute_sn_gcode (line 60) | def execute_sn_gcode(serial_number: str, serial_queue):
  class Wizard (line 72) | class Wizard:
    method __init__ (line 76) | def __init__(self, _app):
    method set_digest (line 117) | def set_digest(self, password):
    method serial_number (line 122) | def serial_number(self):
    method check_username (line 126) | def check_username(self):
    method check_credentials (line 136) | def check_credentials(self, password, repassword):
    method check_serial (line 152) | def check_serial(self):
    method check_connect (line 162) | def check_connect(self):
    method write_settings (line 179) | def write_settings(self, settings):

FILE: prusa/link/web/link_info.py
  function link_info (line 9) | def link_info(req):

FILE: prusa/link/web/main.py
  function instance (line 72) | def instance(req):
  function index (line 83) | def index(req):
  function websocket (line 90) | def websocket(req):
  function api_logs (line 98) | def api_logs(req):
  function api_log (line 132) | def api_log(req, filename):
  function api_info (line 168) | def api_info(req):
  function api_status (line 192) | def api_status(req):
  function api_version (line 280) | def api_version(req):
  function api_login (line 320) | def api_login(req):
  function api_printer (line 332) | def api_printer(req):
  function api_printer_sd (line 402) | def api_printer_sd(req):
  function api_set_ready (line 410) | def api_set_ready(req):
  function api_cancel_ready (line 423) | def api_cancel_ready(req):
  function api_timelapse (line 435) | def api_timelapse(req):
  function api_job (line 446) | def api_job(req):
  function api_job_command (line 519) | def api_job_command(req):
  function job_info (line 570) | def job_info(req):
  function job_stop (line 608) | def job_stop(req, job_id):
  function job_command (line 630) | def job_command(req, job_id, command):
  function api_update (line 672) | def api_update(req, env):
  function api_update_post (line 706) | def api_update_post(req, env):

FILE: prusa/link/web/settings.py
  function set_settings_user (line 34) | def set_settings_user(new_username, new_digest):
  function save_settings (line 42) | def save_settings():
  function update_apikey (line 48) | def update_apikey(api_key):
  function api_ports (line 67) | def api_ports(req):
  function api_settings (line 85) | def api_settings(req):
  function api_settings_set (line 105) | def api_settings_set(req):
  function regenerate_api_key (line 172) | def regenerate_api_key(req):
  function delete_api_key (line 191) | def delete_api_key(req):
  function get_api_sn (line 201) | def get_api_sn(req):
  function api_sn (line 209) | def api_sn(req):

FILE: prusa/link/web/wizard.py
  function check_printer (line 27) | def check_printer(fun):
  function check_step (line 43) | def check_step(step):
  class ConfigFile (line 59) | class ConfigFile:
    method __init__ (line 62) | def __init__(self):
    method write (line 65) | def write(self, data):
    method read (line 71) | def read(self):
    method seek (line 75) | def seek(self, size):
  function configfile_factory (line 80) | def configfile_factory(req):
  function process_printer (line 94) | def process_printer(config):
  function process_network (line 109) | def process_network(config):
  function process_connect (line 116) | def process_connect(config):
  function process_local (line 136) | def process_local(config):
  function parse_settings (line 152) | def parse_settings(buffer):
  function wizard_root (line 178) | def wizard_root(req):
  function wizard_restore_ (line 188) | def wizard_restore_(req):
  function wizard_restore_post (line 194) | def wizard_restore_post(req):
  function wizard_credentials (line 213) | def wizard_credentials(req):
  function wizard_credentials_post (line 220) | def wizard_credentials_post(req):
  function wizard_printer (line 247) | def wizard_printer(req):
  function wizard_printer_post (line 254) | def wizard_printer_post(req):
  function wizard_finish (line 265) | def wizard_finish(req):
  function wizard_serial (line 277) | def wizard_serial(req):
  function wizard_serial_set (line 284) | def wizard_serial_set(req):
  function wizard_finish_skip_post (line 309) | def wizard_finish_skip_post(req):
  function wizard_finish_post (line 336) | def wizard_finish_post(req):
  function check_wizard_access (line 375) | def check_wizard_access(req):

FILE: setup.py
  function fill_requires (line 22) | def fill_requires(filename):
  function doc (line 53) | def doc():
  class BuildStatic (line 59) | class BuildStatic(Command):
    method initialize_options (line 68) | def initialize_options(self):
    method finalize_options (line 71) | def finalize_options(self):
    method run (line 76) | def run(self):

FILE: tests/test_carousel.py
  function test_line (line 11) | def test_line():
  function test_screen_lines (line 27) | def test_screen_lines():
  function test_priority (line 64) | def test_priority():
  function test_lines (line 95) | def test_lines():

FILE: tests/test_ipc_queue.py
  function ipc_consumer (line 20) | def ipc_consumer():
  function test_send_and_close (line 28) | def test_send_and_close(ipc_consumer):
  function test_multiple_sends (line 37) | def test_multiple_sends(ipc_consumer):
  function test_args (line 53) | def test_args(ipc_consumer):
  function test_rights (line 66) | def test_rights():
  function test_signal_resistance (line 73) | def test_signal_resistance(ipc_consumer):
  function test_signal_resistance_reverse (line 101) | def test_signal_resistance_reverse():

FILE: tests/test_item_updater.py
  function updater_instance (line 27) | def updater_instance():
  function validator (line 38) | def validator():
  function test_basics (line 47) | def test_basics(updater_instance: ItemUpdater):
  function test_group (line 87) | def test_group(updater_instance: ItemUpdater):
  function test_scheduled_invalidation (line 148) | def test_scheduled_invalidation(updater_instance: ItemUpdater):
  function test_validation (line 285) | def test_validation(updater_instance: ItemUpdater, validator):
  function test_gather_error (line 317) | def test_gather_error(updater_instance: ItemUpdater):
  function test_timeouts (line 348) | def test_timeouts(updater_instance: ItemUpdater, validator):
  function test_empty_group (line 456) | def test_empty_group():
  function test_group_invalidation (line 462) | def test_group_invalidation(updater_instance: ItemUpdater):
  function test_garb (line 485) | def test_garb(updater_instance: ItemUpdater):
  function test_valid_group_init (line 495) | def test_valid_group_init(updater_instance: ItemUpdater):
  function test_valid_item_doesnt_gather (line 528) | def test_valid_item_doesnt_gather(updater_instance: ItemUpdater):
  function test_subclasses_work (line 550) | def test_subclasses_work(updater_instance: ItemUpdater):
  function test_disabling (line 560) | def test_disabling(updater_instance: ItemUpdater):
  function test_group_updating (line 585) | def test_group_updating(updater_instance: ItemUpdater):
  function test_priority_queue (line 605) | def test_priority_queue():

FILE: tests/test_serial_parser.py
  function test_basic (line 10) | def test_basic():
  function test_inverted_basic (line 22) | def test_inverted_basic():
  function test_basic_removal (line 33) | def test_basic_removal():
  function test_priority (line 45) | def test_priority():
  function test_bump_priority (line 71) | def test_bump_priority():

FILE: tests/util.py
  function waiter (line 7) | def waiter(event: Event):
  class WaitingMock (line 16) | class WaitingMock(Mock):
    method __init__ (line 20) | def __init__(self, *args, side_effect=None, **kwargs):
  class EventSetMock (line 32) | class EventSetMock(Mock):
    method __init__ (line 36) | def __init__(self, *args, side_effect=None, **kwargs):
    method reset_mock (line 46) | def reset_mock(self, *args, **kwargs) -> None:
Condensed preview — 142 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,268K chars).
[
  {
    "path": ".editorconfig",
    "chars": 275,
    "preview": "# EditorConfig <https://EditorConfig.org>\nroot = true\n\n# elementary defaults\n[*]\ncharset = utf-8\nend_of_line = lf\nindent"
  },
  {
    "path": ".github/workflows/pages.yml",
    "chars": 575,
    "preview": "name: Deploy to GitHub Pages\n\non:\n  push:\n    branches: [master]\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  p"
  },
  {
    "path": ".github/workflows/python-tests.yml",
    "chars": 1545,
    "preview": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more inform"
  },
  {
    "path": ".gitignore",
    "chars": 105,
    "preview": "venv\n.idea\n.env\n__pycache__\n*.pyc\nbuild\ndist\n*.egg-info\n*.orig\n.hypothesis\n*.coverage\nhtmlcov\ndocs/*.png\n"
  },
  {
    "path": ".gitmodules",
    "chars": 188,
    "preview": "[submodule \"prusa-link-web\"]\n\tpath = Prusa-Link-Web\n\turl = https://github.com/prusa3d/Prusa-Link-Web.git\n[submodule \"PiS"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 642,
    "preview": "---\nrepos:\n  # - repo: https://github.com/pre-commit/mirrors-yapf\n  #   rev: 'v0.32.0'  # Use the sha / tag you want to "
  },
  {
    "path": ".pylintrc",
    "chars": 322,
    "preview": "[BASIC]\n\ngood-names=i,l,f,sn,ip\n\n[TYPECHECK]\n\ngenerated-members=prctl\n\n[MASTER]\nextension-pkg-whitelist=pydantic\nignore-"
  },
  {
    "path": "CONTRIBUTION.md",
    "chars": 712,
    "preview": "# Contribution\n\n## Developement\n\n**On Rasbian**:\n\nRunning on foreground without install:\n\n```sh\npython3 -m prusa.link -f"
  },
  {
    "path": "ChangeLog",
    "chars": 21877,
    "preview": "# ChangeLog\n\n0.8.2 (2024-12-18)\n    * Fix crc issue\n\n0.8.1 (2024-06-28)\n    * Add a v4l2 workaround so broken camera han"
  },
  {
    "path": "MANIFEST.in",
    "chars": 229,
    "preview": "include *.py\ninclude MANIFEST.in\ninclude requirements.txt\ninclude requirements-pi.txt\ngraft prusa/link/data\ngraft prusa/"
  },
  {
    "path": "MULTIINSTANCE.md",
    "chars": 3325,
    "preview": "# PrusaLink multi instance\nIn this mode, an instance of PrusaLink is created for each new printer\ndetected on any of the"
  },
  {
    "path": "README.md",
    "chars": 6474,
    "preview": "# PrusaLink\n\nPrusaLink is a compatibility layer between 8-bit Prusa 3D printers\n(MK2.5, MK2.5S, MK3, MK3S and MK3S+) and"
  },
  {
    "path": "config.custom.js",
    "chars": 831,
    "preview": "const webpackConfig = require(\"./webpack.config\");\n\nmodule.exports = (env, args) => {\n    const config = {\n        PRINT"
  },
  {
    "path": "docs/Makefile",
    "chars": 170,
    "preview": "#\n# Makefile\n# Martin Užák, 2021-01-14 14:55\n#\n\nUMLFILES = prusalink_states.txt wizard.txt\n\numl: $(UMLFILES)\n\tplantuml $"
  },
  {
    "path": "docs/prusalink_states.txt",
    "chars": 1311,
    "preview": "@startuml\n\nSerial --> RPIenabled\nSerial: Serial Port exists\n\nRPIenabled --> IDPrinter\nRPIenabled: RPI Port is enabled / "
  },
  {
    "path": "docs/wizard.txt",
    "chars": 2282,
    "preview": "@startuml\n\n\nstate \"Add Printer\" as Add\nstate \"PrusaLink Wizard\" as Wizard #lavender\nstate \"Use config\" as WConfig #laven"
  },
  {
    "path": "image_builder/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "image_builder/image_builder.py",
    "chars": 18290,
    "preview": "\"\"\"Following a writeup from here:\nhttps://blog.grandtrunk.net/2023/03/raspberry-pi-4-emulation-with-qemu/\"\"\"\nimport argp"
  },
  {
    "path": "prusa/link/__init__.py",
    "chars": 528,
    "preview": "\"\"\"Original PrusaLink printer adapter.\n\n    Copyright (C) 2024 PrusaResearch\n\"\"\"\n__application__ = \"PrusaLink\"\n__vendor_"
  },
  {
    "path": "prusa/link/__main__.py",
    "chars": 9405,
    "preview": "\"\"\"main() command line function.\"\"\"\n\nimport logging\nimport sys\nimport threading\nfrom argparse import ArgumentParser, Arg"
  },
  {
    "path": "prusa/link/camera_governor.py",
    "chars": 2153,
    "preview": "\"\"\"Implements a simple loop for getting cameras unstuck\nand for auto adding them\"\"\"\nimport logging\nfrom functools import"
  },
  {
    "path": "prusa/link/cameras/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/cameras/encoders.py",
    "chars": 13813,
    "preview": "\"\"\"This file contains encoders for the camera drivers\nEspecially the hardware conversion needs a lot of prep work\"\"\"\n\nim"
  },
  {
    "path": "prusa/link/cameras/picamera_driver.py",
    "chars": 13509,
    "preview": "\"\"\"Contains implementation of a driver for Rpi Cameras\"\"\"\nimport gc\nimport logging\nimport select\nfrom time import time\nf"
  },
  {
    "path": "prusa/link/cameras/v4l2.py",
    "chars": 65669,
    "preview": "# Python bindings for the v4l2 userspace api\n\n# Copyright (C) 1999-2009 the contributors\n\n# This program is free softwar"
  },
  {
    "path": "prusa/link/cameras/v4l2_driver.py",
    "chars": 21792,
    "preview": "\"\"\"Contains implementation of a camera driver utilizing the V4L2 API\"\"\"\nimport ctypes\nimport errno\nimport fcntl\nimport f"
  },
  {
    "path": "prusa/link/conditions.py",
    "chars": 15720,
    "preview": "\"\"\"PrusaLink error states.html\n\nFor more information see prusalink_states.txt.\n\"\"\"\n\nfrom typing import Optional\n\nfrom po"
  },
  {
    "path": "prusa/link/config.py",
    "chars": 12210,
    "preview": "\"\"\"Config class definition.\"\"\"\nimport logging\nfrom logging import Formatter, StreamHandler\nfrom logging.handlers import "
  },
  {
    "path": "prusa/link/const.py",
    "chars": 10716,
    "preview": "\"\"\"\nContains almost every constant for the printer communication part of\nPrusaLink\n\"\"\"\n\nimport uuid\nfrom importlib.resou"
  },
  {
    "path": "prusa/link/daemon.py",
    "chars": 2684,
    "preview": "\"\"\"Daemon class implementation.\"\"\"\nimport logging\nimport sys\nfrom subprocess import Popen\nfrom typing import List\n\nimpor"
  },
  {
    "path": "prusa/link/data/image_builder/boot-message.service",
    "chars": 188,
    "preview": "[Unit]\nDescription=Boot message\n\n[Service]\nType=simple\nExecStart=/bin/sh -c 'stty -F /dev/ttyAMA0 115200; printf \\'M117 "
  },
  {
    "path": "prusa/link/data/image_builder/first-boot.sh",
    "chars": 466,
    "preview": "set_up_port () {\n   # Sets the baudrate and cancels the hangup at the end of a connection\n   stty -F \"$1\" 115200 -hupcl "
  },
  {
    "path": "prusa/link/data/image_builder/manager-start-script.sh",
    "chars": 1208,
    "preview": "# Forward the port 80 to 8080 even on the loopback, so we can ping ourselves\niptables -t nat -A PREROUTING -i wlan0 -p t"
  },
  {
    "path": "prusa/link/data/image_builder/prusalink-start-script.sh",
    "chars": 1082,
    "preview": "# Forward the port 80 to 8080 even on the loopback, so we can ping ourselves\niptables -t nat -A PREROUTING -i wlan0 -p t"
  },
  {
    "path": "prusa/link/data/prusalink.ini",
    "chars": 1041,
    "preview": "[daemon]\n; data_dir is used as default directory for other files, like\n; prusa_printer_settings.ini or threshold_file\n; "
  },
  {
    "path": "prusa/link/interesting_logger.py",
    "chars": 9166,
    "preview": "\"\"\"Implements the InterestingLogRotator and InterestingLogger classes\"\"\"\nimport logging\nimport sys\nimport threading\nimpo"
  },
  {
    "path": "prusa/link/multi_instance/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/multi_instance/__main__.py",
    "chars": 9528,
    "preview": "\"\"\"The module for starting PrusaLink Instance Manager components\"\"\"\nimport argparse\nimport logging\nimport os\nimport pwd\n"
  },
  {
    "path": "prusa/link/multi_instance/config_component.py",
    "chars": 15911,
    "preview": "\"\"\"A module for managing the configuration files of multiple\nPrusaLink instances\"\"\"\nimport grp\nimport logging\nimport os\n"
  },
  {
    "path": "prusa/link/multi_instance/const.py",
    "chars": 1877,
    "preview": "\"\"\"Contains constants used by the multi instance manager\"\"\"\nimport os\nimport re\n\nDEFAULT_UID = 1000  # Default user UID\n"
  },
  {
    "path": "prusa/link/multi_instance/controller.py",
    "chars": 2642,
    "preview": "\"\"\"A module implementing the controller of the PrusaLink Instance Manager\"\"\"\n\nimport logging\nimport os\n\nfrom .config_com"
  },
  {
    "path": "prusa/link/multi_instance/ipc_queue_adapter.py",
    "chars": 5050,
    "preview": "\"\"\"A module implementing the IPC queue message consumer\"\"\"\nimport logging\nimport os\nimport queue\nfrom threading import T"
  },
  {
    "path": "prusa/link/multi_instance/runner_component.py",
    "chars": 2516,
    "preview": "\"\"\"The component that manages PrusaLink instances\nSadly stopping cannot be handled here for readability reasons\"\"\"\nimpor"
  },
  {
    "path": "prusa/link/multi_instance/web.py",
    "chars": 7015,
    "preview": "\"\"\"Init file for web application module.\"\"\"\nimport logging\nfrom hashlib import sha256\nfrom multiprocessing import Lock\nf"
  },
  {
    "path": "prusa/link/printer_adapter/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/printer_adapter/auto_telemetry.py",
    "chars": 5043,
    "preview": "\"\"\"Contains implementation of the ReportingEnsurer class\"\"\"\nfrom re import Match\nfrom time import time\n\nfrom ..const imp"
  },
  {
    "path": "prusa/link/printer_adapter/command.py",
    "chars": 3893,
    "preview": "\"\"\"Contains implementation of the Command class\"\"\"\nimport abc\nimport logging\nimport re\nfrom threading import Event\nfrom "
  },
  {
    "path": "prusa/link/printer_adapter/command_handlers.py",
    "chars": 26071,
    "preview": "\"\"\"\nImplements all command PrusaLink command handlers\nStart, pause, resume and stop print as well as one for executing a"
  },
  {
    "path": "prusa/link/printer_adapter/command_queue.py",
    "chars": 4310,
    "preview": "\"\"\"\nImplements the CommandQueue with CommandAdapter class, the objects of\nwithch are the queue members\n\"\"\"\n\nimport loggi"
  },
  {
    "path": "prusa/link/printer_adapter/file_printer.py",
    "chars": 16180,
    "preview": "\"\"\"Contains implementation of the FilePrinter class\"\"\"\nimport json\nimport logging\nimport os\nfrom collections import dequ"
  },
  {
    "path": "prusa/link/printer_adapter/filesystem/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/printer_adapter/filesystem/sd_card.py",
    "chars": 16993,
    "preview": "\"\"\"Contains implementation of the class for keeping track of the sd status\nand its files\"\"\"\n\nimport calendar\nimport logg"
  },
  {
    "path": "prusa/link/printer_adapter/filesystem/storage.py",
    "chars": 9557,
    "preview": "\"\"\"\nContains the implementation of Storage, FSStorage and FolderStorage for keeping\ntrack of Linux and folder storage.\n\""
  },
  {
    "path": "prusa/link/printer_adapter/filesystem/storage_controller.py",
    "chars": 3671,
    "preview": "\"\"\"\nContains implementation of the  controller for interfacing with the storage\n\"subsystem\", which included the linux fi"
  },
  {
    "path": "prusa/link/printer_adapter/ip_updater.py",
    "chars": 6534,
    "preview": "\"\"\"Contains implementation of the IPUpdater class\"\"\"\n\nimport logging\nimport socket\nfrom time import time\n\nimport pyric  "
  },
  {
    "path": "prusa/link/printer_adapter/job.py",
    "chars": 12552,
    "preview": "\"\"\"Contains implementation of the Job class\"\"\"\n\nimport logging\nimport os\nimport re\n\nfrom blinker import Signal  # type: "
  },
  {
    "path": "prusa/link/printer_adapter/keepalive.py",
    "chars": 2196,
    "preview": "\"\"\"Contains the keepalive implementation\"\"\"\nfrom enum import Enum\nfrom threading import Event, Thread\nfrom time import m"
  },
  {
    "path": "prusa/link/printer_adapter/lcd_printer.py",
    "chars": 22159,
    "preview": "\"\"\"\nShould inform the user about everything important in PrusaLink while\nnod obstructing anything else the printer wrote"
  },
  {
    "path": "prusa/link/printer_adapter/mmu_observer.py",
    "chars": 4271,
    "preview": "\"\"\"Contains the mmu output observing code, that compiles the readouts into\n telemetry values\"\"\"\nfrom re import Match\n\nfr"
  },
  {
    "path": "prusa/link/printer_adapter/model.py",
    "chars": 1223,
    "preview": "\"\"\"Contains implementation of the Model class\"\"\"\n\nfrom .structures.mc_singleton import MCSingleton\nfrom .structures.mode"
  },
  {
    "path": "prusa/link/printer_adapter/print_stat_doubler.py",
    "chars": 1618,
    "preview": "\"\"\"Implements the print stat line doubling\"\"\"\nimport re\nfrom typing import List\n\nfrom ..serial.serial_parser import Thre"
  },
  {
    "path": "prusa/link/printer_adapter/print_stats.py",
    "chars": 3854,
    "preview": "\"\"\"Contains implementation of the PrintStats class\"\"\"\nimport logging\nfrom time import time\n\nfrom ..const import TAIL_COM"
  },
  {
    "path": "prusa/link/printer_adapter/printer_polling.py",
    "chars": 43180,
    "preview": "\"\"\"\nUses info updater to keep up with the printer info.\nHope I can get most of printer polling to use this mechanism.\n\"\""
  },
  {
    "path": "prusa/link/printer_adapter/prusa_link.py",
    "chars": 45698,
    "preview": "\"\"\"Implements the PrusaLink class\"\"\"\nimport logging\nimport os\nimport re\nfrom enum import Enum\nfrom threading import Even"
  },
  {
    "path": "prusa/link/printer_adapter/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/printer_adapter/special_commands.py",
    "chars": 3945,
    "preview": "\"\"\"An implementation of a hidden menu logic\"\"\"\nimport logging\nimport re\nfrom time import time\n\nfrom blinker import Signa"
  },
  {
    "path": "prusa/link/printer_adapter/state_manager.py",
    "chars": 33480,
    "preview": "\"\"\"Contains implementation of the  the StateManager and StateChange classes\"\"\"\nimport logging\nimport re\nfrom collections"
  },
  {
    "path": "prusa/link/printer_adapter/structures/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/printer_adapter/structures/carousel.py",
    "chars": 8949,
    "preview": "\"\"\"Implements the helper classes for LCD printer.\nDoes not depend on the rest of the PrusaLink app\"\"\"\nimport math\nfrom c"
  },
  {
    "path": "prusa/link/printer_adapter/structures/heap.py",
    "chars": 5640,
    "preview": "\"\"\"\nContains implementation of the HeapItem, MinHeap and MaxHeap classes\nI HAVE COPIED THIS FROM THE INTERNET!\nIt turns "
  },
  {
    "path": "prusa/link/printer_adapter/structures/item_updater.py",
    "chars": 19976,
    "preview": "\"\"\"Implements classes for monitoring and updating arbitrary values\"\"\"\n\nimport logging\nfrom math import inf\nfrom multipro"
  },
  {
    "path": "prusa/link/printer_adapter/structures/mc_singleton.py",
    "chars": 610,
    "preview": "\"\"\"Contains implementation of the MCSingleton class\"\"\"\n\n\nclass MCSingleton(type):\n    \"\"\"\n    Classes that use this meta"
  },
  {
    "path": "prusa/link/printer_adapter/structures/model_classes.py",
    "chars": 4487,
    "preview": "\"\"\"\nContains models that were originally intended for sending to the connect.\nPydantic makes a great tool for cleanly se"
  },
  {
    "path": "prusa/link/printer_adapter/structures/module_data_classes.py",
    "chars": 4415,
    "preview": "\"\"\"\nDecided that keeping module data externally will aid with gathering them for\nthe api, definitions of which is what t"
  },
  {
    "path": "prusa/link/printer_adapter/structures/regular_expressions.py",
    "chars": 8289,
    "preview": "\"\"\"Contains every regular expression used in the app as a constant\"\"\"\nimport re\n\nfrom ...const import MMU_PROGRESS_MAP\n\n"
  },
  {
    "path": "prusa/link/printer_adapter/telemetry_passer.py",
    "chars": 15952,
    "preview": "\"\"\"\nThe frequency at which we send telemetry is being determined by quite a\nlot of factore. This module takes care of mo"
  },
  {
    "path": "prusa/link/printer_adapter/updatable.py",
    "chars": 1764,
    "preview": "\"\"\"\nContains implementation of the ThreadedUpdatable class\nThere was an updatable without a thread, but it stopped being"
  },
  {
    "path": "prusa/link/sdk_augmentation/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/sdk_augmentation/command_handler.py",
    "chars": 1224,
    "preview": "\"\"\"Contains implementation of the CommandHandler class\"\"\"\nfrom prusa.connect.printer import Command\n\nfrom ..const import"
  },
  {
    "path": "prusa/link/sdk_augmentation/file.py",
    "chars": 1176,
    "preview": "\"\"\"Contains implementation of the SDFile class which augments the SDK File\"\"\"\nfrom pathlib import Path\n\nfrom prusa.conne"
  },
  {
    "path": "prusa/link/sdk_augmentation/printer.py",
    "chars": 6493,
    "preview": "\"\"\"Contains implementation of the augmented Printer class from the SDK\"\"\"\n\nfrom logging import getLogger\nfrom pathlib im"
  },
  {
    "path": "prusa/link/serial/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "prusa/link/serial/helpers.py",
    "chars": 4664,
    "preview": "\"\"\"Contains helper functions, for instruction enqueuing\"\"\"\nimport re\nfrom threading import Event\nfrom typing import Call"
  },
  {
    "path": "prusa/link/serial/instruction.py",
    "chars": 6094,
    "preview": "\"\"\"\nContains implementation for all the types of instructions enqueueable to the\nserial queue\n\"\"\"\nimport logging\nimport "
  },
  {
    "path": "prusa/link/serial/is_planner_fed.py",
    "chars": 7456,
    "preview": "\"\"\"\nContains implementation of the IsPlannerFed class, with HeapName and TimeValue\nclasses. Tries to guess, whether the "
  },
  {
    "path": "prusa/link/serial/serial.py",
    "chars": 4781,
    "preview": "\"\"\"Own Serail class \"\"\"\nimport errno\nimport fcntl\nimport logging\nimport os\nimport struct\nimport termios\nfrom select impo"
  },
  {
    "path": "prusa/link/serial/serial_adapter.py",
    "chars": 15653,
    "preview": "\"\"\"Contains implementation of the Serial class\"\"\"\n\nimport glob\nimport logging\nimport os\nimport re\nfrom importlib import "
  },
  {
    "path": "prusa/link/serial/serial_parser.py",
    "chars": 7178,
    "preview": "\"\"\"\nContains implementation of the SerialParser and Regex pairing classes\nThe latter is used by the former for tracking "
  },
  {
    "path": "prusa/link/serial/serial_queue.py",
    "chars": 25838,
    "preview": "\"\"\"\nContains implementation of the SerialQueue and the MonitoredSerialQueue\n\nThe idea was to separate the monitoring fun"
  },
  {
    "path": "prusa/link/service_discovery.py",
    "chars": 4860,
    "preview": "\"\"\"\nImplements the things for service discovery\nAs of now only DNS-SD is supported\n\"\"\"\nimport logging\nimport socket\nfrom"
  },
  {
    "path": "prusa/link/static/css/bootstrap.connect.css",
    "chars": 2663,
    "preview": "a {\r\n    color: #fa6831 !important;\r\n}\r\n\r\n.flex-even {\r\n    flex: 1;\r\n}\r\n\r\nsection {\r\n    margin-top: 10px;\r\n}\r\n\r\n#mainm"
  },
  {
    "path": "prusa/link/static/css/bootstrap.prusa-link.css",
    "chars": 1797,
    "preview": "body,\nhtml {\n    font-size: 18px;\n    color: #7a7a7a;\n    background-color: hsl(0, 0%, 4%);\n    height: 100%;\n    font-f"
  },
  {
    "path": "prusa/link/static/index.html",
    "chars": 25060,
    "preview": "<!doctype html><html><head><meta charset=\"utf-8\"/><meta name=\"theme-color\" content=\"#fff\"/><link rel=\"icon\" href=\"data:i"
  },
  {
    "path": "prusa/link/static/main.9b8dc0068f6e6508dfd4.js",
    "chars": 187917,
    "preview": "(()=>{var e={5862:(e,t,a)=>{\"use strict\";a.r(t),a.d(t,{default:()=>i});const i=a.p+\"f844a0a85dde310826fce450c3e149d3.svg"
  },
  {
    "path": "prusa/link/static/main.b3e029296dd89863b3f2.css",
    "chars": 25805,
    "preview": "@-webkit-keyframes zoomOut{0%{opacity:1}50%{opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes zoomOut{0%{op"
  },
  {
    "path": "prusa/link/templates/_footer.html",
    "chars": 590,
    "preview": "{% if wx is defined %}\n        </td>\n        <td></td>\n    </tr></table>\n    <br>\n\n    <footer class=\"footer mt-auto py-"
  },
  {
    "path": "prusa/link/templates/_header.html",
    "chars": 1546,
    "preview": "<!DOCTYPE html>\n<html id=\"html\" lang=\"cs\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>Prusa Link{{ ' | ' + title if t"
  },
  {
    "path": "prusa/link/templates/_wizard.html",
    "chars": 1503,
    "preview": "</div>\n\n<footer class=\"footer mt-auto py-3\">\n    <div class=\"container\">\n\n<div class=\"progress\">\n    {%- if active in ('"
  },
  {
    "path": "prusa/link/templates/error-gone.html",
    "chars": 475,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = '410 Gone' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n\n    <"
  },
  {
    "path": "prusa/link/templates/error-internal-server-error.html",
    "chars": 603,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set wx = True %}\n{% set title = '500 Internal Server Error' -%}\n{#% set refresh = 15 %#"
  },
  {
    "path": "prusa/link/templates/error.html",
    "chars": 244,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set wx = True %}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n\n            <h1"
  },
  {
    "path": "prusa/link/templates/index.html",
    "chars": 766,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Home' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n\n    <div "
  },
  {
    "path": "prusa/link/templates/link_info.html",
    "chars": 6280,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Home' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n\n    <div "
  },
  {
    "path": "prusa/link/templates/multi-instance.html",
    "chars": 898,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Multi-Instance landing page' -%}\n{#% set refresh = 15 %#}\n{% include \"_hea"
  },
  {
    "path": "prusa/link/templates/wizard.html",
    "chars": 9807,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n{% set a"
  },
  {
    "path": "prusa/link/templates/wizard_credentials.html",
    "chars": 4852,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard Credentials' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\""
  },
  {
    "path": "prusa/link/templates/wizard_finish.html",
    "chars": 5645,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n{% set a"
  },
  {
    "path": "prusa/link/templates/wizard_printer.html",
    "chars": 2435,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n{% set a"
  },
  {
    "path": "prusa/link/templates/wizard_restore.html",
    "chars": 2081,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard Restore' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n"
  },
  {
    "path": "prusa/link/templates/wizard_serial.html",
    "chars": 2519,
    "preview": "{# vim:set softtabstop=2: -#}\n{% set title = 'Wizard' -%}\n{#% set refresh = 15 %#}\n{% include \"_header.html\" %}\n{% set a"
  },
  {
    "path": "prusa/link/util.py",
    "chars": 10327,
    "preview": "\"\"\"Contains functions that might be useful outside of their modules\"\"\"\nimport datetime\nimport json\nimport logging\nimport"
  },
  {
    "path": "prusa/link/web/__init__.py",
    "chars": 3525,
    "preview": "\"\"\"Init file for web application module.\"\"\"\nimport logging\nfrom threading import Thread\nfrom time import sleep\nfrom wsgi"
  },
  {
    "path": "prusa/link/web/cameras.py",
    "chars": 13661,
    "preview": "\"\"\"Camera web API - /api/v1/cameras handlers\"\"\"\nfrom datetime import datetime, timedelta\nfrom time import sleep, time\n\nf"
  },
  {
    "path": "prusa/link/web/connection.py",
    "chars": 4976,
    "preview": "\"\"\"/api/connection endpoint handlers\"\"\"\nfrom socket import gethostbyname\nfrom urllib import parse\nfrom urllib.request im"
  },
  {
    "path": "prusa/link/web/controls.py",
    "chars": 7288,
    "preview": "\"\"\"/api/printer endpoint handlers\"\"\"\n\nfrom poorwsgi import state\nfrom poorwsgi.response import JSONResponse\nfrom prusa.c"
  },
  {
    "path": "prusa/link/web/errors.py",
    "chars": 9184,
    "preview": "\"\"\"Zakladní obecná obsluha url.\"\"\"\nimport logging\nfrom sys import exc_info\nfrom traceback import format_tb\n\nfrom poorwsg"
  },
  {
    "path": "prusa/link/web/files.py",
    "chars": 13058,
    "preview": "\"\"\"/api/v1/files endpoint handlers\"\"\"\n\nimport logging\nfrom os import fsync, listdir, replace, rmdir, unlink\nfrom os.path"
  },
  {
    "path": "prusa/link/web/files_legacy.py",
    "chars": 17077,
    "preview": "\"\"\"/api/files legacy endpoint handlers\nThis is a deprecated legacy code\"\"\"\n\nimport logging\nfrom base64 import decodebyte"
  },
  {
    "path": "prusa/link/web/lib/__init__.py",
    "chars": 220,
    "preview": "\"\"\"Root of web.lib module contains some shared tools for web interface.\"\"\"\n\n\ndef try_int(value):\n    \"\"\"Convertor to int"
  },
  {
    "path": "prusa/link/web/lib/auth.py",
    "chars": 4610,
    "preview": "\"\"\"Authorization tools and decorators\"\"\"\nimport logging\nfrom functools import wraps\n\nfrom poorwsgi import state\nfrom poo"
  },
  {
    "path": "prusa/link/web/lib/classes.py",
    "chars": 2179,
    "preview": "\"\"\"Server Classes\n\nMain server classes for handling request.\n\"\"\"\nimport logging\nfrom socketserver import ThreadingMixIn\n"
  },
  {
    "path": "prusa/link/web/lib/core.py",
    "chars": 890,
    "preview": "\"\"\"WSGI application initialization.\"\"\"\nimport os\nfrom hashlib import sha256\nfrom importlib.resources import files  # typ"
  },
  {
    "path": "prusa/link/web/lib/files.py",
    "chars": 17001,
    "preview": "\"\"\"Check and modify an input dictionary using recursion\"\"\"\n\nfrom datetime import datetime\nfrom functools import wraps\nfr"
  },
  {
    "path": "prusa/link/web/lib/view.py",
    "chars": 2653,
    "preview": "\"\"\"Response generate module.\"\"\"\n\nfrom importlib.resources import files\nfrom os.path import join\n\nfrom jinja2 import Envi"
  },
  {
    "path": "prusa/link/web/lib/wizard.py",
    "chars": 6978,
    "preview": "\"\"\"Configuration wizard library.\"\"\"\n\nimport logging\nfrom socket import gethostbyname\nfrom threading import Event\nfrom ur"
  },
  {
    "path": "prusa/link/web/link_info.py",
    "chars": 833,
    "preview": "\"\"\"Debug page of Prusa-Link.\"\"\"\nfrom prusa.connect.printer import __version__ as sdk_version\n\nfrom .. import __version__"
  },
  {
    "path": "prusa/link/web/main.py",
    "chars": 24953,
    "preview": "\"\"\"Main pages and core API\"\"\"\n\nimport datetime\nimport logging\nimport shlex\nimport subprocess\nimport time\nfrom os import "
  },
  {
    "path": "prusa/link/web/settings.py",
    "chars": 7733,
    "preview": "\"\"\"/api/settings endpoint handlers\"\"\"\nfrom secrets import token_urlsafe\n\nfrom poorwsgi import state\nfrom poorwsgi.digest"
  },
  {
    "path": "prusa/link/web/wizard.py",
    "chars": 12077,
    "preview": "\"\"\"Wizard endpoints\"\"\"\n\nfrom configparser import ConfigParser, MissingSectionHeaderError\nfrom functools import wraps\nfro"
  },
  {
    "path": "prusalink-boot",
    "chars": 980,
    "preview": "#!/bin/bash\nUSERNAME=$(id -nu 1000)\nHOME_DIR=$(eval echo \"~$USERNAME\")\nP_SOURCE=\"/boot/prusa_printer_settings.ini\"\nP_DES"
  },
  {
    "path": "public/prusalink.json",
    "chars": 1794,
    "preview": "{\n    \"os_list\": [\n        {\n            \"name\": \"PrusaLink\",\n            \"description\": \"Connect your Prusa 3D printer "
  },
  {
    "path": "requirements-multi.txt",
    "chars": 16,
    "preview": "ipcqueue~=0.9.7\n"
  },
  {
    "path": "requirements-pi.txt",
    "chars": 17,
    "preview": "wiringpi~=2.60.1\n"
  },
  {
    "path": "requirements.txt",
    "chars": 510,
    "preview": "prusa.connect.sdk.printer>=0.8.1\npy-gcode-metadata\npip>=22.2.0\nbidict~=0.22.1\nblinker~=1.5\nextendparser~=0.3.1\njinja2-te"
  },
  {
    "path": "ruff.toml",
    "chars": 7044,
    "preview": "lint.select = [\n    \"F\",    # pyflakes\n    \"E\",    # pycodestyle\n    \"W\",    # pycodestyle\n    \"C90\",  # mccabe\n    \"I\","
  },
  {
    "path": "setup.py",
    "chars": 5291,
    "preview": "\"\"\"Setup.py for PrusaLink software.\"\"\"\nimport logging\nimport os\nimport re\nfrom grp import getgrnam\nfrom shutil import co"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_carousel.py",
    "chars": 5128,
    "preview": "\"\"\"Tests of the LCD Printer component\"\"\"\nfrom time import time\n\nfrom prusa.link.printer_adapter.structures.carousel impo"
  },
  {
    "path": "tests/test_ipc_queue.py",
    "chars": 4249,
    "preview": "\"\"\"Test for the IPC queue adapter.\"\"\"\nimport logging\nimport os\nimport signal\nimport threading\n\nimport pytest\n\nfrom prusa"
  },
  {
    "path": "tests/test_item_updater.py",
    "chars": 22454,
    "preview": "\"\"\"Test of the InfoUpdater component\"\"\"\n\n# pylint:disable=redefined-outer-name too-many-locals too-many-statements\n\nimpo"
  },
  {
    "path": "tests/test_serial_parser.py",
    "chars": 2978,
    "preview": "\"\"\"Tests for the serial parser component\"\"\"\nimport re\nfrom unittest.mock import Mock\n\nfrom prusa.link.serial.serial_pars"
  },
  {
    "path": "tests/util.py",
    "chars": 1481,
    "preview": "\"\"\"Utility functions and classes for tests\"\"\"\nfrom threading import Event\nfrom unittest import mock\nfrom unittest.mock i"
  }
]

About this extraction

This page contains the full source code of the prusa3d/Prusa-Link GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 142 files (1.1 MB), approximately 308.8k tokens, and a symbol index with 1540 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!