Full Code of frank26080115/alpha-fairy for AI

main fd1f2989f952 cached
272 files
41.2 MB
1.4M tokens
400 symbols
1 requests
Download .txt
Showing preview only (5,534K chars total). Download the full file or copy to clipboard to get everything.
Repository: frank26080115/alpha-fairy
Branch: main
Commit: fd1f2989f952
Files: 272
Total size: 41.2 MB

Directory structure:
gitextract_3jorpr5v/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── Full-Features-Guide.md
├── INSTRUCTIONS.md
├── LICENSE
├── README.md
├── arduino_workspace/
│   ├── .gitignore
│   ├── AlphaFairy/
│   │   ├── AlphaFairy.h
│   │   ├── AlphaFairy.ino
│   │   ├── AlphaFairyCamera.cpp
│   │   ├── AlphaFairyCamera.h
│   │   ├── AppUtils.ino
│   │   ├── AutoConnect.ino
│   │   ├── Buttons.ino
│   │   ├── CamUtils.ino
│   │   ├── CmdlineHandlers.ino
│   │   ├── ConfigMenu.ino
│   │   ├── CpuFreq.ino
│   │   ├── DrawingUtils.ino
│   │   ├── DualShutter.ino
│   │   ├── FairyMenu.cpp
│   │   ├── FairyMenu.h
│   │   ├── FocusEncoder.ino
│   │   ├── FocusFrustration.ino
│   │   ├── FocusPull.ino
│   │   ├── FocusStack.ino
│   │   ├── HttpServer.ino
│   │   ├── InfoView.ino
│   │   ├── Intervalometer.ino
│   │   ├── Lepton.ino
│   │   ├── PowerMgmt.ino
│   │   ├── QuickRemote.ino
│   │   ├── RemoteShutter.ino
│   │   ├── Settings.ino
│   │   ├── ShutterStep.ino
│   │   ├── SoundTrigger.ino
│   │   ├── TallyLite.ino
│   │   ├── TimecodeReset.ino
│   │   ├── Trigger.ino
│   │   ├── WifiHandlers.ino
│   │   ├── WifiMenu.ino
│   │   ├── WifiUtils.ino
│   │   ├── alfy_conf.h
│   │   ├── alfy_defs.h
│   │   ├── alfy_types.h
│   │   └── data/
│   │       ├── chk2.txt
│   │       └── chk3.txt
│   ├── libraries/
│   │   ├── AlphaFairyImu/
│   │   │   ├── AlphaFairyImu.cpp
│   │   │   └── AlphaFairyImu.h
│   │   ├── AlphaFairy_NetMgr/
│   │   │   ├── AlphaFairy_NetMgr.cpp
│   │   │   └── AlphaFairy_NetMgr.h
│   │   ├── AsyncTCP/
│   │   │   ├── .gitignore
│   │   │   ├── .travis.yml
│   │   │   ├── CMakeLists.txt
│   │   │   ├── Kconfig.projbuild
│   │   │   ├── LICENSE
│   │   │   ├── README.md
│   │   │   ├── component.mk
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AsyncTCP.cpp
│   │   │       └── AsyncTCP.h
│   │   ├── DebuggingSerial/
│   │   │   ├── DebuggingSerial.cpp
│   │   │   ├── DebuggingSerial.h
│   │   │   └── DebuggingSerialDisable.cpp
│   │   ├── ESPAsyncWebServer/
│   │   │   ├── .gitignore
│   │   │   ├── .travis.yml
│   │   │   ├── CMakeLists.txt
│   │   │   ├── README.md
│   │   │   ├── _config.yml
│   │   │   ├── component.mk
│   │   │   ├── keywords.txt
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AsyncEventSource.cpp
│   │   │       ├── AsyncEventSource.h
│   │   │       ├── AsyncJson.h
│   │   │       ├── AsyncWebSocket.cpp
│   │   │       ├── AsyncWebSocket.h
│   │   │       ├── AsyncWebSynchronization.h
│   │   │       ├── ESPAsyncWebServer.h
│   │   │       ├── SPIFFSEditor.cpp
│   │   │       ├── SPIFFSEditor.h
│   │   │       ├── StringArray.h
│   │   │       ├── WebAuthentication.cpp
│   │   │       ├── WebAuthentication.h
│   │   │       ├── WebHandlerImpl.h
│   │   │       ├── WebHandlers.cpp
│   │   │       ├── WebRequest.cpp
│   │   │       ├── WebResponseImpl.h
│   │   │       ├── WebResponses.cpp
│   │   │       ├── WebServer.cpp
│   │   │       └── edit.htm
│   │   ├── FairyEncoder/
│   │   │   ├── FairyEncoder.cpp
│   │   │   └── FairyEncoder.h
│   │   ├── FairyKeyboard/
│   │   │   ├── FairyKeyboard.cpp
│   │   │   ├── FairyKeyboard.h
│   │   │   └── examples/
│   │   │       └── FairyKeyboardDemo/
│   │   │           └── FairyKeyboardDemo.ino
│   │   ├── Lepton/
│   │   │   ├── Lepton.h
│   │   │   ├── img/
│   │   │   │   ├── ColorT.h
│   │   │   │   ├── ColorT_0000_16.h
│   │   │   │   ├── ColorT_0001_15.h
│   │   │   │   ├── ColorT_0002_14.h
│   │   │   │   ├── ColorT_0003_13.h
│   │   │   │   ├── ColorT_0004_12.h
│   │   │   │   ├── ColorT_0005_11.h
│   │   │   │   ├── ColorT_0006_10.h
│   │   │   │   ├── ColorT_0007_9.h
│   │   │   │   ├── ColorT_0008_8.h
│   │   │   │   ├── ColorT_0009_7.h
│   │   │   │   ├── ColorT_0010_6.h
│   │   │   │   ├── ColorT_0011_5.h
│   │   │   │   ├── ColorT_0012_4.h
│   │   │   │   ├── ColorT_0013_3.h
│   │   │   │   ├── ColorT_0014_2.h
│   │   │   │   ├── ColorT_0015_1.h
│   │   │   │   └── ColorT_0016_0.h
│   │   │   ├── img_table.h
│   │   │   └── lepton.cpp
│   │   ├── M5DisplayExt/
│   │   │   ├── M5DisplayExt.cpp
│   │   │   ├── M5DisplayExt.h
│   │   │   ├── SpriteMgr.cpp
│   │   │   ├── SpriteMgr.h
│   │   │   └── utility/
│   │   │       ├── pngle.c
│   │   │       └── pngle.h
│   │   ├── M5StickC-Plus/
│   │   │   ├── LICENSE
│   │   │   ├── README.md
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AXP192.cpp
│   │   │       ├── AXP192.h
│   │   │       ├── Fonts/
│   │   │       │   ├── ASC16
│   │   │       │   ├── ASC16.h
│   │   │       │   ├── Custom/
│   │   │       │   │   ├── Orbitron_Light_24.h
│   │   │       │   │   ├── Orbitron_Light_32.h
│   │   │       │   │   ├── Roboto_Thin_24.h
│   │   │       │   │   ├── Satisfy_24.h
│   │   │       │   │   └── Yellowtail_32.h
│   │   │       │   ├── Font16.c
│   │   │       │   ├── Font16.h
│   │   │       │   ├── Font32rle.c
│   │   │       │   ├── Font32rle.h
│   │   │       │   ├── Font64rle.c
│   │   │       │   ├── Font64rle.h
│   │   │       │   ├── Font72rle.c
│   │   │       │   ├── Font72rle.h
│   │   │       │   ├── Font7srle.c
│   │   │       │   ├── Font7srle.h
│   │   │       │   ├── GFXFF/
│   │   │       │   │   ├── FreeMono12pt7b.h
│   │   │       │   │   ├── FreeMono18pt7b.h
│   │   │       │   │   ├── FreeMono24pt7b.h
│   │   │       │   │   ├── FreeMono9pt7b.h
│   │   │       │   │   ├── FreeMonoBold12pt7b.h
│   │   │       │   │   ├── FreeMonoBold18pt7b.h
│   │   │       │   │   ├── FreeMonoBold24pt7b.h
│   │   │       │   │   ├── FreeMonoBold9pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique12pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique18pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique24pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique9pt7b.h
│   │   │       │   │   ├── FreeMonoOblique12pt7b.h
│   │   │       │   │   ├── FreeMonoOblique18pt7b.h
│   │   │       │   │   ├── FreeMonoOblique24pt7b.h
│   │   │       │   │   ├── FreeMonoOblique9pt7b.h
│   │   │       │   │   ├── FreeSans12pt7b.h
│   │   │       │   │   ├── FreeSans18pt7b.h
│   │   │       │   │   ├── FreeSans24pt7b.h
│   │   │       │   │   ├── FreeSans9pt7b.h
│   │   │       │   │   ├── FreeSansBold12pt7b.h
│   │   │       │   │   ├── FreeSansBold18pt7b.h
│   │   │       │   │   ├── FreeSansBold24pt7b.h
│   │   │       │   │   ├── FreeSansBold9pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique12pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique18pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique24pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique9pt7b.h
│   │   │       │   │   ├── FreeSansOblique12pt7b.h
│   │   │       │   │   ├── FreeSansOblique18pt7b.h
│   │   │       │   │   ├── FreeSansOblique24pt7b.h
│   │   │       │   │   ├── FreeSansOblique9pt7b.h
│   │   │       │   │   ├── FreeSerif12pt7b.h
│   │   │       │   │   ├── FreeSerif18pt7b.h
│   │   │       │   │   ├── FreeSerif24pt7b.h
│   │   │       │   │   ├── FreeSerif9pt7b.h
│   │   │       │   │   ├── FreeSerifBold12pt7b.h
│   │   │       │   │   ├── FreeSerifBold18pt7b.h
│   │   │       │   │   ├── FreeSerifBold24pt7b.h
│   │   │       │   │   ├── FreeSerifBold9pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic12pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic18pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic24pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic9pt7b.h
│   │   │       │   │   ├── FreeSerifItalic12pt7b.h
│   │   │       │   │   ├── FreeSerifItalic18pt7b.h
│   │   │       │   │   ├── FreeSerifItalic24pt7b.h
│   │   │       │   │   ├── FreeSerifItalic9pt7b.h
│   │   │       │   │   ├── TomThumb.h
│   │   │       │   │   ├── gfxfont.h
│   │   │       │   │   ├── license.txt
│   │   │       │   │   └── print.txt
│   │   │       │   ├── HZK16
│   │   │       │   ├── HZK16.h
│   │   │       │   ├── TrueType/
│   │   │       │   │   └── Not_yet_supported.txt
│   │   │       │   └── glcdfont.c
│   │   │       ├── M5Display.cpp
│   │   │       ├── M5Display.h
│   │   │       ├── M5StickCPlus.cpp
│   │   │       ├── M5StickCPlus.h
│   │   │       ├── RTC.cpp
│   │   │       ├── RTC.h
│   │   │       └── utility/
│   │   │           ├── Button.cpp
│   │   │           ├── Button.h
│   │   │           ├── Config.h
│   │   │           ├── In_eSPI.cpp
│   │   │           ├── In_eSPI.h
│   │   │           ├── In_eSPI_Setup.h
│   │   │           ├── MPU6886.cpp
│   │   │           ├── MPU6886.h
│   │   │           ├── MahonyAHRS.cpp
│   │   │           ├── MahonyAHRS.h
│   │   │           ├── ST7735_Defines.h
│   │   │           ├── ST7735_Init.h
│   │   │           ├── ST7735_Rotation.h
│   │   │           ├── ST7789_Defines.h
│   │   │           ├── ST7789_Init.h
│   │   │           ├── ST7789_Rotation.h
│   │   │           ├── Speaker.cpp
│   │   │           ├── Speaker.h
│   │   │           ├── Sprite.cpp
│   │   │           ├── Sprite.h
│   │   │           ├── qrcode.c
│   │   │           └── qrcode.h
│   │   ├── PtpIpCamera/
│   │   │   ├── PtpIpCamera.cpp
│   │   │   ├── PtpIpCamera.h
│   │   │   ├── PtpIpCameraSend.cpp
│   │   │   ├── PtpIpSonyAlphaCamera.cpp
│   │   │   ├── PtpIpSonyAlphaCamera.h
│   │   │   ├── PtpIpSonyAlphaCameraPropDecoder.cpp
│   │   │   ├── PtpIpSonyAlphaCameraSend.cpp
│   │   │   ├── examples/
│   │   │   │   ├── PtpApDemo/
│   │   │   │   │   └── PtpApDemo.ino
│   │   │   │   └── PtpStaDemo/
│   │   │   │       └── PtpStaDemo.ino
│   │   │   ├── ptpcodes.h
│   │   │   ├── ptpip_utils.cpp
│   │   │   ├── ptpip_utils.h
│   │   │   ├── ptpipdefs.h
│   │   │   └── ptpsonycodes.h
│   │   ├── SerialCmdLine/
│   │   │   ├── SerialCmdLine.cpp
│   │   │   └── SerialCmdLine.h
│   │   ├── SonyCameraInfraredRemote/
│   │   │   ├── SonyCameraInfraredRemote.cpp
│   │   │   └── SonyCameraInfraredRemote.h
│   │   ├── SonyHttpCamera/
│   │   │   ├── SonyHttpCamera.cpp
│   │   │   ├── SonyHttpCamera.h
│   │   │   ├── SonyHttpCameraCmds.cpp
│   │   │   ├── SonyHttpCameraInit.cpp
│   │   │   ├── SonyHttpCameraUtils.cpp
│   │   │   └── examples/
│   │   │       └── HttpJsonDemo/
│   │   │           └── HttpJsonDemo.ino
│   │   └── Wire/
│   │       ├── keywords.txt
│   │       ├── library.properties
│   │       └── src/
│   │           ├── Wire.cpp
│   │           └── Wire.h
│   └── tools/
│       └── ESP32FS/
│           └── tool/
│               └── esp32fs.jar
├── doc/
│   ├── Camera-Reverse-Engineering.md
│   ├── Firmware-Engineering.md
│   ├── M5StickC-MicroPython-Sucks-Rant.md
│   ├── Shutter-Release-Cable-Connector.md
│   ├── Wishlist-for-Sony-Mirrorless-Camera-Remote-Protocol.md
│   └── img/
│       └── menu_map.afdesign
├── platformio.ini
└── screens_240/
    ├── dual_shutter.afphoto
    ├── png2jpg.py
    └── shutter_step.afphoto

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

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
assignees: frank26080115

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Do this
2. Do that
3. Do a dance
4. Do something

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots/Photos/Video**
If applicable, add screenshots/photos/video to help explain your problem.

**Version**
Use the menu system to see the build version. It's under the UTILITIES menu, press the big button while viewing the About app.

**Camera**
What camera are you using it with? What connection method are you using?

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request]"
labels: enhancement
assignees: frank26080115

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

#PlatformIO
.pio
.vscode


================================================
FILE: Full-Features-Guide.md
================================================
# Full Features Guide

![](doc/img/features_family_photo.png)

## Preface

This project is not a product, it is my experiment. Some of the features I've implemented are purely for fun and are gimmicks. Some of them may not work all of the time, and have camera compatibility issues.

## Map of All Menu Items

