Compare commits
117
Commits
285717fe92
..
0.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcfa10bb1f | ||
|
|
febf79a98a | ||
|
|
424104f8ff | ||
|
|
955c78de64 | ||
|
|
958508eb6b | ||
|
|
6aa5d41a42 | ||
|
|
2a27c6d068 | ||
|
|
b73ae7fe74 | ||
|
|
f264efdb12 | ||
|
|
0d32d21d75 | ||
|
|
9b4dfbd180 | ||
|
|
926c786705 | ||
|
|
299623927e | ||
|
|
9a3bb81337 | ||
|
|
73d1839ddd | ||
|
|
cc86533e86 | ||
|
|
bf3f270067 | ||
|
|
cfe838e03b | ||
|
|
7484fe478c | ||
|
|
d41d539435 | ||
|
|
cf6fec78dc | ||
|
|
10d76dde12 | ||
|
|
7b5a63d220 | ||
|
|
c1d5f5d562 | ||
|
|
adfeee063f | ||
|
|
2d3928ed81 | ||
|
|
48249fbd1e | ||
|
|
1a53dccebd | ||
|
|
3e28724b62 | ||
|
|
d86b3fe134 | ||
|
|
1a3d6b125d | ||
|
|
b2020f5512 | ||
|
|
70dc0f018e | ||
|
|
424594488f | ||
|
|
57fdb1c0fb | ||
|
|
5e1694748c | ||
|
|
063a1df851 | ||
|
|
d429966dd4 | ||
|
|
c78f2a9840 | ||
|
|
11f01d3a41 | ||
|
|
973d372521 | ||
|
|
67da8139b3 | ||
|
|
c287aa03a4 | ||
|
|
5d68c8b305 | ||
|
|
def7abbd60 | ||
|
|
9ad8111ce7 | ||
|
|
57d1939be7 | ||
|
|
012992f904 | ||
|
|
c262f222de | ||
|
|
449b3ca161 | ||
|
|
6989035ef8 | ||
|
|
108cf57202 | ||
|
|
a640fbecf8 | ||
|
|
7a5719b46d | ||
|
|
8c3576e397 | ||
|
|
fdb5634ea6 | ||
|
|
5cabba7712 | ||
|
|
a86d405fb0 | ||
|
|
e4b5dc0e6a | ||
|
|
dfc74f94c2 | ||
|
|
3518cbb56d | ||
|
|
8994953254 | ||
|
|
ead39fd04b | ||
|
|
5a7381a0eb | ||
|
|
f69fc90b5c | ||
|
|
5bae283838 | ||
|
|
c7a32fe41f | ||
|
|
d450f362d1 | ||
|
|
6ddcf9b592 | ||
|
|
492c6fd23e | ||
|
|
7c852cf7d1 | ||
|
|
69f357998e | ||
|
|
09f68a3d03 | ||
|
|
7ec7efcb47 | ||
|
|
45af2d0e81 | ||
|
|
0926e9e6e4 | ||
|
|
02b157c02b | ||
|
|
07cc589e59 | ||
|
|
b743a1ca8e | ||
|
|
2ed8017aa2 | ||
|
|
de453fed1d | ||
|
|
c715c18bf7 | ||
|
|
9fa697ae0c | ||
|
|
79294f6b8f | ||
|
|
eceffaa289 | ||
|
|
bb151caee7 | ||
|
|
dd6e649d74 | ||
|
|
ad8cee12ab | ||
|
|
4ecfdea1a1 | ||
|
|
5ed2fe391d | ||
|
|
021f77eab3 | ||
|
|
6d3d25a288 | ||
|
|
9a33030623 | ||
|
|
6414f85257 | ||
|
|
f0d92da8f2 | ||
|
|
8679c8f57c | ||
|
|
899caab70c | ||
|
|
98c8e7e77c | ||
|
|
7198d943b0 | ||
|
|
248af4b8fb | ||
|
|
05a027e2bf | ||
|
|
fa0f27df6a | ||
|
|
2631613b8d | ||
|
|
72aa7ba3f6 | ||
|
|
e08bac2e10 | ||
|
|
12d28e2148 | ||
|
|
85502b417e | ||
|
|
ddec7f78dd | ||
|
|
2f9f86b3dd | ||
|
|
47eb1157ef | ||
|
|
aee239a931 | ||
|
|
1ee8b728f9 | ||
|
|
2c80aca7b5 | ||
|
|
7704772ebe | ||
|
|
4186c7da9e | ||
|
|
802c9d0a30 | ||
|
|
2ccdbeecc8 |
@@ -1,5 +1,2 @@
|
||||
CompileFlags:
|
||||
Add: [-std=c++2a]
|
||||
Remove:
|
||||
- -fno-tree-switch-conversion
|
||||
- -fstrict-volatile-bitfields
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Run formatter from repository root regardless of current directory.
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
# Capture the files already staged for commit so we only re-stage those
|
||||
# paths after formatting.
|
||||
staged_files=()
|
||||
while IFS= read -r -d '' file; do
|
||||
staged_files+=("${file}")
|
||||
done < <(git diff --cached --name-only -z --diff-filter=ACMR)
|
||||
|
||||
# Intentionally format all currently modified tracked C/C++ files.
|
||||
# The helper handles no-op cases and exits 0 when nothing matches.
|
||||
echo "Running clang-format fix before commit..."
|
||||
./bin/clang-format-fix
|
||||
|
||||
# Ensure formatting changes are included in the pending commit without
|
||||
# staging unrelated tracked modifications from other files in the
|
||||
# working tree.
|
||||
if ((${#staged_files[@]})); then
|
||||
git add -- "${staged_files[@]}"
|
||||
fi
|
||||
@@ -1,18 +1,9 @@
|
||||
## Summary
|
||||
|
||||
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
|
||||
* **What is the goal of this PR?** (e.g., Fixes a bug in the user authentication module, Implements the new feature for
|
||||
file uploading.)
|
||||
* **What changes are included?**
|
||||
|
||||
## Additional Context
|
||||
|
||||
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
|
||||
specific areas to focus on).
|
||||
|
||||
---
|
||||
|
||||
### AI Usage
|
||||
|
||||
While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
|
||||
helps set the right context for reviewers.
|
||||
|
||||
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO >**_
|
||||
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on).
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"ref": "refs/heads/release/1.2",
|
||||
"repository": {
|
||||
"full_name": "jpirnay/crosspoint-reader"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"action": "published",
|
||||
"release": {
|
||||
"tag_name": "1.40"
|
||||
},
|
||||
"repository": {
|
||||
"full_name": "jpirnay/crosspoint-reader"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../.skills/SKILL.md
|
||||
+17
-133
@@ -1,24 +1,29 @@
|
||||
name: CI (build)
|
||||
|
||||
on:
|
||||
name: CI
|
||||
'on':
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
clang-format:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
- name: Install clang-format-21
|
||||
run: |
|
||||
@@ -28,132 +33,11 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-format-21
|
||||
|
||||
- name: Run clang-format
|
||||
run: |
|
||||
PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix
|
||||
git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
||||
|
||||
cppcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Run cppcheck
|
||||
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
- name: Run clang-format
|
||||
run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
||||
|
||||
- name: Build CrossPoint
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pio run | tee pio.log
|
||||
|
||||
- name: Extract firmware stats
|
||||
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ram_line="$(grep -E "RAM:\\s" -m1 pio.log || true)"
|
||||
flash_line="$(grep -E "Flash:\\s" -m1 pio.log || true)"
|
||||
echo "ram_line=${ram_line}" >> "$GITHUB_OUTPUT"
|
||||
echo "flash_line=${flash_line}" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "## Firmware build stats"
|
||||
if [ -n "$ram_line" ]; then echo "- ${ram_line}"; else echo "- RAM: not found"; fi
|
||||
if [ -n "$flash_line" ]; then echo "- ${flash_line}"; else echo "- Flash: not found"; fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload firmware.bin artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: firmware.bin
|
||||
path: .pio/build/default/firmware.bin
|
||||
if-no-files-found: error
|
||||
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Run standalone tests
|
||||
run: |
|
||||
set +e
|
||||
shopt -s nullglob
|
||||
|
||||
failed=0
|
||||
tests=("test/run_*.sh")
|
||||
|
||||
if [ ${#tests[@]} -eq 0 ]; then
|
||||
printf 'No tests found in test/ directory\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run_test() {
|
||||
printf '\n== Running %s ==\n' "${1}"
|
||||
if ! bash "${1}"; then
|
||||
failed=1
|
||||
fi
|
||||
}
|
||||
|
||||
for t in ${tests[@]}; do
|
||||
run_test "$t"
|
||||
done
|
||||
|
||||
exit "${failed}"
|
||||
|
||||
# This job is used as the PR required actions check, allows for changes to other steps in the future without breaking
|
||||
# PR requirements.
|
||||
test-status:
|
||||
name: Test Status
|
||||
needs:
|
||||
- build
|
||||
- run-tests
|
||||
- clang-format
|
||||
- cppcheck
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Fail because needed jobs failed
|
||||
# Fail if any job failed or was cancelled (skipped jobs are ok)
|
||||
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
run: exit 1
|
||||
- name: Success
|
||||
run: exit 0
|
||||
run: pio run
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
name: "PR Formatting"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
title-check:
|
||||
name: Title Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check PR Title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,48 +1,40 @@
|
||||
name: Compile Release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
|
||||
- uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pip
|
||||
~/.platformio/.cache
|
||||
key: ${{ runner.os }}-pio
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
run: pip install --upgrade platformio
|
||||
|
||||
- name: Build CrossPoint
|
||||
run: pio run -e gh_release
|
||||
|
||||
- name: Patch min_chip_rev_full to 0
|
||||
run: python3 scripts/patch_min_chip_rev.py .pio/build/gh_release/firmware.bin
|
||||
|
||||
- name: Upload release assets
|
||||
if: ${{ env.ACT != 'true' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
tag_name: ${{ github.event.release.tag_name }}
|
||||
files: |
|
||||
name: CrossPoint-${{ github.ref_name }}
|
||||
path: |
|
||||
.pio/build/gh_release/bootloader.bin
|
||||
.pio/build/gh_release/firmware.bin
|
||||
.pio/build/gh_release/firmware.elf
|
||||
.pio/build/gh_release/firmware.map
|
||||
.pio/build/gh_release/partitions.bin
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
name: Publish Release Candidate
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "release/**"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-release-candidate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
with:
|
||||
version: "latest"
|
||||
enable-cache: false
|
||||
|
||||
- name: Install PlatformIO Core
|
||||
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
|
||||
|
||||
- name: Extract env
|
||||
run: |
|
||||
VERSION="$(python3 - <<'PY'
|
||||
import configparser
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read("platformio.ini")
|
||||
version = config["crosspoint"]["version"].strip()
|
||||
if version.count(".") == 1:
|
||||
version = f"{version}.0"
|
||||
print(version)
|
||||
PY
|
||||
)"
|
||||
|
||||
NEXT_RC="$(python3 - "$VERSION" <<'PY'
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
version = sys.argv[1]
|
||||
pattern = re.compile(rf"^{re.escape(version)}-rc\.(\d+)(?:\.\d+)?$")
|
||||
|
||||
highest = 0
|
||||
tags = subprocess.check_output(
|
||||
["git", "tag", "-l", f"{version}-rc.*"],
|
||||
text=True,
|
||||
)
|
||||
for tag in tags.splitlines():
|
||||
match = pattern.match(tag)
|
||||
if match:
|
||||
highest = max(highest, int(match.group(1)))
|
||||
|
||||
print(highest + 1)
|
||||
PY
|
||||
)"
|
||||
|
||||
echo "RC_TAG=${VERSION}-rc.${NEXT_RC}" >> $GITHUB_ENV
|
||||
echo "RELEASE_NOTES_PATH=${RUNNER_TEMP}/release-notes.md" >> $GITHUB_ENV
|
||||
|
||||
- name: Build CrossPoint Release Candidate
|
||||
env:
|
||||
CROSSPOINT_RC_VERSION: ${{ env.RC_TAG }}
|
||||
run: pio run -e gh_release_rc
|
||||
|
||||
- name: Patch min_chip_rev_full to 0
|
||||
run: python3 scripts/patch_min_chip_rev.py .pio/build/gh_release_rc/firmware.bin
|
||||
|
||||
- name: Generate release notes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
python3 scripts/generate_release_notes.py \
|
||||
--output "${RELEASE_NOTES_PATH}" \
|
||||
--target-sha "${GITHUB_SHA}" \
|
||||
--current-label "${RC_TAG}" \
|
||||
--release-scope any \
|
||||
--exclude-tag "${RC_TAG}"
|
||||
|
||||
- name: Publish prerelease assets
|
||||
if: ${{ env.ACT != 'true' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.RC_TAG }}
|
||||
target_commitish: ${{ github.sha }}
|
||||
name: CrossPoint ${{ env.RC_TAG }}
|
||||
body_path: ${{ env.RELEASE_NOTES_PATH }}
|
||||
prerelease: true
|
||||
files: |
|
||||
.pio/build/gh_release_rc/bootloader.bin
|
||||
.pio/build/gh_release_rc/firmware.bin
|
||||
.pio/build/gh_release_rc/firmware.elf
|
||||
.pio/build/gh_release_rc/firmware.map
|
||||
.pio/build/gh_release_rc/partitions.bin
|
||||
fail_on_unmatched_files: true
|
||||
-18
@@ -3,22 +3,4 @@
|
||||
.DS_Store
|
||||
.vscode
|
||||
lib/EpdFont/fontsrc
|
||||
lib/I18n/I18nKeys.h
|
||||
lib/I18n/I18nStrings.h
|
||||
lib/I18n/I18nStrings.cpp
|
||||
*.generated.h
|
||||
.vs
|
||||
build
|
||||
**/__pycache__/
|
||||
/compile_commands.json
|
||||
/.cache
|
||||
.history/
|
||||
/.venv
|
||||
*.local*
|
||||
*.cpfont
|
||||
!assets/*.cpfont
|
||||
!assets/**/*.cpfont
|
||||
lib/EpdFont/scripts/downloaded_fonts/*
|
||||
lib/EpdFont/scripts/instanced_fonts/*
|
||||
lib/EpdFont/scripts/output/*
|
||||
.editorconfig
|
||||
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
[submodule "open-x4-sdk"]
|
||||
path = open-x4-sdk
|
||||
url = https://github.com/jpirnay/community-sdk.git
|
||||
branch = crosspoint-sdcard-extensions
|
||||
url = https://github.com/open-x4-epaper/community-sdk.git
|
||||
|
||||
@@ -1,872 +0,0 @@
|
||||
# CrossPoint Reader Development Guide
|
||||
|
||||
Project: Open-source e-reader firmware for Xteink X4 (ESP32-C3)
|
||||
Mission: Provide a lightweight, high-performance reading experience focused on EPUB rendering on constrained hardware.
|
||||
|
||||
## AI Agent Identity and Cognitive Rules
|
||||
* Role: Senior Embedded Systems Engineer (ESP-IDF/Arduino-ESP32 specialized).
|
||||
* Primary Constraint: 380KB RAM is the hard ceiling. Stability is non-negotiable.
|
||||
* Evidence-Based Reasoning: Before proposing a change, you MUST cite the specific file path and line numbers that justify the modification.
|
||||
* Anti-Hallucination: Do not assume the existence of libraries or ESP-IDF functions. If you are unsure of an API's availability for the ESP32-C3 RISC-V target, check the open-x4-sdk or official docs first.
|
||||
* No Unfounded Claims: Do not claim performance gains or memory savings without explaining the technical mechanism (e.g., DRAM vs IRAM usage).
|
||||
* Resource Justification: You must justify any new heap allocation (new, malloc, std::vector) or explain why a stack/static alternative was rejected.
|
||||
* Verification: After suggesting a fix, instruct the user on how to verify it (e.g., monitoring heap via Serial or checking a specific cache file).
|
||||
---
|
||||
|
||||
## Development Environment Awareness
|
||||
|
||||
**CRITICAL**: Detect the host platform at session start to choose appropriate tools and commands.
|
||||
|
||||
### Platform Detection
|
||||
```bash
|
||||
# Detect platform (run once per session)
|
||||
uname -s
|
||||
# Returns: MINGW64_NT-* (Windows Git Bash), Linux, Darwin (macOS)
|
||||
```
|
||||
|
||||
**Detection Required**: Run `uname -s` at session start to determine platform
|
||||
|
||||
### Platform-Specific Behaviors
|
||||
- **Windows (Git Bash)**: Unix commands, `C:\` paths in Windows but `/` in bash, limited glob (use `find`+`xargs`)
|
||||
- **Linux/WSL**: Full bash, Unix paths, native glob support
|
||||
|
||||
**Cross-Platform Code Formatting**:
|
||||
```bash
|
||||
find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Platform and Hardware Constraints
|
||||
|
||||
### Hardware Specs
|
||||
* MCU: ESP32-C3 (Single-core RISC-V @ 160MHz)
|
||||
* RAM: ~380KB usable (VERY LIMITED - primary project constraint)
|
||||
* **NO PSRAM**: ESP32-C3 has no PSRAM capability (unlike ESP32-S3)
|
||||
* **Single Buffer Mode**: Only ONE 48KB framebuffer (not double-buffered)
|
||||
* Flash: 16MB (Instruction storage and static data)
|
||||
* Display: 800x480 E-Ink (Slow refresh, monochrome, 1-2s full update)
|
||||
* Framebuffer: 48,000 bytes (800 × 480 ÷ 8)
|
||||
* Storage: SD Card (Used for books and aggressive caching)
|
||||
|
||||
### The Resource Protocol
|
||||
1. Stack Safety: Limit local function variables to < 256 bytes. The ESP32-C3 default stack is small; use std::unique_ptr or static pools for larger buffers.
|
||||
2. Heap Fragmentation: Avoid repeated new/delete in loops. Allocate buffers once during onEnter() and reuse them.
|
||||
3. Flash Persistence: Large constant data (UI strings, lookup tables) MUST be marked static const to stay in Flash (Instruction Bus), freeing DRAM.
|
||||
4. String Policy: Prohibit std::string and Arduino String in hot paths. Use std::string_view for read-only access and snprintf with fixed char[] buffers for construction.
|
||||
5. UI Strings: All user-facing text must use the `tr()` macro (e.g., `tr(STR_LOADING)`) for i18n support. Never hardcode UI strings directly. For the avoidance of doubt, logging messages (LOG_DBG/LOG_ERR) can be hardcoded, but user-facing text must use `tr()`.
|
||||
6. `constexpr` First: Compile-time constants and lookup tables must be `constexpr`, not just `static const`. This moves computation to compile time, enables dead-branch elimination, and guarantees flash placement. Use `static constexpr` for class-level constants.
|
||||
7. `std::vector` Pre-allocation: Always call `.reserve(N)` before any `push_back()` loop. Each growth event allocates a new block (2×), copies all elements, then frees the old one — three heap operations that fragment DRAM. When the final size is unknown, estimate conservatively.
|
||||
8. SPIFFS Write Throttling: Never write a settings file on every user interaction. Guard all writes with a value-change check (`if (newVal == _current) return;`). Progress saves during reading must be debounced — write on activity exit or every N page turns, not on every turn. SPIFFS sectors have a finite erase cycle limit.
|
||||
|
||||
---
|
||||
|
||||
## Project Architecture
|
||||
|
||||
### Build System: PlatformIO
|
||||
|
||||
**PlatformIO is BOTH a VS Code extension AND a CLI tool**:
|
||||
|
||||
1. **VS Code Extension** (Recommended):
|
||||
* Extension ID: `platformio.platformio-ide` (see `.vscode/extensions.json`)
|
||||
* Provides: Toolbar buttons, IntelliSense, integrated build/upload/monitor
|
||||
* Configuration: `.vscode/c_cpp_properties.json`, `.vscode/tasks.json`
|
||||
* Usage: Click Build (✓), Upload (→), or Monitor (🔌) buttons
|
||||
|
||||
2. **CLI Tool** (`pio` command):
|
||||
* **Installation**: Python package (typically `pip install platformio`)
|
||||
* **Windows Location**: `C:\Users\<user>\AppData\Local\Programs\Python\Python3xx\Scripts\pio.exe`
|
||||
* **Verify**: `which pio` (Git Bash) or `where.exe pio` (cmd)
|
||||
* **Usage**: `pio run`, `pio run -t upload`, etc.
|
||||
|
||||
**Configuration Files**:
|
||||
* `platformio.ini`: Main build configuration (committed to git)
|
||||
* `platformio.local.ini`: Local overrides (gitignored, create if needed)
|
||||
* `partitions.csv`: ESP32 flash partition layout
|
||||
|
||||
### Build Environment
|
||||
* **Standard**: C++20 (`-std=c++2a`). No Exceptions, No RTTI.
|
||||
* **Logging**: ALWAYS use `LOG_INF`, `LOG_DBG`, or `LOG_ERR` from `Logging.h`. Raw Serial output is deprecated.
|
||||
* **Environments** (in `platformio.ini`):
|
||||
* `default`: Development (LOG_LEVEL=2, serial enabled)
|
||||
* `gh_release`: Production (LOG_LEVEL=0)
|
||||
* `gh_release_rc`: Release candidate (LOG_LEVEL=1)
|
||||
* `slim`: Minimal build (no serial logging)
|
||||
|
||||
### Critical Build Flags
|
||||
These flags in `platformio.ini` fundamentally affect firmware behavior:
|
||||
|
||||
```cpp
|
||||
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1 // Single framebuffer (saves 48KB RAM!)
|
||||
-DARDUINO_USB_MODE=1 // Enable USB CDC
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1 // Serial available immediately at boot
|
||||
-DXML_CONTEXT_BYTES=1024 // XML parser memory limit (EPUB parsing)
|
||||
-DUSE_UTF8_LONG_NAMES=1 // SD card long filename support
|
||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1 // Avoid zlib name conflicts
|
||||
-DXML_GE=0 // Disable XML general entities (security)
|
||||
```
|
||||
|
||||
**SINGLE_BUFFER_MODE implications**:
|
||||
- Only ONE framebuffer exists (not double-buffered)
|
||||
- Grayscale rendering requires temporary buffer allocation (`renderer.storeBwBuffer()`)
|
||||
- Must call `renderer.restoreBwBuffer()` to free temporary buffers
|
||||
- See [lib/GfxRenderer/GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp) for malloc usage
|
||||
|
||||
### Directory Structure
|
||||
* lib/: Internal libraries (Epub engine, GfxRenderer, UITheme, I18n)
|
||||
* lib/hal/: Hardware Abstraction Layer (HalDisplay, HalGPIO, HalStorage)
|
||||
* lib/I18n/: Internationalization (translations in `translations/*.yaml`, generated string tables)
|
||||
* src/activities/: UI logic using the Activity Lifecycle (onEnter, loop, onExit)
|
||||
* open-x4-sdk/: Low-level SDK (EInkDisplay, InputManager, BatteryMonitor, SDCardManager)
|
||||
* .crosspoint/: SD-based binary cache for EPUB metadata and pre-rendered layout sections
|
||||
|
||||
### Hardware Abstraction Layer (HAL)
|
||||
|
||||
**CRITICAL**: Always use HAL classes, NOT SDK classes directly.
|
||||
|
||||
| HAL Class | Wraps SDK Class | Purpose | Singleton Macro |
|
||||
|-----------|----------------|---------|-----------------|
|
||||
| `HalDisplay` | `EInkDisplay` | E-ink display control | *(none)* |
|
||||
| `HalGPIO` | `InputManager` | Button input handling | *(none)* |
|
||||
| `HalStorage` | `SDCardManager` | SD card file I/O | `Storage` |
|
||||
|
||||
**Location**: [lib/hal/](../lib/hal/)
|
||||
|
||||
**Why HAL?**
|
||||
- Provides consistent error logging per module
|
||||
- Abstracts SDK implementation details
|
||||
- Centralizes resource management
|
||||
|
||||
**Example - HalStorage**:
|
||||
```cpp
|
||||
#include <HalStorage.h>
|
||||
|
||||
// Use Storage singleton (defined via macro)
|
||||
FsFile file;
|
||||
if (Storage.openFileForRead("MODULE", "/path/to/file.bin", file)) {
|
||||
// Read from file
|
||||
file.close(); // Explicit close required
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**: See example above. Uses `FsFile` (SdFat), NOT Arduino `File`.
|
||||
|
||||
---
|
||||
|
||||
## Coding Standards
|
||||
|
||||
### Naming Conventions
|
||||
* Classes: PascalCase (e.g., EpubReaderActivity)
|
||||
* Methods/Variables: camelCase (e.g., renderPage())
|
||||
* Constants: UPPER_SNAKE_CASE (e.g., MAX_BUFFER_SIZE)
|
||||
* Private Members: memberVariable (no prefix)
|
||||
* File Names: Match Class names (e.g., EpubReaderActivity.cpp)
|
||||
|
||||
### Header Guards
|
||||
* Use #pragma once for all header files.
|
||||
|
||||
### Memory Safety and RAII
|
||||
* Smart Pointers: Prefer std::unique_ptr. Avoid std::shared_ptr (unnecessary atomic overhead for a single-core RISC-V).
|
||||
* RAII: Use destructors for cleanup, but call file.close() or vTaskDelete() explicitly for deterministic resource release.
|
||||
|
||||
### ESP32-C3 Platform Pitfalls
|
||||
|
||||
#### `std::string_view` and Null Termination
|
||||
`string_view` is *not* null-terminated. Passing `.data()` to any C-style API (`drawText`, `snprintf`, `strcmp`, SdFat file paths) is undefined behaviour when the view is a substring or a view of a non-null-terminated buffer.
|
||||
|
||||
**Rule**: `string_view` is safe only when passing to C++ APIs that accept `string_view`. For any C API boundary, convert explicitly:
|
||||
```cpp
|
||||
// WRONG - undefined behaviour if view is a substring:
|
||||
renderer.drawText(font, x, y, myView.data(), true);
|
||||
|
||||
// CORRECT - guaranteed null-terminated:
|
||||
renderer.drawText(font, x, y, std::string(myView).c_str(), true);
|
||||
|
||||
// CORRECT - for short strings, use a stack buffer:
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.*s", (int)myView.size(), myView.data());
|
||||
```
|
||||
|
||||
#### `IRAM_ATTR` and Flash Cache Safety
|
||||
All code runs from flash via the instruction cache. During SPI flash operations (OTA write, SPIFFS commit, NVS update) the cache is briefly suspended. Any code that can execute during this window — ISRs in particular — must reside in IRAM or it will crash silently.
|
||||
|
||||
```cpp
|
||||
// ISR handler: must be in IRAM
|
||||
void IRAM_ATTR gpioISR() { ... }
|
||||
|
||||
// Data accessed from IRAM_ATTR code: must be in DRAM, never a flash const
|
||||
static DRAM_ATTR uint32_t isrEventFlags = 0;
|
||||
```
|
||||
|
||||
**Rules**:
|
||||
- All ISR handlers: `IRAM_ATTR`
|
||||
- Data read by `IRAM_ATTR` code: `DRAM_ATTR` (a flash-resident `static const` will fault)
|
||||
- Normal task code does **not** need `IRAM_ATTR`
|
||||
|
||||
#### ISR vs Task Shared State
|
||||
`xSemaphoreTake()` (mutex) **cannot** be called from ISR context — it will crash. Use the correct primitive for each communication direction:
|
||||
|
||||
| Direction | Correct primitive |
|
||||
|---|---|
|
||||
| ISR → task (data) | `xQueueSendFromISR()` + `portYIELD_FROM_ISR()` |
|
||||
| ISR → task (signal) | `xSemaphoreGiveFromISR()` + `portYIELD_FROM_ISR()` |
|
||||
| Task → task | `xSemaphoreTake()` / mutex |
|
||||
| Simple flag (single writer ISR) | `volatile bool` + `portENTER_CRITICAL_ISR()` |
|
||||
|
||||
#### RISC-V Alignment
|
||||
ESP32-C3 faults on unaligned multi-byte loads. Never cast a `uint8_t*` buffer to a wider pointer type and dereference it directly. Use `memcpy` for any unaligned read:
|
||||
|
||||
```cpp
|
||||
// WRONG — faults if buf is not 4-byte aligned:
|
||||
uint32_t val = *reinterpret_cast<const uint32_t*>(buf);
|
||||
|
||||
// CORRECT:
|
||||
uint32_t val;
|
||||
memcpy(&val, buf, sizeof(val));
|
||||
```
|
||||
|
||||
This applies to all cache deserialization code and any raw buffer-to-struct casting. `__attribute__((packed))` structs have the same hazard when accessed via member reference.
|
||||
|
||||
#### Template and `std::function` Bloat
|
||||
Each template instantiation generates a separate binary copy. `std::function<void()>` adds ~2–4 KB per unique signature and heap-allocates its closure. Avoid both in library code and any path called from the render loop:
|
||||
|
||||
```cpp
|
||||
// Avoid — heap-allocating, large binary footprint:
|
||||
std::function<void()> callback;
|
||||
|
||||
// Prefer — zero overhead:
|
||||
void (*callback)() = nullptr;
|
||||
|
||||
// For member function + context (common activity callback pattern):
|
||||
struct Callback { void* ctx; void (*fn)(void*); };
|
||||
```
|
||||
|
||||
When a template is necessary, limit instantiations: use explicit template instantiation in a `.cpp` file to prevent the compiler from generating duplicates across translation units.
|
||||
|
||||
---
|
||||
|
||||
### Error Handling Philosophy
|
||||
|
||||
**Source**: [src/main.cpp:132-143](../src/main.cpp), [lib/GfxRenderer/GfxRenderer.cpp:10](../lib/GfxRenderer/GfxRenderer.cpp)
|
||||
|
||||
**Pattern Hierarchy**:
|
||||
1. **LOG_ERR + return false** (90%): `LOG_ERR("MOD", "Failed: %s", reason); return false;`
|
||||
2. **LOG_ERR + fallback**: `LOG_ERR("MOD", "Unavailable"); useDefault();`
|
||||
3. **assert(false)**: Only for fatal "impossible" states (framebuffer missing)
|
||||
4. **ESP.restart()**: Only for recovery (OTA complete)
|
||||
|
||||
**Rules**: NO exceptions, NO abort(), ALWAYS log before error return
|
||||
|
||||
### Acceptable malloc/free Patterns
|
||||
|
||||
**Source**: [src/activities/home/HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp), [lib/GfxRenderer/GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp)
|
||||
|
||||
Despite "prefer stack allocation," malloc is acceptable for:
|
||||
1. **Large temporary buffers** (> 256 bytes, won't fit on stack)
|
||||
2. **One-time allocations** during activity initialization
|
||||
3. **Bitmap rendering buffers** (variable size, used briefly)
|
||||
|
||||
**Pattern**:
|
||||
```cpp
|
||||
// Allocate
|
||||
auto* buffer = static_cast<uint8_t*>(malloc(bufferSize));
|
||||
if (!buffer) {
|
||||
LOG_ERR("MODULE", "malloc failed: %d bytes", bufferSize);
|
||||
return false; // Handle allocation failure
|
||||
}
|
||||
|
||||
// Use buffer
|
||||
processData(buffer, bufferSize);
|
||||
|
||||
// Free immediately after use
|
||||
free(buffer);
|
||||
buffer = nullptr;
|
||||
```
|
||||
|
||||
**Rules**:
|
||||
- **ALWAYS check for nullptr** after malloc
|
||||
- **Free immediately** after use (don't hold across multiple operations)
|
||||
- **Set to nullptr** after free (avoid use-after-free)
|
||||
- **Document size**: Comment why stack allocation was rejected
|
||||
|
||||
**Examples in codebase**:
|
||||
- Cover image buffers: [HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp)
|
||||
- Text chunk buffers: [TxtReaderActivity.cpp:259](../src/activities/reader/TxtReaderActivity.cpp)
|
||||
- Bitmap rendering: [GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp)
|
||||
- OTA update buffer: [OtaUpdater.cpp:40](../src/network/OtaUpdater.cpp)
|
||||
|
||||
---
|
||||
|
||||
## UI and Orientation Guidelines
|
||||
|
||||
### Orientation-Aware Logic
|
||||
* No Hardcoding: Never assume 800 or 480. Use renderer.getScreenWidth() and renderer.getScreenHeight().
|
||||
* Viewable Area: Use renderer.getOrientedViewableTRBL() to stay within physical bezel margins.
|
||||
|
||||
### Logical Button Mapping
|
||||
|
||||
**Source**: [src/MappedInputManager.cpp:20-55](../src/MappedInputManager.cpp)
|
||||
|
||||
Constraint: Physical button positions are fixed on hardware, but their logical functions change based on user settings and screen orientation.
|
||||
|
||||
**Button Categories**:
|
||||
1. **Physical Fixed** (Up/Down side buttons):
|
||||
- `Button::Up` → Always `HalGPIO::BTN_UP`
|
||||
- `Button::Down` → Always `HalGPIO::BTN_DOWN`
|
||||
|
||||
2. **User Remappable** (Front buttons):
|
||||
- `Button::Back` → Maps to `SETTINGS.frontButtonBack` (hardware index)
|
||||
- `Button::Confirm` → Maps to `SETTINGS.frontButtonConfirm`
|
||||
- `Button::Left` → Maps to `SETTINGS.frontButtonLeft`
|
||||
- `Button::Right` → Maps to `SETTINGS.frontButtonRight`
|
||||
|
||||
3. **Reader-Specific** (Page navigation with optional swap):
|
||||
- `Button::PageBack` → Uses side button (swappable via `SETTINGS.sideButtonLayout`)
|
||||
- `Button::PageForward` → Uses side button (swappable)
|
||||
|
||||
**Implementation**:
|
||||
- Activities use **logical buttons** (e.g., `Button::Confirm`)
|
||||
- `MappedInputManager` translates to **physical hardware buttons**
|
||||
- User can remap front buttons in settings
|
||||
- Orientation changes handled separately by renderer coordinate transforms
|
||||
|
||||
**Rule**: Always use `MappedInputManager::Button::*` enums, never raw `HalGPIO::BTN_*` indices (except in ButtonRemapActivity).
|
||||
|
||||
### UITheme (The GUI Macro)
|
||||
* Rule: All UI rendering must go through the GUI macro (UITheme).
|
||||
* Do not hardcode fonts, colors, or positioning. This ensures orientation-aware layout consistency.
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Singleton Access
|
||||
**Available Singletons**:
|
||||
```cpp
|
||||
#define SETTINGS CrossPointSettings::getInstance() // User settings
|
||||
#define APP_STATE CrossPointState::getInstance() // Runtime state
|
||||
#define GUI UITheme::getInstance() // Current theme
|
||||
#define Storage HalStorage::getInstance() // SD card I/O
|
||||
#define I18N I18n::getInstance() // Internationalization
|
||||
```
|
||||
|
||||
### Activity Lifecycle and Memory Management
|
||||
|
||||
**Source**: [src/main.cpp:132-143](../src/main.cpp)
|
||||
|
||||
**CRITICAL**: Activities are **heap-allocated** and **deleted on exit**.
|
||||
|
||||
```cpp
|
||||
// main.cpp navigation pattern
|
||||
void exitActivity() {
|
||||
if (currentActivity) {
|
||||
currentActivity->onExit();
|
||||
delete currentActivity; // Activity deleted here!
|
||||
currentActivity = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void enterNewActivity(Activity* activity) {
|
||||
currentActivity = activity; // Heap-allocated activity
|
||||
currentActivity->onEnter();
|
||||
}
|
||||
```
|
||||
|
||||
**Memory Implications**:
|
||||
- Activity navigation = `delete` old activity + `new` create next activity
|
||||
- Any memory allocated in `onEnter()` MUST be freed in `onExit()`
|
||||
- FreeRTOS tasks MUST be deleted in `onExit()` before activity destruction
|
||||
- File handles MUST be closed in `onExit()`
|
||||
|
||||
**Activity Pattern**:
|
||||
```cpp
|
||||
void onEnter() { Activity::onEnter(); /* alloc: buffer, tasks */ render(); }
|
||||
void loop() { mappedInput.update(); /* handle input */ }
|
||||
void onExit() { /* free: vTaskDelete, free buffer, close files */ Activity::onExit(); }
|
||||
```
|
||||
|
||||
**Critical**: Free resources in reverse order. Delete tasks BEFORE activity destruction.
|
||||
|
||||
### FreeRTOS Task Guidelines
|
||||
|
||||
**Source**: [src/activities/util/KeyboardEntryActivity.cpp:45-50](../src/activities/util/KeyboardEntryActivity.cpp)
|
||||
|
||||
**Pattern**: See Activity Lifecycle above. `xTaskCreate(&taskTrampoline, "Name", stackSize, this, 1, &handle)`
|
||||
|
||||
**Stack Sizing** (in BYTES, not words):
|
||||
- **2048**: Simple rendering (most activities)
|
||||
- **4096**: Network, EPUB parsing
|
||||
- Monitor: `uxTaskGetStackHighWaterMark()` if crashes
|
||||
|
||||
**Rules**: Always `vTaskDelete()` in `onExit()` before destruction. Use mutex if shared state.
|
||||
|
||||
### Global Font Loading
|
||||
|
||||
**Source**: [src/main.cpp:40-115](../src/main.cpp)
|
||||
|
||||
**All fonts are loaded as global static objects** at firmware startup:
|
||||
- Bookerly: 12, 14, 16, 18pt (4 styles each: regular, bold, italic, bold-italic)
|
||||
- Noto Sans: 12, 14, 16, 18pt (4 styles each)
|
||||
- OpenDyslexic: 8, 10, 12, 14pt (4 styles each)
|
||||
- Ubuntu UI fonts: 10, 12pt (2 styles)
|
||||
|
||||
**Total**: ~80+ global `EpdFont` and `EpdFontFamily` objects
|
||||
|
||||
**Compilation Flag**:
|
||||
```cpp
|
||||
#ifndef OMIT_FONTS
|
||||
// Most fonts loaded here
|
||||
#endif
|
||||
```
|
||||
|
||||
**Implications**:
|
||||
- Fonts stored in **Flash** (marked as `static const` in `lib/EpdFont/builtinFonts/`)
|
||||
- Font rendering data cached in **DRAM** when first used
|
||||
- `OMIT_FONTS` can reduce binary size for minimal builds
|
||||
- Font IDs defined in [src/fontIds.h](../src/fontIds.h)
|
||||
|
||||
**Usage**:
|
||||
```cpp
|
||||
#include "fontIds.h"
|
||||
|
||||
renderer.insertFont(FONT_UI_MEDIUM, ui12FontFamily);
|
||||
renderer.drawText(FONT_UI_MEDIUM, x, y, "Hello", true);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
### Build Commands
|
||||
|
||||
**Via CLI**:
|
||||
```bash
|
||||
# Build firmware (default environment)
|
||||
pio run
|
||||
|
||||
# Build and upload to device
|
||||
pio run -t upload
|
||||
|
||||
# Build specific environment
|
||||
pio run -e gh_release
|
||||
|
||||
# Clean build artifacts
|
||||
pio run -t clean
|
||||
|
||||
# Upload filesystem data (if using SPIFFS/LittleFS)
|
||||
pio run -t uploadfs
|
||||
```
|
||||
|
||||
**Via VS Code**:
|
||||
* Use PlatformIO toolbar: Build (✓), Upload (→), Clean (🗑️)
|
||||
* Or Command Palette: `PlatformIO: Build`, `PlatformIO: Upload`, etc.
|
||||
|
||||
### Monitoring and Debugging
|
||||
|
||||
```bash
|
||||
# Enhanced monitor with color/logging (recommended)
|
||||
python3 scripts/debugging_monitor.py
|
||||
|
||||
# Standard PlatformIO monitor
|
||||
pio device monitor
|
||||
|
||||
# Combined upload + monitor
|
||||
pio run -t upload && pio device monitor
|
||||
```
|
||||
|
||||
**Via VS Code**: Click Monitor (🔌) button in PlatformIO toolbar
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Static analysis (cppcheck)
|
||||
pio check
|
||||
|
||||
# Format code (clang-format) - Windows Git Bash
|
||||
find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i
|
||||
|
||||
# Format code (clang-format) - Linux
|
||||
clang-format -i src/**/*.cpp src/**/*.h
|
||||
```
|
||||
|
||||
### Debugging Crashes
|
||||
|
||||
**Common Crash Causes**:
|
||||
|
||||
1. **Out of Memory** (Most common):
|
||||
```cpp
|
||||
LOG_DBG("MEM", "Free heap: %d bytes", ESP.getFreeHeap());
|
||||
```
|
||||
- Monitor heap usage throughout activity lifecycle
|
||||
- Check if large allocations (>10KB) occur before crash
|
||||
- Verify buffers are freed in `onExit()`
|
||||
|
||||
2. **Stack Overflow**:
|
||||
```cpp
|
||||
LOG_DBG("TASK", "Stack high water: %d", uxTaskGetStackHighWaterMark(taskHandle));
|
||||
```
|
||||
- Occurs during deep recursion or large local variables
|
||||
- Increase task stack size in `xTaskCreate()` (2048 → 4096)
|
||||
- Move large buffers to heap with malloc
|
||||
|
||||
3. **Use-After-Free**:
|
||||
- Activity deleted but task still running
|
||||
- Always `vTaskDelete()` in `onExit()` BEFORE activity destruction
|
||||
- Set pointers to `nullptr` after `free()`
|
||||
|
||||
4. **Corrupt Cache Files**:
|
||||
- Delete `.crosspoint/` directory on SD card
|
||||
- Forces clean re-parse of all EPUBs
|
||||
- Check file format versions in [docs/file-formats.md](../docs/file-formats.md)
|
||||
|
||||
5. **Watchdog Timeout**:
|
||||
- Loop/task blocked for >5 seconds
|
||||
- Add `vTaskDelay(1)` in tight loops
|
||||
- Check for blocking I/O operations
|
||||
|
||||
**Verification Steps**:
|
||||
1. Check serial output for stack traces
|
||||
2. Monitor heap with `ESP.getFreeHeap()` before/after operations
|
||||
3. Verify task deletion with task list (`vTaskList()`)
|
||||
4. Test with `LOG_LEVEL=2` (debug logging enabled)
|
||||
|
||||
---
|
||||
|
||||
## Git Workflow and Repository Awareness
|
||||
|
||||
### Repository Detection Protocol
|
||||
|
||||
**CRITICAL**: ALWAYS verify repository context before git operations. This could be:
|
||||
- A **fork** with `origin` pointing to personal repo, `upstream` to main repo
|
||||
- A **direct clone** with `origin` pointing to main repo
|
||||
- Multiple collaborator remotes
|
||||
|
||||
**Verification Commands** (run at session start):
|
||||
```bash
|
||||
# Check current branch
|
||||
git branch --show-current
|
||||
|
||||
# Check all remotes
|
||||
git remote -v
|
||||
|
||||
# Identify main branch name (could be 'main' or 'master')
|
||||
git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@'
|
||||
|
||||
# Check working tree status
|
||||
git status --short
|
||||
```
|
||||
|
||||
**Example Output** (forked repository):
|
||||
```text
|
||||
origin https://github.com/<your-username>/crosspoint-reader.git (fetch/push)
|
||||
upstream https://github.com/crosspoint-reader/crosspoint-reader.git (fetch/push)
|
||||
```
|
||||
|
||||
### Git Operation Rules
|
||||
|
||||
1. **Never assume branch names**:
|
||||
```bash
|
||||
# Bad: git push origin main
|
||||
# Good: git push origin $(git branch --show-current)
|
||||
```
|
||||
|
||||
2. **Never assume remote names or write permissions**:
|
||||
- **Forked repos**: Push to `origin` (your fork), submit PR to `upstream`
|
||||
- **Direct contributors**: May push feature branches to `upstream`
|
||||
- **Always ask**: "Should I push to origin or create a PR?"
|
||||
|
||||
3. **Check for upstream changes before starting work**:
|
||||
```bash
|
||||
# Sync fork with upstream (if applicable)
|
||||
git fetch upstream
|
||||
git merge upstream/main # or upstream/master
|
||||
```
|
||||
|
||||
4. **Use explicit remote and branch names**:
|
||||
```bash
|
||||
# Check remotes first
|
||||
git remote -v
|
||||
|
||||
# Use explicit syntax
|
||||
git push <remote> <branch>
|
||||
```
|
||||
|
||||
### Branch Naming Convention
|
||||
|
||||
**For feature/fix branches**:
|
||||
```text
|
||||
feature/<short-description> # New features
|
||||
fix/<issue-number>-<description> # Bug fixes
|
||||
refactor/<component-name> # Code refactoring
|
||||
docs/<topic> # Documentation updates
|
||||
```
|
||||
|
||||
**Examples**:
|
||||
- `feature/sd-download-progress`
|
||||
- `fix/123-orientation-crash`
|
||||
- `refactor/hal-storage`
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
**Pattern**:
|
||||
```text
|
||||
<type>: <short summary (50 chars max)>
|
||||
|
||||
<optional detailed description>
|
||||
|
||||
```
|
||||
|
||||
**Types**: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`
|
||||
|
||||
**Example**:
|
||||
```text
|
||||
feat: add real-time SD download progress bar
|
||||
|
||||
Implements progress tracking for book downloads using
|
||||
UITheme progress bar component with heap-safe updates.
|
||||
|
||||
Tested in all 4 orientations with 5MB+ files.
|
||||
```
|
||||
|
||||
### When to Commit
|
||||
|
||||
**DO commit when**:
|
||||
- User explicitly requests: "commit these changes"
|
||||
- Feature is complete and tested on device
|
||||
- Bug fix is verified working
|
||||
- Refactoring preserves all functionality
|
||||
- All tests pass (`pio run` succeeds)
|
||||
|
||||
**DO NOT commit when**:
|
||||
- Changes are untested on actual hardware
|
||||
- Build fails or has warnings
|
||||
- Experimenting or debugging in progress
|
||||
- User hasn't explicitly requested commit
|
||||
- Files excluded by `.gitignore` would be included — always run `git status` and cross-check against `.gitignore` before staging (e.g., `*.generated.h`, `.pio/`, `compile_commands.json`, `platformio.local.ini`)
|
||||
|
||||
**Rule**: **If uncertain, ASK before committing.**
|
||||
|
||||
---
|
||||
|
||||
## Generated Files and Build Artifacts
|
||||
|
||||
### Files Generated by Build Scripts
|
||||
|
||||
**NEVER manually edit these files** - they are regenerated automatically:
|
||||
|
||||
1. **HTML Headers** (generated by `scripts/build_html.py`):
|
||||
- `src/network/html/*.generated.h`
|
||||
- **Source**: HTML templates in `data/html/` directory
|
||||
- **Triggered**: During PlatformIO `pre:` build step
|
||||
- **To modify**: Edit source HTML in `data/html/`, not generated headers
|
||||
|
||||
2. **I18n Headers** (generated by `scripts/gen_i18n.py`):
|
||||
- `lib/I18n/I18nKeys.h`, `lib/I18n/I18nStrings.h`, `lib/I18n/I18nStrings.cpp`
|
||||
- **Source**: YAML translation files in `lib/I18n/translations/` (one per language)
|
||||
- **To modify**: Edit source YAML files, then run `python scripts/gen_i18n.py lib/I18n/translations lib/I18n/`
|
||||
- **Commit**: Source YAML files only. All three generated files (`I18nKeys.h`, `I18nStrings.h`, `I18nStrings.cpp`) are in `.gitignore` and regenerated at build time.
|
||||
|
||||
3. **Build Artifacts** (in `.gitignore`):
|
||||
- `.pio/` - PlatformIO build output
|
||||
- `build/` - Compiled binaries
|
||||
- `*.generated.h` - Any auto-generated headers
|
||||
- `compile_commands.json` - LSP/IDE metadata
|
||||
|
||||
### Modifying Generated Content Workflow
|
||||
|
||||
**To change HTML pages**:
|
||||
1. Edit source: `data/html/<pagename>.html`
|
||||
2. Build: `pio run` (auto-triggers `scripts/build_html.py`)
|
||||
3. Generated headers update: `src/network/html/<pagename>Html.generated.h`
|
||||
4. **Commit ONLY** source HTML, NOT generated `.generated.h` files
|
||||
|
||||
**To add/modify translations (i18n)**:
|
||||
1. Edit or add YAML file: `lib/I18n/translations/<language>.yaml`
|
||||
- Each file must contain: `_language_name`, `_language_code`, `_order`, and `STR_*` keys
|
||||
- English (`english.yaml`) is the reference; missing keys in other languages fall back to English
|
||||
2. Run generator: `python scripts/gen_i18n.py lib/I18n/translations lib/I18n/`
|
||||
3. Generated files update: `I18nKeys.h`, `I18nStrings.h`, `I18nStrings.cpp`
|
||||
4. **Commit** source YAML files only. All three generated files are in `.gitignore` and regenerated at build time.
|
||||
|
||||
**To use translated strings in code**:
|
||||
```cpp
|
||||
#include <I18n.h>
|
||||
// Use tr() macro with StrId enum (defined in generated I18nKeys.h)
|
||||
renderer.drawText(FONT_UI, x, y, tr(STR_LOADING), true);
|
||||
```
|
||||
|
||||
**To add custom fonts**:
|
||||
1. Place source fonts in `lib/EpdFont/fontsrc/` (gitignored)
|
||||
2. Run conversion script (see `lib/EpdFont/README`)
|
||||
3. Update global font objects in `src/main.cpp:40-115`
|
||||
4. Add font ID constant to `src/fontIds.h`
|
||||
|
||||
---
|
||||
|
||||
## Local Development Configuration
|
||||
|
||||
### platformio.local.ini (Personal Overrides)
|
||||
|
||||
**Purpose**: Personal development settings that should NEVER be committed.
|
||||
|
||||
**Use Cases**:
|
||||
- Serial port configuration (varies by machine)
|
||||
- Debug flags for specific testing
|
||||
- Local build optimizations
|
||||
- Developer-specific paths
|
||||
|
||||
**Example** `platformio.local.ini`:
|
||||
```ini
|
||||
# platformio.local.ini (gitignored)
|
||||
[env:default]
|
||||
upload_port = COM7 # Windows: COMx, Linux: /dev/ttyUSBx
|
||||
monitor_port = COM7
|
||||
|
||||
build_flags =
|
||||
${base.build_flags}
|
||||
-DMY_DEBUG_FLAG=1 # Personal debug flags
|
||||
-DTEST_FEATURE_ENABLED=1
|
||||
```
|
||||
|
||||
**Configuration Hierarchy**:
|
||||
1. `platformio.ini` - **Committed**, shared project settings
|
||||
2. `platformio.local.ini` - **Gitignored**, personal overrides
|
||||
3. Local file extends/overrides base config
|
||||
|
||||
**Rules**:
|
||||
- **NEVER commit** `platformio.local.ini`
|
||||
- **NEVER put** personal info (serial ports, credentials) in main `platformio.ini`
|
||||
- Use `${base.build_flags}` to extend (not replace) base flags
|
||||
|
||||
---
|
||||
|
||||
## Testing and Verification Workflow
|
||||
|
||||
### Testing Checklist
|
||||
|
||||
**AI agent scope** (what you CAN verify):
|
||||
1. ✅ **Build**: `pio run -t clean && pio run` (0 errors/warnings)
|
||||
2. ✅ **Quality**: `pio check` + `find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i`
|
||||
3. ✅ **Format**: Commit messages (`feat:`/`fix:`), no `.gitignore`-excluded files staged (e.g., `*.generated.h`, `.pio/`, `platformio.local.ini`)
|
||||
4. ✅ **CI**: Fix GitHub Actions failures before review
|
||||
5. ✅ **Code review**: Ensure orientation-aware logic is correct in all 4 modes by inspecting switch/case coverage
|
||||
|
||||
**Human tester scope** (flag these for the user):
|
||||
6. 🔲 **Device**: Test on hardware
|
||||
7. 🔲 **Orientations**: Verify all 4 modes (Portrait/Inverted/Landscape CW/CCW)
|
||||
8. 🔲 **Heap**: `ESP.getFreeHeap()` > 50KB, no leaks
|
||||
9. 🔲 **Cache**: If EPUB modified, delete `.crosspoint/` and verify re-parse
|
||||
|
||||
### CI/CD Pipeline Awareness
|
||||
|
||||
**GitHub Actions** run automatically on pull requests:
|
||||
|
||||
| Workflow | File | Purpose |
|
||||
|----------|------|---------|
|
||||
| Build Check | `.github/workflows/ci.yml` | Verifies code compiles |
|
||||
| Format Check | `.github/workflows/pr-formatting-check.yml` | Validates clang-format |
|
||||
| Release Build | `.github/workflows/release.yml` | Production releases |
|
||||
| RC Build | `.github/workflows/release_candidate.yml` | Release candidates |
|
||||
|
||||
**Rules**:
|
||||
- **Fix CI failures BEFORE** requesting review
|
||||
- CI runs on: Push to PR, PR updates
|
||||
- Format check fails → Run clang-format locally
|
||||
- Build check fails → Fix compile errors
|
||||
|
||||
---
|
||||
|
||||
## Serial Monitoring and Live Debugging
|
||||
|
||||
### Serial Monitor Options
|
||||
|
||||
1. **Enhanced**: `python3 scripts/debugging_monitor.py` (color-coded, recommended)
|
||||
2. **Standard**: `pio device monitor` (basic, no colors)
|
||||
3. **VS Code**: Monitor (🔌) button (IDE-integrated)
|
||||
|
||||
### Live Debugging Patterns
|
||||
|
||||
**Heap**: `LOG_DBG("MEM", "Free: %d", ESP.getFreeHeap());` (every 5s in loop)
|
||||
**Stack**: `uxTaskGetStackHighWaterMark(nullptr)` (< 512 bytes → increase stack)
|
||||
**Flush**: `logSerial.flush();` (force output before crash)
|
||||
|
||||
**Port Detection**: Windows: `mode` | Linux: `ls /dev/ttyUSB* /dev/ttyACM*` or `dmesg | grep tty`
|
||||
|
||||
---
|
||||
|
||||
## Cache Management and Invalidation
|
||||
|
||||
### Cache Structure on SD Card
|
||||
|
||||
**Location**: `.crosspoint/` directory on SD card root
|
||||
|
||||
**Structure**: `.crosspoint/epub_<hash>/{book.bin, progress.bin, cover.bmp, sections/*.bin}`
|
||||
|
||||
**Hash**: `std::hash<std::string>{}(filepath)` → Moving/renaming file = new hash = lost progress
|
||||
|
||||
### Cache Invalidation Rules
|
||||
|
||||
**Cache is automatically invalidated when**:
|
||||
1. **File format version changes** (see `docs/file-formats.md`)
|
||||
- `book.bin` version number incremented
|
||||
- `section.bin` version number incremented
|
||||
2. **Render settings change**:
|
||||
- Font family or size (`SETTINGS.fontFamily`, `SETTINGS.fontSize`)
|
||||
- Line spacing (`SETTINGS.lineSpacing`)
|
||||
- Paragraph spacing (`SETTINGS.extraParagraphSpacing`)
|
||||
- Screen margins (`SETTINGS.screenMargin`)
|
||||
3. **Viewport dimensions change**:
|
||||
- Screen orientation change
|
||||
- Display resolution change
|
||||
4. **Book file modified**:
|
||||
- Moved, renamed, or content changed (new hash)
|
||||
|
||||
**Manual Cache Clear** (safe operations):
|
||||
```bash
|
||||
# Delete ALL caches (forces full regeneration)
|
||||
rm -rf /path/to/sd/.crosspoint/
|
||||
|
||||
# Delete specific book cache
|
||||
rm -rf /path/to/sd/.crosspoint/epub_<hash>/
|
||||
|
||||
# Keep progress, delete only rendered sections
|
||||
rm -rf /path/to/sd/.crosspoint/epub_<hash>/sections/
|
||||
```
|
||||
|
||||
**When to Clear Cache**:
|
||||
- EPUB parsing errors after code changes to `lib/Epub/`
|
||||
- Corrupt rendering (missing text, wrong layout)
|
||||
- Testing cache generation logic
|
||||
- After modifying:
|
||||
- `lib/Epub/Epub/Section.cpp`
|
||||
- `lib/Epub/Epub/BookMetadataCache.cpp`
|
||||
- Render settings in `CrossPointSettings`
|
||||
|
||||
### Cache File Format Versioning
|
||||
|
||||
**Source**: `lib/Epub/Epub/Section.cpp`, `lib/Epub/Epub/BookMetadataCache.cpp`
|
||||
|
||||
**Current Versions** (as of docs/file-formats.md):
|
||||
- `book.bin`: **Version 7** (header A includes cached `tocReliable` byte)
|
||||
- `section.bin`: **Version 23** (layout structure, includes paragraph LUT)
|
||||
|
||||
**Version Increment Rules**:
|
||||
1. **ALWAYS increment version** BEFORE changing binary structure
|
||||
2. Version mismatch → Cache auto-invalidated and regenerated
|
||||
3. Document format changes in `docs/file-formats.md`
|
||||
|
||||
**Example** (incrementing section format version):
|
||||
```cpp
|
||||
// lib/Epub/Epub/Section.cpp
|
||||
static constexpr uint8_t SECTION_FILE_VERSION = 20; // Was 19, now 20
|
||||
|
||||
// Add new field to structure
|
||||
struct PageLine {
|
||||
// ... existing fields ...
|
||||
uint16_t newField; // New field added
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Philosophy: We are building a dedicated e-reader, not a Swiss Army knife. If a feature adds RAM pressure without significantly improving the reading experience, it is Out of Scope.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Project Governance & Community Principles
|
||||
|
||||
CrossPoint Reader is a community-driven, open-source project. Our goal is to provide a high-quality, open-source
|
||||
firmware alternative for the Xteink X4 hardware. To keep this project productive and welcoming as we grow, we ask all
|
||||
contributors to follow these principles.
|
||||
|
||||
### 1. The "Human First" Rule
|
||||
Technical discussions can get heated, but they should never be personal.
|
||||
- **Assume good intent:** We are all volunteers working on this in our free time. If a comment seems abrasive, assume
|
||||
it’s a language barrier or a misunderstanding before taking offense.
|
||||
- **Focus on the code, not the person:** Critique the implementation, the performance, or the UX. Never the intelligence
|
||||
or character of the contributor.
|
||||
- **Inflammatory language:** Personal attacks, trolling, or exclusionary language (based on race, gender, background,
|
||||
etc.) are not welcome here and will be moderated.
|
||||
|
||||
### 2. A "Do-ocracy" with Guidance
|
||||
CrossPoint thrives because people step up to build what they want to see.
|
||||
- If you want a feature, the best way to get it is to start an
|
||||
[Idea Discussion](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas) or open a PR.
|
||||
- If you want to report a bug, check for duplicates and create an
|
||||
[Issue](https://github.com/crosspoint-reader/crosspoint-reader/issues).
|
||||
- While we encourage experimentation, the maintainers reserve the right to guide the project’s technical direction to
|
||||
ensure stability on the ESP32-C3’s constrained hardware.
|
||||
- For more guidance on the scope of the project, see the [SCOPE.md](SCOPE.md) document.
|
||||
|
||||
### 3. Transparent Communication
|
||||
To keep the project healthy, we keep our "work" in the open.
|
||||
- **Public by Default:** All technical decisions and project management discussions happen in GitHub Issues, Pull
|
||||
Requests, or the public Discussions tab.
|
||||
- **Clarity in Writing:** Because we have a global community with different levels of English proficiency, please be as
|
||||
explicit and clear as possible in your PR descriptions and bug reports.
|
||||
|
||||
### 4. Moderation & Safety
|
||||
The maintainers are responsible for keeping the community a safe place to contribute.
|
||||
- We reserve the right to hide comments, lock threads, or block users who repeatedly violate these principles or engage
|
||||
in harassment.
|
||||
- **Reporting:** If you feel you are being harassed or see behavior that is damaging the community, please reach out
|
||||
privately to @daveallie.
|
||||
@@ -1,49 +1,150 @@
|
||||
# CrossPoint Reader ++
|
||||
# CrossPoint Reader
|
||||
|
||||
This firmware is based on the [crosspoint-reader](https://github.com/crosspoint-reader/crosspoint-reader) for the XTEINK X4, a great piece of software by Dave Allie and others
|
||||
Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink).
|
||||
Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
||||
|
||||
# Why this fork
|
||||
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
|
||||
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
|
||||
|
||||
Unfortunately the official repository suffers from too many good ideas floating around and a lack of clear governance how to deal
|
||||
with these contributions, so it's lacking fundamental fixes for a proper reading experience (rendering issues, sub-par sync
|
||||
capabilities with KOReader, a popular multi-platform open-source epub reader).
|
||||

|
||||
|
||||
Trying to contribute enhancements / fixes became increasingly difficult as the lack of progress effectively prevented further development.
|
||||
## Motivation
|
||||
|
||||
Therefore this branch focuses on real fixes and real improvements while trying to keep up to pace with developments in the main branch.
|
||||
E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited
|
||||
customisation. The **Xteink X4** is an affordable, e-paper device, however the official firmware remains closed.
|
||||
CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truely unlock the device's
|
||||
potential.
|
||||
|
||||
# What's different
|
||||
CrossPoint Reader aims to:
|
||||
* Provide a **fully open-source alternative** to the official firmware.
|
||||
* Offer a **document reader** capable of handling EPUB content on constrained hardware.
|
||||
* Support **customisable font, layout, and display** options.
|
||||
* Run purely on the **Xteink X4 hardware**.
|
||||
|
||||
- Proper KOReader Snychronisation (including https TLS OOM fix)
|
||||
- Fixes for a lot of css rendering issues
|
||||
- Additional sleep screens support (information overlay, transparent pictures over current reader screen)
|
||||
- Clock-Support
|
||||
- Weather information panel
|
||||
- Multiple under-the-hood performance improvements
|
||||
- Book information screen
|
||||
- Markdown-support
|
||||
- WiFi captive portal support
|
||||
- Supporting ~~strikethrough~~, superscript / subscript (*) and tables
|
||||
- Support for used defined actions on double-click / long-click per button (*)
|
||||
- User defined fonts
|
||||
- ...
|
||||
This project is **not affiliated with Xteink**; it's built as a community project.
|
||||
|
||||
## Features & Usage
|
||||
|
||||
- [x] EPUB parsing and rendering
|
||||
- [ ] Image support within EPUB
|
||||
- [x] Saved reading position
|
||||
- [x] File explorer with file picker
|
||||
- [x] Basic EPUB picker from root directory
|
||||
- [x] Support nested folders
|
||||
- [ ] EPUB picker with cover art
|
||||
- [x] Custom sleep screen
|
||||
- [ ] Cover sleep screen
|
||||
- [x] Wifi book upload
|
||||
- [ ] Wifi OTA updates
|
||||
- [ ] Configurable font, layout, and display options
|
||||
- [ ] Screen rotation
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
|
||||
## Installing
|
||||
|
||||
### Web (latest firmware)
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware"
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
### Web (specific firmware version)
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/daveallie/crosspoint-reader/releases)
|
||||
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
### Manual
|
||||
|
||||
See [Development](#development) below.
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* **PlatformIO Core** (`pio`) or **VS Code + PlatformIO IDE**
|
||||
* Python 3.8+
|
||||
* USB-C cable for flashing the ESP32-C3
|
||||
* Xteink X4
|
||||
|
||||
### Checking out the code
|
||||
|
||||
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository:
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/daveallie/crosspoint-reader
|
||||
|
||||
# Or, if you've already cloned without --recursive:
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
### Flashing your device
|
||||
|
||||
Connect your Xteink X4 to your computer via USB-C and run the following command.
|
||||
|
||||
```sh
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
||||
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only
|
||||
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
|
||||
on this constraint.
|
||||
|
||||
### EPUB caching
|
||||
|
||||
The first time chapters of an EPUB are loaded, they are cached to the SD card. Subsequent loads are served from the
|
||||
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
|
||||
|
||||
|
||||
# Choosing the _right_ reader...
|
||||
```
|
||||
.crosspoint/
|
||||
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
|
||||
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
|
||||
│ ├── 0/ # Each chapter is stored in a subdirectory named by its index (based on the spine order)
|
||||
│ │ ├── section.bin # Section metadata (page count)
|
||||
│ │ ├── page_0.bin # Each page is stored in a separate file, it
|
||||
│ │ ├── page_1.bin # contains the position (x, y) and text for each word
|
||||
│ │ └── ...
|
||||
│ ├── 1/
|
||||
│ │ ├── section.bin
|
||||
│ │ ├── page_0.bin
|
||||
│ │ ├── page_1.bin
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
│
|
||||
└── epub_189013891/
|
||||
```
|
||||
|
||||
Your usecase might be completely different from mine, so I try to give an overview of the different reader flavors to my best knowledge. If you know of more variants / have more information, then let me know
|
||||
Deleting the `.crosspoint` directory will clear the cache.
|
||||
|
||||
Last update: May, 15th, 2026
|
||||
Due the way it's currently implemented, the cache is not automatically cleared when the EPUB is deleted and moving an
|
||||
EPUB file will reset the reading progress.
|
||||
|
||||
| Reader | Visual appeal | Functionality | Formats | Pros | Cons | Custom fonts | CJK | Bluetooth |
|
||||
| ------------------------------------ | ------------- | ------------- | ------- | ---- | ---- | ------------ | --- | --- |
|
||||
| [Stock](https://www.xteink.com) | Okay | Reader | XTC, EPUB, TXT | Frequent official updates | Lot of rendering issues | Yes | Yes | Yes |
|
||||
| [THIS FORK: CrossPoint ++](https://github.com/jpirnay/crosspoint-reader) | Okay | Calibre Wireless support; Proper KOReader progress sync; Wi-Fi transfer; Book Info; Clock, Weather Info | EPUB, XTC, TXT, MD | Faster integration of functionality | Small team | Yes | Limited | No |
|
||||
| [CrossPoint](https://github.com/crosspoint-reader/crosspoint-reader) | Okay | Calibre Wireless support; KOReader progress sync; Wi-Fi transfer | EPUB, XTC, TXT | Biggest community | Least common denominator approach | Yes | No | No |
|
||||
| [CrossPet](https://github.com/trilwu/crosspet) | Excellent, Playful | Virtual pet motivator; mini-games | EPUB, TXT | A lot of additional apps | Higher battery drain, Small team, bloaty | Yes | Yes | Yes |
|
||||
| [Papyrix](https://github.com/bigbag/papyrix-reader) | Minimalist | Calibre Wireless support; exFAT support | EPUB, FB2, MD, TXT | A lot of good ideas | Small team | Yes | Yes | No |
|
||||
| [Inx](https://github.com/obijuankenobiii/inx) | Nice | mainly crosspoint functionality plus reading statistics | EPUB, XTC, TXT | Good reading stats | Small team | No | No | No |
|
||||
| [vCodex](https://github.com/franssjz/cpr-vcodex) | Nice | mainly crosspoint functionality plus reading statistics | EPUB, XTC, TXT | Good reading stats | Small team | No | No | No |
|
||||
| [PlusPoint](https://github.com/ngxson/pluspoint-reader) | Okay | Experimental | EPUB, TXT, JS Apps | Support for custom JS apps; better RTC | Based on older code, small team | Yes | Yes | No |
|
||||
| [SUMI](https://github.com/psychoplath9450/SUMI) | Nice | Lua custom apps; Bluetooth keyboard support for notes; Game Boy emulator; Bluetooth Transfer | EPUB, TXT, XTC, MD | Many apps, customizable, Papyrix fork | Single Contributor, **No Wi-Fi** | Yes | Yes | Yes |
|
||||
| [CrossInk](https://github.com/uxjulia/CrossInk) | Okay | Calibre Wireless support; KOReader progress sync; Wi-Fi transfer | EPUB, XTC, TXT | One of the best alternatives | Small team | Yes | No | No |
|
||||
## Contributing
|
||||
|
||||
Contributions are very welcome!
|
||||
|
||||
If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/daveallie/crosspoint-reader/discussions/categories/ideas).
|
||||
If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort.
|
||||
|
||||
### To submit a contribution:
|
||||
|
||||
1. Fork the repo
|
||||
2. Create a branch (`feature/dithering-improvement`)
|
||||
3. Make changes
|
||||
4. Submit a PR
|
||||
|
||||
---
|
||||
|
||||
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
|
||||
|
||||
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I
|
||||
was making CrossPoint.
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# Project Vision & Scope: CrossPoint Reader
|
||||
|
||||
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for the Xteink X4. We believe a
|
||||
dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
|
||||
|
||||
## 1. Core Mission
|
||||
|
||||
To provide a lightweight, high-performance firmware that maximizes the potential of the X4, prioritizing legibility and
|
||||
usability over "swiss-army-knife" functionality.
|
||||
|
||||
## 2. Scope
|
||||
|
||||
### In-Scope
|
||||
|
||||
*These are features that directly improve the primary purpose of the device.*
|
||||
|
||||
* **User Experience:** E.g. User-friendly interfaces, and interactions, both inside the reader and navigating the
|
||||
firmware. This includes things like button mapping, book loading, and book navigation like bookmarks.
|
||||
* **Document Rendering:** E.g. Support for rendering documents (primarily EPUB) and improvements to the rendering
|
||||
engine.
|
||||
* **Format Optimization:** E.g. Efficiently parsing EPUB (CSS/Images) and other documents within the device's
|
||||
capabilities.
|
||||
* **Typography & Legibility:** E.g. Custom font support, hyphenation engines, and adjustable line spacing.
|
||||
* **E-Ink Driver Refinement:** E.g. Reducing full-screen flashes (ghosting management) and improving general rendering.
|
||||
* **Library Management:** E.g. Simple, intuitive ways to organize and navigate a collection of books.
|
||||
* **Local Transfer:** E.g. Simple, "pull" based book loading via a basic web-server or public and widely-used standards.
|
||||
* **Language Support:** E.g. Support for multiple languages both in the reader and in the interfaces.
|
||||
* **Reference Tools:** E.g. Local dictionary lookup. Providing quick, offline definitions to enhance comprehension
|
||||
without breaking focus.
|
||||
|
||||
### Out-of-Scope
|
||||
|
||||
*These items are rejected because they compromise the device's stability or mission.*
|
||||
|
||||
* **Interactive Apps:** No Notepads, Calculators, or Games. This is a reader, not a PDA.
|
||||
* **Active Connectivity:** No RSS readers, News aggregators, or Web browsers. Background Wi-Fi tasks drain the battery
|
||||
and complicate the single-core CPU's execution.
|
||||
* **Media Playback:** No Audio players or Audio-books.
|
||||
* **Complex Annotation:** No typed out notes. These features are better suited for devices with better input
|
||||
capabilities and more powerful chips.
|
||||
|
||||
### In-scope — Technically Unsupported
|
||||
|
||||
*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.*
|
||||
|
||||
* **Clock Display:** The ESP32-C3's RTC drifts significantly during deep sleep; making the clock untrustworthy after any sleep cycle. NTP sync could help, but CrossPoint doesn't connect to the internet on every boot.
|
||||
|
||||
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text — resulting in constant panning and zooming that makes for a poor reading experience on e-ink.
|
||||
|
||||
## 3. Idea Evaluation
|
||||
|
||||
While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be
|
||||
a lightweight, reliable, and performant e-reader. Things which distract or compromise the device's core mission will not
|
||||
be accepted. As a guiding question, consider if your idea improve the "core reading experience" for the average user,
|
||||
and, critically, not distract from that reading experience.
|
||||
|
||||
> **Note to Contributors:** If you are unsure if your idea fits the scope, please open a **Discussion** before you start
|
||||
> coding!
|
||||
+44
-367
@@ -1,55 +1,17 @@
|
||||
# CrossPoint User Guide
|
||||
|
||||
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of the device.
|
||||
|
||||
- [CrossPoint User Guide](#crosspoint-user-guide)
|
||||
- [1. Hardware Overview](#1-hardware-overview)
|
||||
- [Button Layout](#button-layout)
|
||||
- [2. Power \& Startup](#2-power--startup)
|
||||
- [Power On / Off](#power-on--off)
|
||||
- [First Launch](#first-launch)
|
||||
- [3. Screens](#3-screens)
|
||||
- [3.1 Home Screen](#31-home-screen)
|
||||
- [3.2 Reading Mode](#32-reading-mode)
|
||||
- [3.3 Browse Files Screen](#33-browse-files-screen)
|
||||
- [3.4 Recent Books Screen](#34-recent-books-screen)
|
||||
- [3.5 File Transfer Screen](#35-file-transfer-screen)
|
||||
- [3.5.1 Calibre Wireless Transfers](#351-calibre-wireless-transfers)
|
||||
- [3.6 Settings](#36-settings)
|
||||
- [3.6.1 Display](#361-display)
|
||||
- [3.6.2 Reader](#362-reader)
|
||||
- [3.6.3 Controls](#363-controls)
|
||||
- [3.6.4 System](#364-system)
|
||||
- [3.6.5 OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries)
|
||||
- [3.6.6 Web Settings (WiFi + OPDS)](#366-web-settings-wifi--opds)
|
||||
- [3.6.7 KOReader Sync Quick Setup](#367-koreader-sync-quick-setup)
|
||||
- [3.7 Sleep Screen](#37-sleep-screen)
|
||||
- [4. Reading Mode](#4-reading-mode)
|
||||
- [Page Turning](#page-turning)
|
||||
- [Chapter Navigation](#chapter-navigation)
|
||||
- [System Navigation](#system-navigation)
|
||||
- [Supported Languages](#supported-languages)
|
||||
- [5. Chapter Selection Screen](#5-chapter-selection-screen)
|
||||
- [6. Current Limitations \& Roadmap](#6-current-limitations--roadmap)
|
||||
- [7. Troubleshooting Issues \& Escaping Bootloop](#7-troubleshooting-issues--escaping-bootloop)
|
||||
|
||||
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of
|
||||
the device.
|
||||
|
||||
## 1. Hardware Overview
|
||||
|
||||
The device utilises the standard buttons on the Xteink X4 (in the same layout as the manufacturer firmware, by default):
|
||||
The device utilises the standard buttons on the Xtink X4 in the same layout:
|
||||
|
||||
### Button Layout
|
||||
| Location | Buttons |
|
||||
| --------------- | ---------------------------------------------------- |
|
||||
| **Bottom Edge** | **Back**, **Confirm**, **Left**, **Right** |
|
||||
| **Right Side** | **Power**, **Volume Up**, **Volume Down**, **Reset** |
|
||||
|
||||
Button layout can be customized in the **[Controls Settings](#363-controls)**.
|
||||
|
||||
### Taking a Screenshot
|
||||
When the Power Button and Volume Down button are pressed at the same time, it will take a screenshot and save it in the folder `screenshots/`.
|
||||
|
||||
Alternatively, while reading a book, press the **Confirm** button to open the reader menu and select **Take screenshot**.
|
||||
| Location | Buttons |
|
||||
|-----------------|--------------------------------------------|
|
||||
| **Bottom Edge** | **Back**, **Confirm**, **Left**, **Right** |
|
||||
| **Right Side** | **Power**, **Volume Up**, **Volume Down** |
|
||||
|
||||
---
|
||||
|
||||
@@ -57,17 +19,14 @@ Alternatively, while reading a book, press the **Confirm** button to open the re
|
||||
|
||||
### Power On / Off
|
||||
|
||||
To turn the device on or off, **press and hold the Power button for approximately half a second**.
|
||||
In the **[Controls Settings](#363-controls)** you can configure the power button to turn the device off with a short press instead of a long one.
|
||||
|
||||
To reboot the device (for example after a firmware update or if it's frozen), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
|
||||
To turn the device on or off, **press and hold the Power button for half a second**. In **Settings** you can configure
|
||||
the power button to trigger on a short press instead of a long one.
|
||||
|
||||
### First Launch
|
||||
|
||||
Upon turning the device on for the first time, you will be placed on the **[Home](#31-home-screen)** screen.
|
||||
Upon turning the device on for the first time, you will be placed on the **Home** screen.
|
||||
|
||||
> [!NOTE]
|
||||
> On subsequent restarts, the firmware will automatically reopen the last book you were reading.
|
||||
> **Note:** On subsequent restarts, the firmware will automatically reopen the last book you were reading.
|
||||
|
||||
---
|
||||
|
||||
@@ -75,304 +34,51 @@ Upon turning the device on for the first time, you will be placed on the **[Home
|
||||
|
||||
### 3.1 Home Screen
|
||||
|
||||
The Home screen is the main entry point to the firmware. From here you can navigate to **[Reading Mode](#4-reading-mode)** with the most recently read book, the **[Browse Files](#33-browse-files-screen)** screen, the **[Recent Books](#34-recent-books-screen)** screen, the **[File Transfer](#35-file-transfer-screen)** screen, or **[Settings](#36-settings)**.
|
||||
The Home Screen is the main entry point to the firmware. From here you can navigate to the **Book Selection** screen,
|
||||
**Settings** screen, or **File Upload** screen.
|
||||
|
||||
### 3.2 Reading Mode
|
||||
### 3.2 Book Selection (Read)
|
||||
|
||||
See [Reading Mode](#4-reading-mode) below for more information.
|
||||
The Book Selection acts as a folder and file browser.
|
||||
|
||||
### 3.3 Browse Files Screen
|
||||
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up
|
||||
and down through folders and books.
|
||||
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
|
||||
|
||||
The Browse Files screen acts as a file and folder browser.
|
||||
### 3.3 Reading Screen
|
||||
|
||||
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up and down through folders and books. You can also long-press these buttons to scroll a full page up or down.
|
||||
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
|
||||
* **Delete Files:** Hold and release **Confirm** to delete the selected file. You will be given an option to either confirm or cancel deletion. Folder deletion is not supported.
|
||||
See [4. Reading Mode](#4-reading-mode) below for more information.
|
||||
|
||||
### 3.4 Recent Books Screen
|
||||
### 3.4 File Upload Screen
|
||||
|
||||
The Recent Books screen lists the most recently opened books in a chronological view, displaying title and author.
|
||||
|
||||
### 3.5 File Transfer Screen
|
||||
|
||||
The File Transfer screen allows you to upload new e-books to the device. When you enter the screen, you'll be prompted with a WiFi selection dialog and then your X4 will start hosting a web server.
|
||||
The File Upload screen allows you to upload new e-books to the device. When you enter the screen you'll be prompted with
|
||||
a WiFi selection dialog and then your X4 will start hosting a web server.
|
||||
|
||||
See the [webserver docs](./docs/webserver.md) for more information on how to connect to the web server and upload files.
|
||||
|
||||
> [!TIP]
|
||||
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
|
||||
|
||||
### 3.5.1 Calibre Wireless Transfers
|
||||
|
||||
CrossPoint supports sending books from Calibre using the CrossPoint Reader device plugin.
|
||||
|
||||
1. Install the plugin in Calibre:
|
||||
- Head to https://github.com/crosspoint-reader/calibre-plugins/releases to download the latest version of the crosspoint_reader plugin.
|
||||
- Download the zip file.
|
||||
- Open Calibre → Preferences → Plugins → Load plugin from file → Select the zip file.
|
||||
2. On the device: File Transfer → Connect to Calibre → Join a network.
|
||||
3. Make sure your computer is on the same WiFi network.
|
||||
4. In Calibre, click "Send to device" to transfer books.
|
||||
|
||||
### 3.6 Settings
|
||||
### 3.5 Settings
|
||||
|
||||
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
||||
|
||||
#### 3.6.1 Display
|
||||
|
||||
- **Sleep Screen**: Which sleep screen to display when the device sleeps:
|
||||
- "Dark" (default) - The default dark Crosspoint logo sleep screen
|
||||
- **Sleep Screen**: Which sleep screen to display when the device sleeps, options are:
|
||||
- "Dark" (default) - The default dark sleep screen
|
||||
- "Light" - The same default sleep screen, on a white background
|
||||
- "Custom" - Custom images from the SD card; see [Sleep Screen](#37-sleep-screen) below for more information
|
||||
- "Custom" - Custom images from the SD card, see [3.6 Sleep Screen](#36-sleep-screen) below for more information
|
||||
- "Cover" - The book cover image (Note: this is experimental and may not work as expected)
|
||||
- "None" - A blank screen
|
||||
- "Cover + Custom" - The book cover image, falls back to "Custom" behavior
|
||||
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||
- "Crop" - Scale the image down and crop as necessary to try to fill the screen (Note: this is experimental and may not work as expected)
|
||||
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected:
|
||||
- "None" (default) - The cover image will be converted to a grayscale image and displayed as it is
|
||||
- "Contrast" - The image will be displayed as a black & white image without grayscale conversion
|
||||
- "Inverted" - The image will be inverted as in white & black and will be displayed without grayscale conversion
|
||||
- **Status Bar**: Configure the status bar displayed while reading:
|
||||
- "None" - No status bar
|
||||
- "No Progress" - Show status bar without reading progress
|
||||
- "Full w/ Percentage" - Show status bar with book progress (as percentage)
|
||||
- "Full w/ Book Bar" - Show status bar with book progress (as bar)
|
||||
- "Book Bar Only" - Show book progress (as bar)
|
||||
- "Full w/ Chapter Bar" - Show status bar with chapter progress (as bar)
|
||||
- **Hide Battery %**: Configure where to suppress the battery percentage display in the status bar; the battery icon will still be shown:
|
||||
- "Never" (default) - Always show battery percentage
|
||||
- "In Reader" - Show battery percentage everywhere except in reading mode
|
||||
- "Always" - Always hide battery percentage
|
||||
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting; options are every 1, 5, 10, 15, or 30 pages.
|
||||
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book, if disabled,
|
||||
paragraphs will not have vertical space between them, but will have first word indentation.
|
||||
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
|
||||
|
||||
- **UI Theme**: Set which UI theme to use:
|
||||
- "Classic" - The original Crosspoint theme
|
||||
- "Lyra" - The new theme for Crosspoint featuring rounded elements and menu icons
|
||||
- "Lyra Extended" - Lyra, but displays 3 books instead of 1 on the **[Home Screen](#31-home-screen)**
|
||||
- **Sunlight Fading Fix**: Configure whether to enable a software-fix for the issue where white X4 models may fade when used in direct sunlight:
|
||||
- "OFF" (default) - Disable the fix
|
||||
- "ON" - Enable the fix
|
||||
### 3.6 Sleep Screen
|
||||
|
||||
#### 3.6.2 Reader
|
||||
- **Reader Font Family**: Choose the font used for reading:
|
||||
- "Bookerly" (default) - Amazon's reading font
|
||||
- "Noto Sans" - Google's sans-serif font
|
||||
- "Open Dyslexic" - Font designed for readers with dyslexia
|
||||
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium" (default), "Large", or "X Large".
|
||||
You can customize the sleep screen by placing custom images in specific locations on the SD card:
|
||||
|
||||
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal" (default), or "Wide".
|
||||
- **Reader Screen Margin**: Controls the screen margins in Reading Mode between 5 and 40 pixels in 5-pixel increments.
|
||||
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
||||
- **Embedded Style**: Whether to use the EPUB file's embedded HTML and CSS stylisation and formatting; options are "ON" or "OFF".
|
||||
- **Hyphenation**: Whether to hyphenate text in Reading Mode; options are "ON" or "OFF".
|
||||
- **Reading Orientation**: Set the screen orientation for reading EPUB files:
|
||||
- "Portrait" (default) - Standard portrait orientation
|
||||
- "Landscape CW" - Landscape, rotated clockwise
|
||||
- "Inverted" - Portrait, upside down
|
||||
- "Landscape CCW" - Landscape, rotated counter-clockwise
|
||||
- **Extra Paragraph Spacing**: Set how to handle paragraph breaks:
|
||||
- "ON" - Vertical space will be added between paragraphs in Reading Mode
|
||||
- "OFF" - Paragraphs will not have vertical space added, but will have first-line indentation
|
||||
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
||||
|
||||
#### 3.6.3 Controls
|
||||
|
||||
- **Remap Front Buttons**: A menu for customising the function of each bottom edge button.
|
||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from "Prev/Next" (default) to "Next/Prev". This change is only in effect when reading.
|
||||
|
||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skips to the next/previous chapter:
|
||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||
- "Page Scroll" - Long-pressing scrolls a page up/down
|
||||
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
||||
- "Ignore" (default) - Require a long press to turn off the device
|
||||
- "Sleep" - A short press puts the device into sleep mode
|
||||
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
|
||||
|
||||
#### 3.6.4 System
|
||||
|
||||
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep; options are 1, 5, 10 (default), 15 or 30 minutes.
|
||||
|
||||
- **WiFi Networks**: Connect to WiFi networks for file transfers and firmware updates.
|
||||
- **KOReader Sync**: Options for setting up KOReader for syncing book progress.
|
||||
- **OPDS Servers**: Manage one or more OPDS libraries for browsing and downloading books. See [OPDS Servers (Multiple Libraries)](#365-opds-servers-multiple-libraries) below.
|
||||
- **Clear Reading Cache**: Clear the internal SD card cache.
|
||||
- **Check for updates**: Check for Crosspoint firmware updates over WiFi.
|
||||
- **Language**: Set the system language (see **[Supported Languages](#supported-languages)** for more information).
|
||||
|
||||
#### 3.6.5 OPDS Servers (Multiple Libraries)
|
||||
|
||||
CrossPoint supports saving multiple OPDS servers and switching between them when browsing catalogs.
|
||||
|
||||
1. Open **Settings -> System -> OPDS Servers**.
|
||||
2. Select **Add Server** to create a new entry, or select an existing server to edit it.
|
||||
3. Configure these fields:
|
||||
- **Server Name**: Optional display name (for example, "Home Calibre" or "Public Catalog").
|
||||
- **OPDS Server URL**: Full catalog root URL (for Calibre Content Server, usually ends with `/opds`).
|
||||
- **Username / Password**: Optional credentials for authenticated servers.
|
||||
4. Use **Delete Server** inside a server entry to remove it.
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- You can store up to 8 OPDS servers.
|
||||
- OPDS authentication supports HTTP Basic auth. If you use Calibre Content Server with authentication enabled, set it to Basic (not Digest).
|
||||
|
||||
You can also manage OPDS servers from the web interface while in File Transfer mode:
|
||||
|
||||
1. Connect to the device web UI.
|
||||
2. Open `http://<device-ip>/settings`.
|
||||
3. Use the **OPDS Servers** card to add, edit, or delete entries.
|
||||
For web-based WiFi network management, see [Web Settings (WiFi + OPDS)](#366-web-settings-wifi--opds).
|
||||
|
||||
#### 3.6.6 Web Settings (WiFi + OPDS)
|
||||
|
||||
While in **File Transfer** mode, the web settings page includes management cards for both **WiFi Networks** and **OPDS Servers**.
|
||||
|
||||
1. On device: open **File Transfer** and connect to WiFi.
|
||||
1. In a browser, open `http://<device-ip>/settings` or `http://crosspoint.local`.
|
||||
1. In **WiFi Networks**, add, edit, or delete saved network entries (SSID + optional password).
|
||||
1. In **OPDS Servers**, add, edit, or delete OPDS catalogs.
|
||||
|
||||
Behavior notes:
|
||||
|
||||
- Passwords are never shown back in the web UI after saving.
|
||||
- Leaving Password blank while editing keeps the existing saved password unchanged.
|
||||
- The web UI can save hidden-network SSIDs, but connecting to hidden networks still depends on device-side WiFi connection flow.
|
||||
|
||||
#### 3.6.7 KOReader Sync Quick Setup
|
||||
|
||||
CrossPoint can sync reading progress with KOReader-compatible sync servers.
|
||||
It also interoperates with KOReader apps/devices when they use the same server and credentials.
|
||||
|
||||
##### Option A: Free Public Server (`sync.koreader.rocks`)
|
||||
|
||||
1. Register a user once (only if needed):
|
||||
|
||||
```bash
|
||||
USERNAME="user"
|
||||
PASSWORD="pass"
|
||||
PASSWORD_MD5="$(printf '%s' "$PASSWORD" | openssl md5 | awk '{print $2}')"
|
||||
|
||||
curl -i "https://sync.koreader.rocks/users/create" \
|
||||
-H "Accept: application/vnd.koreader.v1+json" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD_MD5\"}"
|
||||
```
|
||||
|
||||
Already have KOReader Sync credentials? Skip registration; basic sync only requires using the same existing username/password on all devices.
|
||||
|
||||
When this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, pick a different username or use that existing account.
|
||||
|
||||
2. On each CrossPoint device:
|
||||
- Go to **Settings -> System -> KOReader Sync**.
|
||||
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
|
||||
- Set **Sync Server URL** to `https://sync.koreader.rocks`, or leave it empty (both use the same default KOReader sync server).
|
||||
- Run **Authenticate**.
|
||||
|
||||
3. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
|
||||
- Choose **Apply Remote** to jump to remote progress.
|
||||
- Choose **Upload Local** to push current progress.
|
||||
|
||||
##### Option B: Self-Hosted Server (Docker Compose)
|
||||
|
||||
1. Start a sync server:
|
||||
|
||||
```bash
|
||||
mkdir -p kosync-quickstart
|
||||
cd kosync-quickstart
|
||||
|
||||
cat > compose.yaml <<'YAML'
|
||||
services:
|
||||
kosync:
|
||||
image: koreader/kosync:latest
|
||||
ports:
|
||||
- "7200:7200"
|
||||
- "17200:17200"
|
||||
volumes:
|
||||
- ./data/redis:/var/lib/redis
|
||||
environment:
|
||||
- ENABLE_USER_REGISTRATION=true
|
||||
restart: unless-stopped
|
||||
YAML
|
||||
|
||||
# Docker
|
||||
docker compose up -d
|
||||
|
||||
# Podman (alternative)
|
||||
podman compose up -d
|
||||
```
|
||||
- **Single Image:** Place a file named `sleep.bmp` in the root directory.
|
||||
- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images
|
||||
inside. If images are found in this directory, they will take priority over the `sleep.png` file, and one will be
|
||||
randomly selected each time the device sleeps.
|
||||
|
||||
> [!NOTE]
|
||||
> `ENABLE_USER_REGISTRATION=true` is convenient for first setup. After creating your users, set it to `false` (or remove it) to avoid unexpected registrations.
|
||||
|
||||
2. Verify the server:
|
||||
|
||||
```bash
|
||||
curl -H "Accept: application/vnd.koreader.v1+json" "http://<server-ip>:17200/healthcheck"
|
||||
# Expected: {"state":"OK"}
|
||||
```
|
||||
|
||||
3. Register a user once.
|
||||
CrossPoint authenticates against KOReader Sync (`koreader/kosync`) using an MD5 key, so register using the MD5 of your password:
|
||||
|
||||
> [!WARNING]
|
||||
> Sending a reusable MD5-derived password over plain HTTP is insecure.
|
||||
> Create unique sync-only credentials and do not reuse main account passwords.
|
||||
> Prefer `https://<server-ip>:7200` whenever traffic leaves a fully trusted LAN or when using untrusted networks.
|
||||
> Use `curl -k` only for self-signed certificate testing.
|
||||
|
||||
```bash
|
||||
USERNAME="user"
|
||||
PASSWORD="pass"
|
||||
PASSWORD_MD5="$(printf '%s' "$PASSWORD" | openssl md5 | awk '{print $2}')"
|
||||
|
||||
curl -i "http://<server-ip>:17200/users/create" \
|
||||
-H "Accept: application/vnd.koreader.v1+json" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD_MD5\"}"
|
||||
```
|
||||
|
||||
If this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, the account already exists.
|
||||
|
||||
4. On each CrossPoint device:
|
||||
- Go to **Settings -> System -> KOReader Sync**.
|
||||
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
|
||||
- Set **Sync Server URL** to `http://<server-ip>:17200`.
|
||||
- Run **Authenticate**.
|
||||
|
||||
If you use the HTTPS listener, use `https://<server-ip>:7200` (`curl -k` only for self-signed certificate testing).
|
||||
|
||||
5. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
|
||||
- Choose **Apply Remote** to jump to remote progress.
|
||||
- Choose **Upload Local** to push current progress.
|
||||
|
||||
### 3.7 Sleep Screen
|
||||
|
||||
The **Sleep Screen** setting controls what is displayed when the device goes to sleep:
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| **Dark** (default) | The CrossPoint logo on a dark background. |
|
||||
| **Light** | The CrossPoint logo on a white background. |
|
||||
| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. |
|
||||
| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. |
|
||||
| **Cover + Custom** | The cover of the currently open book. Falls back to **Custom** behavior if no book is open. |
|
||||
| **None** | A blank screen. |
|
||||
|
||||
#### Cover settings
|
||||
|
||||
When using **Cover** or **Cover + Custom**, two additional settings apply:
|
||||
|
||||
- **Sleep Screen Cover Mode**: **Fit** (scale to fit, white borders) or **Crop** (scale and crop to fill the screen).
|
||||
- **Sleep Screen Cover Filter**: **None** (grayscale), **Contrast** (black & white), or **Inverted** (inverted black & white).
|
||||
|
||||
#### Custom images
|
||||
|
||||
To use custom sleep images, set the sleep screen mode to **Custom** or **Cover + Custom**, then place images on the SD card:
|
||||
|
||||
- **Multiple Images (recommended):** Create a `.sleep` directory in the root of the SD card and place any number of `.bmp` images inside. One will be randomly selected each time the device sleeps. (A directory named `sleep` is also accepted as a fallback.)
|
||||
- **Single Image:** Place a file named `sleep.bmp` in the root directory. This is used as a fallback if no valid images are found in the `.sleep`/`sleep` directory.
|
||||
> You'll need to set the **Sleep Screen** setting to **Custom** in order to use these images.
|
||||
|
||||
> [!TIP]
|
||||
> For best results:
|
||||
@@ -387,34 +93,17 @@ Once you have opened a book, the button layout changes to facilitate reading.
|
||||
|
||||
### Page Turning
|
||||
| Action | Buttons |
|
||||
| ----------------- | ------------------------------------ |
|
||||
|-------------------|--------------------------------------|
|
||||
| **Previous Page** | Press **Left** _or_ **Volume Up** |
|
||||
| **Next Page** | Press **Right** _or_ **Volume Down** |
|
||||
|
||||
The role of the volume (side) buttons can be swapped in the **[Controls Settings](#363-controls)**.
|
||||
|
||||
If the **Short Power Button Click** setting is set to "Page Turn", you can also turn to the next page by briefly pressing the Power button.
|
||||
|
||||
### Chapter Navigation
|
||||
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
||||
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
||||
|
||||
This feature can be disabled in the **[Controls Settings](#363-controls)** to help avoid changing chapters by mistake.
|
||||
|
||||
|
||||
### System Navigation
|
||||
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
|
||||
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)** screen.
|
||||
|
||||
### Supported Languages
|
||||
|
||||
CrossPoint renders text using the following Unicode character blocks, enabling support for a wide range of languages:
|
||||
|
||||
* **Latin Script (Basic, Supplement, Extended-A):** Covers English, German, French, Spanish, Portuguese, Italian, Dutch, Swedish, Norwegian, Danish, Finnish, Polish, Czech, Hungarian, Romanian, Slovak, Slovenian, Turkish, and others.
|
||||
* **Cyrillic Script (Standard and Extended):** Covers Russian, Ukrainian, Belarusian, Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, and others.
|
||||
|
||||
What is not supported: Chinese, Japanese, Korean, Vietnamese, Hebrew, Arabic, Greek and Farsi.
|
||||
* **Return to Home:** Press **Back** to close the book and return to the Book Selection screen.
|
||||
* **Chapter Menu:** Press **Confirm** to open the Table of Contents/Chapter Selection screen.
|
||||
|
||||
---
|
||||
|
||||
@@ -430,21 +119,9 @@ Accessible by pressing **Confirm** while inside a book.
|
||||
|
||||
## 6. Current Limitations & Roadmap
|
||||
|
||||
Please note that this firmware is currently in active development. The following features are **not yet supported** but are planned for future updates:
|
||||
Please note that this firmware is currently in active development. The following features are **not yet supported** but
|
||||
are planned for future updates:
|
||||
|
||||
* **Images:** Embedded images in e-books will not render.
|
||||
* **Cover Images:** Large cover images embedded into EPUB require several seconds (~10s for ~2000 pixel tall image) to convert for sleep screen and home screen thumbnail. Consider optimizing the EPUB with e.g. https://github.com/bigbag/epub-to-xtc-converter to speed this up.
|
||||
|
||||
---
|
||||
|
||||
## 7. Troubleshooting Issues & Escaping Bootloop
|
||||
|
||||
If an issue or crash is encountered while using Crosspoint, feel free to raise an issue ticket and attach the serial monitor logs. The logs can be obtained by connecting the device to a computer and starting a serial monitor. Either [Serial Monitor](https://www.serialmonitor.org/) or the following command can be used:
|
||||
|
||||
```
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
If the device is stuck in a bootloop, press and release the Reset button. Then, press and hold on to the configured Back button and the Power Button to boot to the Home Screen.
|
||||
|
||||
There can be issues with broken cache or config. In this case, delete the `.crosspoint` directory on your SD card (or consider deleting only `settings.json`, `state.json`, or `epub_*` cache directories in the `.crosspoint/` folder).
|
||||
* **Text Formatting:** There are currently no settings to adjust font type, size, line spacing, or margins.
|
||||
* **Rotation**: Different rotation options are not supported.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user