[Click to zoom in](https://raw.githubusercontent.com/frank26080115/alpha-fairy/main/doc/img/menu_map.webp)

[![](doc/img/menu_map_lq.jpg)](https://raw.githubusercontent.com/frank26080115/alpha-fairy/main/doc/img/menu_map.webp)

## Quick Remote

![](doc/img/quickremote_demo.webp)

Press the side button to lock or unlock the motion selection, rotate the remote to change the selection. Pressing the big button will activate the function that is selected.

If the camera is not connected by Wi-Fi, and the infrared emitter feature is enabled, then pressing the big button will send out the remote shutter command via the infrared emitter.

If the camera is not connected by Wi-Fi, and the infrared emitter feature is disabled, and the GPIO feature is enabled, then pressing the big button will send out the remote shutter command via the shutter release cable connected to the GPIO.

The focus function and zoom function are only functional on lenses that have those functions.

## Timed Remote Shutter

Uses a countdown before sending the remote shutter command.

![](doc/img/spin_num_select.webp)

Rotate the remote to change the time delay amount.

## Shutter Trigger

![](screens_240/shuttertrigger.png)

![](doc/img/soundtrigger.jpg)

![](doc/img/pir_motion_sensor.jpg)

The trigger source is configurable between: ALL (any), microphone, external input, IMU

The trigger action is configurable between: photo, video, intervalometer

The option "Arm Delay" is a delay after pressing the Arm button before triggering is allowed. This gives you a chance to run away from the boobytrap you just set.

The option "Start Delay" is the delay between triggering and taking the action.

The option "Re-arm" means automatically arm again after taking the action. This is used for unsupervised security camera style tasks. This can be disabled.

There will be a display of the relevant sensor value on the top of the screen. The triggering threshold is indicated with a tick, and can be adjusted.

The microphone uses a very simple peak detector algorithm, it cannot recognize words.

The external-input mode uses a voltage threshold (represented in 0-100%, 100% maps to 3.3V), it uses the ADC of the ESP32, the pin number is configurable through the main configuration menu. The signal can be inverted. Use this mode with PIR sensors, laser beam-break sensors, weight sensors, etc.

The IMU motion mode uses acceleration (in G units, between 0 and 4 G) and gyro spin (in DPS, between 0 and 500 DPS).

## Talley Light

The entire screen will turn bright red when the camera is recording a movie.

![](doc/img/talleylight.webp)

When the camera stops recording, the red screen will disappear and the remote will go back to normal state. Pressing any button will also clear the screen and make the remote go back to normal.

This feature can be disabled through the configuration menu.

## Virtual Top Panel

This is a simple camera settings display. (inspired by cameras that actually have this as a real feature)

![](doc/img/virtual-top-panel.jpg)

It is activated by using the Quick-Remote feature, but tilting forward to the extreme. Pressing the side-button will lock it into this mode once this mode is displayed. Pressing the big-button will cycle through 4 display styles (landscape vs portrait, black vs white).

Pressing-and-holding the big button, then pressing the side button while the big button is still held, will activate **EDIT-MODE**. This mode is where the user can change the shutter speed, aperture, ISO, or exposure compensation.

In edit-mode, pressing the side button will select an item to edit, tilting the remote left or right will determine the direction of the edit, and pressing the big button will perform the edit.

![](doc/img/tilt_cam_settings.webp)

## Focus Pull

![](doc/img/focus_pull_imu.webp)

Adjusts the camera's manual focus. The adjustment speed is determined by the tilt angle of the remote. Press the big-button to actually perform the adjustment.

## Focus Knob

A focus knob can be attached to the remote. It will adjust focus **linearly** on focus-by-wire lenses, one click of the knob equals a fixed number of focus adjustment steps.

![](doc/img/focusknob_small.jpg)

The focus knob function does not need to be activated through any menus. As long as a focus knob is connected and the camera is in manual focus mode, the focus knob will work.

In the configuration menu, there are two settings that can be adjusted:

 * MF knob steps: the number of steps of MF movement to perform for every click of the knob
 * MF knob large steps: the number of queued steps before the step size changes to the larger step size

These two parameters needs to be calibrated for true linear operation, and the parameters will be different depending on which lens is attached to the camera.

There is also a utility to automatically calibrate the large steps.

![](screens_240/focus_calib.png)

## Intervalometer

![](doc/img/intervalometer_working.webp)

The intervalometer mode has configurable options for

 * shutter bulb time
   * set to zero to use the camera's own shutter speed instead of bulb mode
 * interval between photos
 * start delay before first photo
 * number of photos

![](doc/img/intervalometer_options.webp)

NOTE: in astrophotography mode, instead of interval between photos, the user sets a pause between each photo.

The side-button navigates through items. The big-button will edit the current item. The tilt angle of the remote determines if the big-button will add or subtract from the value. There is a start screen with all of the settings displayed to review, pressing the big-button on this screen will begin the intervalometer.

NOTE: If Wi-Fi is not connected, the method of sending the shutter command will use the infrared emitter or GPIO (shutter release cable), depending on which one is enabled in the configuration. If this happens, the entire Alpha-Fairy is placed into a low power "airplane mode" to extend battery life. A reboot will occur to exit out of airplane mode.

NOTE: Rotating the remote while in the intervalometer app will toggle "red mode", which is meant to be more eye-friendly during night time.

![](doc/img/interval_redmode.jpg)

## Focus Stacking

![](screens_240/focusstack_far_1.png)

When activated, the camera will start to continuously take photos, and perform a focus adjustment in between each photo. The adjustment is from near to far, so it is recommended to start the camera's focusing point at the minimum focusing point of the lens.

https://user-images.githubusercontent.com/1427911/187117478-654adc80-a0a3-48cf-8b0b-643bfd7b2884.mp4

There are two focus step sizes available, indicated by the red arrow.

The camera must be placed in manual focus mode.

To learn more about focus stacking: [click here for the Wikipedia article](https://en.wikipedia.org/wiki/Focus_stacking)

NOTE: the camera's API offers 3 step sizes, but the biggest step size is too large to be useful

## 9-point Focus

![](screens_240/focus_9point.png)

When activated, the camera will start to continuously take photos, and change the focus spot to one of 9 points between each photo.

https://user-images.githubusercontent.com/1427911/187118098-8fa0dbad-dda0-40ed-ad42-8bafc61e2450.mp4

The camera must be:

 * placed in movable spot autofocus mode
 * not using MF
 * the option "AF with shutter" must be turned ON (through the camera's menu system)

## Shutter Speed Step

![](screens_240/shutter_step.png)

This is an experimental feature that does not work very well.

When activated, the camera will start to continuously take photos, and change the shutter speed to a slower speed between each photo.

https://user-images.githubusercontent.com/1427911/187117818-dda28b7c-d5e2-45bd-96e9-6ff3c6a178e0.mp4

The camera must be placed in Shutter Priority mode or Manual Exposure mode.

This is an experimental feature, meant to allow for experimentation with long exposures without touching the camera.

NOTE: this mode does not work very well, the camera does not seem to respond to the speed setting command 100% of the time

## Dual Shutter

This is an experimental feature that does not work very well.

The goal is to take two photos in quick succession, but each photo has a different shutter speed.

The shutter speed (and ISO setting) for the first photo will be the setting on the camera. The second photo's settings will be the settings registered into the remote.

To register settings for the second photo, be on the "register settings" screen. Use the camera's controls to set the settings you want, and then press the big-button. The settings will then be displayed on the screen.

![](doc/img/dualshutter_register.png)

Once the settings are registered, set your camera to the settings for the first photo. Then navigate to the "press to shoot" screen. Now either half-press the shutter button on the camera, or press the big-button on the remote. The sequence of photos will be taken.

![](doc/img/dualshutter_operation.png)

Use photo editing software to merge the two photos.

NOTE: this mode does not work very well, the camera does not seem to respond to the speed setting command 100% of the time

## Focus Frustration

![](screens_240/focus_frust.png)

When the remote is in this mode, and the user repeatedly activates and deactivates autofocus (repeatedly tapping half-pressing the shutter button), the remote will issue a manual focus command to place the focus plane at the minimum focus distance.

This is an experimental feature, meant more as a joke, for those situations when the camera's AF locks onto the background and refuses to focus onto the subject you actually want it to focus on.

NOTE: the reporting rate of the focus status from the camera is rather slow, so the rapid tapping detection doesn't work very reliably

## Timecode Reset

![](screens_240/timecode_reset.png)

Press the big button to send the infrared command that resets timecode on a supported Sony camera. ([see feature request](https://github.com/frank26080115/alpha-fairy/issues/13))

## Auto Connect

![](doc/img/autoconnect_ani.webp)

Scans for Wi-Fi access points. It does two different searches:

 * first searches for any SSIDs that the remote already has in the Wi-Fi profile database
 * if none are found, it then looks for any SSIDs that appears to be a Sony camera

When a SSID is found, a connection will be attempted. If a password is required, the user will be prompted to enter the password.

## Configuration

![](screens_240/config.png)

Some options like the power-save timeout can be edited through the configuration screen.

The side-button navigates through items. The big-button will edit the current item. The tilt angle of the remote determines if the big-button will add or subtract from the value.

There is an explicit save-and-exit item, press the big button on it to save the new settings. If the power button is pressed, the configuration screen will exit without saving.

## Wi-Fi Configuration

![](screens_240/wifi_config.png)

Entering the Wi-Fi configuration menu item will launch the HTTP server, and the remote will begin operating as a soft-AP (access point) with the default SSID and default password (SSID: "fairywifi", password: "1234567890").

The submenu items contain information and QR codes for a smartphone to establish a connection to the remote.

![](doc/img/wificfg_qrcode.jpg)

**NOTE:** the screen is super tiny and your QR scanner might not actually work, sorry! Just type in the credentials manually if this is the case.

![](doc/img/wificonfig_androidwifi.png)

With a smartphone browser, the entire list of Wi-Fi profiles (ie. cameras registered with the remote) can be edited, and also new entries can be added.

![](doc/img/wificonfig_captiveportal.png)

NOTE: this uses a captive portal, but another QR code is provided for a normal web browser too

One of the submenu items is the profile switcher. Rotate the remote to select another profile number. Press the big-button to save the selection, that profile will be used on the next boot. Press and hold the big-button to save and reboot the remote immediately.

One of these submenu items is a factory reset option.

## Wi-Fi Info

![](screens_240/wifiinfo.png)

This will simply display the current Wi-Fi settings, useful to look at while the user is configuring the camera.

![](doc/img/wifiinfo_example.jpg)

When a camera is connected, this screen will also show the camera's name.


================================================
FILE: INSTRUCTIONS.md
================================================
This document covers:

 * Installation and setup of Arduino for ESP32
 * Building the source code and flashing onto a M5StickC-Plus unit
 * Connecting the Alpha-Fairy to a Sony Camera
 * Basic usage of the Alpha-Fairy

# Disclaimer

There are inherent dangers in the use of any software/firmware available for download on the Internet, and we caution you to make sure that you completely understand the potential risks before downloading any of the software/firmware.

The software, firmware, instructions, and code samples available on this website are provided "as is" without warranty of any kind, either express or implied. Use at your own risk.

The use of the software, firmware, instructions, and scripts on this site is done at your own discretion and risk and with agreement that you will be solely responsible for any damage to your electronic devices or loss of data that results from such activities.

You are solely responsible for adequate protection and backup of the data and equipment used in connection with any of the software or firmware, and we will not be liable for any damages that you may suffer in connection with using, modifying or distributing any of this software or firmware. No advice or information, whether oral or written, obtained by you from us or from this website shall create any warranty for the software or firmware.

We make makes no warranty that

* the software or firmware will meet your requirements
* the software or firmware will be uninterrupted, timely, secure or error-free
* the results that may be obtained from the use of the software or firmware will be effective, accurate or reliable
* the quality of the software or firmware will meet your expectations
* any errors in the software or firmware obtained from us will be corrected.

The software, firmware, code sample and their documentation made available on this website:

* could include technical or other mistakes, inaccuracies or typographical errors. We may make changes to the software or firmware or documentation made available on its web site at any time without prior-notice.
* may be out of date, and we make no commitment to update such materials.

We assume no responsibility for errors or omissions in the software or documentation available from its web site.

In no event shall we be liable to you or any third parties for any special, punitive, incidental, indirect or consequential damages of any kind, or any damages whatsoever, including, without limitation, those resulting from loss of use, data or profits, and on any theory of liability, arising out of or in connection with the use of this software or firmware.

# Quick Way: Build with PlatformIO

PlatformIO is an alternative IDE to Arduino, if you choose to use PlatformIO, skip the instruction section about Arduino.

Download a copy of this particular GitHub repo.

![](doc/img_instruct/github_download.png)

Install [Visual Studio Code](https://code.visualstudio.com/download) (aka VSCode)

Then install the extension [PlatformIO](https://platformio.org/platformio-ide), this is done from within VSCode by first clicking on "Extensions"

![](doc/img_instruct/pio_install.png)

After installation, restart the VSCode editor and open the source code directory.

![](doc/img_instruct/pio_open_folder.png)

![](doc/img_instruct/pio_after_open.png)

On the left-hand side, select the PlatformIO icon. Connect the M5StickC-Plus now. Select the right COM port

![](doc/img_instruct/pio_select_com_port.png)

On the left-hand side, click "Project Tasks -> m5stick-c -> General -> Upload". (the toolbox will automatically download the toolchain and build the project the first time it is run)

![](doc/img_instruct/pio_upload.png)

After that, in "Project Tasks -> m5stick-c -> Platform" choose "Build Filesystem Image" and then "Upload Filesystem Image"

![](doc/img_instruct/pio_upload_data.png)

# Normal Way: Arduino IDE, Installation Setup on PC

This is an open source firmware project, it is free for anybody to download and install onto the M5StickC-Plus. I do not sell any pre-installed units, so for you to build one of these Alpha-Fairy remotes, you must follow these installation instructions.

Install [Arduino IDE](https://www.arduino.cc/en/software), please obtain **version 1.8.19**, later versions (such as v 2.x) will not work. Scroll down on the page until the `Legacy IDE (1.8.X)` section.

![](doc/img_instruct/legacy_ide.png)

Download a copy of this particular GitHub repo.

![](doc/img_instruct/github_download.png)

Find the directory called "arduino_workspace". From inside Arduino IDE, use the menu bar, click File->Preferences, put the path to "arduino_workspace" into "Sketchbook Location".

![](doc/img_instruct/sketchbook_location.png)

Install the ESP32 toolchain. From inside Arduino IDE, use the menu bar, click File->Preferences. The "Additional Boards Manager URLS" text box needs to be filled with "https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json", then click "OK".

![](doc/img_instruct/board_manager_url.png)

Then use the board manager to install the ESP32 toolchain. From inside Arduino IDE, use the menu bar, click Tools->Boards->Board Manager. Inside the board manager, search for "ESP32" and install the toolchain. (version 2.0.5 is fine)

![](doc/img_instruct/board_manager_install.png)

Connect the M5StickC-Plus to the computer, follow instructions to install the FTDI driver (if required): https://docs.m5stack.com/en/quick_start/m5stickc_plus/arduino (this may also contain instructions for steps I've already listed)

Close the Arduino IDE and open it again.

**NOTE:** if you are updating the firmware (thank you for keeping up to date with my project and being patient), then you will be repeating the steps below.

Open Arduino IDE, from the menu bar, select the correct board: Tools->Boards->ESP32 Boards->M5StickC

![](doc/img_instruct/select_board.png)

Select the correct serial port: Tools->Port->(select the option that matches the M5StickC)

![](doc/img_instruct/select_com_port.png)

Using Arduino IDE, open the file at "arduino_workspace/AlphaFairy/AlphaFairy.ino", this should be available in the sketchbook directly.

![](doc/img_instruct/open_sketch.png)

Upload the image files: Tools->"ESP32 Sketch Data Upload"

![](doc/img_instruct/sketch_data_upload.png)

(if the "ESP32 Sketch Data Upload" is missing, follow the instructions at https://github.com/me-no-dev/arduino-esp32fs-plugin to install the plugin, which also requires you to restart the Arduino IDE)

**IMPORTANT:** every time you press "ESP32 Sketch Data Upload", the Wi-Fi profiles you have saved **will be erased**. The web interface can be used to view all existing Wi-Fi profiles so you can back them up.

Disable core debug level: Tools->"Core Debug Level:", select "None"

![](doc/img_instruct/disable_debug_level.png)

Click "Upload" on the tool-bar

![](doc/img_instruct/click_upload.png)

# Connecting Your Camera

Make sure your camera's Wi-Fi is configured to use the 2.4 GHz band.

![](doc/img/wifi_freq_24ghz_band.jpg)

From the camera menu, the option "Still Image Save Destination" should be set to "Camera Only". There are actually two places where this is set, one for PC remote, one for smartphone remote. Please set "Camera Only" to both modes.

![](doc/img/smartphone_save_cam_only.webp)

![](doc/img/pc_save_cam_only.webp)

## For PTP capable cameras (newer models) AP mode

When the AlphaFairy code is running on the M5StickC, turn on the camera.

Follow instructions similar to https://support.d-imaging.sony.co.jp/app/imagingedge/en/instruction/4_1_connection.php (note: we are using the Wi-Fi Access Point method, without pairing)

![](doc/img/wifilogin_a1.webp)

From the camera menu, the option "PC control method" should be "Wi-Fi Access Point".

From the camera menu, airplane mode should be disabled, FTP should be disabled, control-with-smartphone should be disabled.

From the camera menu, the option "connect without pairing" should be enabled. **This is important! Sony cameras don't have an option to un-pair, only through a factory reset, and only one pairing can exist!** If you are having trouble with this step (some cameras do not allow "connect without pairing"), then try the STA connection mode (described below).

From the camera menu, connect to the SSID that the AlphaFairy is broadcasting ("fairywifi" by default), the password should be "1234567890"

On the M5StickC's screen, the "no signal" icon should disappear. On the camera's screen, the `Wi-Fi` symbol should be fully white and the `-PC-` icon should be fully white.

## STA mode, both PTP and HTTP JSON-RPC protocol, all other cameras

Turn on the camera, and activate the "Control from Smartphone" function. This may be implemented differently on different cameras:

 * Newer cameras will present a QR code but also have an option to show the SSID and password as text

![](doc/img/wifilogin_a1_sta.webp)

![](doc/img/wifilogin_a6600.webp)

 * Older cameras may have a "Smart Remote Embedded" application inside a collection of applications

![](doc/img/wifilogin_rx100.webp)

On the Alpha-Fairy, use the Auto Connect function. A search for the camera will be initiated and when the camera is found, you will be prompted to input the password. Input the password of the camera to complete the connection.

![](doc/img/autoconnect_ani.webp)

Completing the connection will add the camera to the next available Wi-Fi profile slot. You can chose which profile to boot with by using the Wi-Fi Config menu. Otherwise, using the Auto Connect function again will also work (and the password will be remembered).

## Managing Multiple Cameras

The Alpha-Fairy has 20 available slots for Wi-Fi profiles, each slot can be used for a different camera. Each profile may be used in AP mode (Alpha-Fairy is an access point, camera connects to the Alpha-Fairy) or STA mode (Alpha-Fairy is a station, and connects to the access point presented by the camera).

Slot 0 is always the default slot, AP mode, SSID: "fairywifi", password: "1234567890". This slot cannot be edited. This slot is also used for the web interface.

To select one of the slots/cameras to use, go into the Wi-Fi configuration menu, there is a screen where you can select the slot. To change the slot number, rotate the entire Alpha-Fairy device, a clockwise rotation increments the slot number, counter-clockwise rotation decrements the slot number. Press the big button to save the slot number, and that slot number will be used the next time the Alpha-Fairy boots. (holding the big button will save the slot number and then immediately perform a device reboot)

# General Usage

![](doc/img/4sides.jpg)

There are three buttons: "big", "side", and "power"

The side button navigates, chooses the next item.

The big button is used to either enter a menu item, or activate a menu item.

The power button turns the device on, and holding it down for 4 seconds (or more) will shutdown the device. It also acts as an exit button when you press it quickly.

When the battery is low, recharge the remote with an USB-C cable.

## Usage of Features

[Click Here](Full-Features-Guide.md) to view the Full Features Guide

## Motion Controls

When trying to adjust a configurable option, there will be either a plus (+) or minus (-) sign beside the number. Tilting the device to the right will use plus mode, pressing the big button will add to the number. Tilting the device to the left will use minus mode, pressing the big button will subtract from the number.

![](doc/img/config_inc_dec.webp)

When in delayed remote shutter mode, completely rotating the device will change the delay.

![](doc/img/spin_num_select.webp)

When in focus pull mode or zoom adjust mode, the tilt of the device determines the speed and direction of the adjustment, press the big button to perform the adjustment.

![](doc/img/focus_pull_imu.webp)

When prompted to input a password for a Wi-Fi connection, a keyboard is displayed. To select a key on the keyboard, rotate the keyboard around to change the selection. Pressing the big-button will press the highlighted key. Pressing the side-button will toggle between upper-case and lower-case. Pressing the power button will cancel the keyboard. Use the `<xx` key as backspace, and the `>ENT` key is the enter key.

![](doc/img/imu_keyboard.webp)

## Status Icons

![](doc/img/icon_labels.png)

## Shutter Release Cable Connector

It may be wise to use the intervalometer mode with a shutter release cable, due to the chance of the Wi-Fi connection being unreliable or having too much latency. [See this page for details](doc/Shutter-Release-Cable-Connector.md)

![](doc/img/shutter_release_cable_plugged_in.jpg)

## Focus Knob

A rotary encoder can be purchased (from M5Stack) and connected to the remote to function as a focus pulling knob. It will adjust focus **linearly** with focus-by-wire lenses, one click of the knob equals a fixed number of focus adjustment steps. It is recommended that a larger knob and handle be 3D printed for practical focus pulling, and for attaching to a rig.

![](doc/img/focusknob_1440.jpg)


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Frank Zhao

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Alpha-Fairy

This is a tiny remote control for Sony Alpha cameras.

![](doc/img/main_menu_options.webp)

The hardware platform is a [M5StickC-Plus](https://shop.m5stack.com/products/m5stickc-plus-esp32-pico-mini-iot-development-kit), which is a DIY device with a [ESP32-PICO](https://www.espressif.com/en/products/socs/esp32) inside, along with a colour LCD screen, rechargable battery, some buttons, and a few other features.

![](doc/img/size_comparison.jpg)

This remote combines the features of other simple camera remotes, plus many complex functions that automates some tasks that photographers would like to do. It communicates wirelessly with the camera via [Picture Transfer Protocol](https://en.wikipedia.org/wiki/Picture_Transfer_Protocol), mostly reverse engineered by spying on Sony's own [Imaging Edge Remote](https://imagingedge.sony.net/l/ie-desktop.html#remote) application with [Wireshark](https://www.wireshark.org/). A second protocol uses HTTP and supports more cameras but has less features.

This GitHub repo contains the C++ source code (and graphics files) that can be compiled and loaded into the M5StickC-Plus, so anybody can [create this remote](INSTRUCTIONS.md).

Supported camera models: A1, A6600, A6000, RX100M4, RX0M2

Untested but theoretically can support over PTP protocol: A7SM3, A9M2, A7M4A, A7RM4, A7C, A7M4, ZV-E10, listed on https://support.d-imaging.sony.co.jp/app/sdk/en/index.html

Untested but theoretically can support over HTTP protocol: https://developer.sony.com/develop/cameras/api-information/supported-features-and-compatible-cameras

The fun part about this project for me is to create a usable user interface on a device with only two buttons plus an IMU.

## Main Features

![](doc/img/features_family_photo.png)

 * Remote Shutter (with optional timer), Movie Record, Settings View/Adjust
 * Focus Stack
   * takes consecutive photos as the focus is slowly shifted, this is a technique used in macro photography and some other camera brands offer this in-camera (but not Sony)
 * Focus 9-Point
   * takes consecutive photos as the auto-focus point moves around the scene, to obtain multiple photos focused on multiple objects, convenient for landscape photography
 * Focus Pull
   * with optional focus knob (for **linear** focus pull)
 * Talley Light, Virtual "Top Panel"
 * Sensor Trigger Shutter
   * trigger sources: microphone, voltage input, and/or IMU motion trigger
   * trigger action: take photo, take video (adjustable duration), start intervalometer
   * adjustable delays before arming and after trigger
   * good for security camera applications
 * Dual Shutter
   * takes two consecutive photos, with different shutter speeds, for compositing photos with both sharp subjects and blurred features
 * Intervalometer
   * supports AF-C, different from in-camera intervalometer (which forces AF-S)
 * Astrophotography Intervalometer
   * same as intervalometer but more focused on bulb mode, and uses pause time instead of fixed interval time
 * Focus Frustration
   * is your camera not focusing on the thing you want it to focus on? rapidly tapping the AF button repeatedly will automatically pull the focus back to the nearest possible point
 * [Timecode reset](https://github.com/frank26080115/alpha-fairy/issues/13), can be used to reset timecode on multiple cameras simultaneously

### Minor Features

 * can fall-back to using infrared communication if Wi-Fi is disconnected
 * can use shutter release cable connected to GPIO
 * status bar with battery indicator and connection indicator
 * auto power save
 * configurable options
 * serial port debugging and command line interface
 * smartphone web-browser interface to manage multiple camera logins

## Known Problems

The camera does not re-establish a broken connection. If the remote is turned off (or disconnected for other reasons), you need to turn off the camera and turn it back on (after the remote is turned back on).

The features that can change shutter speed are not reliable. The camera takes a long (and variable) time to register a remote command to change the shutter speed. By long, I mean sometimes up to several seconds, or never.

Battery life is a bit short. Do not use it for intervalometer purposes without an external power source. Also, using Wi-Fi for intervalometer is ill-advised (reliability and latency issues), use a real shutter release cable if possible.

Putting a full frame camera in APS-C mode will disable a ton of features from working.

Only Wi-Fi 2.4 GHz mode is supported.

Other issues will be logged in the Issues tab of GitHub.

## More

 * [Instructions for Setup and Usage](INSTRUCTIONS.md)
 * [Full Features Guide](Full-Features-Guide.md)
 * [Firmware Engineering](doc/Firmware-Engineering.md)
 * [Camera Reverse Engineering](doc/Camera-Reverse-Engineering.md)
 * [My personal website blog post](https://eleccelerator.com/alpha-fairy-wireless-camera-remote/)
 * [Wishlist of things I'd like Sony to change](doc/Wishlist-for-Sony-Mirrorless-Camera-Remote-Protocol.md)

## Demo Videos

#### Focus Stacking:

https://user-images.githubusercontent.com/1427911/187117478-654adc80-a0a3-48cf-8b0b-643bfd7b2884.mp4

#### Shutter Speed Stepping:

https://user-images.githubusercontent.com/1427911/187117818-dda28b7c-d5e2-45bd-96e9-6ff3c6a178e0.mp4

#### Camera Triggering:

https://user-images.githubusercontent.com/1427911/195257775-e0213d1b-5642-413e-9474-2736aee4f98b.mp4

#### Focus Pull Knob:

https://user-images.githubusercontent.com/1427911/195407559-9cf6f15e-1cde-4ad6-bc44-528534690053.mp4


================================================
FILE: arduino_workspace/.gitignore
================================================
tools/*
libraries/Adafruit*
libraries/Arduino*
libraries/FastLED*
libraries/IRremote*
libraries/MAX30100*
libraries/HX711_Arduino_Library
libraries/MFRC522_I2C
libraries/PCA9554
libraries/M5GFX
libraries/PubSubClient
libraries/M5StickC
libraries/TFTTerminal
libraries/M5_ADS1100
libraries/TinyGPSPlus
libraries/M5_ADS1115
libraries/UNIT_ENV
libraries/M5_BM8563
libraries/ClosedCube_TCA9548A
libraries/M5_EzData
libraries/FFT
libraries/M5_FPC1020A
libraries/Wifi/examples
libraries/Wifi/keywords.txt
libraries/Wifi/library.properties

libraries/M5*

libraries/readme.txt

#AlphaFairy/data/*

# Prerequisites
*.d

# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod
*.smod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

#PlatformIO
.pio
.vscode


================================================
FILE: arduino_workspace/AlphaFairy/AlphaFairy.h
================================================
#ifndef _ALPHAFAIRY_H_
#define _ALPHAFAIRY_H_

#include <stdint.h>
#include <stdbool.h>

#include "alfy_conf.h"
#include "alfy_types.h"
#include "alfy_defs.h"

#include <M5StickCPlus.h>
#include <M5DisplayExt.h>
#include <SpriteMgr.h>
#include <FS.h>
#include <SPIFFS.h>
#include <PtpIpCamera.h>
#include <PtpIpSonyAlphaCamera.h>
#include <SonyHttpCamera.h>
#include "AlphaFairyCamera.h"
#include <AlphaFairy_NetMgr.h>
#include <AlphaFairyImu.h>
#include <FairyKeyboard.h>
#include <FairyEncoder.h>
#include <SerialCmdLine.h>
#include <SonyCameraInfraredRemote.h>
#include <DebuggingSerial.h>

#ifdef ENABLE_BUILD_LEPTON
#include <Lepton.h>
#endif

extern PtpIpSonyAlphaCamera ptpcam;
extern SonyHttpCamera       httpcam;
extern AlphaFairyCamera     fairycam;
extern SerialCmdLine        cmdline;
extern configsettings_t     config_settings;
extern AlphaFairyImu        imu;
extern SpriteMgr* sprites;

extern
#ifdef DISABLE_ALL_MSG
    DebuggingSerialDisabled
#else
    DebuggingSerial
#endif
                            dbg_ser;

extern bool app_poll(void);

extern void app_waitAllRelease(void);
extern void app_waitAllReleaseConnecting(void);
extern void app_waitAllReleaseUnsupported(void);
extern void app_sleep(uint32_t x, bool forget_btns);

extern void settings_save(void);

extern void pwr_lcdUndim(void);
extern void pwr_sleepCheck(void);
extern void pwr_tick(bool);

extern bool btnSide_hasPressed(void);
extern bool btnBig_hasPressed(void);
extern bool btnPwr_hasPressed(void);
extern bool btnBoth_hasPressed(void);
extern bool btnAny_hasPressed(void);
extern bool btnSide_isPressed(void);
extern bool btnBig_isPressed(void);
extern bool btnPwr_isPressed(void);
extern bool btnBoth_isPressed(void);
extern bool btnAll_isPressed(void);
extern void btnSide_clrPressed(void);
extern void btnBig_clrPressed(void);
extern void btnPwr_clrPressed(void);
extern void btnBoth_clrPressed(void);
extern void btnAny_clrPressed(void);

#endif


================================================
FILE: arduino_workspace/AlphaFairy/AlphaFairy.ino
================================================
#include "AlphaFairy.h"
#include <M5StickCPlus.h>
#include <M5DisplayExt.h>
#include <SpriteMgr.h>
#include "FairyMenu.h"
#include <PtpIpCamera.h>
#include <PtpIpSonyAlphaCamera.h>
#include <SonyHttpCamera.h>
#include "AlphaFairyCamera.h"
#include <AlphaFairy_NetMgr.h>
#include <AlphaFairyImu.h>
#include <FairyKeyboard.h>
#include <FairyEncoder.h>
#include <SerialCmdLine.h>
#include <SonyCameraInfraredRemote.h>

#ifdef ENABLE_BUILD_LEPTON
#include <Lepton.h>
#endif

PtpIpSonyAlphaCamera ptpcam((char*)"ALPHA-FAIRY", NULL);
SonyHttpCamera       httpcam;
AlphaFairyCamera     fairycam(&ptpcam, &httpcam);

#ifdef DISABLE_ALL_MSG
DebuggingSerialDisabled
#else
DebuggingSerial
#endif
                        dbg_ser(&Serial);

uint32_t gpio_time = 0; // keeps track of the GPIO shutter activation time so it doesn't get stuck

bool airplane_mode = false;

bool redraw_flag = false; // forces menu redraw
SpriteMgr* sprites;

AlphaFairyImu imu;
FairyEncoder  fencoder;

FairySubmenu main_menu(NULL, 0);

void setup()
{
    Serial.begin(SERIAL_PORT_BAUDRATE);
    dbg_ser.enabled = true;

    Wire1.begin(21, 22);
    Wire1.setClock(400000);

    cpufreq_init();

    settings_init();
    btns_init();
    SonyCamIr_Init();

    M5.begin(false, true, false); // do not initialize the LCD, we have our own extended M5Lcd class to initialize later
    M5.IMU.Init();
    M5.IMU.SetGyroFsr(M5.IMU.GFS_500DPS);
    M5.IMU.SetAccelFsr(M5.IMU.AFS_4G);

    M5.Axp.begin();
    M5.Axp.ScreenSwitch(false); // turn off the LCD backlight while initializing, avoids junk being shown on the screen
    M5Lcd.begin(); // our own extended LCD object
    M5Lcd.fillScreen(TFT_BLACK);
    M5.Axp.ScreenBreath(config_settings.lcd_brightness);

    spiffs_init();

    #ifdef PMIC_LOG_ON_BOOT
    pmic_startCoulombCount();
    #endif

    setup_menus();

    cmdline.print_prompt();

    sprites = new SpriteMgr(&M5Lcd);

    httpcam.borrowBuffer(ptpcam.donateBuffer(), DATA_BUFFER_SIZE);

    cam_cb_setup();
    wifi_init();

    dbg_ser.printf("finished setup() at %u ms\r\n", millis());

    // clear the button flags
    btnAny_clrPressed();

    imu.poll();

    pwr_tick(true);

    #ifdef DISABLE_ALL_MSG
    dbg_ser.enabled = false;
    fairycam.set_debugflags(0);
    #endif
    #ifdef MUTE_NETMSG_ON_BOOT
    fairycam.set_debugflags(0);
    #endif

    btnAny_clrPressed();

    srand(lroundf(imu.accX) + lroundf(imu.accY) + lroundf(imu.accZ));
}

void loop()
{
    main_menu.on_execute(); // this runs an internal loop, the loop calls app_poll, and app_poll will call yield, which resets the watchdog
    dbg_ser.println("main menu exited");
    // exited
    pwr_shutdown();
}

FairySubmenu menu_remote  ("/main_remote.png");
FairySubmenu menu_focus   ("/main_focus.png");
FairyCfgApp  menu_interval("/main_interval.png", "/intervalometer.png", MENUITEM_INTERVAL);
FairyCfgApp  menu_astro   ("/main_astro.png"   , "/galaxy_icon.png"   , MENUITEM_ASTRO);
FairySubmenu menu_utils   ("/main_utils.png");
FairySubmenu menu_auto    ("/main_auto.png");

void setup_menus()
{
    // install menu items
    // these calls must be in the correct order because internally the menu system uses a linked list

    // taking advantage of Arduino's automatic function prototype generation
    // each *.ino file can have its own setup_xxx function

    main_menu.install(&menu_remote);
    menu_remote.set_enc_nav(false);
    main_menu.install(&menu_focus);
    menu_focus.set_enc_nav(false);
    setup_intervalometer();
    main_menu.install(&menu_utils   );
    setup_autoconnect();
    #ifdef ENABLE_BUILD_LEPTON
    setup_leptonflir();
    #endif

    setup_qikrmt();
    setup_remoteshutter();
    setup_shuttertrigger();
    setup_dualshutter();
    setup_timecodeReset();

    setup_focusstack();
    setup_shutterstep();
    setup_focuspull();
    setup_focusfrustration();

    setup_wifimenus();
    setup_configmenu();
    setup_focuscalib();
    setup_aboutme();
}

bool app_poll()
{
    static uint8_t busy_cnt = 0; // make sure we actually do something at least sometimes

    // high priority tasks
    if (airplane_mode == false)
    {
        NetMgr_task();
        ptpcam.task();
        httpcam.task();
        #ifdef HTTP_ON_BOOT
        httpsrv_poll();
        #endif
    }

    // do low priority tasks if the networking is not busy
    if (ptpcam.isKindaBusy() == false || airplane_mode != false || busy_cnt > 1) {
        imu.poll();
        cmdline.task();
        fenc_task();
        btnPwr_poll();
        shutterrelease_task();

        if (imu.hasMajorMotion) {
            // do not sleep if the user is moving the device
            imu.hasMajorMotion = false;
            pwr_tick(true);
        }

        pmic_log();

        yield();

        cpufreq_task();
        pwr_lightSleepEnter(); // this doesn't work yet

        busy_cnt = 0;

        return true; // can do more low priority tasks
    }
    else {
        busy_cnt++;
    }
    return false; // should not do more low priority tasks
}

void shutterrelease_task()
{
    if (gpio_time != 0)
    {
        // release the GPIO after a timeout
        uint32_t telapsed = millis() - gpio_time;
        int32_t tlimit = config_settings.intv_bulb;
        tlimit = (tlimit <= 0) ? config_settings.astro_bulb : tlimit;
        tlimit *= 1000; // previous units were in seconds, next unit is in milliseconds
        tlimit = (tlimit <= 0) ? config_settings.shutter_press_time_ms : tlimit;
        if (tlimit > 0 && (telapsed >= tlimit)) {
            safe_all_pins();
            gpio_time = 0;
        }
    }
}

extern int wifi_err_reason;
extern bool prevent_status_bar_thread;

void critical_error(const char* fp)
{
    prevent_status_bar_thread = true; // critical error can happen from the WiFi thread, so prevent the GUI thread from drawing a status bar over the error screen

    cpufreq_boost();
    pwr_tick(true);
    M5.Axp.GetBtnPress(); // clear the button bit
    uint32_t t = millis(), now = t;

    // disconnect
    esp_wifi_disconnect();
    esp_wifi_stop();
    esp_wifi_deinit();
    M5Lcd.setRotation(0);
    M5Lcd.drawPngFile(SPIFFS, fp, 0, 0);

    if (wifi_err_reason != 0)
    {
        // indicate the error code if there is one
        M5Lcd.setTextFont(2);
        M5Lcd.highlight(true);
        M5Lcd.setTextWrap(true);
        M5Lcd.setHighlightColor(TFT_BLACK);
        M5Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
        M5Lcd.setCursor(5, M5Lcd.height() - 16); // bottom of screen
        M5Lcd.printf("REASON: %d", wifi_err_reason);
    }

    while (true)
    {
        pwr_sleepCheck();

        // restart on button press
        if (btnBoth_hasPressed()) {
            ESP.restart();
        }

        // shutdown on power button press
        if (M5.Axp.GetBtnPress() != 0) {
            show_poweroff();
            M5.Axp.PowerOff();
        }

        // if debugging over serial port, or allow the user to plug it in now, repeat the message
        if (((now = millis()) - t) > 2000) {
            Serial.print("CRITICAL ERROR");
            if (wifi_err_reason != 0) {
                Serial.printf(", WIFI REASON %d", wifi_err_reason);
            }
            Serial.println();
            t = now;
        }
    }
}

class AppAboutMe : public FairyMenuItem
{
    public:
        AppAboutMe() : FairyMenuItem("/about.png")
        {
        };

        virtual bool on_execute(void)
        {
            int loop_cnt = 0;
            int16_t ystart = 110;
            M5Lcd.fillRect(0, ystart, M5Lcd.width(), M5Lcd.height() - ystart - 14, TFT_WHITE);
            do
            {
                gui_startMenuPrint();
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #ifndef ENABLE_BUILD_LEPTON
                M5Lcd.print("Build");
                ystart += M5Lcd.fontHeight() + 2;
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #endif
                M5Lcd.printf("V %s", ALFY_VERSION); // found in alfy_conf.h , please change with every new build
                ystart += M5Lcd.fontHeight() + 2;
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                M5Lcd.setTextFont(2);
                #ifdef ENABLE_BUILD_LEPTON
                M5Lcd.print("LEPTON");
                ystart += M5Lcd.fontHeight() + 2;
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #endif
                M5Lcd.print("Debug ");
                #ifdef DISABLE_ALL_MSG
                    M5Lcd.print("OFF");
                #else
                    M5Lcd.print("ON");
                #endif
                ystart += M5Lcd.fontHeight() + 2;
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                M5Lcd.print("CMD-line ");
                #ifdef DISABLE_ALL_MSG
                    M5Lcd.print("OFF");
                #else
                    M5Lcd.print("ON");
                #endif
                ystart += M5Lcd.fontHeight() + 2;
                M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #ifdef DISABLE_POWER_SAVE
                    M5Lcd.print("PWR-save OFF");
                    ystart += M5Lcd.fontHeight() + 2;
                    M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #endif
                #ifdef DISABLE_STATUS_BAR
                    M5Lcd.print("STS-bar OFF");
                    ystart += M5Lcd.fontHeight() + 2;
                    M5Lcd.setCursor(SUBMENU_X_OFFSET, ystart);
                #endif

                // if the screen overflows, try redrawing everything but full-screen
                if (ystart >= M5Lcd.height() - 16) {
                    ystart = 0;
                    M5Lcd.fillRect(0, ystart, M5Lcd.width(), M5Lcd.height() - ystart, TFT_WHITE);
                    ystart = SUBMENU_Y_OFFSET;
                    continue;
                }
                else
                {
                    break;
                }
            }
            while ((loop_cnt++) <= 2);
            app_waitAllRelease();
            return false;
        };
};

void setup_aboutme(void)
{
    static AppAboutMe app;
    menu_utils.install(&app);
}

void spiffs_init(void)
{
    uint8_t fail = 0;
    if (!SPIFFS.begin(false))
    {
        Serial.println("SPIFFS Mount Failed");
        fail = 1;
    }
    else if (!SPIFFS.exists("/about.png"))
    {
        // A file that should exist which we can use to quickly test that the files are present.
        // The main case here is that the user only flashed the firmware, and not the FS, so a
        // single file is a sufficient check.
        fail = 2;
    }
    else if (!SPIFFS.exists(ALFY_VERSION_FILE_CHECK)) // defined in alfy_conf.h
    {
        // use this file to make sure the version matches the files
        // change the file name when files are updated
        fail = 3;
    }

    // If there was any issue finding the images, give the user a helpful message
    if (fail != 0)
    {
        gui_startAppPrint();
        M5Lcd.setTextColor(TFT_RED, TFT_BLACK);
        M5Lcd.setTextFont(4);
        M5Lcd.setCursor(SUBMENU_X_OFFSET, SUBMENU_Y_OFFSET);
        M5Lcd.printf("ERROR!!!");
        M5Lcd.setCursor(SUBMENU_X_OFFSET, SUBMENU_Y_OFFSET + 25);
        if (fail != 3) {
            M5Lcd.printf("Image Files Missing");
        }
        else {
            M5Lcd.printf("Files out-of-date");
        }
        M5Lcd.setTextFont(2);
        M5Lcd.setCursor(SUBMENU_X_OFFSET, SUBMENU_Y_OFFSET + 50);
        M5Lcd.printf("Please use the Arduino IDE");
        M5Lcd.setCursor(SUBMENU_X_OFFSET, SUBMENU_Y_OFFSET + 68);
        if (fail != 3) {
            M5Lcd.printf("to upload the missing files");
        }
        else {
            M5Lcd.printf("to upload the new files");
        }

        // We should still let the user power off... No sense killing the battery.
        while (true)
        {
            yield();
            if (fail == 1) {
                Serial.println("SPIFFS Mount Failed");
            }
            else if (fail == 2) {
                Serial.println("Image files are missing");
            }
            if (M5.Axp.GetBtnPress() != 0) {
                pwr_shutdown();
            }
            delay(100);
        }
    }
}


================================================
FILE: arduino_workspace/AlphaFairy/AlphaFairyCamera.cpp
================================================
#include "AlphaFairyCamera.h"

extern uint32_t shutter_to_millis(uint32_t x);
extern uint32_t parse_shutter_speed_str(char* s);
static int get_idx_in_str_tbl(char* tbl_prt, uint32_t x, uint32_t cvt_mode);
static bool get_str_at_tbl_idx(char* tbl, int idx, char* dst);
static bool get_val_at_tbl_idx(char* tbl, int idx, void* dst, uint32_t cvt_mode);

uint32_t AlphaFairyCamera::getIp(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->getIp();
    }
    if (cam_http->isOperating()) {
        return cam_http->getIp();
    }
    return 0;
}

char* AlphaFairyCamera::getCameraName(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->getCameraName();
    }
    if (cam_http->isOperating()) {
        return cam_http->getCameraName();
    }
    return NULL;
}

void AlphaFairyCamera::wait_while_busy(uint32_t min_wait, uint32_t max_wait, volatile bool* exit_signal)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->wait_while_busy(min_wait, max_wait, exit_signal);
    }
    if (cam_http->isOperating()) {
        return cam_http->wait_while_busy(min_wait, max_wait, exit_signal);
    }
}

void AlphaFairyCamera::wait_while_saving(uint32_t min_wait, uint32_t max_wait_get, uint32_t max_wait_save)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->wait_while_saving(min_wait, max_wait_get, max_wait_save);
    }
    if (cam_http->isOperating()) {
        return cam_http->wait_while_saving(min_wait, max_wait_get, max_wait_save);
    }
}

bool AlphaFairyCamera::is_movierecording(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->is_movierecording();
    }
    if (cam_http->isOperating()) {
        return cam_http->is_movierecording();
    }
    return false;
}

bool AlphaFairyCamera::is_manuallyfocused(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->is_manuallyfocused();
    }
    if (cam_http->isOperating()) {
        return cam_http->is_manuallyfocused() == SHCAM_FOCUSMODE_MF;
    }
    return false;
}

bool AlphaFairyCamera::is_focused(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->is_focused;
    }
    if (cam_http->isOperating()) {
        return cam_http->is_focused;
    }
    return false;
}

uint32_t AlphaFairyCamera::get_exposureMode(void)
{
    int32_t x = 0;
    if (cam_ptp->isOperating()) {
        if (cam_ptp->has_property(PTP_PROPCODE_ExposureProgramMode)) {
            x = cam_ptp->get_property(PTP_PROPCODE_ExposureProgramMode);
            if ((x & 0x8000) != 0) {
                return x; // bit 15  is set so just return it since it's a part of the enums
            }
            if ((x & 0xF0000) != 0) {
                return x & 0xFFFF; // mask off to match the enums
            }

            // handle classic PTP codes
            if (x > 0) {
                // convert it to the Sony codes
                switch (x)
                {
                    case PTP_EXPOPROGMODE_MANUAL:
                        return SONYALPHA_EXPOMODE_M;
                    case PTP_EXPOPROGMODE_AUTO:
                        return SONYALPHA_EXPOMODE_P;
                    case PTP_EXPOPROGMODE_A:
                        return SONYALPHA_EXPOMODE_A;
                    case PTP_EXPOPROGMODE_S:
                        return SONYALPHA_EXPOMODE_S;
                }
            }
        }
        if (cam_ptp->has_property(SONYALPHA_PROPCODE_ExposeIndex)) {
            x = cam_ptp->get_property(SONYALPHA_PROPCODE_ExposeIndex);
            if (x >= SONYALPHA_EXPOMODE_IntelligentAuto && x <= 0x9000) {
                return x;
            }
        }
    }
    if (cam_http->isOperating())
    {
        // convert string to enumeration
        char* str_em = cam_http->get_str_expomode();
        if (memcmp("Manual", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_M;
        }
        if (memcmp("Program Auto", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_P;
        }
        if (memcmp("Aperture", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_A;
        }
        if (memcmp("Shutter", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_S;
        }
        if (memcmp("Intelligent Auto", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_IntelligentAuto;
        }
        if (memcmp("Superior Auto", str_em, 5) == 0) {
            return SONYALPHA_EXPOMODE_SuperiorAuto;
        }
    }
    return x;
}

bool AlphaFairyCamera::need_wait_af(void)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->need_wait_af();
    }
    if (cam_http->isOperating()) {
        return cam_http->need_wait_af();
    }
    return false;
}

bool AlphaFairyCamera::cmd_AutoFocus(bool onoff)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_AutoFocus(onoff);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_AutoFocus(onoff);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_Shutter(bool openclose)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_Shutter(openclose);
    }
    if (openclose && cam_http->isOperating()) {
        cam_http->cmd_Shoot();
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_FocusPointSet(int16_t x, int16_t y)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_FocusPointSet(x, y);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_FocusPointSet16(x, y);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_Shoot(int t)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_Shoot(t);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_Shoot();
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_MovieRecord(bool onoff)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_MovieRecord(onoff);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_MovieRecord(onoff);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_MovieRecordToggle()
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_MovieRecordToggle();
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_MovieRecordToggle();
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_ManualFocusMode(bool onoff, bool precheck)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_ManualFocusMode(onoff, precheck);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_ManualFocusMode(onoff, precheck);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_ManualFocusToggle(bool onoff)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_ManualFocusToggle(onoff);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_ManualFocusToggle(onoff);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_IsoSet(uint32_t x)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_IsoSet(x);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_IsoSet(x);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_ShutterSpeedSet(uint32_t x)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_ShutterSpeedSet32(x);
    }
    if (cam_http->isOperating() && cam_http->tbl_shutterspd != NULL) {
        int idx = getIdx_shutter(x);
        if (idx >= 0)
        {
            char sbuff[32];
            bool r = get_str_at_tbl_idx(cam_http->tbl_shutterspd, idx, sbuff);
            if (r)
            {
                int slen = strlen(sbuff);
                if (sbuff[0] == '1' && sbuff[1] == '/') {
                    // do nothing
                }
                else if (sbuff[0] >= '0' && sbuff[0] <= '9') {
                    sbuff[slen] = '\\';
                    sbuff[slen + 1] = '"';
                    sbuff[slen + 2] = 0;
                }
                cam_http->cmd_ShutterSpeedSetStr(sbuff);
                return true;
            }
        }
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_ApertureSet(uint32_t x)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_ApertureSet((uint16_t)x);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_ApertureSet32(x);
        return true;
    }
    return false;
}

bool AlphaFairyCamera::cmd_ExpoCompSet(int32_t x)
{
    if (cam_ptp->isOperating()) {
        return cam_ptp->cmd_ExpoCompSet(x);
    }
    if (cam_http->isOperating()) {
        cam_http->cmd_ExpoCompSet32(x);
        return true;
    }
    return false;
}

void AlphaFairyCamera::set_debugflags(uint32_t x)
{
    if (cam_ptp != NULL) {
        cam_ptp->set_debugflags(x);
    }
    if (cam_http != NULL) {
        cam_http->set_debugflags(x);
    }
}

void AlphaFairyCamera::force_disconnect(void)
{
    if (cam_ptp != NULL) {
        cam_ptp->force_disconnect();
    }
    if (cam_http != NULL) {
        cam_http->force_disconnect();
    }
}

static int get_idx_in_str_tbl(char* tbl_ptr, uint32_t x, uint32_t cvt_mode)
{
    uint32_t oricomp = x;
    if (cvt_mode == SONYALPHA_PROPCODE_ShutterSpeed) {
        oricomp = shutter_to_millis(x);
    }
    else if (cvt_mode == SONYALPHA_PROPCODE_ISO) {
        oricomp &= 0xFFFFFF;
    }

    int commas = 0;

    int has_min = -1;
    uint32_t min_dist;

    char dst[32];
    int i, j = strlen(tbl_ptr);
    for (i = 0; i < j; i++) {
        char c = tbl_ptr[i];
        if (i == 0 || c == ',') // first entry or found comma
        {
            i++;
            int k;
            for (k = 0, dst[0] = 0; i < j && k < 30; i++)
            {
                c = tbl_ptr[i];
                if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '+' || c == '-' || c == '.' || c == '/') // look for valid numeric characters
                {
                    // record valid character into string
                    dst[k] = c;
                    dst[k+1] = 0;
                    k++;
                }
                else if (c == ',') // found another comma, end of entry, go back to outer loop
                {
                    i--;
                    break;
                }
            }

            if (k > 0) // if has result
            {
                if (cvt_mode == SONYALPHA_PROPCODE_ShutterSpeed)
                {
                    uint32_t y = parse_shutter_speed_str(dst);
                    uint32_t yms = shutter_to_millis(y);
                    if (oricomp == yms) {
                        return commas;
                    }
                    else {
                        uint32_t dist = (oricomp > yms) ? (oricomp - yms) : (yms - oricomp);
                        if (has_min < 0 || dist < min_dist) {
                            has_min = commas;
                            min_dist = dist;
                        }
                    }
                }
                else if (cvt_mode == SONYALPHA_PROPCODE_ISO)
                {
                    if (dst[0] >= '0' && dst[0] <= '9') {
                        uint32_t y = atoi(dst);
                        if (oricomp == y) {
                            return commas;
                        }
                        uint32_t dist = (oricomp > y) ? (oricomp - y) : (y - oricomp);
                        if (has_min < 0 || dist < min_dist) {
                            has_min = commas;
                            min_dist = dist;
                        }
                    }
                    else {
                        if (oricomp == 0 || oricomp == 0xFFFFFF) {
                            return commas;
                        }
                    }
                }
                else if (cvt_mode == SONYALPHA_PROPCODE_Aperture)
                {
                    uint32_t y = (uint32_t)lround(100.0f * atof(dst));
                    if (y >= (oricomp - 6) && y <= (oricomp + 6)) {
                        return commas;
                    }
                    uint32_t dist = (oricomp > y) ? (oricomp - y) : (y - oricomp);
                    if (has_min < 0 || (dist < min_dist && min_dist < 200)) {
                        has_min = commas;
                        min_dist = dist;
                    }
                }
            }

            commas++;
        }
    }

    if (has_min >= 0) {
        return has_min;
    }

    return -1;
}

static bool get_str_at_tbl_idx(char* tbl, int idx, char* dst)
{
    int commas = 0;
    int i, j = strlen(tbl);
    for (i = 0; i < j; i++)
    {
        char c = tbl[i];
        if (c == ',')
        {
            commas++;
        }
        if (commas == idx)
        {
            i++;
            int k;
            for (k = 0; k < 30 && i < j; i++)
            {
                c = tbl[i];
                if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '+' || c == '-' || c == '.' || c == '/') {
                    dst[k] = c;
                    dst[k + 1] = 0;
                    k++;
                }
                else if (c == ',')
                {
                    break;
                }
            }
            if (k > 0)
            {
                return true;
            }
        }
    }
    return false;
}

static bool get_val_at_tbl_idx(char* tbl, int idx, void* dst, uint32_t cvt_mode)
{
    char sbuff[32];
    if (idx < 0) {
        idx = 0;
    }
    while (idx >= 0)
    {
        bool r = get_str_at_tbl_idx(tbl, idx, (char*) sbuff);
        if (r)
        {
            if (cvt_mode == SONYALPHA_PROPCODE_ShutterSpeed)
            {
                uint32_t* p = (uint32_t*)dst;
                (*p) = parse_shutter_speed_str((char*)sbuff);
                return true;
            }
            else if (cvt_mode == SONYALPHA_PROPCODE_Aperture)
            {
                uint32_t* p = (uint32_t*)dst;
                (*p) = (uint32_t)lround(100.0f * atof(sbuff));
                return true;
            }
            else if (cvt_mode == SONYALPHA_PROPCODE_ISO)
            {
                uint32_t* p = (uint32_t*)dst;
                if (sbuff[0] >= '0' && sbuff[0] <= '9') {
                    (*p) = (uint32_t)atoi(sbuff);
                }
                else {
                    (*p) = 0;
                }
                return true;
            }
        }
        idx--;
    }
    return false;
}

int AlphaFairyCamera::getIdx_shutter(uint32_t x)
{
    if (cam_ptp->isOperating() && cam_ptp->table_shutter_speed != NULL) {
        uint32_t* tbl = (uint32_t*)&(cam_ptp->table_shutter_speed[1]);
        uint32_t i, j = cam_ptp->table_shutter_speed[0];
        for (i = 0; i < j; i++) {
            if (tbl[i] == x) {
                return i;
            }
        }
    }
    if (cam_http->isOperating() && cam_http->tbl_shutterspd != NULL) {
        return get_idx_in_str_tbl(cam_http->tbl_shutterspd, x, SONYALPHA_PROPCODE_ShutterSpeed);
    }
    return -1;
}

int AlphaFairyCamera::getIdx_aperture(uint32_t x)
{
    if (cam_ptp->isOperating() && cam_ptp->table_aperture != NULL) {
        uint16_t* tbl = (uint16_t*)&(cam_ptp->table_aperture[1]);
        uint32_t i, j = cam_ptp->table_aperture[0];
        for (i = 0; i < j; i++) {
            if (tbl[i] <= (x + 6) && tbl[i] >= (x - 6)) {
                return i;
            }
        }
    }
    if (cam_http->isOperating() && cam_http->tbl_aperture != NULL) {
        return get_idx_in_str_tbl(cam_http->tbl_aperture, x, SONYALPHA_PROPCODE_Aperture);
    }
    return -1;
}

int AlphaFairyCamera::getIdx_iso(uint32_t x)
{
    if (cam_ptp->isOperating() && cam_ptp->table_iso != NULL) {
        uint32_t* tbl = (uint32_t*)&(cam_ptp->table_iso[1]);
        uint32_t i, j = cam_ptp->table_iso[0];
        for (i = 0; i < j; i++) {
            if (tbl[i] == x) {
                return i;
            }
        }
        if (x == 0) {
            x = 0xFFFFFF;
        }
        for (i = 0; i < j; i++) {
            if (tbl[i] == x) {
                return i;
            }
        }
    }
    if (cam_http->isOperating() && cam_http->tbl_iso != NULL) {
        return get_idx_in_str_tbl(cam_http->tbl_iso, x, SONYALPHA_PROPCODE_ISO);
    }
    return -1;
}

int AlphaFairyCamera::getIdx_expoComp(int32_t x)
{
    float fx = x;
    return (int)lround(fx / 333.3f);
}

uint32_t AlphaFairyCamera::getVal_shutter(int idx)
{
    idx = idx < 0 ? 0 : idx;
    if (cam_ptp->isOperating() && cam_ptp->table_shutter_speed != NULL) {
        uint32_t* tbl = (uint32_t*)&(cam_ptp->table_shutter_speed[1]);
        if (idx >= cam_ptp->table_shutter_speed[0]) {
            return tbl[cam_ptp->table_shutter_speed[0] - 1];
        }
        else if (idx < 0) {
            return tbl[0];
        }
        return tbl[idx];
    }
    if (cam_http->isOperating() && cam_http->tbl_shutterspd != NULL) {
        uint32_t v;
        bool r = get_val_at_tbl_idx(cam_http->tbl_shutterspd, idx, &v, SONYALPHA_PROPCODE_ShutterSpeed);
        if (r) {
            return v;
        }
    }

    return 0;
}

uint32_t AlphaFairyCamera::getVal_aperture(int idx)
{
    idx = idx < 0 ? 0 : idx;
    if (cam_ptp->isOperating() && cam_ptp->table_aperture != NULL) {
        uint16_t* tbl = (uint16_t*)&(cam_ptp->table_aperture[1]);
        if (idx >= cam_ptp->table_aperture[0]) {
            return tbl[cam_ptp->table_aperture[0] - 1];
        }
        else if (idx < 0) {
            return tbl[0];
        }
        return tbl[idx];
    }
    if (cam_http->isOperating() && cam_http->tbl_aperture != NULL) {
        uint32_t v;
        bool r = get_val_at_tbl_idx(cam_http->tbl_aperture, idx, &v, SONYALPHA_PROPCODE_Aperture);
        if (r) {
            return v;
        }
    }

    return 0;
}

uint32_t AlphaFairyCamera::getVal_iso(int idx)
{
    idx = idx < 0 ? 0 : idx;
    if (cam_ptp->isOperating() && cam_ptp->table_iso != NULL) {
        uint32_t* tbl = (uint32_t*)&(cam_ptp->table_iso[1]);
        if (idx >= cam_ptp->table_iso[0]) {
            return tbl[cam_ptp->table_iso[0] - 1];
        }
        else if (idx < 0) {
            return tbl[0];
        }
        return tbl[idx];
    }
    if (cam_http->isOperating() && cam_http->tbl_iso != NULL) {
        uint32_t v;
        bool r = get_val_at_tbl_idx(cam_http->tbl_iso, idx, &v, SONYALPHA_PROPCODE_ISO);
        if (r) {
            return v;
        }
    }

    return 0;
}

int32_t AlphaFairyCamera::getVal_expoComp(int idx)
{
    return idx * 333;
}


================================================
FILE: arduino_workspace/AlphaFairy/AlphaFairyCamera.h
================================================
#ifndef _ALPHAFAIRYCAMERA_H_
#define _ALPHAFAIRYCAMERA_H_

// this class exists to make the main application code neater
// it encapsulates the PtpIpSonyAlphaCamera and SonyHttpCamera class
// so that common functions can be called upon the AlphaFairyCamera class
// and the AlphaFairyCamera class decides which protocol to send the command with, depending on how the camera is actually connected

#include <PtpIpSonyAlphaCamera.h>
#include <SonyHttpCamera.h>

class AlphaFairyCamera
{
    public:
        AlphaFairyCamera(PtpIpSonyAlphaCamera* cam_p, SonyHttpCamera* cam_h) { cam_ptp = cam_p; cam_http = cam_h; };
    private:
        PtpIpSonyAlphaCamera* cam_ptp;
        SonyHttpCamera*       cam_http;
    public:
        uint32_t  getIp           (void);
        char*     getCameraName   (void);
        void wait_while_busy(uint32_t min_wait, uint32_t max_wait, volatile bool* exit_signal = NULL);
        void wait_while_saving(uint32_t min_wait, uint32_t max_wait_get, uint32_t max_wait_save);
        inline bool isOperating() { return cam_ptp->isOperating() || cam_http->isOperating(); };
        void force_disconnect(void);
        bool is_movierecording(void);
        bool is_manuallyfocused(void);
        uint32_t get_exposureMode(void);
        bool need_wait_af(void);
        bool is_focused(void);
        bool cmd_AutoFocus(bool onoff);
        bool cmd_Shutter(bool openclose);
        bool cmd_FocusPointSet(int16_t x, int16_t y);
        bool cmd_Shoot(int t);
        bool cmd_MovieRecord(bool onoff);
        bool cmd_MovieRecordToggle();
        bool cmd_ManualFocusMode(bool onoff, bool precheck = false);
        bool cmd_ManualFocusToggle(bool onoff);
        bool cmd_IsoSet(uint32_t x);          // input parameter expects format matching PTP mode
        bool cmd_ShutterSpeedSet(uint32_t x); // input parameter expects format matching PTP mode
        bool cmd_ApertureSet(uint32_t x);     // input parameter expects format matching PTP mode
        bool cmd_ExpoCompSet(int32_t x);      // input parameter expects format matching PTP mode

        int      getIdx_shutter (uint32_t x); // input parameter expects format matching PTP mode
        int      getIdx_aperture(uint32_t x); // input parameter expects format matching PTP mode
        int      getIdx_iso     (uint32_t x); // input parameter expects format matching PTP mode
        int      getIdx_expoComp(int32_t  x); // input parameter expects format matching PTP mode
        uint32_t getVal_shutter (int idx);    // output return format matches PTP mode
        uint32_t getVal_aperture(int idx);    // output return format matches PTP mode
        uint32_t getVal_iso     (int idx);    // output return format matches PTP mode
        int32_t  getVal_expoComp(int idx);    // output return format matches PTP mode

        void set_debugflags(uint32_t x);
};

#endif


================================================
FILE: arduino_workspace/AlphaFairy/AppUtils.ino
================================================
#include "AlphaFairy.h"

#ifdef ENABLE_BUILD_LEPTON
extern bool lepton_enable_poll;
#endif

void app_waitAllReleaseGfx(uint8_t waitgfx)
{
    btnAny_clrPressed();
    if (btnSide_isPressed() == false && btnBig_isPressed() == false)
    {
        return;
    }

    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = false;
    #endif

    cpufreq_boost();

    if (waitgfx == WAITGFX_CONNECTING)
    {
        gui_drawConnecting(true);
    }
    else if (waitgfx == WAITGFX_UNSUPPORTED)
    {
        M5Lcd.drawPngFile(SPIFFS, "/unsupported.png", 0, 0);
    }

    uint32_t now = millis();
    uint32_t last_time = now;
    do
    {
        app_poll();

        if (waitgfx == WAITGFX_CONNECTING)
        {
            gui_drawConnecting(false);
        }

        if (btnSide_isPressed() || btnBig_isPressed()) {
            last_time = millis();
        }
    }
    while ((last_time - (now = millis())) < BTN_DEBOUNCE);

    redraw_flag = true;

    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = true;
    #endif
}

void app_waitAllRelease()
{
    app_waitAllReleaseGfx(WAITGFX_NONE);
}

void app_waitAnyPress(bool can_sleep)
{
    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = false;
    #endif
    while (true)
    {
        app_poll();
        if (can_sleep == false) {
            pwr_tick(true);
        }
        if (btnAny_hasPressed()) {
            break;
        }
    }
    btnAny_clrPressed();
    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = true;
    #endif
}

void app_waitAllReleaseConnecting()
{
    app_waitAllReleaseGfx(WAITGFX_CONNECTING);
}

void app_waitAllReleaseUnsupported()
{
    app_waitAllReleaseGfx(WAITGFX_UNSUPPORTED);
}

void app_sleep(uint32_t x, bool forget_btns)
{
    uint32_t tstart = millis();
    uint32_t now;
    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = false;
    #endif
    while (((now = millis()) - tstart) < x) {
        app_poll();
    }
    if (forget_btns) {
        btnAny_clrPressed();
    }
    #ifdef ENABLE_BUILD_LEPTON
    lepton_enable_poll = true;
    #endif
}

int8_t imu_getFocusPull()
{
    int n = 0;
    int ang = imu.getPitchAdj();
    int aang = (ang < 0) ? (-ang) : (ang);
    if (aang >= 2) { // deadzone
        n = aang / 7;
        n = n > 3 ? 3 : n;
    }
    return (ang < 0) ? (-n) : (n);
}

int focus_tiltToStepSize(int8_t tilt)
{
    // translate tilt into Sony's focus step sizes
    int atilt = tilt < 0 ? -tilt : tilt;
    int n = (atilt ==  2) ?  SONYALPHA_FOCUSSTEP_FARTHER_MEDIUM : ((atilt ==  3) ?  SONYALPHA_FOCUSSTEP_FARTHER_LARGE : n);
    return (tilt < 0) ? -n : n;
}

uint32_t shutter_to_millis(uint32_t x)
{
    uint16_t* p16 = (uint16_t*)&x;
    if (x == 0 || x == 0xFFFFFFFF) {
        return 0;
    }
    uint32_t nn = p16[1];
    uint32_t dd = p16[0];
    nn *= 1000;
    float n = nn;
    float d = dd;
    float y = d != 0 ? (n/d) : 0;
    return lroundf(y);
}

void gui_formatSecondsTime(int32_t x, char* str, bool shorten)
{
    // format time in 00:00:00 format when provided in seconds
    // optionally shortens to 0H:00 format when the time is very long
    int i = 0;
    if (x < 0) {
        // negative sign
        i += sprintf(&(str[i]), "-");
        x *= -1;
    }
    uint32_t mins = x / 60;
    uint32_t hrs = mins / 60;
    mins %= 60;
    uint32_t secs = x % 60;
    if (hrs > 0) {
        i += sprintf(&(str[i]), "%u", hrs);
        if (shorten) {
            // add a H just so we understand it's HH:MM instead of MM:SS
            i += sprintf(&(str[i]), "H");
        }
        i += sprintf(&(str[i]), ":");
        if (mins < 10) {
            i += sprintf(&(str[i]), "0");
        }
    }
    if (shorten && hrs > 0) {
        i += sprintf(&(str[i]), "%u", mins);
    }
    else {
        i += sprintf(&(str[i]), "%u:%02u", mins, secs);
    }
}

void gui_formatShutterSpeed(uint32_t x, char* str)
{
    uint16_t* p16 = (uint16_t*)&x;
    if (x == 0 || x == 0xFFFFFFFF) {
        sprintf(str, "BULB");
        return;
    }
    uint16_t nn = p16[1];
    uint16_t dd = p16[0];
    float n = nn;
    float d = dd;
    float y = d != 0 ? (n/d) : 0;
    sprintf(str, "%0.1f\"", y);
    if (y >= 4 || nn == dd || y == 2.0 || y == 3.0) {
        sprintf(str, "%u\"", lroundf(y));
        return;
    }
    if (y >= 0.35) {
        return;
    }
    if (nn >= 10 && dd > 10) {
        return;
    }
    if (nn == 1) {
        sprintf(str, "1/%u", dd);
    }
    return;
}

void gui_formatISO(uint32_t x, char* str)
{
    uint16_t* p16 = (uint16_t*)&x;
    if (x == 0) {
        sprintf(str, "???");
        return;
    }
    if (x == 0xFFFFFF) {
        sprintf(str, "AUTO");
        return;
    }
    x &= 0xFFFFFF;
    sprintf(str, "%u", x);
    return;
}

int file_readLine(File* f, char* tgt, int charlimit)
{
    int i = 0;
    if (f->available() <= 0) {
        return -1;
    }

    while (f->available() > 0) // until end of file
    {
        char c = f->read();
        
        if (c != '\r' && c != '\n' && c != '\0') // is not terminator
        {
            if (i < charlimit - 1) // if there is room in string buffer
            {
                // append char to string
                tgt[i] = c;
                i += 1;
                tgt[i] = 0;
            }
        }
        else // is terminator
        {
            // end the string
            tgt[i] = 0;
            if (i > 0) { // this trims the start of a line
                i += 1;
                break;
            }
        }
    }
    return i;
}

void dissolve_restart(uint16_t colour)
{
    uint32_t t = millis();
    cpufreq_boost();
    esp_wifi_disconnect();
    esp_wifi_stop();
    esp_wifi_deinit();
    while (btnBig_isPressed())
    {
        int x = rand() % M5Lcd.width();
        int y = rand() % M5Lcd.height();
        if ((millis() - t) < 5000)
        {
            M5Lcd.fillRect(x, y, 1, 1, colour);
        }
        else
        {
            M5Lcd.fillRect(x, y, 1, 1, TFT_BLACK);
            int32_t b = config_settings.lcd_brightness - (((millis() - t) - 5000) / 1250);
            M5.Axp.ScreenBreath(b);
        }
    }
    ESP.restart();
}

int32_t get_pinCfgGpio(int32_t x)
{
    switch (x)
    {
        #ifndef ENABLE_BUILD_LEPTON
        case PINCFG_G0 : return 0;
        case PINCFG_G25: return 25;
        #endif
        case PINCFG_G26: return 26;
        case PINCFG_G32: return 32;
        case PINCFG_G33: return 33;
        #ifndef ENABLE_BUILD_LEPTON
        case PINCFG_G36: return 36;
        #endif
    }
    return -1;
}

void safe_all_pins()
{
    #ifndef ENABLE_BUILD_LEPTON
    pinMode(0, INPUT);
    pinMode(25, INPUT);
    #endif
    pinMode(26, INPUT);
    pinMode(32, INPUT);
    pinMode(33, INPUT);
    #ifndef ENABLE_BUILD_LEPTON
    pinMode(36, INPUT);
    #endif
}


================================================
FILE: arduino_workspace/AlphaFairy/AutoConnect.ino
================================================
#include "AlphaFairy.h"
#include <FairyKeyboard.h>

bool autoconnect_active = false;
int  autoconnect_status = 0;

extern bool airplane_mode;

void autoconnect_poll()
{
    yield();
    cpufreq_boost();
    imu.poll();
    cmdline.task();
    pwr_sleepCheck(); // this will only dim the screen, because pwr_tick is always being called
    if (imu.hasMajorMotion) {
        imu.hasMajorMotion = false;
        pwr_tick(true);
    }
    else {
        pwr_tick(false);
    }
    btnPwr_quickPoll();
}

class AppAutoConnect : public FairyMenuItem
{
    public:
        AppAutoConnect() : FairyMenuItem("/main_auto.png")
        {
        };

        // hide this item when a camera is actually connected
        virtual bool can_navTo(void)
        {
            if (fairycam.isOperating()) {
                return false;
            }
            if (airplane_mode) {
                return false;
            }
            return FairyMenuItem::can_navTo();
        };

        virtual bool on_execute(void)
        {
            autoconnect_active = true;
            autoconnect_status = 0;

            uint8_t scan_failed_cnt = 0;
            uint8_t result_code = 0;
            uint8_t result_profile = 0;
            wifiprofile_t profile;

            bool first_loop = true;
            bool user_quit = false;

            int draw_idx = 0;
            uint32_t t = millis();
            uint32_t now = t;

            WiFi.mode(WIFI_STA);
            WiFi.disconnect(); // halt wifi activity for now

            btnAny_clrPressed();

            dbg_ser.println("autoconnect starting");

            int scan_ret;

            while (true)
            {
                autoconnect_poll();

                if (((now = millis()) - t) > 333) // time for new animation frame
                {
                    t = now;
                    // rotate the icon's position in a loop
                    int x, y;
                    switch (draw_idx)
                    {
                        case 0: x = 71; y = 141; break;
                        case 1: x =  8; y = 141; break;
                        case 2: x =  8; y =  39; break;
                        case 3: x = 71; y =  39; break;
                    }
                    draw_idx = (draw_idx + 1) % 4;

                    M5Lcd.setRotation(0);
                    M5Lcd.fillRect(0,  39, M5Lcd.width(), 62, TFT_WHITE); // remove old icons
                    M5Lcd.fillRect(0, 141, M5Lcd.width(), 62, TFT_WHITE); // remove old icons
                    M5Lcd.drawPngFile(SPIFFS, "/autoconn_icon.png", x, y);
                    gui_drawStatusBar(false);
                }

                if (first_loop) {
                    scan_ret = 0;
                    first_loop = false;
                }
                else if (scan_ret <= 0) {
                    scan_ret = WiFi.scanComplete();
                }

                if (scan_ret > 0) // has results
                {
                    scan_failed_cnt = 0;
                    dbg_ser.printf("autoconnect scan complete, %u results\r\n", scan_ret);
                    int i, j;
                    // first check if anything is broadcasting a SSID that we already have in database
                    for (i = 1; i <= WIFIPROFILE_LIMIT; i++) // for all profiles
                    {
                        if (wifiprofile_getProfile(i, &profile)) // profile exists
                        {
                            for (j = 0; j < scan_ret; j++) // for all scan results
                            {
                                if (strcmp(profile.ssid, WiFi.SSID(j).c_str()) == 0) // SSID matches
                                {
                                    dbg_ser.printf("autoconnect found matching SSID[%u, %u]: %s\r\n", i, j, profile.ssid);
                                    result_profile = i;
                                    result_code = (strlen(profile.password) > 0) ? AUTOCONNRES_FOUND_EXISTING : AUTOCONNRES_FOUND_EXISTING_NEED_PASSWORD;
                                    break;
                                }
                            }
                        }
                        else if (wifiprofile_isBlank(i))
                        {
                            dbg_ser.printf("autoconnect reached end of database at %u\r\n", i);
                            // when WIFIPROFILE_LIMIT is large, we shouldn't waste time with the file system reads
                            break;
                        }
                    }

                    if (result_code == AUTOCONNRES_NONE) // we didn't find anything in our existing database
                    {
                        dbg_ser.printf("autoconnect no existing database entry\r\n");

                        // check if any entry has a SSID that looks like a Sony camera
                        for (i = 0; i < scan_ret; i++)
                        {
                            char* ssid_str = (char*)WiFi.SSID(i).c_str();
                            if (strlen(ssid_str) > 0) {
                                dbg_ser.printf(" [%u]scanned SSID: %s\r\n", i, ssid_str);
                            }
                            if (memcmp("DIRECT-", ssid_str, 7) == 0) // looks like a Sony camera
                            {
                                dbg_ser.printf("autoconnect new SSID: %s\r\n", ssid_str);
                                result_code = AUTOCONNRES_FOUND_NEW;
                                // use the data structure as a cache, we can save it into SPIFFS later quickly
                                strncpy(profile.ssid, ssid_str, WIFI_STRING_LEN);
                                profile.password[0] = 0;
                                profile.opmode = WIFIOPMODE_STA;
                                profile.guid[0] = 0;
                                break;
                            }
                            // TODO: check for compatible cameras only?
                            // TODO: handle multiple new cameras? currently only handles one new entity
                        }
                    }

                    if (result_code == AUTOCONNRES_NONE)
                    {
                        // nothing found, restart the scan
                        dbg_ser.printf("autoconnect re-scan\r\n");
                        scan_ret = WiFi.scanNetworks(true, false, false);
                    }
                    else
                    {
                        // found something, we can quit this loop
                        break;
                    }
                }
                else if (scan_ret != WIFI_SCAN_RUNNING)
                {
                    // either the scan isn't running or no results were found, (re)start the scan
                    dbg_ser.printf("autoconnect scan start (code %d)\r\n", scan_ret);
                    if (scan_ret == WIFI_SCAN_FAILED) {
                        scan_failed_cnt++;
                    }
                    scan_ret = WiFi.scanNetworks(true, false, false);
                }

                // maybe the user wants to cancel
                user_quit |= btnSide_hasPressed() || btnPwr_hasPressed();
                if (user_quit)
                {
                    dbg_ser.printf("autoconnect user quit\r\n");
                    result_code = AUTOCONNRES_QUIT;
                    esp_wifi_scan_stop();
                    WiFi.scanDelete();
                    btnSide_clrPressed();
                    btnPwr_clrPressed();
                    break;
                }

                if (scan_failed_cnt > 5) {
                    critical_error("/wifi_error.png");
                }
            }

            WiFi.scanDelete();

            if (user_quit || result_code == AUTOCONNRES_QUIT)
            {
                // user quit, go back to normal
                NetMgr_reset();
                wifiprofile_connect(config_settings.wifi_profile);
                goto all_done_exit;
            }

            // use this buffer as a cache for wifi_pswdPromptDrawCb()
            strncpy(NetMgr_getSSID(), profile.ssid, WIFI_STRING_LEN);

            if (result_code == AUTOCONNRES_FOUND_NEW)
            {
                // if the SSID does not exist in our database, then find a slot for it
                result_profile = 0;
                int k;
                for (k = 1; k <= WIFIPROFILE_LIMIT; k++)
                {
                    if (wifiprofile_isBlank(k))
                    {
                        result_profile = k;
                        dbg_ser.printf("autoconnect new camera blank spot %u\r\n", k);
                        break;
                    }
                }
            }

            if (result_profile != 0)
            {
                bool need_ask = (result_code == AUTOCONNRES_FOUND_EXISTING_NEED_PASSWORD || result_code == AUTOCONNRES_FOUND_NEW); // do not need to ask if the database already has entry
                bool can_save = result_code == AUTOCONNRES_FOUND_NEW;
                user_quit = wifi_newConnectOrPrompt(result_profile, &profile, need_ask, can_save);
                goto all_done_exit;
            }
            else
            {
                // result_profile is zero but the user didn't quit? the only reason should be that the database is now full, but could be another reason
                // the database is YUGE, so... I'm not going to handle this error gracefully
                wifi_err_reason = 0;
                critical_error("/wifi_error.png");
            }

            all_done_exit:

            if (result_code >= AUTOCONNRES_FOUND_EXISTING && user_quit == false)
            {
                FairySubmenu* p = dynamic_cast<FairySubmenu*>((FairySubmenu*)get_parent());
                p->rewind();
                if (result_profile != config_settings.wifi_profile) {
                    config_settings.wifi_profile = result_profile;
                    dbg_ser.printf("autoconnect saving profile %u\r\n", result_profile);
                    settings_save();
                }
            }

            dbg_ser.printf("autoconnect exiting function\r\n");
            app_waitAllRelease();
            autoconnect_active = false;
            redraw_flag = true;

            return false;
        };
};

extern FairySubmenu main_menu;
void setup_autoconnect()
{
    static AppAutoConnect app;
    main_menu.install(&app);
}


================================================
FILE: arduino_workspace/AlphaFairy/Buttons.ino
================================================
#include "AlphaFairy.h"
#include <Arduino.h>
#include <stdbool.h>

/*
interrupts are used to catch new button press events
a simple debounce algorithm is used
*/

#define PIN_BTN_SIDE     39
#define PIN_BTN_BIG      37
#define GPIO_BTN_SIDE    GPIO_NUM_39
#define GPIO_BTN_BIG     GPIO_NUM_37

//#define BTNS_DEBUG

volatile uint32_t btnSide_downTime = 0;
volatile uint32_t btnBig_downTime  = 0;
volatile uint32_t btnSide_clrTime  = 0;
volatile uint32_t btnBig_clrTime   = 0;
volatile uint32_t btnSide_cnt      = 0;
volatile uint32_t btnBig_cnt       = 0;
volatile uint32_t btnPwr_cnt       = 0;
volatile uint32_t btnSide_cnt_prev = 0;
volatile uint32_t btnBig_cnt_prev  = 0;
volatile uint32_t btnPwr_cnt_prev  = 0;

extern uint32_t pwr_last_tick;
extern uint32_t lcddim_last_tick;
extern void pwr_lcdUndim(void);

void IRAM_ATTR btnSide_isr()
{
    if (digitalRead(PIN_BTN_SIDE) != LOW) {
        // guard against ESP32 hardware bug 
        // https://github.com/espressif/arduino-esp32/issues/5055
        // https://github.com/espressif/esp-idf/commit/d890a516a1097f0a07788e203fdb1a82bb83520e
        return;
    }

    #if defined(ENABLE_LIGHT_SLEEP) && defined(ENABLE_LIGHT_SLEEP_GPIOWAKE)
    // if we went to sleep, then the mode is GPIO_INTR_LOW_LEVEL, we need to change it back to GPIO_INTR_NEGEDGE
    gpio_set_intr_type(GPIO_BTN_SIDE, GPIO_INTR_NEGEDGE);
    #endif

    uint32_t now = millis();
    if ((now - btnSide_downTime) > BTN_DEBOUNCE) {
        btnSide_cnt++;
    }
    btnSide_downTime = now;
}

void IRAM_ATTR btnBig_isr()
{
    if (digitalRead(PIN_BTN_BIG) != LOW) {
        // guard against ESP32 hardware bug 
        // https://github.com/espressif/arduino-esp32/issues/5055
        // https://github.com/espressif/esp-idf/commit/d890a516a1097f0a07788e203fdb1a82bb83520e
        return;
    }

    #if defined(ENABLE_LIGHT_SLEEP) && defined(ENABLE_LIGHT_SLEEP_GPIOWAKE)
    // if we went to sleep, then the mode is GPIO_INTR_LOW_LEVEL, we need to change it back to GPIO_INTR_NEGEDGE
    gpio_set_intr_type(GPIO_BTN_BIG, GPIO_INTR_NEGEDGE);
    #endif

    uint32_t now = millis();
    if ((now - btnBig_downTime) > BTN_DEBOUNCE) {
        btnBig_cnt++;
    }
    btnBig_downTime = now;
}

void btns_init()
{
    pinMode(PIN_BTN_SIDE, INPUT_PULLUP);
    attachInterrupt(PIN_BTN_SIDE, btnSide_isr, FALLING);
    pinMode(PIN_BTN_BIG, INPUT_PULLUP);
    attachInterrupt(PIN_BTN_BIG, btnBig_isr, FALLING);
}

void btns_poll()
{
    #ifdef TRY_CATCH_MISSED_GPIO_ISR
    // this function attempts to catch missed ISRs
    static char prev_side = HIGH;
    static char prev_big  = HIGH;
    char x;

    volatile uint32_t now = millis();

    if ((x = digitalRead(PIN_BTN_SIDE)) != prev_side) {
        if (x == LOW && btnSide_downTime == 0 && (now - btnSide_clrTime) > 100) {
            btnSide_cnt++;
            btnSide_downTime = now;
        }
        prev_side = x;
    }
    if ((x = digitalRead(PIN_BTN_BIG)) != prev_big) {
        if (x == LOW && btnBig_downTime == 0 && (now - btnBig_clrTime) > 100) {
            btnBig_cnt++;
            btnBig_downTime = now;
        }
        prev_big = x;
    }
    #endif
}

bool btnSide_hasPressed() {
    volatile bool x = btnSide_cnt_prev != btnSide_cnt;
    #ifdef BTNS_DEBUG
    static uint32_t rpt_time = 0;
    #endif
    #ifdef TRY_CATCH_MISSED_GPIO_ISR
    if (x == false) {
        btns_poll();
        x = btnSide_cnt_prev != btnSide_cnt;
    }
    #endif
    if (x)
    {
        #ifdef BTNS_DEBUG
        Serial.printf("btnSide_cnt %u %u\r\n", btnSide_cnt, btnSide_cnt_prev);
        rpt_time = 0;
        #endif
        cpufreq_boost();
        return true;
    }
    #ifdef BTNS_DEBUG
    uint32_t now = millis();
    if ((now - rpt_time) > 500) {
        rpt_time = now;
        Serial.printf("not btnSide_cnt %u %u\r\n", btnSide_cnt, btnSide_cnt_prev);
    }
    #endif
    return false;
}

void btnSide_clrPressed() {
    uint32_t now = millis();
    #ifdef BTNS_DEBUG
    Serial.printf("clr btnSide_cnt %u %u\r\n", btnSide_cnt, btnSide_cnt_prev);
    #endif
    btnSide_cnt_prev = btnSide_cnt;
    //btnSide_downTime = 0;
    btnSide_clrTime  = now;
    pwr_last_tick    = now;
    lcddim_last_tick = now;
    pwr_lcdUndim();
}

bool btnBig_hasPressed() {
    volatile bool x = btnBig_cnt_prev != btnBig_cnt;
    #ifdef BTNS_DEBUG
    static uint32_t rpt_time = 0;
    #endif
    #ifdef TRY_CATCH_MISSED_GPIO_ISR
    if (x == false) {
        btns_poll();
        x = btnBig_cnt_prev != btnBig_cnt;
    }
    #endif
    if (x) {
        #ifdef BTNS_DEBUG
        Serial.printf("btnBig_cnt %u %u\r\n", btnBig_cnt, btnBig_cnt_prev);
        rpt_time = 0;
        #endif
        cpufreq_boost();
        return true;
    }
    #ifdef BTNS_DEBUG
    uint32_t now = millis();
    if ((now - rpt_time) > 500) {
        rpt_time = now;
        Serial.printf("not btnBig_cnt %u %u\r\n", btnBig_cnt, btnBig_cnt_prev);
    }
    #endif
    return false;
}

void btnBig_clrPressed() {
    uint32_t now = millis();
    #ifdef BTNS_DEBUG
    Serial.printf("clr btnBig_cnt %u %u\r\n", btnBig_cnt, btnBig_cnt_prev);
    #endif
    btnBig_cnt_prev  = btnBig_cnt;
    //btnBig_downTime  = 0;
    btnBig_clrTime   = now;
    pwr_last_tick    = now;
    lcddim_last_tick = now;
    pwr_lcdUndim();
}

bool btnPwr_hasPressed() {
    volatile bool x = btnPwr_cnt != btnPwr_cnt_prev;
    return x;
}

void btnPwr_clrPressed() {
    uint32_t now = millis();
    btnPwr_cnt_prev = btnPwr_cnt;
    pwr_last_tick = now;
    lcddim_last_tick = now;
}

bool btnSide_isPressed() {
    return (digitalRead(PIN_BTN_SIDE) == LOW);
}

bool btnBig_isPressed() {
    return (digitalRead(PIN_BTN_BIG) == LOW);
}

bool btnBoth_hasPressed() {
    bool x;
    x |= btnSide_hasPressed();
    x |= btnBig_hasPressed();
    return x;
}

void btnBoth_clrPressed() {
    btnSide_clrPressed();
    btnBig_clrPressed();
}

bool btnAny_hasPressed() {
    bool x;
    x |= btnSide_hasPressed();
    x |= btnBig_hasPressed();
    x |= btnPwr_hasPressed();
    x |= M5.Axp.GetBtnPress() != 0;
    return x;
}

void btnAny_clrPressed() {
    btnSide_clrPressed();
    btnBig_clrPressed();
    btnPwr_clrPressed();
}

void btnPwr_poll()
{
    uint32_t now = millis();
    static uint32_t btn_last_time = 0;
    if ((now - btn_last_time) > 100 || btn_last_time == 0) {
        btn_last_time = now;
        btnPwr_quickPoll();
    }
}

void btnPwr_quickPoll()
{
    uint8_t b = M5.Axp.GetBtnPress();
    if (b != 0) {
        btnPwr_cnt++;
        dbg_ser.printf("user pressed power button\r\n");
        pwr_tick(true);
        cpufreq_boost();
    }
}

#if defined(HTTP_MOCKBTNS_ENABLE)

#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
extern AsyncWebServer* httpServer;

void btn_installMockServer()
{
    httpServer->on("/btnbig", HTTP_GET, [] (AsyncWebServerRequest* request)
    {
        btnBig_cnt++;
        AsyncResponseStream* response = request->beginResponseStream("text/html");
        response->printf("ok");
        request->send(response);
    });

    httpServer->on("/btnside", HTTP_GET, [] (AsyncWebServerRequest* request)
    {
        btnSide_cnt++;
        AsyncResponseStream* response = request->beginResponseStream("text/html");
        response->printf("ok");
        request->send(response);
    });

    httpServer->on("/btnpwr", HTTP_GET, [] (AsyncWebServerRequest* request)
    {
        btnPwr_cnt++;
        AsyncResponseStream* response = request->beginResponseStream("text/html");
        response->printf("ok");
        request->send(response);
    });
}

#endif


================================================
FILE: arduino_workspace/AlphaFairy/CamUtils.ino
================================================
#include "AlphaFairy.h"

extern bool airplane_mode;

void cam_shootQuick()
{
    // convenience function for quickly taking a photo without complicated connectivity checks
    if (airplane_mode == false && ptpcam.isOperating()) {
        ptpcam.cmd_Shoot(config_settings.shutter_press_time_ms);
    }
    else if (airplane_mode == false && httpcam.isOperating()) {
        httpcam.cmd_Shoot();
    }
    else
    {
        if (config_settings.pin_shutter != PINCFG_NONE && config_settings.pin_shutter != config_settings.pin_exinput) {
            cam_shootQuickGpio();
        }
        else if (config_settings.infrared_enabled) {
            SonyCamIr_Shoot();
        }
    }
}

void cam_shootQuickGpio()
{
    // convenience function for quickly taking a photo without complicated connectivity checks
    int32_t pin = get_pinCfgGpio(config_settings.pin_shutter);
    if (pin < 0) {
        return;
    }
    #ifdef SHUTTER_GPIO_ACTIVE_HIGH
    pinMode(pin, OUTPUT);
    digitalWrite(pin, HIGH);
    app_sleep(config_settings.shutter_press_time_ms, false);
    digitalWrite(pin, LOW);
    #else
    digitalWrite(pin, LOW);
    pinMode(pin, OUTPUT);
    digitalWrite(pin, LOW);
    app_sleep(config_settings.shutter_press_time_ms, false);
    pinMode(pin, INPUT);
    #endif
}

void cam_shootOpen()
{
    // convenience function for quickly taking a photo without complicated connectivity checks
    if (airplane_mode == false && ptpcam.isOperating())
    {
        ptpcam.cmd_Shutter(true);
        if (gpio_time != 0)
        {
            int32_t pin = get_pinCfgGpio(config_settings.pin_shutter);
            if (pin >= 0)
            {
                #ifdef SHUTTER_GPIO_ACTIVE_HIGH
                digitalWrite(pin, LOW);
                #endif
                pinMode(pin, INPUT);
            }
            gpio_time = 0;
        }
    }
    else if (airplane_mode == false && httpcam.isOperating()) {
        httpcam.cmd_Shoot();
    }
    else
    {
        if (config_settings.pin_shutter != PINCFG_NONE && config_settings.pin_shutter != config_settings.pin_exinput)
        {
            int32_t pin = get_pinCfgGpio(config_settings.pin_shutter);
            if (pin >= 0)
            {
                #ifdef SHUTTER_GPIO_ACTIVE_HIGH
                pinMode(pin, OUTPUT);
                digitalWrite(pin, HIGH);
                #else
                digitalWrite(pin, LOW);
                pinMode(pin, OUTPUT);
                digitalWrite(pin, LOW);
                #endif
            }
            gpio_time = millis();
        }
        else if (config_settings.infrared_enabled) {
            SonyCamIr_Shoot();
        }
    }
}

void cam_shootClose()
{
    // convenience function for quickly taking a photo without complicated connectivity checks
    if (airplane_mode == false && ptpcam.isOperating()) {
        ptpcam.cmd_Shutter(false);
        if (gpio_time != 0)
        {
            int32_t pin = get_pinCfgGpio(config_settings.pin_shutter);
            if (pin >= 0)
            {
                #ifdef SHUTTER_GPIO_ACTIVE_HIGH
                digitalWrite(pin, LOW);
                #endif
                pinMode(pin, INPUT);
            }
            gpio_time = 0;
        }
    }
    else if (airplane_mode == false && httpcam.isOperating()) {
        // do nothing
    }
    else
    {
        if (config_settings.pin_shutter != PINCFG_NONE && config_settings.pin_shutter != config_settings.pin_exinput)
        {
            gpio_time = 0; // stop the timer
            int32_t pin = get_pinCfgGpio(config_settings.pin_shutter);
            if (pin >= 0)
            {
                #ifdef SHUTTER_GPIO_ACTIVE_HIGH
                digitalWrite(pin, LOW);
                #else
                pinMode(pin, INPUT);
                #endif
            }
        }
        else if (config_settings.infrared_enabled) {
            // do nothing
        }
    }
}

void cam_videoStart()
{
    // convenience function for quickly taking a video without complicated connectivity checks
    if (airplane_mode == false && ptpcam.isOperating()) {
        ptpcam.cmd_MovieRecord(true);
    }
    else if (airplane_mode == false && httpcam.isOperating()) {
        httpcam.cmd_MovieRecord(true);
    }
    else
    {
        if (config_settings.infrared_enabled) {
            SonyCamIr_Movie();
        }
    }
}

void cam_videoStop()
{
    // convenience function for quickly taking a video without complicated connectivity checks
    if (airplane_mode == false && ptpcam.isOperating()) {
        ptpcam.cmd_MovieRecord(false);
    }
    else if (airplane_mode == false && httpcam.isOperating()) {
        httpcam.cmd_MovieRecord(false);
    }
    else
    {
        if (config_settings.infrared_enabled) {
            SonyCamIr_Movie();
        }
    }
}


================================================
FILE: arduino_workspace/AlphaFairy/CmdlineHandlers.ino
================================================
#include "AlphaFairy.h"
#include <SerialCmdLine.h>

/*
handles command line commands
this is used only for testing
*/

void factory_reset_func(void* cmd, char* argstr, Stream* stream);
#ifndef DISABLE_CMD_LINE
void focuscalib_func(void* cmd, char* argstr, Stream* stream);
void shoot_func     (void* cmd, char* argstr, Stream* stream);
void echo_func      (void* cmd, char* argstr, Stream* stream);
void memcheck_func  (void* cmd, char* argstr, Stream* stream);
void statscheck_func(void* cmd, char* argstr, Stream* stream);
void reboot_func    (void* cmd, char* argstr, Stream* stream);
void imu_func       (void* cmd, char* argstr, Stream* stream);
void imushow_func   (void* cmd, char* argstr, Stream* stream);
void mic_func       (void* cmd, char* argstr, Stream* stream);
void pwr_func       (void* cmd, char* argstr, Stream* stream);
void btncnt_func    (void* cmd, char* argstr, Stream* stream);
void debug_func     (void* cmd, char* argstr, Stream* stream);
void camdebug_func  (void* cmd, char* argstr, Stream* stream);
void infrared_func  (void* cmd, char* argstr, Stream* stream);
void savewifi_func  (void* cmd, char* argstr, Stream* stream);
void dumpwifi_func  (void* cmd, char* argstr, Stream* stream);
void wifipwr_func   (void* cmd, char* argstr, Stream* stream);
void pmiclog_func   (void* cmd, char* argstr, Stream* stream);
void listlog_func   (void* cmd, char* argstr, Stream* stream);
void readlog_func   (void* cmd, char* argstr, Stream* stream);
void readhex_func   (void* cmd, char* argstr, Stream* stream);
#endif

const cmd_def_t cmds[] = {
  { "factoryreset", factory_reset_func},
  #ifndef DISABLE_CMD_LINE
  { "focuscalib", focuscalib_func },
  { "shoot"    , shoot_func },
  { "echo"     , echo_func },
  { "mem"      , memcheck_func },
  { "imu"      , imu_func },
  { "imushow"  , imushow_func },
  { "mic"      , mic_func },
  { "pwr"      , pwr_func },
  { "btncnt"   , btncnt_func },
  { "stats"    , statscheck_func },
  { "reboot"   , reboot_func },
  { "debug"    , debug_func },
  { "camdebug" , camdebug_func },
  { "ir"       , infrared_func },
  { "savewifi" , savewifi_func },
  { "dumpwifi" , dumpwifi_func },
  { "wifipwr"  , wifipwr_func },
  { "pmiclog"  , pmiclog_func },
  { "listlog"  , listlog_func },
  { "readlog"  , readlog_func },
  { "readhex"  , readhex_func },
  #endif
  { "", NULL }, // end of table
};

SerialCmdLine cmdline(&Serial, (cmd_def_t*)cmds, false, (char*)">>>", (char*)"???", true, 512);

extern bool redraw_flag;

void factory_reset_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  settings_default();
  settings_save();
  stream->println("factory reset performed");
}

#ifndef DISABLE_CMD_LINE

void focuscalib_func(void* cmd, char* argstr, Stream* stream)
{
    fenc_calibrate();
}

void shoot_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  if (fairycam.isOperating())
  {
    stream->println("shoot");
    fairycam.cmd_Shoot(250);
  }
  else
  {
    stream->println("camera not connected");
  }
}

void echo_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  stream->println(argstr);
}

void reboot_func(void* cmd, char* argstr, Stream* stream)
{
  stream->println("rebooting...\r\n\r\n");
  ESP.restart();
}

void memcheck_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  stream->printf("free heap mem: %u\r\n", ESP.getFreeHeap());
}

void statscheck_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  #ifdef PTPIP_KEEP_STATS
  stream->printf("ptpipcam stats: %u  %u  %u\r\n", ptpcam.stats_tx, ptpcam.stats_acks, ptpcam.stats_pkts);
  #else
  stream->printf("ptpipcam stats not available\r\n");
  #endif
}

void imu_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  stream->printf("imu:   %0.1f   %0.1f   %0.1f\r\n", imu.pitch, imu.roll, imu.yaw);
}

void imushow_func(void* cmd, char* argstr, Stream* stream)
{
    gui_startAppPrint();
    M5Lcd.setTextFont(4);
    int spin_cnt = 0;
    while (true)
    {
        app_poll();
        pwr_tick(true);
        stream->printf("imu:   %0.1f   %0.1f   %0.1f\r\n", imu.pitch, imu.roll, imu.yaw);
        M5Lcd.setCursor(SUBMENU_X_OFFSET, SUBMENU_Y_OFFSET);
        M5Lcd.printf("%0.1f , %0.1f    ", imu.roll, imu.pitch); gui_blankRestOfLine(); M5Lcd.println(); gui_setCursorNextLine();
        M5Lcd.printf("%0.1f , %0.1f    ", imu.roll_adj, imu.pitch_adj); gui_blankRestOfLine(); M5Lcd.println(); gui_setCursorNextLine();
        M5Lcd.printf("%d", imu.pitch_accum); gui_blankRestOfLine(); M5Lcd.println(); gui_setCursorNextLine();
        int spin = imu.getSpin();
        if (spin > 0) {
            spin_cnt++;
        }
        else if (spin < 0) {
            spin_cnt--;
        }
        M5Lcd.printf("spin  %d", spin_cnt); gui_blankRestOfLine();
        if (spin != 0) {
            imu.resetSpin();
        }
        if (btnAny_hasPressed()) {
            stream->printf("user exit\r\n");
            delay(100);
            ESP.restart();
        }
    }
}

extern volatile int32_t mictrig_lastMax, mictrig_filteredMax;

void mic_func(void* cmd, char* argstr, Stream* stream)
{
    #if 0
    gui_startAppPrint();
    mictrig_drawIcon();
    while (true)
    {
        pwr_tick(true);
        app_poll();
        mictrig_poll();
        stream->printf("mic:    %d   %d\r\n", mictrig_lastMax, mictrig_filteredMax);
        mictrig_drawLevel();
        if (btnAny_hasPressed()) {
            stream->printf("user exit\r\n");
            delay(100);
            ESP.restart();
        }
    }
    #endif
}

void pwr_func(void* cmd, char* argstr, Stream* stream)
{
  pwr_tick(true);
  stream->printf("pwr:   %0.3f   %0.3f   %0.3f   %0.3f\r\n",
    M5.Axp.GetBatVoltage(),
    M5.Axp.GetBatCurrent(),
    M5.Axp.GetVBusVoltage(),
    M5.Axp.GetVBusCurrent()
    );
}

extern volatile uint32_t btnSide_cnt, btnBig_cnt, btnPwr_cnt;
void btncnt_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    stream->printf("btn cnt:   %u    %u    %u\r\n", btnSide_cnt, btnBig_cnt, btnPwr_cnt);
}


extern DebuggingSerial dbg_ser;
void debug_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    dbg_ser.enabled = atoi(argstr) != 0;
    stream->printf("debugging output = %u\r\n", dbg_ser.enabled);
    dbg_ser.println("test output from debugging serial port");
}

void camdebug_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    int x = atoi(argstr);
    ptpcam.set_debugflags(x);
    httpcam.set_debugflags(x);
    stream->printf("camera debugging output = %u\r\n", x);
    ptpcam.test_debug_msg("test from ptpcam debug serport\r\n");
    httpcam.test_debug_msg("test from httpcam debug serport\r\n");
}

void infrared_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    stream->printf("infrared test fire\r\n");
    SonyCamIr_Shoot();
}

void savewifi_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    char delim[] = ",";
    char *ptr = strtok(argstr, delim);
    int i = 0;
    int profile_num;
    wifiprofile_t profile;
    memset(&profile, 0, sizeof(wifiprofile_t));
    while (ptr != NULL)
    {
        switch (i)
        {
            case 0:
                profile_num = atoi(ptr);
                break;
            case 1:
                strncpy(profile.ssid, ptr, WIFI_STRING_LEN);
                break;
            case 2:
                strncpy(profile.password, ptr, WIFI_STRING_LEN);
                break;
            case 3:
                if (memcmp("sta", ptr, 3) == 0 || memcmp("STA", ptr, 3) == 0) {
                    profile.opmode = WIFIOPMODE_STA;
                }
            case 4:
                strncpy(profile.guid, ptr, 17);
                break;
            default:
                break;
        }
        ptr = strtok(NULL, delim);
        i++;
    }
    if (profile_num > 0 && profile_num <= WIFIPROFILE_LIMIT) {
        if (profile.ssid[0] != 0) {
            wifiprofile_writeProfile(profile_num, &profile);
            stream->printf("WiFi profile %d written\r\n", profile_num);
        }
        else {
            wifiprofile_deleteProfile(profile_num);
            stream->printf("WiFi profile %d deleted\r\n", profile_num);
        }
    }
    else {
        stream->printf("ERROR: WiFi profile %d write disallowed\r\n", profile_num);
    }
    redraw_flag = true;
}

void dumpwifi_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    int i;
    stream->printf("WiFi Profile Dump:\r\n");
    for (i = 0; i <= WIFIPROFILE_LIMIT; i++)
    {
        wifiprofile_t profile;
        memset(&profile, 0, sizeof(wifiprofile_t));
        if (wifiprofile_getProfile(i, &profile)) {
            stream->printf("%d,%s,%s,%s,%s\r\n", i, profile.ssid, profile.password, profile.opmode == WIFIOPMODE_STA ? "sta" : "ap", profile.guid);
        }
    }
    stream->printf("\r\n");
}

void wifipwr_func(void* cmd, char* argstr, Stream* stream)
{
    pwr_tick(true);
    int8_t x;
    int ret;
    if (strlen(argstr) == 0) {
        ret = (int)esp_wifi_get_max_tx_power(&x);
        stream->printf("WiFi get pwr %d , return code %d\r\n", x, ret);
    }
    else {
        x = atoi(argstr);
        ret = (int)esp_wifi_set_max_tx_power(x);
        stream->printf("WiFi set pwr %d , return code %d\r\n", x, ret);
    }
}

void listlog_func(void* cmd, char* argstr, Stream* stream)
{
    File root = SPIFFS.open("/");
    File file = root.openNextFile();
    stream->println("listing log files");
    while(file)
    {
        if (memcmp(file.name(), "pwrlog_", 7) == 0)
        {
            stream->print("file: ");
            stream->println(file.name());
        }
        file = root.openNextFile();
    }
    stream->println("end of file listing");
}

void readlog_func(void* cmd, char* argstr, Stream* stream)
{
    File f = SPIFFS.open(argstr);
    stream->println();
    while (f.available() > 0) {
        stream->write((uint8_t)(f.read()));
    }
    f.close();
    stream->println();
}

void readhex_func(void* cmd, char* argstr, Stream* stream)
{
    int i = 0;
    File f = SPIFFS.open(argstr);
    stream->println();
    while (f.available() > 0) {
        stream->printf("0x%02X, ", (uint8_t)(f.read()));
        i++;
        if ((i % 16) == 0) {
            stream->printf("\r\n");
        }
    }
    f.close();
    stream->println();
}

void pmiclog_func(void* cmd, char* argstr, Stream* stream)
{
    pmic_startCoulombCount();
    stream->println("coulomb count started");
}

#endif


================================================
FILE: arduino_workspace/AlphaFairy/ConfigMenu.ino
================================================
#include "AlphaFairy.h"
#include "FairyMenu.h"

static bool has_saved = false;

bool config_save_exit(void*)
{
    has_saved = true;
    settings_save();
    return true;
}

class PageLcdBrightness : public FairyCfgItem
{
    public:
        PageLcdBrightness(const char* disp_name, int32_t* linked_var, int32_t val_min, int32_t val_max, int32_t step_size, uint16_t fmt_flags) : FairyCfgItem(disp_name, linked_var, val_min, val_max, step_size, fmt_flags)
        {
        };

        virtual void on_readjust(void)
        {
            M5.Axp.ScreenBreath(config_settings.lcd_brightness);
        };

        virtual void on_eachFrame(void)
        {
            pwr_tick(true);
        };
};

class AppConfigMenu : public FairyCfgApp
{
    public:
        AppConfigMenu() : FairyCfgApp("/config.png", "/config_icon.png")
        {
install(new FairyCfgItem("focus pause"            , (int32_t*)&(config_settings.focus_pause_time_ms    ),    0, 1000,    10, TXTFMT_BYTENS   ));
install(new FairyCfgItem("MF knob steps"          , (int32_t*)&(config_settings.fenc_multi             ), -100,  100,     1, TXTFMT_NONE     ));
install(new FairyCfgItem("MF knob large steps"    , (int32_t*)&(config_settings.fenc_large             ),    0, 1000,     1, TXTFMT_NONE     ));
install(new FairyCfgItem("shutter press duration" , (int32_t*)&(config_settings.shutter_press_time_ms  ),    0, 1000,    10, TXTFMT_BYTENS   ));
install(new FairyCfgItem("MF return"              , (int32_t*)&(config_settings.manual_focus_return    ),    0,    1,     1, TXTFMT_BOOL     ));
install(new FairyCfgItem("Tv step delay"          , (int32_t*)&(config_settings.shutter_step_time_ms   ),    0, 5000,    10, TXTFMT_BYTENS   ));
install(new FairyCfgItem("Tally Light en"         , (int32_t*)&(config_settings.tallylite              ),    0,    3,     1, TXTFMT_TALLEYLITE));
install(new FairyCfgItem("power save time (s)"    , (int32_t*)&(config_settings.pwr_save_secs          ),    0, 1000,    10, TXTFMT_BYTENS | TXTFMT_ZEROINF));
install(new PageLcdBrightness("LCD bright"        , (int32_t*)&(config_settings.lcd_brightness         ),    7,   12,     1, TXTFMT_LCDBRITE ));
install(new FairyCfgItem("LCD dim time (s)"       , (int32_t*)&(config_settings.lcd_dim_secs           ),    0, 1000,     1, TXTFMT_BYTENS | TXTFMT_ZEROINF));
install(new FairyCfgItem("WiFi power"             , (int32_t*)&(config_settings.wifi_pwr               ),    0,   14,     1, TXTFMT_BYTENS   ));
install(new FairyCfgItem("SSDP timeout (s)"       , (int32_t*)&(config_settings.ssdp_timeout           ),    0, 1000,     1, TXTFMT_BYTENS   ));
install(new FairyCfgItem("IR en"                  , (int32_t*)&(config_settings.infrared_enabled       ),    0,    1,     1, TXTFMT_BOOL     ));
install(new FairyCfgItem("camera protocol"        , (int32_t*)&(config_settings.protocol               ),    0,    2,     1, TXTFMT_PROTOCOL ));
install(new FairyCfgItem("pin - shutter rel."     , (int32_t*)&(config_settings.pin_shutter            ), 0, PINCFG_END - 1, 1, TXTFMT_PINCFG));
install(new FairyCfgItem("pin - ext input"        , (int32_t*)&(config_settings.pin_exinput            ), 0, PINCFG_END - 1, 1, TXTFMT_PINCFG));
install(new FairyCfgItem("Save + Exit", config_save_exit, "/back_icon.png"));
        };

        virtual bool on_execute(void)
        {
            has_saved = false;
            if (_backup == NULL) {
                _backup = (configsettings_t*)malloc(sizeof(configsettings_t));
            }
            memcpy(_backup, &config_settings, sizeof(configsettings_t)); // allows for changes to be undone

            bool exit = FairyCfgApp::on_execute();

            if (has_saved == false)
            {
                // user quit via pwr button press, so do not save the settings
                memcpy(&config_settings, _backup, sizeof(configsettings_t));
                M5.Axp.ScreenBreath(config_settings.lcd_brightness);
            }
            else
            {
                if (_backup->wifi_pwr != config_settings.wifi_pwr) {
                    NetMgr_setWifiPower((wifi_power_t)wifipwr_table[config_settings.wifi_pwr]);
                }
                while (_backup->pin_shutter != config_settings.pin_shutter || _backup->pin_exinput != config_settings.pin_exinput) {
                    ESP.restart();
                }
            }

            if (_backup != NULL) {
                free(_backup);
                _backup = NULL;
            }

            return exit;
        }

    protected:
        configsettings_t* _backup = NULL;
};

extern FairySubmenu menu_utils;
void setup_configmenu(void)
{
    static AppConfigMenu app;
    menu_utils.install(&app);
}


================================================
FILE: arduino_workspace/AlphaFairy/CpuFreq.ino
================================================
#include "AlphaFairy.h"

#include "driver/uart.h"

static uint32_t cpufreq_xtal = 0;

static const uint32_t cpufreq_cpuFreqMax = 240;
static uint32_t cpufreq_cpuFreqMin = 80; // change later based on crystal

static uint32_t cpufreq_timestamp = 0;
static uint32_t cpufreq_cpuFreqLast = 80;
static uint32_t cpufreq_tgtFreq = 240;

extern "C" {
extern uint32_t _get_effective_baudrate(uint32_t baudrate);
};

void cpufreq_init(void)
{
    cpufreq_xtal = getXtalFrequencyMhz();
    dbg_ser.printf("XTAL freq: %u MHz\r\n", cpufreq_xtal);

    #if 0
    // these frequencies are technically possible but breaks WiFi
    switch (cpufreq_xtal)
    {
        case 40:
            cpufreq_cpuFreqMin = 10; // this is the one for M5StickC-Plus (ESP32-PICO)
            break;
        case 26:
            cpufreq_cpuFreqMin = 13;
            break;
        case 24:
            cpufreq_cpuFreqMin = 12;
            break;
    }
    #else
        #ifdef ENABLE_CPU_FREQ_SCALING
        cpufreq_cpuFreqMin = 80; // this seems to work
        #else
        cpufreq_cpuFreqMin = 240;
        #endif
    #endif
    dbg_ser.printf("CPU freq range: %u - %u MHz\r\n", cpufreq_cpuFreqMax, cpufreq_cpuFreqMin);

    cpufreq_cpuFreqLast = getCpuFrequencyMhz();
    cpufreq_tgtFreq = cpufreq_cpuFreqLast;

    dbg_ser.printf("CPU freq on boot: %u MHz\r\n", cpufreq_cpuFreqLast);

    if (cpufreq_cpuFreqLast != cpufreq_cpuFreqMax) {
        cpufreq_boost();
    }
}

static volatile bool cpufreq_mtx = false;

void cpufreq_setTarget(uint32_t mhz)
{
    #ifdef ENABLE_CPU_FREQ_SCALING
    cpufreq_tgtFreq = mhz;
    if (mhz == cpufreq_cpuFreqMax) {
        cpufreq_change(mhz);
    }
    #endif
}

void cpufreq_change(uint32_t mhz)
{
    #ifdef ENABLE_CPU_FREQ_SCALING
    if (cpufreq_mtx) {
        return;
    }
    cpufreq_mtx = true;
    if (cpufreq_cpuFreqLast != mhz)
    {
        setCpuFrequencyMhz(mhz);
        cpufreq_cpuFreqLast = mhz;

        if (mhz == cpufreq_tgtFreq)
        {
            uart_config_t uart_config;
            uart_config.baud_rate           = _get_effective_baudrate(SERIAL_PORT_BAUDRATE);
            uart_config.data_bits           = UART_DATA_8_BITS;
            uart_config.parity              = UART_PARITY_DISABLE;
            uart_config.stop_bits           = UART_STOP_BITS_2;
            uart_config.flow_ctrl           = UART_HW_FLOWCTRL_DISABLE;
            uart_config.rx_flow_ctrl_thresh = 122;
            uart_config.source_clk          = UART_SCLK_APB;
            uart_param_config(0, &uart_config);

            dbg_ser.printf("new CPU freq %u MHz\r\n", mhz);
        }
    }
    cpufreq_mtx = false;
    #endif
}

void cpufreq_boost(void)
{
    #ifdef ENABLE_CPU_FREQ_SCALING
    uint32_t now = millis();
    cpufreq_timestamp = now;
    cpufreq_setTarget(cpufreq_cpuFreqMax);
    #endif
}

void cpufreq_task(void)
{
    #ifdef ENABLE_CPU_FREQ_SCALING
    uint32_t now = millis();
    if (cpufreq_tgtFreq != cpufreq_cpuFreqLast)
    {
        if (cpufreq_tgtFreq < cpufreq_cpuFreqLast)
        {
            // there is a errata that says to not change directly from 240 MHz to 80 or 40 MHz immediately, instead, step down
            if (cpufreq_cpuFreqLast == 240) {
                cpufreq_change(160);
            }
            else if (cpufreq_cpuFreqLast == 160) {
                cpufreq_change(80);
            }
        }
        else if (cpufreq_tgtFreq > cpufreq_cpuFreqLast)
        {
            /*
            if (cpufreq_cpuFreqLast <= 40) {
                cpufreq_change(80);
            }
            else if (cpufreq_cpuFreqLast == 80) {
                cpufreq_change(160);
            }
            else if (cpufreq_cpuFreqLast == 160) {
                cpufreq_change(240);
            }
            */
            cpufreq_change(cpufreq_tgtFreq);
        }
    }
    if ((now - cpufreq_timestamp) < 1000) {
        return;
    }
    cpufreq_setTarget(cpufreq_cpuFreqMin);
    #endif
}


================================================
FILE: arduino_workspace/AlphaFairy/DrawingUtils.ino
================================================
#include "AlphaFairy.h"
#include <M5DisplayExt.h>

void gui_drawVerticalDots(int x_offset, int y_margin, int y_offset, int dot_radius, int dot_cnt, int dot_idx, bool reverse, uint16_t back_color, uint16_t fore_color)
{
    // draws a line of vertical dots, with one dot that's highlighted
    // the calling function can move the highlighted dot to indicate progress/count-down/busy-status
    // defaults to being in the middle of the screen but the offsets can be defined
    int lcd_width  = M5Lcd.width();
    int lcd_height = M5Lcd.height();
    int x = (lcd_width / 2) + x_offset;
    int y_span = lcd_height - (2 * y_margin);
    int i;

    cpufreq_boost(); // drawing these dots almost always mean we are in a long execution

    if (reverse) {
        dot_idx %= dot_cnt;
        dot_idx = dot_cnt - dot_idx - 1;
    }

    for (i = 0; i < dot_cnt; i++)
    {
        int y = y_margin + y_offset + ((y_span * i) / (dot_cnt - 1));
        uint32_t color = (i == (dot_idx % dot_cnt)) ? fore_color : back_color;
        M5Lcd.fillCircle(x, y, dot_radius, color);
    }
}

void gui_startAppPrint()
{
    // setup for printing text for a specific application
    // all black screen, rotated landscape, white text
    M5Lcd.fillScreen(TFT_BLACK);
    M5Lcd.setRotation(1);
    M5Lcd.highlight(true);
    M5Lcd.setTextWrap(true);
    M5Lcd.setHighlightColor(TFT_BLACK); // there's no frame buffer, so use the highlight function to prevent messy overlapping text
    M5Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
}

void gui_startMenuPrint()
{
    // setup for printing text on a menu screen
    // white text, large font
    M5Lcd.setTextFont(4);
    M5Lcd.highlight(true);
    M5Lcd.setTextWrap(false);
    M5Lcd.setTextColor(TFT_BLACK, TFT_WHITE);
    M5Lcd.setHighlightColor(TFT_WHITE);
}

void gui_drawConnecting(bool first)
{
    // this function will blink between a sequence of images that indicates that we are waiting for the camera to connect
    // this code can be tweaked for more animation frames if needed
    // right now it just goes between 0 and 1
    static char conn_filename[] = "/connecting0.png";
    static int last_idx = -1;
    static uint32_t t;
    if (first) {
        last_idx = -1;
        t = millis();
    }
    int cur_idx;
    uint32_t now = millis();
    uint32_t dt = now - t;
    dt %= 1400;
    cur_idx = dt / 700;
    if (cur_idx != last_idx) {
        last_idx = cur_idx;
        conn_filename[11] = '0' + cur_idx;
        cpufreq_boost();
        M5Lcd.setRotation(0);
        M5Lcd.drawPngFile(SPIFFS, conn_filename, 0, 0);
    }
    redraw_flag = true;
}

void gui_setCursorNextLine()
{
    // the new-line sequence only shifts Y and puts X back to zero
    // so this wrapper call restores X but uses the new Y
    M5Lcd.setCursor(SUBMENU_X_OFFSET, M5Lcd.getCursorY());
}

void gui_blankRestOfLine()
{
    uint32_t margin = (M5Lcd.getRotation() == 0) ? 8 : 65;
    uint32_t lim = M5Lcd.width() - margin;
    while (M5Lcd.getCursorX() < lim) {
        M5Lcd.print(" ");
    }
}

void gui_drawTopThickLine(uint16_t thickness, uint16_t colour)
{
    M5Lcd.fillRect(0, 0, M5Lcd.width(), thickness, colour);
}

void gui_drawSpinStatus(uint16_t thickness, uint16_t bgcolour)
{
    int ang = imu.pitch_accum;
    int x;
    if (ang > 0) {
        x = ang;
        x = x > M5Lcd.width() ? M5Lcd.width() : x;
        M5Lcd.fillRect(0, 0, x, thickness, TFT_LIGHTGREY);
        M5Lcd.fillRect(x + 1, 0, M5Lcd.width() - x, thickness, bgcolour);
    }
    else if (ang < 0) {
        x = -ang;
        x = x > M5Lcd.width() ? M5Lcd.width() : x;
        M5Lcd.fillRect(0, 0, M5Lcd.width() - x, thickness, bgcolour);
        M5Lcd.fillRect(M5Lcd.width() - x + 1, 0, x, thickness, TFT_LIGHTGREY);
    }
    else {
        gui_drawTopThickLine(thickness, bgcolour);
    }
}

void gui_showVal(int32_t x, uint32_t txtfmt, Print* printer)
{
    char str[64]; int i = 0;
    uint32_t txtfmt_masked = txtfmt & TXTFMT_BASEMASK;
    if (txtfmt_masked == TXTFMT_BOOL) {
        if (x == 0) {
            i += sprintf(&(str[i]), "NO");
        }
        else {
            i += sprintf(&(str[i]), "YES");
        }
    }
    else if (txtfmt_masked == TXTFMT_BULB) {
        if (x == 0) {
            // when bulb = 0, the shutter speed setting on the camera is used
            i += sprintf(&(str[i]), "(Tv)");
        }
        else {
            gui_formatSecondsTime(x, str, false);
        }
    }
    else if (txtfmt_masked == TXTFMT_TIMELONG) {
        gui_formatSecondsTime(x, str, true);
    }
    else if (txtfmt_masked == TXTFMT_TIME) {
        gui_formatSecondsTime(x, str, false);
    }
    else if (txtfmt_masked == TXTFMT_TIMEMS) {
        // if time is provided in milliseconds
        // print the time as usual (after calculating the whole seconds)
        x = x < 0 ? 0 : x;
        uint32_t tsec = x / 1000;
        uint32_t tsubsec = (x / 100) % 10; // get one decimal place
        gui_formatSecondsTime(tsec, str, false);
        // add one decimal place
        sprintf(&(str[strlen(str)]), ".%d", tsubsec);
    }
    else if (txtfmt_masked == TXTFMT_SHUTTER) {
        gui_formatShutterSpeed(x, str);
    }
    else if (txtfmt_masked == TXTFMT_ISO) {
        gui_formatISO(x, str);
    }
    else if (txtfmt_masked == TXTFMT_PROTOCOL) {
        if (x == ALLOWEDPROTOCOL_ALL) {
            i += sprintf(&(str[i]), "ALL");
        }
        else if (x == ALLOWEDPROTOCOL_PTP) {
            i += sprintf(&(str[i]), "PTP (newer)");
        }
        else if (x == ALLOWEDPROTOCOL_HTTP) {
            i += sprintf(&(str[i]), "HTTP (older)");
        }
    }
    else if (txtfmt_masked == TXTFMT_TRIGSRC) {
        if ((txtfmt & TXTFMT_SMALL) == 0)
        {
            if (x == TRIGSRC_ALL) {
                i += sprintf(&(str[i]), "all");
            }
            else if (x == TRIGSRC_MIC) {
                i += sprintf(&(str[i]), "mic");
            }
            else if (x == TRIGSRC_EXINPUT) {
                i += sprintf(&(str[i]), "ext-input");
            }
            else if (x == TRIGSRC_IMU) {
                i += sprintf(&(str[i]), "IMU");
            }
            #ifdef ENABLE_BUILD_LEPTON
            else if (x == TRIGSRC_THERMAL) {
                i += sprintf(&(str[i]), "FLIR");
            }
            #endif
        }
        else
        {
            if (x == TRIGSRC_ALL) {
                i += sprintf(&(str[i]), "ALL");
            }
            else if (x == TRIGSRC_MIC) {
                i += sprintf(&(str[i]), "MIC");
            }
            else if (x == TRIGSRC_EXINPUT) {
                i += sprintf(&(str[i]), "EXT");
            }
            else if (x == TRIGSRC_IMU) {
                i += sprintf(&(str[i]), "IMU");
            }
            #ifdef ENABLE_BUILD_LEPTON
            else if (x == TRIGSRC_THERMAL) {
                i += sprintf(&(str[i]), "FLIR");
            }
            #endif
        }
    }
    else if (txtfmt_masked == TXTFMT_TRIGACT) {
        if ((txtfmt & TXTFMT_SMALL) == 0)
        {
            if (x == TRIGACT_PHOTO) {
                i += sprintf(&(str[i]), "photo");
            }
            else if (x == TRIGACT_VIDEO) {
                i += sprintf(&(str[i]), "video");
            }
            else if (x == TRIGACT_INTERVAL) {
                i += sprintf(&(str[i]), "interval");
            }
        }
        else
        {
            if (x == TRIGACT_PHOTO) {
                i += sprintf(&(str[i]), "PIC");
            }
            else if (x == TRIGACT_VIDEO) {
                i += sprintf(&(str[i]), "VID");
            }
            else if (x == TRIGACT_INTERVAL) {
                i += sprintf(&(str[i]), "INTV");
            }
        }
    }
    else if (txtfmt_masked == TXTFMT_PINCFG) {
        if (x == PINCFG_NONE) {
            i += sprintf(&(str[i]), "none");
        }
        else {
            int pin = get_pinCfgGpio(x);
            if (pin >= 0) {
                i += sprintf(&(str[i]), "G%u", pin);
            }
            else {
                i += sprintf(&(str[i]), "G??");
            }
        }
    }
    else if (txtfmt_masked == TXTFMT_TALLEYLITE) {
        if (x == TALLYLITE_OFF) {
            i += sprintf(&(str[i]), "OFF");
        }
        else if (x == TALLYLITE_SCREEN) {
            i += sprintf(&(str[i]), "SCREEN");
        }
        else if (x == TALLYLITE_LED) {
            i += sprintf(&(str[i]), "LED");
        }
        else if (x == TALLYLITE_BOTH) {
            i += sprintf(&(str[i]), "BOTH");
        }
    }
    else if ((txtfmt & TXTFMT_DIVHUNDRED) != 0) {
        float xx = x;
        xx /= 100.0;
        i += sprintf(&(str[i]), "%0.2f", xx);
    }
    else {
        i += sprintf(&(str[i]), "%d", x);
    }

    if (((txtfmt & TXTFMT_ZEROOFF) != 0 && x == 0) || ((txtfmt & TXTFMT_NEGOFF) != 0 && x < 0)) {
        i = sprintf(&(str[0]), "OFF");
    }
    else if (((txtfmt & TXTFMT_ZEROINF) != 0 && x == 0) || ((txtfmt & TXTFMT_NEGINF) != 0 && x < 0)) {
        i = sprintf(&(str[0]), "inf.");
    }

    if (i > 0 && ((txtfmt & TXTFMT_ALLCAPS) != 0) || ((txtfmt & TXTFMT_ALLLOWER) != 0))
    {
        uint8_t j;
        for (j = 0; j < i; j++)
        {
            char c = str[j];
            if (c == 0) {
                break;
            }
            if ((txtfmt & TXTFMT_ALLCAPS) != 0)
            {
                if (c >= 'a' && c <= 'z') {
                    str[j] -= 'a';
                    str[j] += 'A';
                }
            }
            else if ((txtfmt & TXTFMT_ALLLOWER) != 0)
            {
                if (c >= 'A' && c <= 'A') {
                    str[j] += 'a' - 'A';
                }
            }
        }
    }

    if (txtfmt_masked == TXTFMT_LCDBRITE) {
        M5.Axp.ScreenBreath(x);
    }

    if (printer != NULL) {
        printer->print(str);
    }
}

int8_t gui_drawFocusPullState(int y)
{
    int8_t dir = imu_getFocusPull();
    if (y < -30) {
        return dir;
    }
    uint16_t pink = 0xFF3C;
    fpull_drawOneArrowLeft ( 5 +  0, y, dir <= -3 ? TFT_RED : pink);
    fpull_drawOneArrowLeft ( 5 + 20, y, dir <= -2 ? TFT_RED : pink);
    fpull_drawOneArrowLeft ( 5 + 40, y, dir <= -1 ? TFT_RED : pink);
    fpull_drawOneArrowRight(71 +  0, y, dir >=  1 ? TFT_RED : pink);
    fpull_drawOneArrowRight(71 + 20, y, dir >=  2 ? TFT_RED : pink);
    fpull_drawOneArrowRight(71 + 40, y, dir >=  3 ? TFT_RED : pink);
    return dir;
}

void gui_drawLevelBar(int32_t lvl1, int32_t lvl2, int32_t thresh1, int32_t thresh2)
{
    static TFT_eSprite* level_canvas = NULL;
    if (level_canvas == NULL) {
        level_canvas = new TFT_eSprite(&M5Lcd);
        level_canvas->createSprite(M5Lcd.width() - GENERAL_ICON_WIDTH, MICTRIG_LEVEL_MARGIN);
    }

    #define MICTRIG_LEVEL_BAR_HEIGHT   8
    #define MICTRIG_LEVEL_TRIG_HEIGHT 12

    int16_t ysplit = MICTRIG_LEVEL_TRIG_HEIGHT / 2;

    level_canvas->fillSprite(TFT_BLACK);
    if (lvl1 >= 0 && lvl2 < 0) {
        level_canvas->fillRect(0      , 0, lvl1, MICTRIG_LEVEL_BAR_HEIGHT    , TFT_RED  );
    }
    else if (lvl1 < 0 && lvl2 >= 0) {
        level_canvas->fillRect(0      , 0, lvl2, MICTRIG_LEVEL_BAR_HEIGHT    , TFT_RED  );
    }
    else if (lvl1 >= 0 && lvl2 >= 0) {
        level_canvas->fillRect(0      , 0     , lvl1, ysplit, TFT_RED  );
        level_canvas->fillRect(0      , ysplit, lvl2, ysplit, TFT_RED  );
    }
    if (thresh1 >= 0 && thresh2 < 0) {
        level_canvas->fillRect(thresh1, 0, 3   , MICTRIG_LEVEL_TRIG_HEIGHT, lvl2 > lvl1 ? TFT_DARKGREEN : TFT_GREEN);
    }
    else if (thresh2 >= 0 && thresh1 < 0) {
        level_canvas->fillRect(thresh2, 0, 3   , MICTRIG_LEVEL_TRIG_HEIGHT, lvl2 > lvl1 ? TFT_GREEN : TFT_DARKGREEN);
    }
    else if (thresh1 >= 0 && thresh2 >= 0) {
        level_canvas->fillRect(thresh1, 0     , 3   , ysplit, TFT_GREEN);
        level_canvas->fillRect(thresh2, ysplit, 3   , ysplit, TFT_GREEN);
    }
    level_canvas->pushSprite(0, 0);
}

void draw_borderRect(int16_t thickness, uint16_t colour)
{
    int16_t i;
    for (i = 0; i < thickness; i++)
    {
        M5Lcd.drawRect(0 + i, 0 + i, M5Lcd.width() - 1 - (i * 2), M5Lcd.height() - 1 - (i * 2), colour);
    }
}

void interval_drawTimerStart()
{
    //M5Lcd.drawPngFile(SPIFFS, "/timer_blank.png", M5Lcd.width() - GENERAL_ICON_WIDTH, M5Lcd.height() - GENERAL_ICON_WIDTH);
    M5Lcd.fillRect(M5Lcd.width() - GENERAL_ICON_WIDTH, M5Lcd.height() - GENERAL_ICON_WIDTH, GENERAL_ICON_WIDTH, GENERAL_ICON_WIDTH, TFT_BLACK);
}

void interval_drawTimerLine(int16_t cx, int16_t cy, int8_t i, uint16_t colour)
{
    float hand = 12.0;

    float ang = (-M_PI * 2.0f * (float)i) / CLOCK_ANG_DIV;
    float nx0 = hand * sin(ang);
    float ny0 = hand * cos(ang);
    float nx1 = 1.8 * sin(ang + (M_PI / 2.0f));
    float ny1 = 1.8 * cos(ang + (M_PI / 2.0f));
    float nx2 = 1.8 * sin(ang - (M_PI / 2.0f));
    float ny2 = 1.8 * cos(ang - (M_PI / 2.0f));

    M5Lcd.fillTriangle(
        cx + lround(nx0), cy + lround(ny0),
        cx + lround(nx1), cy + lround(ny1),
        cx + lround(nx2), cy + lround(ny2),
        colour);
}

void interval_drawTimerCircle(int16_t cx, int16_t cy)
{
    // draws a thick circle
    M5Lcd.drawCircle(cx, cy, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx, cy, 19, TFT_WHITE);
    M5Lcd.drawCircle(cx, cy + 1, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx, cy - 1, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx + 1, cy, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx - 1, cy, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx + 1, cy + 1, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx - 1, cy + 1, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx + 1, cy - 1, 18, TFT_WHITE);
    M5Lcd.drawCircle(cx - 1, cy - 1, 18, TFT_WHITE);
}

void fpull_drawOneArrowLeft(int16_t x, int16_t y, uint16_t colour)
{
    M5Lcd.fillTriangle(x    , y + 11, x + 11    , y + 11, x + 8     , y     , colour);
    M5Lcd.fillTriangle(x    , y + 11, x + 11    , y + 11, x + 8 + 11, y     , colour);
    M5Lcd.fillTriangle(x + 8, y     , x + 8 + 11, y     , x         , y + 11, colour);
    M5Lcd.fillTriangle(x + 8, y     , x + 8 + 11, y     , x + 11    , y + 11, colour);

    M5Lcd.fillTriangle(x    , y + 12, x + 11    , y + 11, x + 8     , y + 24, colour);
    M5Lcd.fillTriangle(x    , y + 12, x + 11    , y + 11, x + 8 + 11, y + 24, colour);
    M5Lcd.fillTriangle(x + 8, y + 24, x + 8 + 11, y + 24, x         , y + 12, colour);
    M5Lcd.fillTriangle(x + 8, y + 24, x + 8 + 11, y + 24, x + 11    , y + 12, colour);
}

void fpull_drawOneArrowRight(int16_t x, int16_t y, uint16_t colour)
{
    M5Lcd.fillTriangle(x    , y     , x + 11    , y     , x + 8     , y + 11, colour);
    M5Lcd.fillTriangle(x    , y     , x + 11    , y     , x + 11 + 8, y + 11, colour);
    M5Lcd.fillTriangle(x + 8, y + 11, x + 11 + 8, y + 11, x         , y     , colour);
    M5Lcd.fillTriangle(x + 8, y + 11, x + 11 + 8, y + 11, x + 11    , y     , colour);

    M5Lcd.fillTriangle(x    , y + 24, x + 11    , y + 24, x + 8     , y + 12, colour);
    M5Lcd.fillTriangle(x    , y + 24, x + 11    , y + 24, x + 11 + 8, y + 12, colour);
    M5Lcd.fillTriangle(x + 8, y + 12, x + 11 + 8, y + 12, x         , y + 24, colour);
    M5Lcd.fillTriangle(x + 8, y + 12, x + 11 + 8, y + 12, x + 11    , y + 24, colour);
}


================================================
FILE: arduino_workspace/AlphaFairy/DualShutter.ino
================================================
#include "AlphaFairy.h"
#include "FairyMenu.h"

speed_t dual_shutter_next      = { 0, 0, "" };
speed_t dual_shutter_iso       = { 0, 0, "" };
speed_t dual_shutter_last_tv   = { 0, 0, "" };
speed_t dual_shutter_last_iso  = { 0, 0, "" };

void dual_shutter_shoot(bool already_focused, bool read_button, speed_t* restore_shutter, speed_t* restore_iso)
{
    bool starting_mf = false;
    bool starting_mf_ignore = false;
    bool need_restore_af = false;
    bool need_restore_ss = false;
    bool need_restore_iso = false;

    if (httpcam.isOperating() && httpcam.is_manuallyfocused() == SHCAM_FOCUSMODE_NONE) {
        starting_mf_ignore = true;
    }


    if (already_focused == false) {
        // only care about MF if we are not already focused
        starting_mf = fairycam.is_manuallyfocused();
    }

    if (already_focused == false && starting_mf == false && starting_mf_ignore == false) {
        // the camera won't actually take a photo if it's not focused (when shutter tries to open, it'll lag to focus)
        // so we turn on AF
        fairycam.cmd_AutoFocus(true);
        need_restore_af = true;
    }

    fairycam.cmd_Shutter(true);

    uint32_t t = millis(), now = t;
    uint32_t shutter_ms = 500;

    if (ptpcam.isOperating() && restore_shutter != NULL && restore_shutter->flags == SPEEDTYPE_PTP) {
        shutter_ms = shutter_to_millis(restore_shutter->u32 == 0 ? ptpcam.get_property(SONYALPHA_PROPCODE_ShutterSpeed) : restore_shutter->u32);
    }
    else if (httpcam.isOperating() && restore_shutter != NULL && restore_shutter->flags == SPEEDTYPE_HTTP) {
        shutter_ms = shutter_to_millis(parse_shutter_speed_str(restore_shutter->str));
    }

    // first wait is for minimum press time
    while (((now = millis()) - t) < shutter_ms && (now - t) < config_settings.shutter_press_time_ms) {
        ptpcam.poll();
        httpcam.poll();
    }

    // release button and wait for the rest of the time
    fairycam.cmd_Shutter(false);

    while (((now = millis()) - t) < shutter_ms && fairycam.isOperating()) {
        ptpcam.poll();
        httpcam.poll();
        if (read_button) {
            if (btnBig_isPressed() == false) {
                break;
            }
        }
    }

    // opportunity for early pause
    if (read_button && btnBig_isPressed() == false) {
        goto last_step;
    }

    fairycam.wait_while_saving(0, 500, DEFAULT_SAVE_TIMEOUT);

    // set the shutter speed for second shot
    need_restore_ss = true;
    if (ptpcam.isOperating() && dual_shutter_next.flags == SPEEDTYPE_PTP) {
        ptpcam.cmd_ShutterSpeedSet32(dual_shutter_next.u32);
    }
    else if (httpcam.isOperating() && dual_shutter_next.flags == SPEEDTYPE_HTTP) {
        httpcam.cmd_ShutterSpeedSetStr(dual_shutter_next.str);
    }

    fairycam.wait_while_busy(config_settings.shutter_step_time_ms, DEFAULT_BUSY_TIMEOUT);

    // change ISO if required
    if (ptpcam.isOperating() && restore_iso != NULL && dual_shutter_iso.flags == SPEEDTYPE_PTP && restore_iso->flags == SPEEDTYPE_PTP && dual_shutter_iso.u32 != restore_iso->u32) {
        need_restore_iso = true;
        ptpcam.cmd_IsoSet(dual_shutter_iso.u32);
    }
    else if (httpcam.isOperating() && restore_iso != NULL && dual_shutter_iso.flags == SPEEDTYPE_HTTP && restore_iso->flags == SPEEDTYPE_HTTP && strcmp(dual_shutter_iso.str, restore_iso->str) != 0) {
        need_restore_iso = true;
        httpcam.cmd_IsoSetStr(dual_shutter_iso.str);
    }
    if (need_restore_iso) {
        fairycam.wait_while_busy(config_settings.shutter_step_time_ms, DEFAULT_BUSY_TIMEOUT);
    }

    // start second shot
    fairycam.cmd_Shutter(true);
    t = millis(); now = t;

    if (ptpcam.isOperating() && dual_shutter_next.flags == SPEEDTYPE_PTP) {
        shutter_ms = shutter_to_millis(dual_shutter_next.u32 == 0 ? ptpcam.get_property(SONYALPHA_PROPCODE_ShutterSpeed) : dual_shutter_next.u32);
    }
    else if (httpcam.isOperating() && dual_shutter_next.flags == SPEEDTYPE_HTTP) {
        shutter_ms = shutter_to_millis(parse_shutter_speed_str(dual_shutter_next.str));
    }

    while (((now = millis()) - t) < shutter_ms && (now - t) < config_settings.shutter_press_time_ms) {
        ptpcam.poll();
        httpcam.poll();
    }
    fairycam.cmd_Shutter(false);
    while (((now = millis()) - t) < shutter_ms && ptpcam.isOperating()) {
        ptpcam.poll();
        httpcam.poll();
        if (read_button) {
            if (btnBig_isPressed() == false) {
                break;
            }
        }
    }

    last_step:
    // attempt to restore camera to original state if needed
    // this sends the commands over and over again until the change is effective
    t = millis(); now = t;
    uint32_t timeout = 800;
    if (need_restore_ss && restore_shutter != NULL)
    {
        uint32_t cur_ss;
        uint32_t compare_ss;
        if (ptpcam.isOperating() && restore_shutter->flags == SPEEDTYPE_PTP) {
            compare_ss = restore_shutter->u32;
        }
        else if (httpcam.isOperating() && restore_shutter->flags == SPEEDTYPE_HTTP) {
            compare_ss = parse_shutter_speed_str(restore_shutter->str);
        }
        do
        {
            if (ptpcam.isOperating() && restore_shutter->flags == SPEEDTYPE_PTP && restore_shutter->u32 != 0) {
                ptpcam.cmd_ShutterSpeedSet32(restore_shutter->u32);
                ptpcam.wait_while_busy(100, DEFAULT_BUSY_TIMEOUT);
                cur_ss = ptpcam.get_property(SONYALPHA_PROPCODE_ShutterSpeed);
                if (cur_ss == compare_ss) {
                    break;
                }
            }
            else if (httpcam.isOperating() && restore_shutter->flags == SPEEDTYPE_HTTP && restore_shutter->str[0] != 0) {
                httpcam.cmd_ShutterSpeedSetStr(restore_shutter->str);
                httpcam.wait_while_busy(100, DEFAULT_BUSY_TIMEOUT);
                cur_ss = httpcam.get_shutterspd_32();
                if (cur_ss == compare_ss) {
                    break;
                }
            }
        }
        while (((now = millis()) - t) < timeout || btnBig_isPressed());
    }

    t = millis(); now = t;
    if (need_restore_iso && restore_iso != NULL) {
        uint32_t cur_iso;
        do
        {
            if (ptpcam.isOperating() && restore_iso->flags == SPEEDTYPE_PTP && restore_iso->u32 != 0) {
                ptpcam.cmd_IsoSet(restore_iso->u32);
                ptpcam.wait_while_busy(100, DEFAULT_BUSY_TIMEOUT);
                cur_iso = ptpcam.get_property(SONYALPHA_PROPCODE_ISO);
                if (cur_iso == restore_iso->u32) {
                    break;
                }
            }
            else if (httpcam.isOperating() && restore_iso->flags == SPEEDTYPE_HTTP && restore_iso->str[0] != 0) {
                httpcam.cmd_IsoSetStr(restore_iso->str);
                httpcam.wait_while_busy(100, DEFAULT_BUSY_TIMEOUT);
                char* cur_iso_str = httpcam.get_iso_str();
                if (strcmp(cur_iso_str, restore_iso->str) == 0) {
                    break;
                }
            }
        }
        while (((now = millis()) - t) < timeout || btnBig_isPressed());
    }

    if (already_focused) {
        // wait for user to let go of button
        t = millis(); now = t;
        do
        {
            app_poll();
            if (ptpcam.isOperating())
            {
                if (ptpcam.get_property(SONYALPHA_PROPCODE_FocusFound) == SONYALPHA_FOCUSSTATUS_NONE) {
                    break;
                }
            }
            else if (httpcam.isOperating())
            {
                if (httpcam.is_focused == false) {
                    break;
                }
            }
        }
        while (((now = millis()) - t) < timeout);
    }
    if (need_restore_af && starting_mf_ignore == false) {
        fairycam.wait_while_busy(config_settings.shutter_step_time_ms, DEFAULT_BUSY_TIMEOUT);
        fairycam.cmd_AutoFocus(false);
    }
    app_waitAllRelease();
    return;
}

void dualshutter_drawText()
{
    gui_startMenuPrint();
    M5Lcd.fillRect(0, 52, M5Lcd.width(), 102 - 52, TFT_WHITE);
    if (dual_shutter_next.flags == SPEEDTYPE_NONE)
    {
        M5Lcd.setTextFont(4);
        M5Lcd.setCursor(0, 70);
        M5Lcd.printf("  ");
        M5Lcd.setCursor(6, 70);
        M5Lcd.printf(" NOT  SET");
        gui_blankRestOfLine();
    }
    else
    {
        M5Lcd.setTextFont(2);
        M5Lcd.setCursor(15, 65);
        M5Lcd.printf("Tv ");
        if (dual_shutter_next.flags == SPEEDTYPE_PTP) {
            gui_showVal(dual_shutter_next.u32, TXTFMT_SHUTTER, (Print*)&M5Lcd);
        }
        else {
            M5Lcd.print(dual_shutter_next.str);
        }
        gui_blankRestOfLine();
        M5Lcd.setCursor(15, 65 + 18);
        M5Lcd.printf("ISO ");
        if (dual_shutter_iso.flags == SPEEDTYPE_PTP) {
            gui_showVal(dual_shutter_iso.u32, TXTFMT_ISO, (Print*)&M5Lcd);
        }
        else {
            M5Lcd.print(dual_shutter_iso.str);
        }
        gui_blankRestOfLine();
    }
}

void dualshutter_logSettings()
{
    if (ptpcam.isOperating() && ptpcam.has_property(SONYALPHA_PROPCODE_ShutterSpeed) && ptpcam.has_property(SONYALPHA_PROPCODE_ISO) && ptpcam.has_property(SONYALPHA_PROPCODE_FocusFound) && ptpcam.get_property(SONYALPHA_PROPCODE_FocusFound) == SONYALPHA_FOCUSSTATUS_NONE)
    {
        // remember last known setting
        uint32_t tv  = ptpcam.get_property(SONYALPHA_PROPCODE_ShutterSpeed);
        uint32_t iso = ptpcam.get_property(SONYALPHA_PROPCODE_ISO);
        // only remember if it's not the same (workaround for the camera not responding to restore commands)
        dual_shutter_last_tv.u32    = (tv  != dual_shutter_next.u32 || dual_shutter_next.flags != SPEEDTYPE_PTP) ?  tv : dual_shutter_last_tv.u32;
        dual_shutter_last_iso.u32   = (iso != dual_shutter_iso.u32  || dual_shutter_iso.flags  != SPEEDTYPE_PTP) ? iso : dual_shutter_last_iso.u32;
        dual_shutter_last_tv.flags  = SPEEDTYPE_PTP;
        dual_shutter_last_iso.flags = SPEEDTYPE_PTP;
    }
}

class AppDualShutter : public FairyMenuItem
{
    public:
        AppDualShutter() : FairyMenuItem("/dualshutter_reg.png")
        {
        };

        virtual void on_navTo(void)
        {
            _is_armed = false;
            FairyMenuItem::on_navTo();
        };

        virtual void draw_mainImage(void)
        {
            if (_is_armed == false)
            {
                FairyMenuItem::draw_mainImage();
            }
            else
            {
                cpufreq_boost();
                M5Lcd.setRotation(0);
                M5Lcd.drawPngFile(SPIFFS, "/dualshutter_shoot.png", _main_img_x, _main_img_y);
            }
        };

        virtual void on_redraw(void)
        {
            FairyMenuItem::on_redraw();
            draw_text();
        };

        virtual void on_spin(int8_t x)
        {
            // toggle mode on spin
            if (x != 0)
            {
                if (_is_armed) {
                    _is_armed = false;
                }
                else {
                    //if (dual_shutter_next.flags != SPEEDTYPE_NONE && fairycam.isOperating())
                    {
                        _is_armed = true;
                    }
                }
                set_redraw();
            }
        };

        virtual void on_eachFrame(void)
        {
            gui_drawSpinStatus(5, TFT_WHITE);

            if (_is_armed)
            {
                if ((ptpcam.isOperating() && ptpcam.has_property(SONYALPHA_PROPCODE_FocusFound) && ptpcam.get_property(SONYALPHA_PROPCODE_FocusFound) == SONYALPHA_FOCUSSTATUS_FOCUSED) || (httpcam.isOperating() && httpcam.is_focused))
                {
                    // trigger via shutter half press
                    gui_drawTopThickLine(8, TFT_RED); // indicate
                    dual_shutter_shoot(true, false, &dual_shutter_last_tv, &dual_shutter_last_iso);
                    gui_drawTopThickLine(8, TFT_WHITE);
                }
            }
            dualshutter_logSettings();
        };

        virtual bool on_execute(void)
        {
            if (must_be_connected() == false) {
                return false;
            }

            if (_is_armed == false)
            {
                bool gotdata = false;
                if (ptpcam.isOperating())
                {
                    if (ptpcam.has_property(SONYALPHA_PROPCODE_ShutterSpeed) && ptpcam.has_property(SONYALPHA_PROPCODE_ISO))
                    {
                        dual_shutter_next.flags = SPEEDTYPE_PTP;
                        dual_shutter_iso.flags  = SPEEDTYPE_PTP;
                        dual_shutter_next.u32 = ptpcam.get_property(SONYALPHA_PROPCODE_ShutterSpeed);
                        dual_shutter_iso.u32  = ptpcam.get_property(SONYALPHA_PROPCODE_ISO);
                        dbg_ser.printf("dualshutter 0x%08X %u\r\n", dual_shutter_next, dual_shutter_iso);
                        gotdata = true;
                    }
                }
                else if (httpcam.isOperating())
                {
                    if (strlen(httpcam.get_shutterspd_str()) > 0 && strlen(httpcam.get_iso_str()) > 0)
                    {
                        dual_shutter_next.flags = SPEEDTYPE_HTTP;
                        dual_shutter_iso.flags  = SPEEDTYPE_HTTP;
                        strcpy(dual_shutter_next.str, httpcam.get_shutterspd_str());
                        strcpy(dual_shutter_iso.str,  httpcam.get_iso_str());
                        gotdata = true;
                    }
                }

                if (gotdata == false) {
                    dbg_ser.println("dualshutter no data from camera");
                }

                draw_text();

                app_waitAllRelease();
            }
            else if (_is_armed)
            {
                gui_drawTopThickLine(8, TFT_RED);
                dual_shutter_shoot(false, true, &dual_shutter_last_tv, &dual_shutter_last_iso);
                gui_drawTopThickLine(8, TFT_WHITE);
                app_waitAllRelease();
            }

            return false;
        };

    protected:

        bool _is_armed = false;

        void draw_text(void)
        {
            dualshutter_drawText();
        };
};

extern FairySubmenu menu_remote;
void setup_dualshutter()
{
    static AppDualShutter app;
    menu_remote.install(&app);
}


================================================
FILE: arduino_workspace/AlphaFairy/FairyMenu.cpp
================================================
#include "FairyMenu.h"
#include "AlphaFairy.h"
#include <M5DisplayExt.h>

extern M5DisplayExt M5Lcd;

extern void gui_startAppPrint(void);
extern void gui_drawStatusBar(bool);
extern void gui_showVal(int32_t x, uint32_t txtfmt, Print* printer);

extern void tallylite_task(void);

extern void handle_user_reauth(void); // shows the wifi error screen and offers the user a way of changing wifi password

#ifdef ENABLE_BUILD_LEPTON
extern void lepton_encRead(bool* sw, int16_t* inc, int16_t* rem);
extern void lepton_encClear(void);
#endif

int8_t FairyCfgApp::prev_tilt = 0;
bool FairyCfgItem::dirty = false;

FairyMenuItem::FairyMenuItem(const char* img_fname, uint16_t id)
{
    _id = id;
    if (img_fname != NULL) {
        _main_img = (char*)malloc(strlen(img_fname) + 2);
        strcpy(_main_img, img_fname);
    }
    else {
        _main_img = NULL;
    }
}

void FairyMenuItem::draw_mainImage(void)
{
    cpufreq_boost();
    M5Lcd.setRotation(0);
    M5Lcd.drawPngFile(SPIFFS, _main_img, _main_img_x, _main_img_y);
    // if you need to overlay something else on top of the main image, then override this function, call it first, then do whatever you need to do
}

void FairyMenuItem::draw_statusBar(void)
{
    gui_drawStatusBar(false); // if the background is black, then override this virtual function
}

FairySubmenu::FairySubmenu(const char* img_fname, uint16_t id) : FairyMenuItem(img_fname, id)
{
}

bool FairySubmenu::on_execute(void)
{
    uint32_t t = millis();
    cpufreq_boost();
    rewind();
    imu.resetSpin();
    FairyMenuItem* itm = (FairyMenuItem*)cur_node->item;
    itm->on_navTo(); // this does a redraw

    bool run_normally = true;

    if (itm->get_quickEnter())
    {
        while (btnBig_isPressed()) // must hold button
        {
            app_poll();
            if ((millis() - t) > 1000) // must hold button for this long
            {
                run_normally = false;
                break;
            }
        }

        if (run_normally == false) // quick enter has triggered
        {
            sprites->unload_all();
            itm->on_execute();
            sprites->unload_all();
            itm->on_navOut(); // direct exit anyways
        }
    }

    if (run_normally)
    {
        app_waitAllRelease();

        do
        {
            if (app_poll()) // true if low priority tasks can execute
            {
                tallylite_task();

                if (task()) // true if user wants to exit out of submenu
                {
                    itm->on_navOut();
                    break;
                }
                pwr_sleepCheck();
            }
        } 
        while (true);
    }

    // user exit
    set_redraw();
    return false;
}

void FairySubmenu::install(FairyItem* itm)
{
    itm->set_parent((void*)this, this->_id);

    if (head_node == NULL) // first node of the list, need to be assigned to head_node
    {
        head_node = (FairyItemNode_t*)malloc(sizeof(FairyItemNode_t));
        head_node->item = itm;
        head_node->next_node = (void*)head_node;
        head_node->prev_node = (void*)head_node;
        cur_node = head_node;
    }
    else // not first node, need to insert between head node and tail node, to become the new tail node
    {
        FairyItemNode_t* tail_node = get_tailNode();
        FairyItemNode_t* new_node = (FairyItemNode_t*)malloc(sizeof(FairyItemNode_t));
        new_node->item = itm;
        new_node->next_node  = (void*)head_node;
        new_node->prev_node  = (void*)tail_node;
        tail_node->next_node = (void*)new_node;
        head_node->prev_node = (void*)new_node;
    }
}

FairyItem* FairySubmenu::nav_next(void)
{
    FairyItemNode_t* n = cur_node;
    uint8_t i;
    if (cur_node == NULL) {
        return NULL;
    }
    for (i = 0; i < 10; i++) // this is a for loop just to prevent infinite loops
    {
        n = (FairyItemNode_t*)(n->next_node);
        // find the next node that isn't hidden
        if (n->item->can_navTo())
        {
            cur_node = n;
            return cur_node->item;
        }
    }
    return NULL;
}

#ifdef ENABLE_BUILD_LEPTON
FairyItem* FairySubmenu::nav_prev(void)
{
    FairyItemNode_t* n = cur_node;
    uint8_t i;
    if (cur_node == NULL) {
        return NULL;
    }
    for (i = 0; i < 10; i++) // this is a for loop just to prevent infinite loops
    {
        n = (FairyItemNode_t*)(n->prev_node);
        // find the next node that isn't hidden
        if (n->item->can_navTo())
        {
            cur_node = n;
            return cur_node->item;
        }
    }
    return NULL;
}
#endif

bool FairySubmenu::task(void)
{
    handle_user_reauth();

    FairyMenuItem* itm;
    bool redraw = redraw_flag;

    #ifdef ENABLE_BUILD_LEPTON
    bool enc_center_btn;
    int16_t enc_nav;
    #endif

    itm = (FairyMenuItem*)cur_node->item;

    bool to_nav = false;

    if (btnSide_hasPressed())
    {
        // next button pressed
        btnSide_clrPressed();
        to_nav = true;
    }
    else if (_bigbtn_nav)
    {
        // big button becomes next button
        if (btnBig_hasPressed())
        {
            btnBig_clrPressed();
            to_nav = true;
        }
    }
    #ifdef ENABLE_BUILD_LEPTON
    if (_enc_nav)
    {
        lepton_encRead(&enc_center_btn, &enc_nav, NULL);
        if (enc_nav > 0)
        {
            to_nav = true;
        }
        else if (enc_nav < 0)
        {
            to_nav = false;
            itm->on_navOut();
            itm = (FairyMenuItem*)nav_prev();
            itm->on_navTo();
            imu.resetSpin();
        }
    }
    #endif

    if (to_nav) // next button pressed
    {
        itm->on_navOut();
        itm = (FairyMenuItem*)nav_next();
        itm->on_navTo();
        imu.resetSpin();
    }

    itm = (FairyMenuItem*)cur_node->item;

    redraw |= itm->check_redraw();

    if (redraw)
    {
        itm->on_redraw();
        redraw_flag = false;
    }

    // user has spun the IMU
    if (imu.getSpin() != 0)
    {
        itm->on_spin(imu.getSpin());
        imu.resetSpin();
    }
    #ifdef ENABLE_BUILD_LEPTON
    else if (_enc_nav == false)
    {
        lepton_encRead(&enc_center_btn, &enc_nav, NULL);
        if (enc_nav != 0)
        {
            itm->on_spin(enc_nav);
        }
    }
    #endif

    itm->on_eachFrame();
    itm->draw_statusBar(); // the status bar function has its own frame rate control

    #ifdef ENABLE_BUILD_LEPTON
    // waits until encoder stops
    while (enc_nav != 0) {
        lepton_encRead(&enc_center_btn, &enc_nav, NULL);
        app_poll();
    }

    if (enc_center_btn)
    {
        // encoder center button will act as big button
        sprites->unload_all();
        bool need_exit = itm->on_execute();
        sprites->unload_all();
        if (need_exit) {
            return true;
        }
        if (itm->get_quitOnExit()) {
            return true;
        }
        if (itm->get_quitToNext()) {
            itm->on_navOut();
            itm = (FairyMenuItem*)nav_next();
            itm->on_navTo();
            imu.resetSpin();
        }
    }
    else
    #endif

    if (_bigbtn_nav)
    {
        // do nothing here, big button is acting as the next button
    }
    else if (btnBig_hasPressed())
    {
        btnBig_clrPressed();
        sprites->unload_all();
        bool need_exit = itm->on_execute();
        sprites->unload_all();
        if (need_exit) {
            return true;
        }
        if (itm->get_quitOnExit()) {
            return true;
        }
        if (itm->get_quitToNext()) {
            itm->on_navOut();
            itm = (FairyMenuItem*)nav_next();
            itm->on_navTo();
            imu.resetSpin();
        }
    }

    if (btnPwr_hasPressed())
    {
        btnPwr_clrPressed();
        return true;
    }

    return false;
}

FairyCfgItem::FairyCfgItem(const char* disp_name, int32_t* linked_var, int32_t val_min, int32_t val_max, int32_t step_size, uint32_t fmt_flags)
{
    set_name(disp_name);
    _linked_ptr = linked_var;
    _fmt_flags = fmt_flags;
    _val_min = val_min;
    _val_max = val_max;
    _step_size = step_size;
    if ((_fmt_flags & TXTFMT_AUTOCFG) == TXTFMT_AUTOCFG)
    {
        _fmt_flags = 0;
        if (_val_min == 0 && _val_max == 1 && _step_size == 1) {
            _fmt_flags = TXTFMT_BOOL;
        }
        else if (_val_min <= 1 && _val_max >= 1000 && _step_size == 1) {
            _fmt_flags |= TXTFMT_BYTENS;
        }
    }
}

FairyCfgItem::FairyCfgItem(const char* disp_name, bool (*cb)(void*), const char* icon)
{
    _cb = cb;
    if (icon != NULL) {
        set_icon(icon);
    }
    set_name(disp_name);
}

void FairyCfgItem::set_name(const char* x)
{
    _disp_name = (char*)malloc(strlen(x) + 2);
    strcpy(_disp_name, x);
    set_font(-1);
}

void FairyCfgItem::set_icon(const char* x)
{
    _icon_fpath = (char*)malloc(strlen(x) + 2);
    strcpy(_icon_fpath, x);
    _icon_width = GENERAL_ICON_WIDTH;
}

void FairyCfgItem::set_font(int fn)
{
    if (fn < 0) // auto set
    {
        uint16_t dim1 = M5Lcd.width();
        uint16_t dim2 = M5Lcd.height();
        uint16_t dim = dim1 > dim2 ? dim1 : dim2;
        uint16_t w = M5Lcd.textWidth((const char*)_disp_name, _font_num = 4);
        if (w >= dim - _icon_width - 10) {
            _font_num = 2;
        }
    }
    else
    {
        _font_num = fn;
    }
    _line0_height = M5Lcd.fontHeight(_font_num);
}

void FairyCfgItem::draw_name(void)
{
    int y = get_y(0);
    M5Lcd.setCursor(_margin_x, y);
    M5Lcd.setTextFont(_font_num);
    M5Lcd.print(_disp_name);
    M5Lcd.setTextFont(4);
    M5Lcd.fillRect(M5Lcd.getCursorX(), y, M5Lcd.width() - M5Lcd.getCursorX() - _icon_width, M5Lcd.fontHeight(), TFT_BLACK);
    draw_icon();
}

void FairyCfgItem::draw_icon(void)
{
    if (_icon_fpath != NULL && _icon_width > 0)
    {
        M5Lcd.drawPngFile(SPIFFS, _icon_fpath, M5Lcd.width() - _icon_width, 0);
    }
    FairyCfgApp* p = dynamic_cast<FairyCfgApp*>((FairyCfgApp*)get_parent());
    if (p != NULL)
    {
        p->draw_icon();
    }
}

void FairyCfgItem::draw_statusBar(void)
{
    gui_drawStatusBar(true);
}

void FairyCfgItem::blank_text(void)
{
    M5Lcd.fillRect(M5Lcd.width() - GENERAL_ICON_WIDTH, 0                    , GENERAL_ICON_WIDTH                , GENERAL_ICON_WIDTH + _margin_y    , TFT_BLACK);
    M5Lcd.fillRect(0                                 , _margin_y            , M5Lcd.width() - _icon_width       , GENERAL_ICON_WIDTH                , TFT_BLACK);
    M5Lcd.fillRect(0                                 , GENERAL_ICON_WIDTH   , M5Lcd.width() - GENERAL_ICON_WIDTH, GENERAL_ICON_WIDTH - 5            , TFT_BLACK);
}

void FairyCfgItem::on_navTo(void)
{
    on_redraw();
}

void FairyCfgItem::on_redraw(void)
{
    blank_text();
    draw_name();
    draw_value(imu.getTilt());
}

void FairyCfgItem::draw_value(int8_t tilt)
{
    if (_linked_ptr == NULL) {
        return;
    }
    int y = get_y(1);
    M5Lcd.setCursor(_margin_x, y);
    M5Lcd.setTextFont(4);
    gui_showVal(get_val(), _fmt_flags, (Print*)&M5Lcd);
    if (tilt != 0) {
        M5Lcd.print((tilt > 0) ? " +> " : " <- "); // indicate if button press will increment or decrement
    }
    blank_line();
}

void FairyCfgItem::draw_value(void)
{
    draw_value(imu.getTilt());
}

void FairyCfgItem::blank_line(void)
{
    M5Lcd.fillRect(M5Lcd.getCursorX(), M5Lcd.getCursorY(), M5Lcd.width() - M5Lcd.getCursorX() - 60, M5Lcd.fontHeight(), TFT_BLACK);
}

void FairyCfgItem::on_tiltChange(void)
{
    draw_value();
}

void FairyCfgItem::on_checkAdjust(int8_t tilt)
{
    // this function handles:
    //  * displaying the arrows beside the value according to tilt
    //  * changing the value on button press
    //  * changing the value even faster when the button is held down

    int32_t next_step = 0; // this will latch the direction of change during button-hold

    int16_t rx, ry;
    rx = M5Lcd.getCursorX(); ry = M5Lcd.getCursorY();

    #ifdef ENABLE_BUILD_LEPTON
    bool enc_btn = false;
    int16_t enc_inc = 0;
    int16_t enc_rem;
    int16_t enc_inc_prev = 0;
    #endif

    if (btnBig_hasPressed())
    {
        if (tilt > 0)
        {
            (*_linked_ptr) += _step_size;
            if ((*_linked_ptr) >= _val_max) { // limit the range
                (*_linked_ptr) = _val_max;
            }
            else {
                next_step = _step_size; // indicate that change has been made
            }
            dirty = true;
        }
        else if (tilt < 0)
        {
            (*_linked_ptr) -= _step_size;
            if ((*_linked_ptr) <= _val_min) { // limit the range
                (*_linked_ptr) = _val_min;
            }
            else {
                next_step = -_step_size; // indicate that change has been made
            }
            dirty = true;
        }
        else
        {
            // flip boolean variable even if there's no tilt
            if (_fmt_flags == TXTFMT_BOOL) {
                (*_linked_ptr) = ((*_linked_ptr) == 0) ? 1 : 0;
                dirty = true;
            }
        }
        draw_value(tilt);
        on_drawLive();
        on_readjust();
        M5Lcd.setCursor(rx, ry);

        btnBig_clrPressed();
    }
    #ifdef ENABLE_BUILD_LEPTON
    else
    {
        lepton_encRead(&enc_btn, &enc_inc, &enc_rem);
        if (enc_inc > 0)
        {
            (*_linked_ptr) += _step_size;
            if ((*_linked_ptr) >= _val_max) { // limit the range
                (*_linked_ptr) = _val_max;
            }
            else {
                next_step = _step_size; // indicate that change has been made
            }
            dirty = true;
        }
        else if (enc_inc < 0)
        {
            (*_linked_ptr) -= _step_size;
            if ((*_linked_ptr) <= _val_min) { // limit the range
                (*_linked_ptr) = _val_min;
            }
            else {
                next_step = -_step_size; // indicate that change has been made
            }
            dirty = true;
        }
        if (enc_inc != 0)
        {
            draw_value(tilt);
            on_drawLive();
            on_readjust();
            M5Lcd.setCursor(rx, ry);
        }
        enc_inc_prev = enc_inc;
    }
    #endif

    if (next_step != 0 && _fmt_flags != TXTFMT_BOOL) // has pressed
    {
        uint32_t press_time = millis();
        uint32_t dly = btnBig_isPressed() ? 500 : 1000; // press-and-hold repeating delay
        int step_cnt = 0; // used to make sure at least some steps are done at minimum step size
        int tens = 10 * next_step * ((next_step < 0) ? (-1) : (1)); // if the step size starts at 1 or 10, these cases are handled
        while (true) // is press-and-hold
        {
            if (btnBig_isPressed() == false)
            {
                #ifdef ENABLE_BUILD_LEPTON
                #if 0 // this chunk of code doesn't work
                lepton_encRead(&enc_btn, &enc_inc, &enc_rem);
                if (enc_inc != 0 || enc_rem != 0)
                {
                    if (enc_inc != 0 && enc_inc != enc_inc_prev && enc_rem == 0)
                    {
                        break;
                    }
                }
                else if (enc_inc == 0 && enc_rem == 0)
                {
                    break;
                }
                else
                #endif
                #endif
                break;
            }
            app_poll();
            on_extraPoll();
            on_drawLive();

            uint32_t now = millis();
            if ((now - press_time) >= dly)
            {
                press_time = now;
                // make the required delay shorter for the next iteration
                // this makes the changes "accelerate"
                dly *= 3;
                dly /= 4;
                // impose a limit on the delay
                if ((_fmt_flags & TXTFMT_BYTENS) != 0) {
                    dly = (dly < 100) ? 100 : dly;
                }
                step_cnt++;
                (*_linked_ptr) += next_step;
                if ((*_linked_ptr) >= _val_max) { // limit the range
                    (*_linked_ptr) = _val_max;
                    break;
                }
                else if ((*_linked_ptr) <= _val_min) { // limit the range
                    (*_linked_ptr) = _val_min;
                    break;
                }
                if ((*_linked_ptr) >= 10 && ((*_linked_ptr) % tens) == 0 && step_cnt > 5 && (_fmt_flags & TXTFMT_BYTENS) != 0) {
                    step_cnt = 0;
                    tens *= 10;
                    next_step *= 10;
                }
                draw_value(tilt);
                on_drawLive();
                on_readjust();
                M5Lcd.setCursor(rx, ry);
            }
        }
        draw_value(tilt);
        on_drawLive();
        on_readjust();
        M5Lcd.setCursor(rx, ry);
    }

    #ifdef ENABLE_BUILD_LEPTON
    if (enc_inc_prev != 0)
    {
        lepton_encClear();
    }
    #endif
}

bool FairyCfgItem::on_execute(void)
{
    if (_cb == NULL) {
        return false;
    }
    return _cb(this);
}

int16_t FairyCfgItem::get_y(int8_t linenum)
{
    if (linenum == 0)
    {
        return _margin_y;
    }
    return _margin_y + _line0_height + ((linenum - 1) * (M5Lcd.fontHeight(4) + _line_space));
}

FairyCfgApp::FairyCfgApp(const char* img_fname, const char* icon_fname, uint16_t id) : FairySubmenu(img_fname, id)
{
    if (icon_fname != NULL) {
        _icon_fname = (char*)malloc(strlen(icon_fname) + 2);
        strcpy(_icon_fname, icon_fname);
        _icon_width = GENERAL_ICON_WIDTH;
    }
    _enc_nav = false;
}

void FairyCfgApp::draw_icon(void)
{
    if (_icon_fname == NULL || _icon_width == 0) {
        return;
    }
    M5Lcd.drawPngFile(SPIFFS, _icon_fname, M5Lcd.width() - _icon_width, M5Lcd.height() - _icon_width);
}

// this function is similar to FairySubmenu::task(void)
bool FairyCfgApp::task(void)
{
    handle_user_reauth();

    FairyCfgItem* itm;
    bool redraw = redraw_flag;

    bool enc_btn; int16_t enc_inc;

    itm = (FairyCfgItem*)cur_node->item;

    if (btnSide_hasPressed())
    {
        btnSide_clrPressed();
        itm->on_navOut();
        itm = (FairyCfgItem*)nav_next();
        itm->on_navTo();
        redraw = false;
    }

    itm = (FairyCfgItem*)cur_node->item;
    itm->on_eachFrame();

    itm->draw_statusBar(); // the status bar function has its own frame rate control

    if (redraw)
    {
        cpufreq_boost();
        itm->on_redraw();
        redraw_flag = false;
    }

    if (itm->is_func())
    {
        if (btnBig_hasPressed())
        {
            btnBig_clrPressed();
            bool ret = itm->on_execute();
            if (ret) {
                return ret;
            }
        }
        #ifdef ENABLE_BUILD_LEPTON
        else
        {
            lepton_encRead(&enc_btn, &enc_inc, NULL);
            if (enc_btn)
            {
                bool ret = itm->on_execute();
                if (ret) {
                    return ret;
                }
            }
        }
        #endif
    }
    else if (itm->is_value())
    {
        int8_t tilt = imu.getTilt();
        if (tilt != prev_tilt) {
            itm->on_tiltChange();
        }
        itm->on_checkAdjust(tilt);
        prev_tilt = tilt;
    }
    else
    {
        // this case is not supposed to ever happen, but we need to clear the big button's event flag
        if (btnBig_hasPressed()) {
            btnBig_clrPressed();
        }
    }

    if (btnPwr_hasPressed())
    {
        btnPwr_clrPressed();
        return true;
    }

    return false;
}

// this function is similar to FairySubmenu::on_execute(void)
bool FairyCfgApp::on_execute(void)
{
    cpufreq_boost();
    rewind();
    FairyCfgItem* itm = (FairyCfgItem*)cur_node->item;
    gui_startAppPrint();
    itm->on_navTo();
    app_waitAllRelease();
    while (true)
    {
        if (app_poll())
        {
            if (task())
            {
                itm->on_navOut();
                break;
            }
            pwr_sleepCheck();
        }
    }
    M5Lcd.setRotation(0);
    set_redraw();
    return false;
}

bool FairyMenuItem::must_be_connected(void)
{
    if (fairycam.isOperating() == false) {
        app_waitAllReleaseConnecting();
        return false;
    }
    return true;
}

bool FairyMenuItem::must_be_ptp(void)
{
    if (must_be_connected() == false) {
        return false;
    }
    if (httpcam.isOperating()) {
        app_waitAllReleaseUnsupported();
        return false;
    }
    return true;
}


================================================
FILE: arduino_workspace/AlphaFairy/FairyMenu.h
================================================
#ifndef _FAIRYMENU_H_
#define _FAIRYMENU_H_

#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#include "AlphaFairy.h"

extern bool redraw_flag;
extern void cpufreq_boost(void);

// the base class that every type of menu item is derived from
class FairyItem
{
    public:
        virtual bool     on_execute       (void) { return false; }; // performs a the app's action, return true for "should exit out of submenu"

        // for some shared items, it'll be useful to know what parent menu is holding it
        inline  uint16_t get_id           (void) { return _id; };
        virtual void     set_parent(void* x, uint16_t id) { _parent = x; _parent_id = id; };
        inline  uint16_t get_parentId     (void) { return _parent_id; };
        inline  void*    get_parent       (void) { return _parent; };

        virtual bool     can_navTo        (void) { return true; }; // used to hide an item
        virtual void     on_navTo         (void) { };              // usually used to draw an item
        virtual void     on_navOut        (void) { };              // usually used to stop something
        virtual void     on_eachFrame     (void) { };              // usually used to poll something specific and/or to update text

        inline void      set_redraw       (void) { cpufreq_boost(); redraw_flag = true; } // forces a redraw on next loop, useful for when app execution has drawn extra stuff that needs to disappear

    protected:
        void* _parent = NULL;
        uint16_t _id, _parent_id = 0;
};

// used for linked list
typedef struct
{
    FairyItem* item;
    void* next_node;
    void* prev_node;
}
FairyItemNode_t;

class FairyMenuItem : public FairyItem
{
    public:
        FairyMenuItem(const char* img_fname, uint16_t id = 0);
        virtual void     reset            (void) {};
        virtual bool     on_execute       (void) { return false; };      // do the thing, return true means "exit submenu" // by default, do nothing and do not exit
        virtual bool     can_navTo        (void) { return true; };       // used to hide an item // by default, do not hide
        virtual void     on_navTo         (void) { on_redraw(); };       // usually used to draw the main image
        virtual void     on_navOut        (void) {};
        virtual void     on_eachFrame     (void) {};
        virtual void     on_spin          (int8_t x) {};                 // handle IMU spin, do not actually do actions to the imu object here
        virtual void     on_redraw        (void) { draw_mainImage(); };  // usually used to draw the main image
        virtual bool     check_redraw     (void) { return false; };
        virtual void     draw_mainImage   (void);
        virtual void     draw_statusBar   (void);
        inline  char*    get_mainImage    (void) { return _main_img; };
        inline  int16_t  get_mainImage_X  (void) { return _main_img_x; };
        inline  int16_t  get_mainImage_Y  (void) { return _main_img_y; };

        inline  void     set_quickEnter   (bool x) { _can_quickEnter = x; };
        inline  bool     get_quickEnter   (void)   { return _can_quickEnter; };
        inline  bool     get_quitOnExit   (void)   { return _quitOnExit; };
        inline  bool     get_quitToNext   (void)   { return _quitToNext; };

    protected:
        char* _main_img;
        int16_t _main_img_x = 0, _main_img_y = 0;

        bool _can_quickEnter = false;
        bool _quitOnExit = false;
        bool _quitToNext = false;

        bool must_be_connected(void); // convenient to show the "connecting..." animation when required // returns true if connected, false if disconnected
        bool must_be_ptp(void);       // convenient to show the "unsupported camera" error screen, also uses must_be_connected // returns true if connected and supported, false if otherwise unable to operate
};

class FairySubmenu : public FairyMenuItem
{
    // this class represents a submenu with items that are full screen, mostly white background, in portrait orientation
    public:
        FairySubmenu(const char* img_fname, uint16_t id = 0);
        inline  void set_bigbtn_nav(bool x) { _bigbtn_nav = x; }; // allows the use of the big button as a next button, which disables on_execute completely
        inline  void set_enc_nav(bool x) { _enc_nav = x; };
        virtual void install(FairyItem* itm);                     // adds item to linked list
        virtual bool on_execute(void);                            // usually used for a menu loop
        virtual bool task(void);                                  // usually used to do stuff inside the menu loop, return true means "exit submenu"
        inline  void rewind(void) { cur_node = head_node; };      // reset to showing the first item in the linked list
        FairyItem* nav_next(void);                                // navigate to the next item that is not hidden
        #ifdef ENABLE_BUILD_LEPTON
        FairyItem* nav_prev(void);                                // navigate to the prev item that is not hidden
        #endif

        // linked list getters
        inline FairyItemNode_t* get_headNode(void) { return head_node; };
        inline FairyItemNode_t* get_tailNode(void) { if (head_node == NULL ) return NULL; return (FairyItemNode_t*)(head_node->prev_node); };

    protected:
        // linked list
        FairyItemNode_t* head_node = NULL;
        FairyItemNode_t* cur_node = NULL;

        bool _bigbtn_nav = false; // allows the use of the big button as a next button, which disables on_execute completely
        bool _enc_nav = true;     // allows the lepton encoder to navigate
};

class FairyCfgItem : public FairyItem
{
    // this class is a page within the "configurable app" style of apps, the apps usually consist of many pages of settings and one page for execution
    public:
        // initialize as a page representing a setting item
        FairyCfgItem(const char* disp_name, int32_t* linked_var, int32_t val_min, int32_t val_max, int32_t step_size, uint32_t fmt_flags);

        // initialize as a page representing a way to execute action
        FairyCfgItem(const char* disp_name, bool (*cb)(void*), const char* icon = NULL);

               void    set_icon(const char* icon);              // sets the icon for the top-right corner
               void    set_font(int fn);                        // set the font size of the first line of text, use a negative number for auto-sizing
        inline int32_t get_val (void) { return *_linked_ptr; }; // get the actual value of the configurable item

        // a way of checking what kind of page this is
        inline bool is_value(void) { return _linked_ptr != NULL; };
        inline bool is_func (void) { return _cb != NULL; };

        virtual bool     on_execute     (void); // do the thing, by default, this calls the registered callback function, return true means "exit submenu"
        virtual bool     can_navTo      (void) { return true; };
        virtual void     on_navTo       (void);
        virtual void     on_navOut      (void) { if (_autosave && dirty) { dirty = false; settings_save(); } }; // auto-save if the value changed
        virtual void     on_redraw      (void);
        virtual void     on_eachFrame   (void) { on_extraPoll(); on_drawLive(); };
        virtual void     on_readjust    (void) {};
        virtual void     on_drawLive    (void) {}; // this is used for drawing even faster than on_eachFrame, example: mic level bar
        virtual void     on_extraPoll   (void) {}; // this is used for polling even faster than on_eachFrame, example: mic sample reading
        virtual void     on_tiltChange  (void);
        virtual void     on_checkAdjust (int8_t);
        virtual void     draw_statusBar (void);
        virtual void     draw_value(void), draw_value(int8_t);
        virtual void     draw_name(void);
        virtual void     draw_icon(void);
        virtual void     blank_text(void); // blanks entire text area
        virtual void     blank_line(void); // blanks rest of line

    protected:
        uint16_t _margin_x = SUBMENU_X_OFFSET, _margin_y = SUBMENU_Y_OFFSET, _line0_height = 16, _line_space = 1, _font_num = 4; // default graphic config
        char* _disp_name;
        char* _icon_fpath = NULL;
        int16_t _icon_width = 0;
        int32_t* _linked_ptr = NULL;
        bool (*_cb)(void*) = NULL;
        int32_t _val_min, _val_max, _step_size;
        uint32_t _fmt_flags;
        bool _autosave = false;
        static bool dirty;

        void set_name(const char*);
        int16_t get_y(int8_t linenum); // gets the Y coordinate of a text line, compensating for margin and font sizes, 0 indexed
};

class FairyCfgApp : public FairySubmenu
{
    // this class represents an app with configurable items, mostly black background with icons, in landscape orientation
    public:
        FairyCfgApp(const char* img_fname, const char* icon_fname, uint16_t id = 0);
        virtual void install(FairyCfgItem* itm) { FairySubmenu::install((FairyItem*)itm); };
        virtual bool on_execute(void); // this is the app loop, return true means "exit app"
        virtual bool task(void);       // the inner part of the app loop, return true means "exit app"
        virtual bool has_icon(void) { return _icon_fname != NULL; };
        virtual void draw_icon(void);

    protected:
        char* _icon_fname = NULL;
        int16_t _icon_width = 0;
        static int8_t prev_tilt;
};

#endif


================================================
FILE: arduino_workspace/AlphaFairy/FocusEncoder.ino
================================================
#include "AlphaFairy.h"
#include <FairyEncoder.h>

bool fenc_enabled = true;
int32_t fenc_val;

void fenc_task()
{
    static bool prev_can_run = false;

    if (config_settings.pin_shutter == PINCFG_G32 || config_settings.pin_shutter == PINCFG_G33 || config_settings.pin_exinput == PINCFG_G32 || config_settings.pin_exinput == PINCFG_G33) {
        fenc_enabled = false;
        return;
    }

    bool can_run = ptpcam.isOperating() && ptpcam.is_manuallyfocused() && fenc_enabled;
    // we don't want to waste IO time if the camera is not connected or not in manual focus mode
    if (prev_can_run != can_run) {
        fenc_val = 0;
        fencoder.read(true); // this call here checks if the knob is reconnected
        if (can_run) {
            pwr_tick(true);
            dbg_ser.println("focus knob can run");
        }
        else {
            dbg_ser.println("focus knob cannot run");
        }
    }
    prev_can_run = can_run;
    if (can_run == false) {
        return;
    }

    uint32_t now = millis();
    static uint32_t prev_execute_time = 0;
    static uint32_t prev_move_time = 0;
    static uint32_t wait_time = 0;

    // multiplier can't be 0
    if (config_settings.fenc_multi == 0) {
        config_settings.fenc_multi = 1;
    }

    // absolute value of multiplier is used to calculate speed
    int32_t absmulti = config_settings.fenc_multi < 0 ? -config_settings.fenc_multi : config_settings.fenc_multi;

    fencoder.task(); // do the I2C IO operations required to read encoder
    int16_t d = fencoder.read(true); // get the relative step count

    if (d != 0)
    {
        pwr_tick(true);
        cpufreq_boost();

        int32_t additional = d * config_settings.fenc_multi;
        #if 1
        // if there are queued steps remaining but the direction changed, immediately change direction
        if (additional * fenc_val < 0)
        {
            fenc_val = additional;
        }
        else
        #endif
        {
            fenc_val += additional; // queue up the steps
        }

        uint32_t tspan = now - prev_move_time;
        if (absmulti > 1) {
            // this is guessing the pause time for multiple steps per tick
            wait_time = tspan / (absmulti * 2);
            if (wait_time > config_settings.focus_pause_time_ms) {
                wait_time = config_settings.focus_pause_time_ms;
            }
        }
        else {
            wait_time = 0;
        }
        prev_move_time = now;
    }
    else if (d == 0 && (fenc_val > 1 || fenc_val < -1) && (now - prev_move_time) > 500)
    {
        // stop the movement if knob stopped moving for a while
        fenc_val = 0;
    }

    // do the wait by simply not doing anything
    if ((now - prev_execute_time) < wait_time && wait_time > 0) {
        return;
    }

    // NOTE: every click of the encoder wheel is actually equal to 2 ticks!

    while (fenc_val > 1 || fenc_val < -1) // while there are steps to do
    {
        int16_t absval = fenc_val < 0 ? -fenc_val : fenc_val;
        int16_t large_step = config_settings.fenc_large * 2;
        //dbg_ser.printf("focus knob %d\r\n", fenc_val);

        ptpcam.cmd_ManualFocusStep(((absval >= large_step && large_step > 2) ? SONYALPHA_FOCUSSTEP_FARTHER_LARGE : SONYALPHA_FOCUSSTEP_FARTHER_MEDIUM)  // large step size if needed, default medium step size
                                   * (fenc_val < 0 ? -1 : 1) * (config_settings.fenc_multi < 0 ? -1 : 1)                                                // account for direction
                                   );

        fenc_val -= ((absval >= large_step && large_step > 2) ? large_step : 2) // take away the appropriate amount from the queue
                        * (fenc_val < 0 ? -1 : 1) // account for direction
                        ;

        prev_execute_time = now;
        if (wait_time != 0) { // do only one loop if a wait has been specified
            break;
        }
        if (millis() - now > 200) { // don't hang the thread
            break;
        }

        cpufreq_boost();
    }
}

bool fenc_canOperate()
{
    return ptpcam.isOperating() && ptpcam.is_manuallyfocused() && fencoder.avail();
}

void fenc_calib_sleep(uint32_t x)
{
    uint32_t tstart = millis();
    uint32_t now;
    while (((now = millis()) - tstart) < x) {
        pwr_tick(true);
        ptpcam.task();
        yield();
    }
}

// lens manual focus calibration
// checks how many "medium steps" fit into one "large step"
bool fenc_calibrate()
{
    #define FENC_CHECK_CALIB_FAILED() do { if (ptpcam.isOperating() == false) {                      Serial.printf("MF-Calib: FAILED\r\n"); return false; } } while (0)
    #define FENC_CHECK_BTN_QUIT()     do { if (btnPwr_hasPressed())           { btnPwr_clrPressed(); Serial.printf("MF-Calib: QUIT\r\n");   return false; } } while (0)
    #define FENC_DOT_TICK()           do { gui_drawVerticalDots(0, 40, -1, 5, 5, dot_idx++, false, TFT_GREEN, TFT_RED); } while (0)

    Serial.println("Manual Focus Calibration Start");
    ptpcam.set_debugflags(0);
    redraw_flag = true;
    FENC_CHECK_CALIB_FAILED();

    int32_t fdist_now;
    int dot_idx = 0;

    // we must be in MF mode to use manual focus adjustment commands
    bool starting_mf = ptpcam.is_manuallyfocused();
    if (starting_mf == false)
    {
        ptpcam.cmd_ManualFocusMode(true, false);
    }

    bool do_one_more = true;
    uint32_t t = millis(), now = t;
    // move the focus point to minimum focus (nearest), using large steps so it's fast
    while (((now = millis()) - t) < 3000 || do_one_more)
    {
        pwr_tick(true);
        FENC_CHECK_CALIB_FAILED();
        ptpcam.cmd_ManualFocusStep(SONYALPHA_FOCUSSTEP_CLOSER_LARGE);
        fenc_calib_sleep(config_settings.focus_pause_time_ms);
        FENC_DOT_TICK();

        // check if we've reached the end, and do just one more step to be sure
        fdist_now = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
        if (fdist_now <= 0)
        {
            if (do_one_more) {
                do_one_more = false;
            }
            else {
                break;
            }
        }
    }

    int32_t fdist_near = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
    Serial.printf("MF-Calib: done homing, dist 0x%08X == %d\r\n", fdist_near, fdist_near);

    // do exactly just one large step farther away
    ptpcam.cmd_ManualFocusStep(SONYALPHA_FOCUSSTEP_FARTHER_LARGE);
    fenc_calib_sleep(config_settings.focus_pause_time_ms * 8);
    int32_t fdist_lrg = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
    Serial.printf("MF-Calib: one large step, dist 0x%08X == %d\r\n", fdist_lrg);

    // do a few medium steps farther away until the focus distance indicator changes again, this makes the measurement more accurate
    int med_steps = 0, med_extra = 0;
    while (true)
    {
        pwr_tick(true);
        FENC_CHECK_CALIB_FAILED();
        FENC_CHECK_BTN_QUIT();
        med_extra++;
        ptpcam.cmd_ManualFocusStep(SONYALPHA_FOCUSSTEP_FARTHER_MEDIUM);
        fenc_calib_sleep(config_settings.focus_pause_time_ms * 4);
        FENC_DOT_TICK();

        // see where the distance is now and check if it changed
        fdist_now = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
        if (fdist_now > fdist_lrg) {
            break;
        }
    }

    Serial.printf("MF-Calib: %u extra medium steps, dist 0x%08X == %d\r\n", fdist_now);

    do_one_more = true;
    t = millis();
    // go back to minimum focus distance quickly
    while (((now = millis()) - t) < 3000 || do_one_more)
    {
        pwr_tick(true);
        FENC_CHECK_CALIB_FAILED();
        ptpcam.cmd_ManualFocusStep(SONYALPHA_FOCUSSTEP_CLOSER_LARGE);
        fenc_calib_sleep(config_settings.focus_pause_time_ms);
        FENC_DOT_TICK();

        // check if we've reached the end, and do just one more step to be sure
        fdist_now = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
        if (fdist_now <= 0)
        {
            if (do_one_more) {
                do_one_more = false;
            }
            else {
                break;
            }
        }
    }

    Serial.printf("MF-Calib: home again\r\n");

    // do medium steps until we've reached the same distance as the large-steps-plus-extra-medium-steps
    while (true)
    {
        pwr_tick(true);
        FENC_CHECK_CALIB_FAILED();
        FENC_CHECK_BTN_QUIT();
        med_steps++;
        ptpcam.cmd_ManualFocusStep(SONYALPHA_FOCUSSTEP_FARTHER_MEDIUM);
        fenc_calib_sleep(config_settings.focus_pause_time_ms * 4);
        FENC_DOT_TICK();

        // check if we've reached the target
        fdist_now = ptpcam.get_property(SONYALPHA_PROPCODE_ManualFocusDist);
        if (fdist_now > fdist_lrg) {
            break;
        }
    }

    int delta_steps = med_steps - med_extra;
    Serial.printf("MF-Calib: %u medium steps equals one large step\r\n", delta_steps);
    config_settings.fenc_large = delta_steps;
    settings_save();

    if (starting_mf == false)
    {
        ptpcam.cmd_ManualFocusMode(false, false);
    }

    Serial.printf("Manual Focus Calibration Done\r\n");
    return true;
}

void focus_calib_write(uint16_t colour)
{
    M5Lcd.setCursor(10, 116);
    M5Lcd.setTextFont(4);
    M5Lcd.highlight(true);
    M5Lcd.setHighlightColor(TFT_WHITE);
    M5Lcd.setTextColor(colour, TFT_WHITE);
    M5Lcd.printf("%u", config_settings.fenc_large);
}

#include "FairyMenu.h"

class AppFocusCalib : public FairyMenuItem
{
    public:
        AppFocusCalib() : FairyMenuItem("/focus_calib.png") {
        };

        virtual void on_navTo(void)
        {
            if (config_settings.fenc_large != 0 && _success != 2) {
                _success = 1;
            }
            FairyMenuItem::on_navTo();
        };

        virtual void on_redraw(void)
        {
            FairyMenuItem::on_redraw();
            if (_success > 0) {
                focus_calib_write(_success == 1 ? TFT_BLACK : TFT_RED);
            }
        };

        virtual bool on_execute(void)
        {
            if (must_be_ptp() == false) {
                return false;
            }

            M5Lcd.drawPngFile(SPIFFS, "/focus_calib.png", 0, 0); // clear screen, removes text
            bool success = fenc_calibrate();
            _success = success ? 1 : 2;
            set_redraw();
            return false;
        };

    protected:
        int _success = 0;
};

extern FairySubmenu menu_utils;
void setup_focuscalib()
{
    static AppFocusCalib app;
    menu_utils.install(&app);
}


================================================
FILE: arduino_workspace/AlphaFairy/FocusFrustration.ino
================================================
#include "AlphaFairy.h"
#include "FairyMenu.h"

class AppFocusFrustration : public FairyMenuItem
{
    public:
        AppFocusFrustration() : FairyMenuItem("/focus_frust.png")
        {
        };

        virtual void on_navTo(void)
        {
            var_reset();
            FairyMenuItem::on_navTo();
        }

        virtual void on_eachFrame(void)
        {
            if (ptpcam.isOperating() == false) {
                var_reset();
                return;
            }
            if (ptpcam.has_property(SONYALPHA_PROPCODE_FocusFound) == false) {
                return;
            }

            cpufreq_boost();

            uint32_t x;

            /*
            track the card space and buffer space, reset the tap counter if the user actually takes a photo
            */
            uint32_t cardspace = 0;

            if (ptpcam.has_property(SONYALPHA_PROPCODE_ObjectInMemory))
            {
                x = ptpcam.get_property(SONYALPHA_PROPCODE_ObjectInMemory);
                if (x != _objinmem) {
                    // buffer space has changed
                    _cnt = 0;
                    _objinmem = x;
                    dbg_ser.printf("focusfrust obj in mem %u\r\n", x);
                }
            }

            if (ptpcam.has_property(SONYALPHA_PROPCODE_MemoryRemaining_Card1))
            {
                x = ptpcam.get_property(SONYALPHA_PROPCODE_MemoryRemaining_Card1);
                cardspace += x;
            }
            if (ptpcam.has_property(SONYALPHA_PROPCODE_MemoryRemaining_Card2))
            {
                x = ptpcam.get_property(SONYALPHA_PROPCODE_MemoryRemaining_Card2);
                cardspace += x;
            }
            if (cardspace != _cardrem) {
                // cardspace has changed
                _cnt = 0;
                _cardrem = cardspace;
                dbg_ser.printf("focusfrust card space %u\r\n", cardspace);
            }

            x = ptpcam.get_property(SONYALPHA_PROPCODE_FocusFound);
            uint32_t now = millis();
            if (x != SONYALPHA_FOCUSSTATUS_NONE)
            {
                // shutter button half press
                if (_state_prev == false) {
                    _last_time_on = now;
                    pwr_tick(true);
                    dbg_ser.printf("focusfrust pressed %u\r\n", now);
                }
                _state_prev = true;
            }
            else
            {
                // shutter button released
                if (_state_prev)
                {
                    pwr_tick(true);
                    if ((now - _last_time_on) < 500 && (now - _last_time_off) < 1000)
                    {
                        // the tap was rapid enough
                        dbg_ser.printf("focusfrust released %u\r\n", now);
                        _cnt += 1;
                    }
                    else
                    {
                        _cnt = 0;
                    }
                    _last_time_off = now;
                }
                _state_prev = false;
            }

            if (_cnt >= 4)
            {
                dbg_ser.println("focusfrust cnt reached limit");
                M5Lcd.fillRect(0, 0, M5Lcd.width(), 12, TFT_RED);
                execute();
                _cnt = 0;
                M5Lcd.fillRect(0, 0, M5Lcd.width(), 12, TFT_WHITE);
            }
        };

        virtual bool on_execute(void)
        {
            if (must_be_connected() == false) {
                return false;
            }

            execute();

            return false;
        };

    protected:

        bool     _state_prev    = false;
        uint32_t _last_time_on  = 0;
        uint32_t _last_time_off = 0;
        uin
Download .txt
gitextract_3jorpr5v/

├── .github/
│   └── ISSUE_TEMPLATE/
│       ├── bug_report.md
│       └── feature_request.md
├── .gitignore
├── Full-Features-Guide.md
├── INSTRUCTIONS.md
├── LICENSE
├── README.md
├── arduino_workspace/
│   ├── .gitignore
│   ├── AlphaFairy/
│   │   ├── AlphaFairy.h
│   │   ├── AlphaFairy.ino
│   │   ├── AlphaFairyCamera.cpp
│   │   ├── AlphaFairyCamera.h
│   │   ├── AppUtils.ino
│   │   ├── AutoConnect.ino
│   │   ├── Buttons.ino
│   │   ├── CamUtils.ino
│   │   ├── CmdlineHandlers.ino
│   │   ├── ConfigMenu.ino
│   │   ├── CpuFreq.ino
│   │   ├── DrawingUtils.ino
│   │   ├── DualShutter.ino
│   │   ├── FairyMenu.cpp
│   │   ├── FairyMenu.h
│   │   ├── FocusEncoder.ino
│   │   ├── FocusFrustration.ino
│   │   ├── FocusPull.ino
│   │   ├── FocusStack.ino
│   │   ├── HttpServer.ino
│   │   ├── InfoView.ino
│   │   ├── Intervalometer.ino
│   │   ├── Lepton.ino
│   │   ├── PowerMgmt.ino
│   │   ├── QuickRemote.ino
│   │   ├── RemoteShutter.ino
│   │   ├── Settings.ino
│   │   ├── ShutterStep.ino
│   │   ├── SoundTrigger.ino
│   │   ├── TallyLite.ino
│   │   ├── TimecodeReset.ino
│   │   ├── Trigger.ino
│   │   ├── WifiHandlers.ino
│   │   ├── WifiMenu.ino
│   │   ├── WifiUtils.ino
│   │   ├── alfy_conf.h
│   │   ├── alfy_defs.h
│   │   ├── alfy_types.h
│   │   └── data/
│   │       ├── chk2.txt
│   │       └── chk3.txt
│   ├── libraries/
│   │   ├── AlphaFairyImu/
│   │   │   ├── AlphaFairyImu.cpp
│   │   │   └── AlphaFairyImu.h
│   │   ├── AlphaFairy_NetMgr/
│   │   │   ├── AlphaFairy_NetMgr.cpp
│   │   │   └── AlphaFairy_NetMgr.h
│   │   ├── AsyncTCP/
│   │   │   ├── .gitignore
│   │   │   ├── .travis.yml
│   │   │   ├── CMakeLists.txt
│   │   │   ├── Kconfig.projbuild
│   │   │   ├── LICENSE
│   │   │   ├── README.md
│   │   │   ├── component.mk
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AsyncTCP.cpp
│   │   │       └── AsyncTCP.h
│   │   ├── DebuggingSerial/
│   │   │   ├── DebuggingSerial.cpp
│   │   │   ├── DebuggingSerial.h
│   │   │   └── DebuggingSerialDisable.cpp
│   │   ├── ESPAsyncWebServer/
│   │   │   ├── .gitignore
│   │   │   ├── .travis.yml
│   │   │   ├── CMakeLists.txt
│   │   │   ├── README.md
│   │   │   ├── _config.yml
│   │   │   ├── component.mk
│   │   │   ├── keywords.txt
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AsyncEventSource.cpp
│   │   │       ├── AsyncEventSource.h
│   │   │       ├── AsyncJson.h
│   │   │       ├── AsyncWebSocket.cpp
│   │   │       ├── AsyncWebSocket.h
│   │   │       ├── AsyncWebSynchronization.h
│   │   │       ├── ESPAsyncWebServer.h
│   │   │       ├── SPIFFSEditor.cpp
│   │   │       ├── SPIFFSEditor.h
│   │   │       ├── StringArray.h
│   │   │       ├── WebAuthentication.cpp
│   │   │       ├── WebAuthentication.h
│   │   │       ├── WebHandlerImpl.h
│   │   │       ├── WebHandlers.cpp
│   │   │       ├── WebRequest.cpp
│   │   │       ├── WebResponseImpl.h
│   │   │       ├── WebResponses.cpp
│   │   │       ├── WebServer.cpp
│   │   │       └── edit.htm
│   │   ├── FairyEncoder/
│   │   │   ├── FairyEncoder.cpp
│   │   │   └── FairyEncoder.h
│   │   ├── FairyKeyboard/
│   │   │   ├── FairyKeyboard.cpp
│   │   │   ├── FairyKeyboard.h
│   │   │   └── examples/
│   │   │       └── FairyKeyboardDemo/
│   │   │           └── FairyKeyboardDemo.ino
│   │   ├── Lepton/
│   │   │   ├── Lepton.h
│   │   │   ├── img/
│   │   │   │   ├── ColorT.h
│   │   │   │   ├── ColorT_0000_16.h
│   │   │   │   ├── ColorT_0001_15.h
│   │   │   │   ├── ColorT_0002_14.h
│   │   │   │   ├── ColorT_0003_13.h
│   │   │   │   ├── ColorT_0004_12.h
│   │   │   │   ├── ColorT_0005_11.h
│   │   │   │   ├── ColorT_0006_10.h
│   │   │   │   ├── ColorT_0007_9.h
│   │   │   │   ├── ColorT_0008_8.h
│   │   │   │   ├── ColorT_0009_7.h
│   │   │   │   ├── ColorT_0010_6.h
│   │   │   │   ├── ColorT_0011_5.h
│   │   │   │   ├── ColorT_0012_4.h
│   │   │   │   ├── ColorT_0013_3.h
│   │   │   │   ├── ColorT_0014_2.h
│   │   │   │   ├── ColorT_0015_1.h
│   │   │   │   └── ColorT_0016_0.h
│   │   │   ├── img_table.h
│   │   │   └── lepton.cpp
│   │   ├── M5DisplayExt/
│   │   │   ├── M5DisplayExt.cpp
│   │   │   ├── M5DisplayExt.h
│   │   │   ├── SpriteMgr.cpp
│   │   │   ├── SpriteMgr.h
│   │   │   └── utility/
│   │   │       ├── pngle.c
│   │   │       └── pngle.h
│   │   ├── M5StickC-Plus/
│   │   │   ├── LICENSE
│   │   │   ├── README.md
│   │   │   ├── library.json
│   │   │   ├── library.properties
│   │   │   └── src/
│   │   │       ├── AXP192.cpp
│   │   │       ├── AXP192.h
│   │   │       ├── Fonts/
│   │   │       │   ├── ASC16
│   │   │       │   ├── ASC16.h
│   │   │       │   ├── Custom/
│   │   │       │   │   ├── Orbitron_Light_24.h
│   │   │       │   │   ├── Orbitron_Light_32.h
│   │   │       │   │   ├── Roboto_Thin_24.h
│   │   │       │   │   ├── Satisfy_24.h
│   │   │       │   │   └── Yellowtail_32.h
│   │   │       │   ├── Font16.c
│   │   │       │   ├── Font16.h
│   │   │       │   ├── Font32rle.c
│   │   │       │   ├── Font32rle.h
│   │   │       │   ├── Font64rle.c
│   │   │       │   ├── Font64rle.h
│   │   │       │   ├── Font72rle.c
│   │   │       │   ├── Font72rle.h
│   │   │       │   ├── Font7srle.c
│   │   │       │   ├── Font7srle.h
│   │   │       │   ├── GFXFF/
│   │   │       │   │   ├── FreeMono12pt7b.h
│   │   │       │   │   ├── FreeMono18pt7b.h
│   │   │       │   │   ├── FreeMono24pt7b.h
│   │   │       │   │   ├── FreeMono9pt7b.h
│   │   │       │   │   ├── FreeMonoBold12pt7b.h
│   │   │       │   │   ├── FreeMonoBold18pt7b.h
│   │   │       │   │   ├── FreeMonoBold24pt7b.h
│   │   │       │   │   ├── FreeMonoBold9pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique12pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique18pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique24pt7b.h
│   │   │       │   │   ├── FreeMonoBoldOblique9pt7b.h
│   │   │       │   │   ├── FreeMonoOblique12pt7b.h
│   │   │       │   │   ├── FreeMonoOblique18pt7b.h
│   │   │       │   │   ├── FreeMonoOblique24pt7b.h
│   │   │       │   │   ├── FreeMonoOblique9pt7b.h
│   │   │       │   │   ├── FreeSans12pt7b.h
│   │   │       │   │   ├── FreeSans18pt7b.h
│   │   │       │   │   ├── FreeSans24pt7b.h
│   │   │       │   │   ├── FreeSans9pt7b.h
│   │   │       │   │   ├── FreeSansBold12pt7b.h
│   │   │       │   │   ├── FreeSansBold18pt7b.h
│   │   │       │   │   ├── FreeSansBold24pt7b.h
│   │   │       │   │   ├── FreeSansBold9pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique12pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique18pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique24pt7b.h
│   │   │       │   │   ├── FreeSansBoldOblique9pt7b.h
│   │   │       │   │   ├── FreeSansOblique12pt7b.h
│   │   │       │   │   ├── FreeSansOblique18pt7b.h
│   │   │       │   │   ├── FreeSansOblique24pt7b.h
│   │   │       │   │   ├── FreeSansOblique9pt7b.h
│   │   │       │   │   ├── FreeSerif12pt7b.h
│   │   │       │   │   ├── FreeSerif18pt7b.h
│   │   │       │   │   ├── FreeSerif24pt7b.h
│   │   │       │   │   ├── FreeSerif9pt7b.h
│   │   │       │   │   ├── FreeSerifBold12pt7b.h
│   │   │       │   │   ├── FreeSerifBold18pt7b.h
│   │   │       │   │   ├── FreeSerifBold24pt7b.h
│   │   │       │   │   ├── FreeSerifBold9pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic12pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic18pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic24pt7b.h
│   │   │       │   │   ├── FreeSerifBoldItalic9pt7b.h
│   │   │       │   │   ├── FreeSerifItalic12pt7b.h
│   │   │       │   │   ├── FreeSerifItalic18pt7b.h
│   │   │       │   │   ├── FreeSerifItalic24pt7b.h
│   │   │       │   │   ├── FreeSerifItalic9pt7b.h
│   │   │       │   │   ├── TomThumb.h
│   │   │       │   │   ├── gfxfont.h
│   │   │       │   │   ├── license.txt
│   │   │       │   │   └── print.txt
│   │   │       │   ├── HZK16
│   │   │       │   ├── HZK16.h
│   │   │       │   ├── TrueType/
│   │   │       │   │   └── Not_yet_supported.txt
│   │   │       │   └── glcdfont.c
│   │   │       ├── M5Display.cpp
│   │   │       ├── M5Display.h
│   │   │       ├── M5StickCPlus.cpp
│   │   │       ├── M5StickCPlus.h
│   │   │       ├── RTC.cpp
│   │   │       ├── RTC.h
│   │   │       └── utility/
│   │   │           ├── Button.cpp
│   │   │           ├── Button.h
│   │   │           ├── Config.h
│   │   │           ├── In_eSPI.cpp
│   │   │           ├── In_eSPI.h
│   │   │           ├── In_eSPI_Setup.h
│   │   │           ├── MPU6886.cpp
│   │   │           ├── MPU6886.h
│   │   │           ├── MahonyAHRS.cpp
│   │   │           ├── MahonyAHRS.h
│   │   │           ├── ST7735_Defines.h
│   │   │           ├── ST7735_Init.h
│   │   │           ├── ST7735_Rotation.h
│   │   │           ├── ST7789_Defines.h
│   │   │           ├── ST7789_Init.h
│   │   │           ├── ST7789_Rotation.h
│   │   │           ├── Speaker.cpp
│   │   │           ├── Speaker.h
│   │   │           ├── Sprite.cpp
│   │   │           ├── Sprite.h
│   │   │           ├── qrcode.c
│   │   │           └── qrcode.h
│   │   ├── PtpIpCamera/
│   │   │   ├── PtpIpCamera.cpp
│   │   │   ├── PtpIpCamera.h
│   │   │   ├── PtpIpCameraSend.cpp
│   │   │   ├── PtpIpSonyAlphaCamera.cpp
│   │   │   ├── PtpIpSonyAlphaCamera.h
│   │   │   ├── PtpIpSonyAlphaCameraPropDecoder.cpp
│   │   │   ├── PtpIpSonyAlphaCameraSend.cpp
│   │   │   ├── examples/
│   │   │   │   ├── PtpApDemo/
│   │   │   │   │   └── PtpApDemo.ino
│   │   │   │   └── PtpStaDemo/
│   │   │   │       └── PtpStaDemo.ino
│   │   │   ├── ptpcodes.h
│   │   │   ├── ptpip_utils.cpp
│   │   │   ├── ptpip_utils.h
│   │   │   ├── ptpipdefs.h
│   │   │   └── ptpsonycodes.h
│   │   ├── SerialCmdLine/
│   │   │   ├── SerialCmdLine.cpp
│   │   │   └── SerialCmdLine.h
│   │   ├── SonyCameraInfraredRemote/
│   │   │   ├── SonyCameraInfraredRemote.cpp
│   │   │   └── SonyCameraInfraredRemote.h
│   │   ├── SonyHttpCamera/
│   │   │   ├── SonyHttpCamera.cpp
│   │   │   ├── SonyHttpCamera.h
│   │   │   ├── SonyHttpCameraCmds.cpp
│   │   │   ├── SonyHttpCameraInit.cpp
│   │   │   ├── SonyHttpCameraUtils.cpp
│   │   │   └── examples/
│   │   │       └── HttpJsonDemo/
│   │   │           └── HttpJsonDemo.ino
│   │   └── Wire/
│   │       ├── keywords.txt
│   │       ├── library.properties
│   │       └── src/
│   │           ├── Wire.cpp
│   │           └── Wire.h
│   └── tools/
│       └── ESP32FS/
│           └── tool/
│               └── esp32fs.jar
├── doc/
│   ├── Camera-Reverse-Engineering.md
│   ├── Firmware-Engineering.md
│   ├── M5StickC-MicroPython-Sucks-Rant.md
│   ├── Shutter-Release-Cable-Connector.md
│   ├── Wishlist-for-Sony-Mirrorless-Camera-Remote-Protocol.md
│   └── img/
│       └── menu_map.afdesign
├── platformio.ini
└── screens_240/
    ├── dual_shutter.afphoto
    ├── png2jpg.py
    └── shutter_step.afphoto
Download .txt
SYMBOL INDEX (400 symbols across 64 files)

FILE: arduino_workspace/AlphaFairy/AlphaFairyCamera.cpp
  function get_idx_in_str_tbl (line 340) | static int get_idx_in_str_tbl(char* tbl_ptr, uint32_t x, uint32_t cvt_mode)
  function get_str_at_tbl_idx (line 441) | static bool get_str_at_tbl_idx(char* tbl, int idx, char* dst)
  function get_val_at_tbl_idx (line 478) | static bool get_val_at_tbl_idx(char* tbl, int idx, void* dst, uint32_t c...

FILE: arduino_workspace/AlphaFairy/AlphaFairyCamera.h
  function class (line 12) | class AlphaFairyCamera

FILE: arduino_workspace/AlphaFairy/FairyMenu.cpp
  function FairyItem (line 134) | FairyItem* FairySubmenu::nav_next(void)
  function FairyItem (line 155) | FairyItem* FairySubmenu::nav_prev(void)

FILE: arduino_workspace/AlphaFairy/FairyMenu.h
  function class (line 14) | class FairyItem
  type FairyItemNode_t (line 38) | typedef struct
  function class (line 46) | class FairyMenuItem : public FairyItem
  function class (line 82) | class FairySubmenu : public FairyMenuItem
  function class (line 111) | class FairyCfgItem : public FairyItem
  function class (line 163) | class FairyCfgApp : public FairySubmenu

FILE: arduino_workspace/AlphaFairy/alfy_types.h
  type configsettings_t (line 9) | typedef struct
  type speed_t (line 82) | typedef struct
  type wifiprofile_t (line 90) | typedef struct

FILE: arduino_workspace/libraries/AlphaFairyImu/AlphaFairyImu.h
  function class (line 7) | class AlphaFairyImu

FILE: arduino_workspace/libraries/AlphaFairy_NetMgr/AlphaFairy_NetMgr.cpp
  function NetMgr_beginAP (line 48) | void NetMgr_beginAP(char* ssid, char* password)
  function NetMgr_beginSTA (line 65) | void NetMgr_beginSTA(char* ssid, char* password)
  function NetMgr_regCallback (line 83) | void NetMgr_regCallback(void(*cb_evt)(void), void(*cb_disconn)(uint8_t, ...
  function NetMgr_taskAP (line 94) | void NetMgr_taskAP()
  function NetMgr_taskSTA (line 116) | void NetMgr_taskSTA()
  function NetMgr_eventHandler (line 148) | void NetMgr_eventHandler(WiFiEvent_t event, WiFiEventInfo_t info)
  function NetMgr_task (line 203) | void NetMgr_task()
  function NetMgr_findInTableSta (line 226) | int8_t NetMgr_findInTableSta(tcpip_adapter_sta_info_t* sta)
  function NetMgr_insertInTableSta (line 238) | int8_t NetMgr_insertInTableSta(tcpip_adapter_sta_info_t* sta)
  function NetMgr_findInTableIp (line 262) | int8_t NetMgr_findInTableIp(uint32_t ip)
  function NetMgr_tableSync (line 274) | void NetMgr_tableSync(tcpip_adapter_sta_list_t* lst)
  function NetMgr_getConnectableClient (line 299) | uint32_t NetMgr_getConnectableClient(void)
  function NetMgr_markClientCameraPtp (line 315) | void NetMgr_markClientCameraPtp(uint32_t ip)
  function NetMgr_markClientCameraHttp (line 325) | void NetMgr_markClientCameraHttp(uint32_t ip)
  function NetMgr_markClientPhoneHttp (line 335) | void NetMgr_markClientPhoneHttp(uint32_t ip)
  function NetMgr_markClientError (line 345) | void NetMgr_markClientError(uint32_t ip)
  function NetMgr_markClientDisconnect (line 355) | void NetMgr_markClientDisconnect(uint32_t ip)
  function NetMgr_shouldReportError (line 366) | bool NetMgr_shouldReportError(void)
  function NetMgr_hasActiveClients (line 385) | bool NetMgr_hasActiveClients(void)
  function NetMgr_getOpMode (line 397) | uint8_t NetMgr_getOpMode()
  function NetMgr_reset (line 402) | void NetMgr_reset()
  function NetMgr_reboot (line 409) | void NetMgr_reboot()
  function NetMgr_setWifiPower (line 424) | void NetMgr_setWifiPower(wifi_power_t pwr)
  function NetMgr_getRssi (line 431) | bool NetMgr_getRssi(uint32_t ip, int* outres)
  function WiFiUDP (line 486) | WiFiUDP* NetMgr_getSsdpSock() {

FILE: arduino_workspace/libraries/AsyncTCP/src/AsyncTCP.cpp
  function _init_async_event_queue (line 96) | static inline bool _init_async_event_queue(){
  function _send_async_event (line 106) | static inline bool _send_async_event(lwip_event_packet_t ** e){
  function _prepend_async_event (line 110) | static inline bool _prepend_async_event(lwip_event_packet_t ** e){
  function _get_async_event (line 114) | static inline bool _get_async_event(lwip_event_packet_t ** e){
  function _remove_events_with_arg (line 118) | static bool _remove_events_with_arg(void * arg){
  function _handle_async_event (line 154) | static void _handle_async_event(lwip_event_packet_t * e){
  function _async_service_task (line 188) | static void _async_service_task(void *pvParameters){
  function _start_async_task (line 216) | static bool _start_async_task(){
  function _tcp_clear_events (line 233) | static int8_t _tcp_clear_events(void * arg) {
  function _tcp_connected (line 243) | static int8_t _tcp_connected(void * arg, tcp_pcb * pcb, int8_t err) {
  function _tcp_poll (line 256) | static int8_t _tcp_poll(void * arg, struct tcp_pcb * pcb) {
  function _tcp_recv (line 268) | static int8_t _tcp_recv(void * arg, struct tcp_pcb * pcb, struct pbuf *p...
  function _tcp_sent (line 291) | static int8_t _tcp_sent(void * arg, struct tcp_pcb * pcb, uint16_t len) {
  function _tcp_error (line 304) | static void _tcp_error(void * arg, int8_t err) {
  function _tcp_dns_found (line 315) | static void _tcp_dns_found(const char * name, struct ip_addr * ipaddr, v...
  function _tcp_accept (line 332) | static int8_t _tcp_accept(void * arg, AsyncClient * client) {
  type tcpip_api_call_data (line 350) | struct tcpip_api_call_data
  function err_t (line 374) | static err_t _tcp_output_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 383) | static esp_err_t _tcp_output(tcp_pcb * pcb, int8_t closed_slot) {
  function err_t (line 394) | static err_t _tcp_write_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 403) | static esp_err_t _tcp_write(tcp_pcb * pcb, int8_t closed_slot, const cha...
  function err_t (line 417) | static err_t _tcp_recved_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 427) | static esp_err_t _tcp_recved(tcp_pcb * pcb, int8_t closed_slot, size_t l...
  function err_t (line 439) | static err_t _tcp_close_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 448) | static esp_err_t _tcp_close(tcp_pcb * pcb, int8_t closed_slot) {
  function err_t (line 459) | static err_t _tcp_abort_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 468) | static esp_err_t _tcp_abort(tcp_pcb * pcb, int8_t closed_slot) {
  function err_t (line 479) | static err_t _tcp_connect_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 485) | static esp_err_t _tcp_connect(tcp_pcb * pcb, int8_t closed_slot, ip_addr...
  function err_t (line 499) | static err_t _tcp_bind_api(struct tcpip_api_call_data *api_call_msg){
  function esp_err_t (line 505) | static esp_err_t _tcp_bind(tcp_pcb * pcb, ip_addr_t * addr, uint16_t por...
  function err_t (line 518) | static err_t _tcp_listen_api(struct tcpip_api_call_data *api_call_msg){
  function tcp_pcb (line 525) | static tcp_pcb * _tcp_listen_with_backlog(tcp_pcb * pcb, uint8_t backlog) {
  function AsyncClient (line 592) | AsyncClient& AsyncClient::operator=(const AsyncClient& other){
  function AsyncClient (line 614) | AsyncClient & AsyncClient::operator+=(const AsyncClient &other) {
  type pbuf (line 786) | struct pbuf
  type ip_addr (line 980) | struct ip_addr
  function IPAddress (line 1095) | IPAddress AsyncClient::remoteIP() {
  function IPAddress (line 1103) | IPAddress AsyncClient::localIP() {
  type ip_addr (line 1201) | struct ip_addr
  type tcp_pcb (line 1205) | struct tcp_pcb
  type tcp_pcb (line 1209) | struct tcp_pcb
  type pbuf (line 1209) | struct pbuf
  type tcp_pcb (line 1213) | struct tcp_pcb
  type tcp_pcb (line 1217) | struct tcp_pcb
  type tcp_pcb (line 1221) | struct tcp_pcb

FILE: arduino_workspace/libraries/AsyncTCP/src/AsyncTCP.h
  type std (line 45) | typedef std::function<void(void*, AsyncClient*)> AcConnectHandler;
  type std (line 46) | typedef std::function<void(void*, AsyncClient*, size_t len, uint32_t tim...
  type std (line 47) | typedef std::function<void(void*, AsyncClient*, int8_t error)> AcErrorHa...
  type std (line 48) | typedef std::function<void(void*, AsyncClient*, void *data, size_t len)>...
  type std (line 49) | typedef std::function<void(void*, AsyncClient*, struct pbuf *pb)> AcPack...
  type std (line 50) | typedef std::function<void(void*, AsyncClient*, uint32_t time)> AcTimeou...
  type tcp_pcb (line 52) | struct tcp_pcb
  type ip_addr (line 53) | struct ip_addr
  function class (line 55) | class AsyncClient {
  function class (line 188) | class AsyncServer {

FILE: arduino_workspace/libraries/DebuggingSerial/DebuggingSerial.h
  function class (line 12) | class DebuggingSerial : public HardwareSerial
  function class (line 24) | class DebuggingSerialDisabled : public HardwareSerial

FILE: arduino_workspace/libraries/DebuggingSerial/DebuggingSerialDisable.cpp
  type tm (line 35) | struct tm
  type tm (line 49) | struct tm

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp
  function String (line 23) | static String generateEventMessage(const char *message, const char *even...

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncEventSource.h
  type std (line 51) | typedef std::function<void(AsyncEventSourceClient *client)> ArEventHandl...
  function class (line 53) | class AsyncEventSourceMessage {
  function class (line 69) | class AsyncEventSourceClient {
  function class (line 98) | class AsyncEventSource: public AsyncWebHandler {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncJson.h
  function class (line 55) | class ChunkPrint : public Print {
  function class (line 82) | class AsyncJsonResponse: public AsyncAbstractResponse {
  function setLength (line 119) | size_t setLength() {
  function getSize (line 131) | size_t getSize() { return _jsonBuffer.size(); }
  function _fillBuffer (line 133) | size_t _fillBuffer(uint8_t *data, size_t len){
  function class (line 145) | class PrettyAsyncJsonResponse: public AsyncJsonResponse {
  type std (line 172) | typedef std::function<void(AsyncWebServerRequest *request, JsonVariant &...
  function class (line 174) | class AsyncCallbackJsonWebHandler: public AsyncWebHandler {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp
  function webSocketSendFrameWindow (line 34) | size_t webSocketSendFrameWindow(AsyncClient *client){
  function webSocketSendFrame (line 43) | size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcod...
  class AsyncWebSocketControl (line 233) | class AsyncWebSocketControl {
    method AsyncWebSocketControl (line 241) | AsyncWebSocketControl(uint8_t opcode, uint8_t *data=NULL, size_t len=0...
    method finished (line 262) | virtual bool finished() const { return _finished; }
    method opcode (line 263) | uint8_t opcode(){ return _opcode; }
    method len (line 264) | uint8_t len(){ return _len + 2; }
    method send (line 265) | size_t send(AsyncClient *client){
  function IPAddress (line 830) | IPAddress AsyncWebSocketClient::remoteIP() {
  function AsyncWebSocketClient (line 899) | AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id){
  function AsyncWebSocketMessageBuffer (line 1198) | AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size)
  function AsyncWebSocketMessageBuffer (line 1208) | AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data,...

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h
  type AwsFrameInfo (line 54) | typedef struct {
  type AwsClientStatus (line 78) | typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClie...
  type AwsFrameType (line 79) | typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08...
  type AwsMessageStatus (line 80) | typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageSta...
  type AwsEventType (line 81) | typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ER...
  function class (line 83) | class AsyncWebSocketMessageBuffer {
  function lock (line 100) | void lock() { _lock = true; }
  function unlock (line 101) | void unlock() { _lock = false; }
  function length (line 103) | size_t length() { return _len; }
  function count (line 104) | uint32_t count() { return _count; }
  function canDelete (line 105) | bool canDelete() { return (!_count && !_lock); }
  function class (line 111) | class AsyncWebSocketMessage {
  function class (line 125) | class AsyncWebSocketBasicMessage: public AsyncWebSocketMessage {
  function class (line 141) | class AsyncWebSocketMultiMessage: public AsyncWebSocketMessage {
  function class (line 157) | class AsyncWebSocketClient {
  type std (line 240) | typedef std::function<void(AsyncWebSocket * server, AsyncWebSocketClient...
  function class (line 243) | class AsyncWebSocket: public AsyncWebHandler {
  function class (line 338) | class AsyncWebSocketResponse: public AsyncWebServerResponse {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h
  function class (line 11) | class AsyncWebLock
  function class (line 47) | class AsyncWebLock
  function class (line 66) | class AsyncWebLockGuard

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h
  type WebRequestMethod (line 61) | typedef enum {
  type WebRequestMethodComposite (line 76) | typedef uint8_t WebRequestMethodComposite;
  type std (line 77) | typedef std::function<void(void)> ArDisconnectHandler;
  function class (line 83) | class AsyncWebParameter {
  function class (line 105) | class AsyncWebHeader {
  type RequestedConnectionType (line 129) | typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT...
  type std (line 131) | typedef std::function<size_t(uint8_t*, size_t, size_t)> AwsResponseFiller;
  type std (line 132) | typedef std::function<String(const String&)> AwsTemplateProcessor;
  function class (line 134) | class AsyncWebServerRequest {
  function class (line 330) | class AsyncWebHandler {
  type WebResponseState (line 354) | typedef enum {
  function class (line 358) | class AsyncWebServerResponse {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/SPIFFSEditor.cpp
  type ExcludeListS (line 272) | struct ExcludeListS {
  function matchWild (line 279) | static bool matchWild(const char *pattern, const char *testee) {
  function addExclude (line 301) | static bool addExclude(const char *item){
  function loadExcludeList (line 321) | static void loadExcludeList(fs::FS &_fs, const char *filename){
  function isExcluded (line 363) | static bool isExcluded(fs::FS &_fs, const char *filename) {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/SPIFFSEditor.h
  function class (line 5) | class SPIFFSEditor: public AsyncWebHandler {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/StringArray.h
  type std (line 42) | typedef std::function<void(const T&)> OnRemove;
  type std (line 43) | typedef std::function<bool(const T&)> Predicate;
  function class (line 48) | class Iterator {
  function length (line 83) | size_t length() const {
  function count_if (line 92) | size_t count_if(Predicate predicate) const {
  function T (line 106) | const T* nth(size_t N) const {
  function remove (line 116) | bool remove(const T& t){
  function remove_first (line 139) | bool remove_first(Predicate predicate){
  function free (line 161) | void free(){
  function class (line 175) | class StringArray : public LinkedList<String> {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp
  function checkBasicAuthentication (line 32) | bool checkBasicAuthentication(const char * hash, const char * username, ...
  function getMD5 (line 61) | static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 byt...
  function String (line 89) | static String genRandomMD5(){
  function String (line 103) | static String stringMD5(const String& in){
  function String (line 112) | String generateDigestHash(const char * username, const char * password, ...
  function String (line 130) | String requestDigestAuthentication(const char * realm){
  function checkDigestAuthentication (line 144) | bool checkDigestAuthentication(const char * header, const char * method,...

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h
  function class (line 32) | class AsyncStaticWebHandler: public AsyncWebHandler {
  function class (line 66) | class AsyncCallbackWebHandler: public AsyncWebHandler {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebHandlers.cpp
  function AsyncStaticWebHandler (line 45) | AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){
  function AsyncStaticWebHandler (line 50) | AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char*...
  function AsyncStaticWebHandler (line 55) | AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char...
  function AsyncStaticWebHandler (line 60) | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char...
  function AsyncStaticWebHandler (line 65) | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm*...
  function AsyncStaticWebHandler (line 72) | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t las...
  function AsyncStaticWebHandler (line 76) | AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebRequest.cpp
  function strContains (line 294) | bool strContains(String src, String find, bool mindcase = true) {
  function AsyncWebHeader (line 621) | AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) con...
  function AsyncWebHeader (line 630) | AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelp...
  function AsyncWebHeader (line 644) | AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const {
  function AsyncWebParameter (line 678) | AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, b...
  function AsyncWebParameter (line 687) | AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHe...
  function AsyncWebParameter (line 701) | AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const {
  function AsyncWebServerResponse (line 729) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, ...
  function AsyncWebServerResponse (line 733) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, co...
  function AsyncWebServerResponse (line 739) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File conte...
  function AsyncWebServerResponse (line 745) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &st...
  function AsyncWebServerResponse (line 749) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const Stri...
  function AsyncWebServerResponse (line 753) | AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(con...
  function AsyncResponseStream (line 759) | AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const S...
  function AsyncWebServerResponse (line 763) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code...
  function AsyncWebServerResponse (line 767) | AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code...
  function String (line 888) | const String& AsyncWebServerRequest::arg(const String& name) const {
  function String (line 897) | const String& AsyncWebServerRequest::arg(const __FlashStringHelper * dat...
  function String (line 912) | const String& AsyncWebServerRequest::arg(size_t i) const {
  function String (line 916) | const String& AsyncWebServerRequest::argName(size_t i) const {
  function String (line 920) | const String& AsyncWebServerRequest::pathArg(size_t i) const {
  function String (line 925) | const String& AsyncWebServerRequest::header(const char* name) const {
  function String (line 930) | const String& AsyncWebServerRequest::header(const __FlashStringHelper * ...
  function String (line 945) | const String& AsyncWebServerRequest::header(size_t i) const {
  function String (line 950) | const String& AsyncWebServerRequest::headerName(size_t i) const {
  function String (line 955) | String AsyncWebServerRequest::urlDecode(const String& text) const {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebResponseImpl.h
  function class (line 32) | class AsyncBasicResponse: public AsyncWebServerResponse {
  function class (line 42) | class AsyncAbstractResponse: public AsyncWebServerResponse {
  function class (line 67) | class AsyncFileResponse: public AsyncAbstractResponse {
  function class (line 82) | class AsyncStreamResponse: public AsyncAbstractResponse {
  function class (line 91) | class AsyncCallbackResponse: public AsyncAbstractResponse {
  function class (line 101) | class AsyncChunkedResponse: public AsyncAbstractResponse {
  function class (line 111) | class AsyncProgmemResponse: public AsyncAbstractResponse {

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebResponses.cpp
  function String (line 126) | String AsyncWebServerResponse::_assembleHead(uint8_t version){

FILE: arduino_workspace/libraries/ESPAsyncWebServer/src/WebServer.cpp
  function ON_STA_FILTER (line 24) | bool ON_STA_FILTER(AsyncWebServerRequest *request) {
  function ON_AP_FILTER (line 28) | bool ON_AP_FILTER(AsyncWebServerRequest *request) {
  function AsyncWebRewrite (line 60) | AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){
  function AsyncWebRewrite (line 69) | AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){
  function AsyncWebHandler (line 73) | AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){
  function AsyncCallbackWebHandler (line 127) | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestM...
  function AsyncCallbackWebHandler (line 138) | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestM...
  function AsyncCallbackWebHandler (line 148) | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestM...
  function AsyncCallbackWebHandler (line 157) | AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHa...
  function AsyncStaticWebHandler (line 165) | AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::...

FILE: arduino_workspace/libraries/FairyEncoder/FairyEncoder.h
  function class (line 10) | class FairyEncoder

FILE: arduino_workspace/libraries/FairyKeyboard/FairyKeyboard.h
  function class (line 22) | class FairyKeyboard

FILE: arduino_workspace/libraries/Lepton/Lepton.h
  function class (line 16) | class Lepton {

FILE: arduino_workspace/libraries/Lepton/lepton.cpp
  function ESP_DelayUS (line 19) | static void ESP_DelayUS(uint64_t us)
  function onVsyncISR (line 46) | static void IRAM_ATTR onVsyncISR()
  function WaitForVsync (line 181) | bool WaitForVsync()

FILE: arduino_workspace/libraries/M5DisplayExt/M5DisplayExt.cpp
  function read16 (line 7) | uint16_t read16(fs::File &f) {
  function read32 (line 14) | uint32_t read32(fs::File &f) {
  function jpgReadFile (line 143) | static uint32_t jpgReadFile(JDEC *decoder, uint8_t *buf, uint32_t len) {
  function jpgRead (line 154) | static uint32_t jpgRead(JDEC *decoder, uint8_t *buf, uint32_t len) {
  function jpgWrite (line 163) | static uint32_t jpgWrite(JDEC *decoder, void *bitmap, JRECT *rect) {
  function jpgDecode (line 235) | static bool jpgDecode(jpg_file_decoder_t *jpeg,
  type _png_draw_params (line 323) | struct _png_draw_params {
  function pngle_draw_callback (line 336) | static void pngle_draw_callback(pngle_t *pngle, uint32_t x, uint32_t y, ...

FILE: arduino_workspace/libraries/M5DisplayExt/M5DisplayExt.h
  function class (line 10) | class M5DisplayExt : public M5Display {

FILE: arduino_workspace/libraries/M5DisplayExt/SpriteMgr.cpp
  function TFT_eSprite (line 94) | TFT_eSprite* SpriteMgr::get(const char* fp)
  function sprmgr_item_t (line 107) | sprmgr_item_t* SpriteMgr::last(void)
  function fletcher16_str (line 150) | static uint16_t fletcher16_str(const uint8_t* data)

FILE: arduino_workspace/libraries/M5DisplayExt/SpriteMgr.h
  type sprmgr_item_t (line 12) | typedef struct
  function class (line 21) | class SpriteMgr

FILE: arduino_workspace/libraries/M5DisplayExt/utility/pngle.c
  type pngle_state_t (line 49) | typedef enum {
  type pngle_chunk_t (line 59) | typedef enum {
  type _pngle_t (line 72) | struct _pngle_t {
  function read_uint8 (line 131) | static inline uint8_t  read_uint8(const uint8_t *p)
  function read_uint32 (line 136) | static inline uint32_t read_uint32(const uint8_t *p)
  function U32_CLAMP_ADD (line 145) | static inline uint32_t U32_CLAMP_ADD(uint32_t a, uint32_t b, uint32_t top)
  function pngle_reset (line 154) | void pngle_reset(pngle_t *pngle)
  function pngle_t (line 186) | pngle_t *pngle_new()
  function pngle_destroy (line 196) | void pngle_destroy(pngle_t *pngle)
  function pngle_get_width (line 210) | uint32_t pngle_get_width(pngle_t *pngle)
  function pngle_get_height (line 216) | uint32_t pngle_get_height(pngle_t *pngle)
  function pngle_ihdr_t (line 222) | pngle_ihdr_t *pngle_get_ihdr(pngle_t *pngle)
  function is_trans_color (line 230) | static int is_trans_color(pngle_t *pngle, uint16_t *value, size_t n)
  function scanline_ringbuf_push (line 240) | static inline void scanline_ringbuf_push(pngle_t *pngle, uint8_t value)
  function get_value (line 246) | static inline uint16_t get_value(pngle_t *pngle, size_t *ridx, int *bitc...
  function pngle_draw_pixels (line 280) | static int pngle_draw_pixels(pngle_t *pngle, size_t scanline_ringbuf_xidx)
  function paeth (line 353) | static inline int paeth(int a, int b, int c)
  function set_interlace_pass (line 365) | static int set_interlace_pass(pngle_t *pngle, uint_fast8_t pass)
  function setup_gamma_table (line 388) | static int setup_gamma_table(pngle_t *pngle, uint32_t png_gamma)
  function pngle_on_data (line 414) | static int pngle_on_data(pngle_t *pngle, const uint8_t *p, int len)
  function pngle_handle_chunk (line 490) | static int pngle_handle_chunk(pngle_t *pngle, const uint8_t *buf, size_t...
  function pngle_feed_internal (line 663) | static int pngle_feed_internal(pngle_t *pngle, const uint8_t *buf, size_...
  function pngle_feed (line 816) | int pngle_feed(pngle_t *pngle, const void *buf, size_t len)
  function pngle_set_display_gamma (line 835) | void pngle_set_display_gamma(pngle_t *pngle, double display_gamma)
  function pngle_set_init_callback (line 845) | void pngle_set_init_callback(pngle_t *pngle, pngle_init_callback_t callb...
  function pngle_set_draw_callback (line 851) | void pngle_set_draw_callback(pngle_t *pngle, pngle_draw_callback_t callb...
  function pngle_set_done_callback (line 857) | void pngle_set_done_callback(pngle_t *pngle, pngle_done_callback_t callb...
  function pngle_set_user_data (line 863) | void pngle_set_user_data(pngle_t *pngle, void *user_data)

FILE: arduino_workspace/libraries/M5DisplayExt/utility/pngle.h
  type pngle_t (line 35) | typedef struct _pngle_t pngle_t;
  type pngle_ihdr_t (line 68) | typedef struct _pngle_ihdr_t {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/AXP192.h
  function class (line 12) | class AXP192 {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/gfxfont.h
  type GFXglyph (line 14) | typedef struct {              // Data stored PER GLYPH
  type GFXfont (line 21) | typedef struct {           // Data stored for FONT AS A WHOLE:

FILE: arduino_workspace/libraries/M5StickC-Plus/src/M5Display.cpp
  function read16 (line 470) | uint16_t read16(fs::File &f) {
  function read32 (line 477) | uint32_t read32(fs::File &f) {
  function jpgReadFile (line 596) | static uint32_t jpgReadFile(JDEC *decoder, uint8_t *buf, uint32_t len) {
  function jpgRead (line 607) | static uint32_t jpgRead(JDEC *decoder, uint8_t *buf, uint32_t len) {
  function jpgWrite (line 616) | static uint32_t jpgWrite(JDEC *decoder, void *bitmap, JRECT *rect) {
  function jpgDecode (line 688) | static bool jpgDecode(jpg_file_decoder_t *jpeg,

FILE: arduino_workspace/libraries/M5StickC-Plus/src/M5Display.h
  type jpeg_div_t (line 12) | typedef enum {
  type Hzk16Types (line 21) | typedef enum
  function class (line 28) | class M5Display : public TFT_eSPI {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/M5StickCPlus.h
  function class (line 85) | class M5StickCPlus {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/RTC.h
  type RTC_TimeTypeDef (line 6) | typedef struct {
  type RTC_DateTypeDef (line 12) | typedef struct {
  function class (line 19) | class RTC {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/Button.h
  function class (line 18) | class Button {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/In_eSPI.cpp
  function readByte (line 766) | uint8_t readByte(void) {
  function busDir (line 796) | void busDir(uint32_t mask, uint8_t mode) {
  function writeBlock (line 5134) | void writeBlock(uint16_t color, uint32_t repeat) {
  function writeBlock (line 5187) | void writeBlock(uint16_t color, uint32_t repeat) {
  function writeBlock (line 5243) | void writeBlock(uint16_t color, uint32_t repeat) {
  function writeBlock (line 5312) | void writeBlock(uint16_t color, uint32_t repeat) {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/In_eSPI.h
  function swap_coord (line 628) | inline void swap_coord(T &a, T &b) {
  type setup_t (line 642) | typedef struct {
  type fontinfo (line 704) | typedef struct {
  function class (line 759) | class TFT_eSPI : public Print {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/MPU6886.h
  function class (line 52) | class MPU6886 {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/MahonyAHRS.cpp
  function MahonyAHRSupdate (line 53) | void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay,
  function MahonyAHRSupdateIMU (line 163) | void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay,
  function invSqrt (line 252) | float invSqrt(float x) {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/Speaker.h
  function class (line 15) | class SPEAKER {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/Sprite.h
  function class (line 11) | class TFT_eSprite : public TFT_eSPI {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/qrcode.c
  function max (line 111) | static int max(int a, int b) {
  function getAlphanumeric (line 127) | static int8_t getAlphanumeric(char c) {
  function isAlphanumeric (line 159) | static bool isAlphanumeric(const char *text, uint16_t length) {
  function isNumeric (line 168) | static bool isNumeric(const char *text, uint16_t length) {
  function getModeBits (line 185) | static char getModeBits(uint8_t version, uint8_t mode) {
  type BitBucket (line 213) | typedef struct BitBucket {
  function bb_getGridSizeBytes (line 230) | static uint16_t bb_getGridSizeBytes(uint8_t size) {
  function bb_getBufferSizeBytes (line 234) | static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
  function bb_initBuffer (line 238) | static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data,
  function bb_initGrid (line 247) | static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
  function bb_appendBits (line 255) | static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t le...
  function bb_setBit (line 269) | static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
  function bb_invertBit (line 279) | static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y,
  function bb_getBit (line 292) | static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
  function applyMask (line 304) | static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t...
  function setFunctionModule (line 345) | static void setFunctionModule(BitBucket *modules, BitBucket *isFunction,
  function drawFinderPattern (line 353) | static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction,
  function drawAlignmentPattern (line 370) | static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction,
  function drawFormatBits (line 382) | static void drawFormatBits(BitBucket *modules, BitBucket *isFunction,
  function drawVersion (line 427) | static void drawVersion(BitBucket *modules, BitBucket *isFunction,
  function drawFunctionPatterns (line 458) | static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction,
  function drawCodewords (line 524) | static void drawCodewords(BitBucket *modules, BitBucket *isFunction,
  function getPenaltyScore (line 570) | static uint32_t getPenaltyScore(BitBucket *modules) {
  function rs_multiply (line 663) | static uint8_t rs_multiply(uint8_t x, uint8_t y) {
  function rs_init (line 674) | static void rs_init(uint8_t degree, uint8_t *coeff) {
  function rs_getRemainder (line 696) | static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data,
  function encodeDataCodewords (line 718) | static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_...
  function performErrorCorrection (line 781) | static void performErrorCorrection(uint8_t version, uint8_t ecc,
  function qrcode_getBufferSize (line 869) | uint16_t qrcode_getBufferSize(uint8_t version) {
  function qrcode_initBytes (line 874) | int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version,
  function qrcode_initText (line 961) | int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version,
  function qrcode_getModule (line 967) | bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {

FILE: arduino_workspace/libraries/M5StickC-Plus/src/utility/qrcode.h
  type QRCode (line 60) | typedef struct QRCode {

FILE: arduino_workspace/libraries/PtpIpCamera/PtpIpCamera.cpp
  type pbuf (line 753) | struct pbuf
  type pbuf (line 762) | struct pbuf

FILE: arduino_workspace/libraries/PtpIpCamera/PtpIpCamera.h
  type ptpip_init_substep_t (line 53) | typedef struct
  type ptpipcam_prop_t (line 61) | typedef struct
  function class (line 80) | class PtpIpCamera

FILE: arduino_workspace/libraries/PtpIpCamera/PtpIpSonyAlphaCamera.h
  function class (line 30) | class PtpIpSonyAlphaCamera : public PtpIpCamera

FILE: arduino_workspace/libraries/PtpIpCamera/PtpIpSonyAlphaCameraPropDecoder.cpp
  function propdecoder_print_hex (line 389) | void propdecoder_print_hex(uint16_t datatype, uint8_t* dptr, int cnt)

FILE: arduino_workspace/libraries/PtpIpCamera/ptpip_utils.cpp
  function copy_bytes_to_utf16 (line 8) | uint32_t copy_bytes_to_utf16(void* dest, void* src, int n) {
  function copy_utf16_to_bytes (line 28) | uint32_t copy_utf16_to_bytes(void* dest, void* src, int n) {
  function copyn_utf16_to_bytes (line 45) | void copyn_utf16_to_bytes(void* dest, void* src, uint32_t n) {
  function buffer_consume (line 62) | void buffer_consume(uint8_t buff[], uint32_t* buff_idx, uint32_t read_cn...
  function print_buffer_hex (line 85) | void print_buffer_hex(uint8_t* data, uint32_t len)
  function decode_chunk_to_uint (line 105) | uint32_t decode_chunk_to_uint(uint16_t data_type, uint8_t* data_chunk, u...
  function decode_chunk_to_int (line 116) | int32_t decode_chunk_to_int(uint16_t data_type, uint8_t* data_chunk, uin...
  function property_data_get_size (line 130) | uint8_t property_data_get_size(uint16_t data_type)
  function camera_name_check (line 144) | bool camera_name_check(char* instr, const char* needle)

FILE: arduino_workspace/libraries/PtpIpCamera/ptpipdefs.h
  type ptpip_pkthdr_t (line 15) | typedef struct __attribute__((packed))
  type ptpip_pkt_cmdreq_t (line 22) | typedef struct __attribute__((packed))
  type ptpip_pkt_cmdack_t (line 30) | typedef struct __attribute__((packed))
  type ptpip_pkt_eventreq_t (line 39) | typedef struct __attribute__((packed))
  type ptpip_pkt_operreq_t (line 46) | typedef struct __attribute__((packed))
  type ptpip_pkt_operresp_t (line 55) | typedef struct __attribute__((packed))
  type ptpip_pkt_startdata_t (line 62) | typedef struct __attribute__((packed))
  type ptpip_pkt_data_t (line 71) | typedef struct __attribute__((packed))
  type ptpip_pkt_enddata_t (line 78) | typedef struct __attribute__((packed))
  type ptpip_pkt_event_t (line 85) | typedef struct __attribute__((packed))

FILE: arduino_workspace/libraries/SerialCmdLine/SerialCmdLine.h
  type cmd_def_t (line 14) | typedef struct
  function class (line 21) | class SerialCmdLine

FILE: arduino_workspace/libraries/SonyCameraInfraredRemote/SonyCameraInfraredRemote.cpp
  function SonyCamIr_Init (line 33) | void SonyCamIr_Init()
  function SonyCamIr_SendRaw (line 59) | void SonyCamIr_SendRaw(uint16_t addr, uint8_t cmd)
  function SonyCamIr_SendRawX (line 84) | void SonyCamIr_SendRawX(uint16_t addr, uint8_t cmd, uint8_t xtimes)
  function SonyCamIr_SendRawBits (line 93) | void SonyCamIr_SendRawBits(uint32_t data, uint8_t numbits, uint8_t xtimes)
  function SonyCamIr_Shoot (line 117) | void SonyCamIr_Shoot()
  function SonyCamIr_Shoot2S (line 122) | void SonyCamIr_Shoot2S()
  function SonyCamIr_Movie (line 127) | void SonyCamIr_Movie()

FILE: arduino_workspace/libraries/SonyHttpCamera/SonyHttpCamera.h
  function class (line 79) | class SonyHttpCamera
  function get_shutterspd_32 (line 214) | inline uint32_t get_shutterspd_32   (void) { return parse_shutter_speed_...
  function get_shutterspd_idx (line 215) | inline int      get_shutterspd_idx  (void) { return (tbl_shutterspd != N...
  function get_iso_idx (line 217) | inline int      get_iso_idx         (void) { return (tbl_iso != NULL) ? ...
  function get_aperture_idx (line 219) | inline int      get_aperture_idx    (void) { return (tbl_aperture != NUL...
  function get_expocomp (line 227) | inline int   get_expocomp    (void) { return expocomp;     }

FILE: arduino_workspace/libraries/SonyHttpCamera/SonyHttpCameraUtils.cpp
  function scan_json_for_key (line 33) | bool scan_json_for_key(char* data, int32_t datalen, const char* keystr, ...
  function parse_json_err_num (line 172) | bool parse_json_err_num(const char* data, int* outnum)
  function count_commas (line 196) | int count_commas(char* data)
  function get_idx_within_strtbl (line 209) | int get_idx_within_strtbl(char* tbl, char* needle)
  function get_txt_within_strtbl (line 242) | bool get_txt_within_strtbl(char* tbl, int idx, char* tgt)
  function strcpy_no_slash (line 278) | void strcpy_no_slash(char* dst, char* src)
  function parse_shutter_speed_str (line 293) | uint32_t parse_shutter_speed_str(char* s)

FILE: arduino_workspace/libraries/Wire/src/Wire.h
  function class (line 48) | class TwoWire: public Stream
Condensed preview — 272 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,626K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 783,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: bug\nassignees: frank26080115\n\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 632,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[Feature Request]\"\nlabels: enhancement\nassigne"
  },
  {
    "path": ".gitignore",
    "chars": 1825,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": "Full-Features-Guide.md",
    "chars": 12789,
    "preview": "# Full Features Guide\n\n![](doc/img/features_family_photo.png)\n\n## Preface\n\nThis project is not a product, it is my exper"
  },
  {
    "path": "INSTRUCTIONS.md",
    "chars": 13168,
    "preview": "This document covers:\n\n * Installation and setup of Arduino for ESP32\n * Building the source code and flashing onto a M5"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2022 Frank Zhao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 5569,
    "preview": "# Alpha-Fairy\n\nThis is a tiny remote control for Sony Alpha cameras.\n\n![](doc/img/main_menu_options.webp)\n\nThe hardware "
  },
  {
    "path": "arduino_workspace/.gitignore",
    "chars": 2687,
    "preview": "tools/*\nlibraries/Adafruit*\nlibraries/Arduino*\nlibraries/FastLED*\nlibraries/IRremote*\nlibraries/MAX30100*\nlibraries/HX71"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AlphaFairy.h",
    "chars": 1943,
    "preview": "#ifndef _ALPHAFAIRY_H_\n#define _ALPHAFAIRY_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n\n#include \"alfy_conf.h\"\n#include"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AlphaFairy.ino",
    "chars": 12221,
    "preview": "#include \"AlphaFairy.h\"\n#include <M5StickCPlus.h>\n#include <M5DisplayExt.h>\n#include <SpriteMgr.h>\n#include \"FairyMenu.h"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AlphaFairyCamera.cpp",
    "chars": 18387,
    "preview": "#include \"AlphaFairyCamera.h\"\n\nextern uint32_t shutter_to_millis(uint32_t x);\nextern uint32_t parse_shutter_speed_str(ch"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AlphaFairyCamera.h",
    "chars": 2870,
    "preview": "#ifndef _ALPHAFAIRYCAMERA_H_\n#define _ALPHAFAIRYCAMERA_H_\n\n// this class exists to make the main application code neater"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AppUtils.ino",
    "chars": 6754,
    "preview": "#include \"AlphaFairy.h\"\n\n#ifdef ENABLE_BUILD_LEPTON\nextern bool lepton_enable_poll;\n#endif\n\nvoid app_waitAllReleaseGfx(u"
  },
  {
    "path": "arduino_workspace/AlphaFairy/AutoConnect.ino",
    "chars": 10425,
    "preview": "#include \"AlphaFairy.h\"\n#include <FairyKeyboard.h>\n\nbool autoconnect_active = false;\nint  autoconnect_status = 0;\n\nexter"
  },
  {
    "path": "arduino_workspace/AlphaFairy/Buttons.ino",
    "chars": 7540,
    "preview": "#include \"AlphaFairy.h\"\n#include <Arduino.h>\n#include <stdbool.h>\n\n/*\ninterrupts are used to catch new button press even"
  },
  {
    "path": "arduino_workspace/AlphaFairy/CamUtils.ino",
    "chars": 4788,
    "preview": "#include \"AlphaFairy.h\"\n\nextern bool airplane_mode;\n\nvoid cam_shootQuick()\n{\n    // convenience function for quickly tak"
  },
  {
    "path": "arduino_workspace/AlphaFairy/CmdlineHandlers.ino",
    "chars": 10485,
    "preview": "#include \"AlphaFairy.h\"\n#include <SerialCmdLine.h>\n\n/*\nhandles command line commands\nthis is used only for testing\n*/\n\nv"
  },
  {
    "path": "arduino_workspace/AlphaFairy/ConfigMenu.ino",
    "chars": 4679,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nstatic bool has_saved = false;\n\nbool config_save_exit(void*)\n{\n    has_s"
  },
  {
    "path": "arduino_workspace/AlphaFairy/CpuFreq.ino",
    "chars": 3941,
    "preview": "#include \"AlphaFairy.h\"\n\n#include \"driver/uart.h\"\n\nstatic uint32_t cpufreq_xtal = 0;\n\nstatic const uint32_t cpufreq_cpuF"
  },
  {
    "path": "arduino_workspace/AlphaFairy/DrawingUtils.ino",
    "chars": 15164,
    "preview": "#include \"AlphaFairy.h\"\n#include <M5DisplayExt.h>\n\nvoid gui_drawVerticalDots(int x_offset, int y_margin, int y_offset, i"
  },
  {
    "path": "arduino_workspace/AlphaFairy/DualShutter.ino",
    "chars": 14387,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nspeed_t dual_shutter_next      = { 0, 0, \"\" };\nspeed_t dual_shutter_iso "
  },
  {
    "path": "arduino_workspace/AlphaFairy/FairyMenu.cpp",
    "chars": 20555,
    "preview": "#include \"FairyMenu.h\"\n#include \"AlphaFairy.h\"\n#include <M5DisplayExt.h>\n\nextern M5DisplayExt M5Lcd;\n\nextern void gui_st"
  },
  {
    "path": "arduino_workspace/AlphaFairy/FairyMenu.h",
    "chars": 9464,
    "preview": "#ifndef _FAIRYMENU_H_\n#define _FAIRYMENU_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"Alp"
  },
  {
    "path": "arduino_workspace/AlphaFairy/FocusEncoder.ino",
    "chars": 10559,
    "preview": "#include \"AlphaFairy.h\"\n#include <FairyEncoder.h>\n\nbool fenc_enabled = true;\nint32_t fenc_val;\n\nvoid fenc_task()\n{\n    s"
  },
  {
    "path": "arduino_workspace/AlphaFairy/FocusFrustration.ino",
    "chars": 5158,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nclass AppFocusFrustration : public FairyMenuItem\n{\n    public:\n        A"
  },
  {
    "path": "arduino_workspace/AlphaFairy/FocusPull.ino",
    "chars": 2011,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nvoid focus_pull(bool live, int bar_y)\n{\n    bool starting_mf = ptpcam.is"
  },
  {
    "path": "arduino_workspace/AlphaFairy/FocusStack.ino",
    "chars": 17610,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nclass AppFocusStack : public FairyMenuItem\n{\n    public:\n        AppFocu"
  },
  {
    "path": "arduino_workspace/AlphaFairy/HttpServer.ino",
    "chars": 19451,
    "preview": "#include \"AlphaFairy.h\"\n#include <WiFi.h>\n#include <FS.h>\n#include <SPIFFS.h>\n\n/*\nimplement a simple web page interface "
  },
  {
    "path": "arduino_workspace/AlphaFairy/InfoView.ino",
    "chars": 30094,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\n#define INFOSCR_CORNER_MARGIN 65\n#define INFOSCR_MIDDIV_MARGIN 5\n#define"
  },
  {
    "path": "arduino_workspace/AlphaFairy/Intervalometer.ino",
    "chars": 16786,
    "preview": "#include \"AlphaFairy.h\"\n#include <M5DisplayExt.h>\n#include \"FairyMenu.h\"\n\nstatic uint32_t intervalometer_start_time;\n\nex"
  },
  {
    "path": "arduino_workspace/AlphaFairy/Lepton.ino",
    "chars": 33745,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\n#ifdef ENABLE_BUILD_LEPTON\n\n#include <Lepton.h>\n#include <FairyEncoder.h"
  },
  {
    "path": "arduino_workspace/AlphaFairy/PowerMgmt.ino",
    "chars": 15564,
    "preview": "#include \"AlphaFairy.h\"\n#include <M5StickCPlus.h>\n#include <M5DisplayExt.h>\n\n#include \"esp_pm.h\"\n#include \"esp32/pm.h\"\n\n"
  },
  {
    "path": "arduino_workspace/AlphaFairy/QuickRemote.ino",
    "chars": 13420,
    "preview": "#include \"AlphaFairy.h\"\n\n#define QIKRMT_ROLL_SPAN 60\n#define QIKRMT_HYSTER    3\n#define QIKRMT_FPULL_Y   198\n\nuint8_t qi"
  },
  {
    "path": "arduino_workspace/AlphaFairy/RemoteShutter.ino",
    "chars": 8380,
    "preview": "#include \"AlphaFairy.h\"\n\nextern bool airplane_mode;\n\nvoid remote_shutter(uint8_t time_delay, bool use_gui)\n{\n    uint32_"
  },
  {
    "path": "arduino_workspace/AlphaFairy/Settings.ino",
    "chars": 4801,
    "preview": "#include \"AlphaFairy.h\"\n#include <EEPROM.h>\n\nextern uint32_t crc32_le(uint32_t crc, uint8_t const *buf, uint32_t len);\ne"
  },
  {
    "path": "arduino_workspace/AlphaFairy/ShutterStep.ino",
    "chars": 6016,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nextern int32_t infoscr_reqShutter;\n\nclass AppShutterStep : public FairyM"
  },
  {
    "path": "arduino_workspace/AlphaFairy/SoundTrigger.ino",
    "chars": 9000,
    "preview": "#include \"AlphaFairy.h\"\n#include <M5StickCPlus.h>\n#include <driver/i2s.h>\n\n/*\nNOTE: the newer I2S code in ESP-IDF has no"
  },
  {
    "path": "arduino_workspace/AlphaFairy/TallyLite.ino",
    "chars": 2238,
    "preview": "#include \"AlphaFairy.h\"\n\nextern bool redraw_flag;\nbool tallylite_nopoll = false; // used to prevent recursion in app_pol"
  },
  {
    "path": "arduino_workspace/AlphaFairy/TimecodeReset.ino",
    "chars": 688,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n#include <SonyCameraInfraredRemote.h>\n\nclass AppTimecodeReset : public Fa"
  },
  {
    "path": "arduino_workspace/AlphaFairy/Trigger.ino",
    "chars": 26216,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nint32_t trigger_source = TRIGSRC_MIC;\nint32_t trigger_action = TRIGACT_P"
  },
  {
    "path": "arduino_workspace/AlphaFairy/WifiHandlers.ino",
    "chars": 6481,
    "preview": "#include \"AlphaFairy.h\"\n\nextern bool autoconnect_active;\nextern int autoconnect_status;\nextern bool signal_wifiauthfaile"
  },
  {
    "path": "arduino_workspace/AlphaFairy/WifiMenu.ino",
    "chars": 14339,
    "preview": "#include \"AlphaFairy.h\"\n#include \"FairyMenu.h\"\n\nstatic uint32_t wifinfo_previp = 0;\n\nbool wifinfo_check_redraw(void)\n{\n "
  },
  {
    "path": "arduino_workspace/AlphaFairy/WifiUtils.ino",
    "chars": 14260,
    "preview": "#include \"AlphaFairy.h\"\n\nextern void wifi_onConnect(void);\nextern void wifi_onDisconnect(uint8_t, int);\nint wifi_err_rea"
  },
  {
    "path": "arduino_workspace/AlphaFairy/alfy_conf.h",
    "chars": 1494,
    "preview": "#ifndef _ALFY_CONF_H_\r\n#define _ALFY_CONF_H_\r\n\r\n#define ALFY_VERSION            \"1.0.14\"    // change this with every ne"
  },
  {
    "path": "arduino_workspace/AlphaFairy/alfy_defs.h",
    "chars": 3523,
    "preview": "#ifndef _ALFY_DEFS_H_\n#define _ALFY_DEFS_H_\n\n#include \"alfy_conf.h\"\n\n#define CONFIGSETTINGS_MAGIC 0xDEADBEEF\n\nenum\n{\n   "
  },
  {
    "path": "arduino_workspace/AlphaFairy/alfy_types.h",
    "chars": 1792,
    "preview": "#ifndef _ALFY_TYPES_H_\n#define _ALFY_TYPES_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n#include \"alfy_conf.h\"\n#include "
  },
  {
    "path": "arduino_workspace/AlphaFairy/data/chk2.txt",
    "chars": 7,
    "preview": "{\\rtf1}"
  },
  {
    "path": "arduino_workspace/AlphaFairy/data/chk3.txt",
    "chars": 7,
    "preview": "{\\rtf1}"
  },
  {
    "path": "arduino_workspace/libraries/AlphaFairyImu/AlphaFairyImu.cpp",
    "chars": 5032,
    "preview": "#include \"AlphaFairyImu.h\"\n#include <Wire.h>\n#include <Arduino.h>\n#include <M5StickCPlus.h>\n\n#include <math.h>\n\n#define "
  },
  {
    "path": "arduino_workspace/libraries/AlphaFairyImu/AlphaFairyImu.h",
    "chars": 822,
    "preview": "#ifndef _ALPHAFAIRYIMU_H_\n#define _ALPHAFAIRYIMU_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n\nclass AlphaFairyImu\n{\n   "
  },
  {
    "path": "arduino_workspace/libraries/AlphaFairy_NetMgr/AlphaFairy_NetMgr.cpp",
    "chars": 13454,
    "preview": "#include \"AlphaFairy_NetMgr.h\"\n\n#include <WiFi.h>\n#include \"esp_wifi.h\"\n\n#ifndef WIFI_STRING_LEN\n#define WIFI_STRING_LEN"
  },
  {
    "path": "arduino_workspace/libraries/AlphaFairy_NetMgr/AlphaFairy_NetMgr.h",
    "chars": 1500,
    "preview": "#ifndef _ALPHAFAIRY_NETMGR_H_\n#define _ALPHAFAIRY_NETMGR_H_\n\n#include <stdint.h>\n\n#include <WiFi.h>\n#include <WiFiUdp.h>"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/.gitignore",
    "chars": 27,
    "preview": ".DS_Store\n.github\nexamples\n"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/.travis.yml",
    "chars": 842,
    "preview": "sudo: false\nlanguage: python\nos:\n  - linux\n\ngit:\n  depth: false\n\nstages:\n  - build\n\njobs:\n  include:\n\n    - name: \"Ardui"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/CMakeLists.txt",
    "chars": 208,
    "preview": "set(COMPONENT_SRCDIRS\n    \"src\"\n)\n\nset(COMPONENT_ADD_INCLUDEDIRS\n    \"src\"\n)\n\nset(COMPONENT_REQUIRES\n    \"arduino-esp32\""
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/Kconfig.projbuild",
    "chars": 738,
    "preview": "menu \"AsyncTCP Configuration\"\n\nchoice ASYNC_TCP_RUNNING_CORE\n    bool \"Core on which AsyncTCP's thread is running\"\n    d"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/LICENSE",
    "chars": 7651,
    "preview": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/README.md",
    "chars": 1245,
    "preview": "https://github.com/me-no-dev/AsyncTCP\nca8ac5f919d02bea07b474531981ddbfd64de97c\n\n# AsyncTCP \n[![Build Status](https://tra"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/component.mk",
    "chars": 80,
    "preview": "COMPONENT_ADD_INCLUDEDIRS := src\nCOMPONENT_SRCDIRS := src\nCXXFLAGS += -fno-rtti\n"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/library.json",
    "chars": 425,
    "preview": "{\n  \"name\":\"AsyncTCP\",\n  \"description\":\"Asynchronous TCP Library for ESP32\",\n  \"keywords\":\"async,tcp\",\n  \"authors\":\n  {\n"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/library.properties",
    "chars": 214,
    "preview": "name=AsyncTCP\nversion=1.1.1\nauthor=Me-No-Dev\nmaintainer=Me-No-Dev\nsentence=Async TCP Library for ESP32\nparagraph=Async T"
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/src/AsyncTCP.cpp",
    "chars": 34897,
    "preview": "/*\n  Asynchronous TCP library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This file "
  },
  {
    "path": "arduino_workspace/libraries/AsyncTCP/src/AsyncTCP.h",
    "chars": 7534,
    "preview": "/*\n  Asynchronous TCP library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This file "
  },
  {
    "path": "arduino_workspace/libraries/DebuggingSerial/DebuggingSerial.cpp",
    "chars": 575,
    "preview": "#include <DebuggingSerial.h>\n\nDebuggingSerial::DebuggingSerial(HardwareSerial* s) : HardwareSerial(0)\n{\n    this->ser_ob"
  },
  {
    "path": "arduino_workspace/libraries/DebuggingSerial/DebuggingSerial.h",
    "chars": 2440,
    "preview": "/*\nthis is a wrapper around a HardwareSerial object but with an enable flag\nso that an application can put print stateme"
  },
  {
    "path": "arduino_workspace/libraries/DebuggingSerial/DebuggingSerialDisable.cpp",
    "chars": 3301,
    "preview": "#include \"DebuggingSerial.h\"\n\n\nDebuggingSerialDisabled::DebuggingSerialDisabled(HardwareSerial* s) : HardwareSerial(0)\n{"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/.gitignore",
    "chars": 34,
    "preview": ".vscode\n.DS_Store\n.github\nexamples"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/.travis.yml",
    "chars": 1335,
    "preview": "sudo: false\n\nlanguage: python\n\nos:\n  - linux\n\ngit:\n  depth: false\n\nstages:\n  - build\n\njobs:\n  include:\n\n    - name: \"Bui"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/CMakeLists.txt",
    "chars": 286,
    "preview": "set(COMPONENT_SRCDIRS\n    \"src\"\n)\n\nset(COMPONENT_ADD_INCLUDEDIRS\n    \"src\"\n)\n\nset(COMPONENT_REQUIRES\n    \"arduino-esp32\""
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/README.md",
    "chars": 61726,
    "preview": "https://github.com/me-no-dev/ESPAsyncWebServer\nf71e3d427b5be9791a8a2c93cf8079792c3a9a26\n\n# ESPAsyncWebServer \n[![Build S"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/_config.yml",
    "chars": 26,
    "preview": "theme: jekyll-theme-cayman"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/component.mk",
    "chars": 80,
    "preview": "COMPONENT_ADD_INCLUDEDIRS := src\nCOMPONENT_SRCDIRS := src\nCXXFLAGS += -fno-rtti\n"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/keywords.txt",
    "chars": 53,
    "preview": "JsonArray\tKEYWORD1\nadd\tKEYWORD2\ncreateArray\tKEYWORD3\n"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/library.json",
    "chars": 835,
    "preview": "{\n  \"name\":\"ESP Async WebServer\",\n  \"description\":\"Asynchronous HTTP and WebSocket Server Library for ESP8266 and ESP32\""
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/library.properties",
    "chars": 258,
    "preview": "name=ESP Async WebServer\nversion=1.2.3\nauthor=Me-No-Dev\nmaintainer=Me-No-Dev\nsentence=Async Web Server for ESP8266 and E"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp",
    "chars": 10022,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n  Thi"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncEventSource.h",
    "chars": 4258,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n\n  Thi"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncJson.h",
    "chars": 7496,
    "preview": "// AsyncJson.h\n/*\n  Async Response to use with ArduinoJson and AsyncWebServer\n  Written by Andrew Melvin (SticilFace) wi"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp",
    "chars": 33612,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h",
    "chars": 12708,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h",
    "chars": 1404,
    "preview": "#ifndef ASYNCWEBSYNCHRONIZATION_H_\n#define ASYNCWEBSYNCHRONIZATION_H_\n\n// Synchronisation is only available on ESP32, as"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h",
    "chars": 18862,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/SPIFFSEditor.cpp",
    "chars": 32823,
    "preview": "#include \"SPIFFSEditor.h\"\n#include <FS.h>\n\n//File: edit.htm.gz, Size: 4151\n#define edit_htm_gz_len 4151\nconst uint8_t ed"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/SPIFFSEditor.h",
    "chars": 888,
    "preview": "#ifndef SPIFFSEditor_H_\n#define SPIFFSEditor_H_\n#include <ESPAsyncWebServer.h>\n\nclass SPIFFSEditor: public AsyncWebHandl"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/StringArray.h",
    "chars": 4764,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp",
    "chars": 6804,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebAuthentication.h",
    "chars": 1543,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h",
    "chars": 5911,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebHandlers.cpp",
    "chars": 7655,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebRequest.cpp",
    "chars": 32810,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebResponseImpl.h",
    "chars": 5390,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebResponses.cpp",
    "chars": 24679,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/WebServer.cpp",
    "chars": 5732,
    "preview": "/*\n  Asynchronous WebServer library for Espressif MCUs\n\n  Copyright (c) 2016 Hristo Gochkov. All rights reserved.\n  This"
  },
  {
    "path": "arduino_workspace/libraries/ESPAsyncWebServer/src/edit.htm",
    "chars": 17182,
    "preview": "<!--This is the plain html source of the hex encoded Editor-Page embedded in SPIFFSEditor.cpp -->\n<!DOCTYPE html>\n<html "
  },
  {
    "path": "arduino_workspace/libraries/FairyEncoder/FairyEncoder.cpp",
    "chars": 2882,
    "preview": "#include \"FairyEncoder.h\"\n\n#define ENCODER_REG  0x10\n#define BUTTON_REG   0x20\n#define RGB_LED_REG  0x30\n\nFairyEncoder::"
  },
  {
    "path": "arduino_workspace/libraries/FairyEncoder/FairyEncoder.h",
    "chars": 1854,
    "preview": "#ifndef _FAIRYENCODER_H_\n#define _FAIRYENCODER_H_\n\n#include \"Arduino.h\"\n#include \"Wire.h\"\n\n#include <stdint.h>\n#include "
  },
  {
    "path": "arduino_workspace/libraries/FairyKeyboard/FairyKeyboard.cpp",
    "chars": 16846,
    "preview": "#include \"FairyKeyboard.h\"\n\n#define KBD_LINE_SPACING         24\n#define KBD_LEFT_MARGIN           7\n#define KBD_DIVIDER_"
  },
  {
    "path": "arduino_workspace/libraries/FairyKeyboard/FairyKeyboard.h",
    "chars": 3817,
    "preview": "#ifndef _FAIRYKEYBOARD_H_\n#define _FAIRYKEYBOARD_H_\n\n#include <Arduino.h>\n#include <M5StickCPlus.h>\n#include <M5Display."
  },
  {
    "path": "arduino_workspace/libraries/FairyKeyboard/examples/FairyKeyboardDemo/FairyKeyboardDemo.ino",
    "chars": 2232,
    "preview": "#include <FairyKeyboard.h>\n#include <M5StickCPlus.h>\n\n#define PIN_BTN_SIDE 39\n#define PIN_BTN_BIG 37\nFairyKeyboard kbd(&"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/Lepton.h",
    "chars": 5325,
    "preview": "#ifndef Lepton_h\n#define Lepton_h\n\n#include \"Arduino.h\"\n\n#include \"img_table.h\"\n#include \"img/ColorT.h\"\n\n#define FLIR_X "
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT.h",
    "chars": 466,
    "preview": "#include \"ColorT_0000_16.h\"\n#include \"ColorT_0001_15.h\"\n#include \"ColorT_0002_14.h\"\n#include \"ColorT_0003_13.h\"\n#include"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0000_16.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0000_16[3650];\r\r\nconst uint16_t ColorT_0000_16[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0001_15.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0001_15[3650];\r\r\nconst uint16_t ColorT_0001_15[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0002_14.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0002_14[3650];\r\r\nconst uint16_t ColorT_0002_14[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0003_13.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0003_13[3650];\r\r\nconst uint16_t ColorT_0003_13[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0004_12.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0004_12[3650];\r\r\nconst uint16_t ColorT_0004_12[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0005_11.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0005_11[3650];\r\r\nconst uint16_t ColorT_0005_11[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0006_10.h",
    "chars": 30624,
    "preview": "// extern const uint16_t ColorT_0006_10[3650];\r\r\nconst uint16_t ColorT_0006_10[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0007_9.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0007_9[3650];\r\r\nconst uint16_t ColorT_0007_9[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0008_8.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0008_8[3650];\r\r\nconst uint16_t ColorT_0008_8[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0009_7.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0009_7[3650];\r\r\nconst uint16_t ColorT_0009_7[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0010_6.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0010_6[3650];\r\r\nconst uint16_t ColorT_0010_6[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0011_5.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0011_5[3650];\r\r\nconst uint16_t ColorT_0011_5[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0012_4.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0012_4[3650];\r\r\nconst uint16_t ColorT_0012_4[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0013_3.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0013_3[3650];\r\r\nconst uint16_t ColorT_0013_3[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0014_2.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0014_2[3650];\r\r\nconst uint16_t ColorT_0014_2[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0015_1.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0015_1[3650];\r\r\nconst uint16_t ColorT_0015_1[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img/ColorT_0016_0.h",
    "chars": 30622,
    "preview": "// extern const uint16_t ColorT_0016_0[3650];\r\r\nconst uint16_t ColorT_0016_0[3650] = {\r\r\n\t0xffff, 0xffff, 0xffff, 0xffff"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/img_table.h",
    "chars": 12640,
    "preview": "#ifndef img_table_h\n#define img_table_h\n\n#if 0\nconst uint16_t colormap_golden[] = {\n0x0004, 0x0004, 0x0004, 0x0004, 0x00"
  },
  {
    "path": "arduino_workspace/libraries/Lepton/lepton.cpp",
    "chars": 10151,
    "preview": "#include \"Arduino.h\"\n#include \"Lepton.h\"\n#include \"Wire.h\"\n#include \"SPI.h\"\n\n#define LEPTON_WAIT_TIMEOUT 1000\n\n#define r"
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/M5DisplayExt.cpp",
    "chars": 11384,
    "preview": "#include \"M5DisplayExt.h\"\n\n// These read 16- and 32-bit types from the SD card file.\n// BMP data is stored little-endian"
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/M5DisplayExt.h",
    "chars": 1934,
    "preview": "#ifndef _M5DISPLAYEXT_H_\n#define _M5DISPLAYEXT_H_\n\n#include <M5Display.h>\n#include <Arduino.h>\n#include <FS.h>\n#include "
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/SpriteMgr.cpp",
    "chars": 4057,
    "preview": "#include \"SpriteMgr.h\"\n#include <FS.h>\n#include <SPIFFS.h>\n\nstatic uint16_t fletcher16_str(const uint8_t* data);\n\nSprite"
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/SpriteMgr.h",
    "chars": 909,
    "preview": "#ifndef _SPRITEMGR_H_\n#define _SPRITEMGR_H_\n\n#include <stdint.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stri"
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/utility/pngle.c",
    "chars": 27052,
    "preview": "/*-\n * MIT License\n *\n * Copyright (c) 2019 kikuchan\n *\n * Permission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "arduino_workspace/libraries/M5DisplayExt/utility/pngle.h",
    "chars": 2914,
    "preview": "/*-\n * MIT License\n *\n * Copyright (c) 2019 kikuchan\n *\n * Permission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2020 M5Stack\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/README.md",
    "chars": 2579,
    "preview": "https://github.com/m5stack/M5StickC-Plus\n4677f3432bafe011b0116c6443c36547ade3fd34 - Do not update this library beyond th"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/library.json",
    "chars": 390,
    "preview": "{\n  \"name\": \"M5StickCPlus\",\n  \"description\": \"An ESP32 Arduino board\",\n  \"keywords\": \"M5StickCPlus\",\n  \"authors\": {\n    "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/library.properties",
    "chars": 291,
    "preview": "name=M5StickCPlus\nversion=0.0.8\nauthor=M5Stack\nmaintainer=M5Stack\nsentence=Library for M5StickC Plus development kit\npar"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/AXP192.cpp",
    "chars": 10042,
    "preview": "#include \"AXP192.h\"\n\nAXP192::AXP192() {\n}\n\nvoid AXP192::begin(void) {\n    Wire1.begin(21, 22);\n    Wire1.setClock(400000"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/AXP192.h",
    "chars": 2475,
    "preview": "#ifndef __AXP192_H__\n#define __AXP192_H__\n\n#include <Arduino.h>\n#include <Wire.h>\n\n#define SLEEP_MSEC(us) (((uint64_t)us"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/ASC16.h",
    "chars": 26092,
    "preview": "/**************************\n *\n * ASC16\n *\n **************************/\n#ifndef _ASC16_\n#define _ASC16_\nconst uint8_t AS"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Custom/Orbitron_Light_24.h",
    "chars": 22062,
    "preview": "// Created by http://oleddisplay.squix.ch/ Consider a donation\n// In case of problems make sure that you are using the f"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Custom/Orbitron_Light_32.h",
    "chars": 35045,
    "preview": "// Created by http://oleddisplay.squix.ch/ Consider a donation\n// In case of problems make sure that you are using the f"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Custom/Roboto_Thin_24.h",
    "chars": 17710,
    "preview": "// Created by http://oleddisplay.squix.ch/ Consider a donation\n// In case of problems make sure that you are using the f"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Custom/Satisfy_24.h",
    "chars": 22251,
    "preview": "// Created by http://oleddisplay.squix.ch/ Consider a donation\n// In case of problems make sure that you are using the f"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Custom/Yellowtail_32.h",
    "chars": 35359,
    "preview": "// Created by http://oleddisplay.squix.ch/ Consider a donation\n// In case of problems make sure that you are using the f"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font16.c",
    "chars": 24941,
    "preview": "// Font 2\n\n#include <pgmspace.h>\n\n// Width has been increased by 1 pixel so pixel lengths are calculated correctly\n// fo"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font16.h",
    "chars": 245,
    "preview": "#include <Fonts/Font16.c>\n\n#define nr_chrs_f16   96\n#define chr_hgt_f16   16\n#define baseline_f16  13\n#define data_size_"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font32rle.c",
    "chars": 36568,
    "preview": "// Font 4\n//\n// This font has been 8 bit Run Length Encoded to save FLASH space\n//\n// This font contains 96 ASCII charac"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font32rle.h",
    "chars": 248,
    "preview": "#include <Fonts/Font32rle.c>\n\n#define nr_chrs_f32   96\n#define chr_hgt_f32   26\n#define baseline_f32  19\n#define data_si"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font64rle.c",
    "chars": 12830,
    "preview": "// Font 6 is intended to display numbers and time\n//\n// This font has been 8 bit Run Length Encoded to save FLASH space\n"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font64rle.h",
    "chars": 248,
    "preview": "#include <Fonts/Font64rle.c>\n\n#define nr_chrs_f64   96\n#define chr_hgt_f64   48\n#define baseline_f64  36\n#define data_si"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font72rle.c",
    "chars": 16586,
    "preview": "// Font 8\n//\n// This font has been 8 bit Run Length Encoded to save FLASH space\n//\n// It is a Arial 75 pixel height font"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font72rle.h",
    "chars": 248,
    "preview": "#include <Fonts/Font72rle.c>\n\n#define nr_chrs_f72   96\n#define chr_hgt_f72   75\n#define baseline_f72  73\n#define data_si"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font7srle.c",
    "chars": 11389,
    "preview": "// Font 7\n//\n// This font has been 8 bit Run Length Encoded to save FLASH space\n//\n// This is a 7 segment font intended "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/Font7srle.h",
    "chars": 248,
    "preview": "#include <Fonts/Font7srle.c>\n\n#define nr_chrs_f7s   96\n#define chr_hgt_f7s   48\n#define baseline_f7s  47\n#define data_si"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMono12pt7b.h",
    "chars": 13854,
    "preview": "const uint8_t FreeMono12pt7bBitmaps[] PROGMEM = {\n    0x49, 0x24, 0x92, 0x48, 0x01, 0xF8, 0xE7, 0xE7, 0x67, 0x42, 0x42, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMono18pt7b.h",
    "chars": 24172,
    "preview": "const uint8_t FreeMono18pt7bBitmaps[] PROGMEM = {\n    0x27, 0x77, 0x77, 0x77, 0x77, 0x22, 0x22, 0x20, 0x00, 0x6F, 0xF6, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMono24pt7b.h",
    "chars": 40442,
    "preview": "const uint8_t FreeMono24pt7bBitmaps[] PROGMEM = {\n    0x73, 0x9C, 0xE7, 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x10, 0x84, 0x21, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMono9pt7b.h",
    "chars": 9757,
    "preview": "const uint8_t FreeMono9pt7bBitmaps[] PROGMEM = {\n    0xAA, 0xA8, 0x0C, 0xED, 0x24, 0x92, 0x48, 0x24, 0x48, 0x91, 0x2F, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBold12pt7b.h",
    "chars": 15515,
    "preview": "const uint8_t FreeMonoBold12pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xF6, 0x66, 0x60, 0x6F, 0x60, 0xE7, 0xE7, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBold18pt7b.h",
    "chars": 28800,
    "preview": "const uint8_t FreeMonoBold18pt7bBitmaps[] PROGMEM = {\n    0x77, 0xFF, 0xFF, 0xFF, 0xFF, 0xFB, 0x9C, 0xE7, 0x39, 0xC4, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBold24pt7b.h",
    "chars": 47700,
    "preview": "const uint8_t FreeMonoBold24pt7bBitmaps[] PROGMEM = {\n    0x38, 0xFB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xF3, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBold9pt7b.h",
    "chars": 10868,
    "preview": "const uint8_t FreeMonoBold9pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xD2, 0x1F, 0x80, 0xEC, 0x89, 0x12, 0x24, 0x40, 0x3"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBoldOblique12pt7b.h",
    "chars": 17137,
    "preview": "const uint8_t FreeMonoBoldOblique12pt7bBitmaps[] PROGMEM = {\n    0x1C, 0xF3, 0xCE, 0x38, 0xE7, 0x1C, 0x61, 0x86, 0x00, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBoldOblique18pt7b.h",
    "chars": 31641,
    "preview": "const uint8_t FreeMonoBoldOblique18pt7bBitmaps[] PROGMEM = {\n    0x0F, 0x07, 0xC7, 0xE3, 0xF1, 0xF0, 0xF8, 0xFC, 0x7C, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBoldOblique24pt7b.h",
    "chars": 53043,
    "preview": "const uint8_t FreeMonoBoldOblique24pt7bBitmaps[] PROGMEM = {\n    0x01, 0xE0, 0x3F, 0x07, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoBoldOblique9pt7b.h",
    "chars": 11884,
    "preview": "const uint8_t FreeMonoBoldOblique9pt7bBitmaps[] PROGMEM = {\n    0x39, 0xCC, 0x67, 0x31, 0x8C, 0x07, 0x38, 0x6C, 0xD9, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoOblique12pt7b.h",
    "chars": 15384,
    "preview": "const uint8_t FreeMonoOblique12pt7bBitmaps[] PROGMEM = {\n    0x11, 0x11, 0x12, 0x22, 0x22, 0x00, 0x0E, 0xE0, 0xE7, 0xE7,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoOblique18pt7b.h",
    "chars": 26826,
    "preview": "const uint8_t FreeMonoOblique18pt7bBitmaps[] PROGMEM = {\n    0x00, 0x1C, 0x38, 0x70, 0xC1, 0x83, 0x06, 0x18, 0x30, 0x60,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoOblique24pt7b.h",
    "chars": 45529,
    "preview": "const uint8_t FreeMonoOblique24pt7bBitmaps[] PROGMEM = {\n    0x01, 0xC0, 0xF0, 0x3C, 0x0E, 0x03, 0x81, 0xE0, 0x78, 0x1C,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeMonoOblique9pt7b.h",
    "chars": 10690,
    "preview": "const uint8_t FreeMonoOblique9pt7bBitmaps[] PROGMEM = {\n    0x11, 0x22, 0x24, 0x40, 0x00, 0xC0, 0xDE, 0xE5, 0x29, 0x00, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSans12pt7b.h",
    "chars": 17080,
    "preview": "const uint8_t FreeSans12pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xCF, 0x3C, 0xF3, 0x8A, 0x20, 0x06, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSans18pt7b.h",
    "chars": 30948,
    "preview": "const uint8_t FreeSans18pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x20, 0x3F, 0xFC, 0xE3, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSans24pt7b.h",
    "chars": 51878,
    "preview": "const uint8_t FreeSans24pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x76, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSans9pt7b.h",
    "chars": 11788,
    "preview": "const uint8_t FreeSans9pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xF8, 0xC0, 0xDE, 0xF7, 0x20, 0x09, 0x86, 0x41, 0x91, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBold12pt7b.h",
    "chars": 18403,
    "preview": "const uint8_t FreeSansBold12pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xFF, 0x76, 0x66, 0x60, 0xFF, 0xF0, 0xF3, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBold18pt7b.h",
    "chars": 33077,
    "preview": "const uint8_t FreeSansBold18pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xE7, 0x39, 0xCE, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBold24pt7b.h",
    "chars": 56224,
    "preview": "const uint8_t FreeSansBold24pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBold9pt7b.h",
    "chars": 12324,
    "preview": "const uint8_t FreeSansBold9pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFF, 0xFE, 0x48, 0x7E, 0xEF, 0xDF, 0xBF, 0x74, 0x40, 0x1"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBoldOblique12pt7b.h",
    "chars": 20648,
    "preview": "const uint8_t FreeSansBoldOblique12pt7bBitmaps[] PROGMEM = {\n    0x1C, 0x3C, 0x78, 0xE1, 0xC3, 0x8F, 0x1C, 0x38, 0x70, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBoldOblique18pt7b.h",
    "chars": 38071,
    "preview": "const uint8_t FreeSansBoldOblique18pt7bBitmaps[] PROGMEM = {\n    0x06, 0x01, 0xC0, 0x7C, 0x1F, 0x0F, 0xC3, 0xE0, 0xF8, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBoldOblique24pt7b.h",
    "chars": 64520,
    "preview": "const uint8_t FreeSansBoldOblique24pt7bBitmaps[] PROGMEM = {\n    0x01, 0xE0, 0x07, 0xF0, 0x1F, 0xC0, 0xFF, 0x03, 0xF8, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansBoldOblique9pt7b.h",
    "chars": 13857,
    "preview": "const uint8_t FreeSansBoldOblique9pt7bBitmaps[] PROGMEM = {\n    0x21, 0x8E, 0x73, 0x18, 0xC6, 0x21, 0x19, 0xCE, 0x00, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansOblique12pt7b.h",
    "chars": 19530,
    "preview": "const uint8_t FreeSansOblique12pt7bBitmaps[] PROGMEM = {\n    0x0C, 0x61, 0x86, 0x18, 0x63, 0x0C, 0x30, 0xC2, 0x18, 0x61,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansOblique18pt7b.h",
    "chars": 36023,
    "preview": "const uint8_t FreeSansOblique18pt7bBitmaps[] PROGMEM = {\n    0x03, 0x83, 0x81, 0xC0, 0xE0, 0x70, 0x78, 0x38, 0x1C, 0x0E,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansOblique24pt7b.h",
    "chars": 60471,
    "preview": "const uint8_t FreeSansOblique24pt7bBitmaps[] PROGMEM = {\n    0x01, 0xE0, 0x3C, 0x0F, 0x81, 0xE0, 0x3C, 0x07, 0x80, 0xF0,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSansOblique9pt7b.h",
    "chars": 13239,
    "preview": "const uint8_t FreeSansOblique9pt7bBitmaps[] PROGMEM = {\n    0x10, 0x84, 0x22, 0x10, 0x84, 0x42, 0x10, 0x08, 0x00, 0xDE, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerif12pt7b.h",
    "chars": 16263,
    "preview": "const uint8_t FreeSerif12pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xFE, 0xA8, 0x3F, 0xCF, 0x3C, 0xF3, 0x8A, 0x20, 0x0C, 0x40,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerif18pt7b.h",
    "chars": 29225,
    "preview": "const uint8_t FreeSerif18pt7bBitmaps[] PROGMEM = {\n    0x6F, 0xFF, 0xFF, 0xFE, 0x66, 0x66, 0x66, 0x64, 0x40, 0x00, 0x6F,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerif24pt7b.h",
    "chars": 49013,
    "preview": "const uint8_t FreeSerif24pt7bBitmaps[] PROGMEM = {\n    0x77, 0xBF, 0xFF, 0xFF, 0xFF, 0xFB, 0x9C, 0xE7, 0x39, 0xCE, 0x61,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerif9pt7b.h",
    "chars": 11351,
    "preview": "const uint8_t FreeSerif9pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xEA, 0x03, 0xDE, 0xF7, 0x20, 0x11, 0x09, 0x04, 0x82, 0x4F, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBold12pt7b.h",
    "chars": 17170,
    "preview": "const uint8_t FreeSerifBold12pt7bBitmaps[] PROGMEM = {\n    0x7F, 0xFF, 0x77, 0x66, 0x22, 0x00, 0x6F, 0xF7, 0xE3, 0xF1, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBold18pt7b.h",
    "chars": 31721,
    "preview": "const uint8_t FreeSerifBold18pt7bBitmaps[] PROGMEM = {\n    0x7B, 0xEF, 0xFF, 0xFF, 0xF7, 0x9E, 0x71, 0xC7, 0x0C, 0x20, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBold24pt7b.h",
    "chars": 54353,
    "preview": "const uint8_t FreeSerifBold24pt7bBitmaps[] PROGMEM = {\n    0x3C, 0x7E, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x7E, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBold9pt7b.h",
    "chars": 11820,
    "preview": "const uint8_t FreeSerifBold9pt7bBitmaps[] PROGMEM = {\n    0xFF, 0xF4, 0x92, 0x1F, 0xF0, 0xCF, 0x3C, 0xE3, 0x88, 0x13, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBoldItalic12pt7b.h",
    "chars": 18861,
    "preview": "const uint8_t FreeSerifBoldItalic12pt7bBitmaps[] PROGMEM = {\n    0x07, 0x07, 0x07, 0x0F, 0x0E, 0x0E, 0x0C, 0x0C, 0x08, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBoldItalic18pt7b.h",
    "chars": 34693,
    "preview": "const uint8_t FreeSerifBoldItalic18pt7bBitmaps[] PROGMEM = {\n    0x01, 0xC0, 0x7C, 0x0F, 0x81, 0xF0, 0x3E, 0x07, 0x80, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBoldItalic24pt7b.h",
    "chars": 56907,
    "preview": "const uint8_t FreeSerifBoldItalic24pt7bBitmaps[] PROGMEM = {\n    0x00, 0x3C, 0x00, 0xFC, 0x01, 0xF8, 0x07, 0xF0, 0x0F, 0"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifBoldItalic9pt7b.h",
    "chars": 12885,
    "preview": "const uint8_t FreeSerifBoldItalic9pt7bBitmaps[] PROGMEM = {\n    0x0C, 0x31, 0xC6, 0x18, 0x41, 0x08, 0x20, 0x0E, 0x38, 0x"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifItalic12pt7b.h",
    "chars": 17233,
    "preview": "const uint8_t FreeSerifItalic12pt7bBitmaps[] PROGMEM = {\n    0x0C, 0x31, 0xC6, 0x18, 0x43, 0x0C, 0x20, 0x84, 0x10, 0x03,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifItalic18pt7b.h",
    "chars": 30843,
    "preview": "const uint8_t FreeSerifItalic18pt7bBitmaps[] PROGMEM = {\n    0x01, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x81, 0xE0, 0x70, 0x1C,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifItalic24pt7b.h",
    "chars": 52667,
    "preview": "const uint8_t FreeSerifItalic24pt7bBitmaps[] PROGMEM = {\n    0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x01, 0xF0, 0x1E, 0x01,"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/FreeSerifItalic9pt7b.h",
    "chars": 11836,
    "preview": "const uint8_t FreeSerifItalic9pt7bBitmaps[] PROGMEM = {\n    0x11, 0x12, 0x22, 0x24, 0x40, 0x0C, 0xDE, 0xE5, 0x40, 0x04, "
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/TomThumb.h",
    "chars": 23771,
    "preview": "/**\n** The original 3x5 font is licensed under the 3-clause BSD license:\n**\n** Copyright 1999 Brian J. Swetland\n** Copyr"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/gfxfont.h",
    "chars": 1067,
    "preview": "// Adopted by Bodmer to support TFT_HX8357_Due library.\n\n// Font structures for newer Adafruit_GFX (1.1 and later).\n// E"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/license.txt",
    "chars": 1626,
    "preview": "This TFT_eSPI library has been developed from the Adafruit_GFX library:\n\nhttps://github.com/adafruit/Adafruit-GFX-Librar"
  },
  {
    "path": "arduino_workspace/libraries/M5StickC-Plus/src/Fonts/GFXFF/print.txt",
    "chars": 1628,
    "preview": "#define TT1 TomThumb\n\n#define FF1 FreeMono9pt7b\n#define FF2 FreeMono12pt7b\n#define FF3 FreeMono18pt7b\n#define FF4 FreeMo"
  }
]

// ... and 72 more files (download for full content)

About this extraction

This page contains the full source code of the frank26080115/alpha-fairy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 272 files (41.2 MB), approximately 1.4M tokens, and a symbol index with 400 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!