Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Makefile for Capy Reader firmware
# Wraps PlatformIO commands for convenience

.PHONY: all build build-release release upload upload-release flash flash-release \
clean format check monitor size erase build-fs upload-fs sleep-screen gh-release changelog help \
test test-build test-run test-clean fontconvert-bin reader-test

# Default target
all: help

# Build targets
build: ## Build firmware (default environment)
pio run

build-release: ## Build release firmware
pio run -e gh_release

release: build-release ## Alias for build-release

# Upload targets
upload: ## Build and flash to device
pio run --target upload

upload-release: ## Build and flash release firmware
pio run -e gh_release --target upload

# Aliases
flash: upload ## Alias for upload

flash-release: upload-release ## Alias for upload-release

# Clean
clean: ## Clean build artifacts
pio run --target clean

# Code quality
format: ## Format code with clang-format
./bin/clang-format-fix

check: ## Run static analysis (cppcheck)
pio check

# Device/debug
monitor: ## Open serial monitor
pio device monitor

size: ## Show firmware size
pio run --target size

erase: ## Erase device flash
pio run --target erase

# Filesystem
build-fs: ## Build filesystem image
pio run --target buildfs

upload-fs: ## Upload filesystem to device
pio run --target uploadfs

# Release
tag: ## Create and push a version tag (triggers GitHub release)
@read -p "Enter tag version (e.g., 1.0.0): " TAG; \
if [[ $$TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \
git tag -a v$$TAG -m "v$$TAG"; \
git push origin v$$TAG; \
echo "Tag v$$TAG created and pushed successfully."; \
else \
echo "Invalid tag format. Please use X.Y.Z (e.g., 1.0.0)"; \
exit 1; \
fi

gh-release: build-release ## Create GitHub release with firmware
ifndef VERSION
$(error VERSION is required. Usage: make gh-release VERSION=0.1.1 [NOTES="..."])
endif
ifdef NOTES
gh release create v$(VERSION) .pio/build/gh_release/firmware.bin \
--repo wildfire070/CapInk \
--title "Capy v$(VERSION)" \
--notes "$(NOTES)"
else
gh release create v$(VERSION) .pio/build/gh_release/firmware.bin \
--repo wildfire070/CapInk \
--title "Capy v$(VERSION)" \
--generate-notes
endif

changelog: ## Generate CHANGELOG.md from git history
@echo "Generating CHANGELOG.md..."
@echo "" > CHANGELOG.md; \
previous_tag=0; \
for current_tag in $$(git tag --sort=-creatordate); do \
if [ "$$previous_tag" != 0 ]; then \
tag_date=$$(git log -1 --pretty=format:'%ad' --date=short $${previous_tag}); \
printf "\n## $${previous_tag} ($${tag_date})\n\n" >> CHANGELOG.md; \
git log $${current_tag}...$${previous_tag} --pretty=format:'* %s [[%an](mailto:%ae)]' --reverse | grep -v Merge >> CHANGELOG.md; \
printf "\n" >> CHANGELOG.md; \
fi; \
previous_tag=$${current_tag}; \
done; \
if [ "$$previous_tag" != 0 ]; then \
tag_date=$$(git log -1 --pretty=format:'%ad' --date=short $${previous_tag}); \
printf "\n## $${previous_tag} ($${tag_date})\n\n" >> CHANGELOG.md; \
git log $${previous_tag} --pretty=format:'* %s [[%an](mailto:%ae)]' --reverse | grep -v Merge >> CHANGELOG.md; \
printf "\n" >> CHANGELOG.md; \
fi
@echo "CHANGELOG.md generated successfully."

# Image conversion
sleep-screen: ## Convert image to sleep screen BMP
ifdef INPUT
ifdef OUTPUT
cd scripts && node create-sleep-screen.mjs ../$(INPUT) ../$(OUTPUT) $(ARGS)
else
@echo "Usage: make sleep-screen INPUT=<image> OUTPUT=<bmp> [ARGS='--dither --bits 8']"
endif
else
@echo "Usage: make sleep-screen INPUT=<image> OUTPUT=<bmp> [ARGS='--dither --bits 8']"
@echo "Example: make sleep-screen INPUT=photo.jpg OUTPUT=sleep.bmp"
endif

## Unit Tests:

test: test-build test-run ## Build and run all unit tests

test-build: ## Build unit tests
@mkdir -p test/build
@cd test/build && cmake .. -DCMAKE_BUILD_TYPE=Debug && cmake --build . --parallel

test-run: ## Run unit tests (build first if needed)
@if [ ! -d test/build/bin ]; then $(MAKE) test-build; fi
@test/scripts/run_tests.sh

test-clean: ## Clean test build artifacts
@rm -rf test/build

## Tools:

fontconvert-bin: ## Build Go fontconvert-bin tool (CJK .bin font converter)
$(MAKE) -C tools/fontconvert-bin build

reader-test: ## Build desktop reader-test tool (process books without flashing)
@mkdir -p tools/reader-test/build
@cd tools/reader-test/build && cmake .. && cmake --build . --parallel
@echo "Built: tools/reader-test/build/reader-test"
ifdef FILE
@tools/reader-test/build/reader-test $(FILE) $(OUTPUT)
endif

## Help:

help: ## Show this help
@echo "Capy - Build System"
@echo ""
@echo "Usage: make [target]"
@echo ""
@awk 'BEGIN {FS = ":.*##"; section=""} \
/^##/ { section=substr($$0, 4); next } \
/^[a-zA-Z_-]+:.*##/ { \
if (section != "") { printf "\n\033[1m%s\033[0m\n", section; section="" } \
printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 \
}' $(MAKEFILE_LIST)
@echo ""
69 changes: 69 additions & 0 deletions lib/BookFusionSync/BookFusionBookIdStore.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "BookFusionBookIdStore.h"

#include <ArduinoJson.h>
#include <HalStorage.h>
#include <Logging.h>
#include <MD5Builder.h>

#include <cstdio>
#include <cstring>

void BookFusionBookIdStore::buildSidecarPath(const char* epubPath, char* outPath, size_t maxLen) {
MD5Builder md5;
md5.begin();
md5.add(epubPath);
md5.calculate();

// Result: /.crosspoint/bookfusion_<32hexchars>.json (55 chars total)
snprintf(outPath, maxLen, "/.crosspoint/bookfusion_%s.json", md5.toString().c_str());
}

uint32_t BookFusionBookIdStore::loadBookId(const char* epubPath) {
char sidecarPath[64];
buildSidecarPath(epubPath, sidecarPath, sizeof(sidecarPath));

if (!Storage.exists(sidecarPath)) {
return 0;
}

String json = Storage.readFile(sidecarPath);
if (json.isEmpty()) {
return 0;
}

JsonDocument doc;
if (deserializeJson(doc, json) != DeserializationError::Ok) {
LOG_ERR("BFS", "Sidecar JSON parse error: %s", sidecarPath);
return 0;
}

const uint32_t bookId = doc["book_id"] | (uint32_t)0;
LOG_DBG("BFS", "Loaded book_id=%lu for %s", (unsigned long)bookId, epubPath);
return bookId;
}

bool BookFusionBookIdStore::saveBookId(const char* epubPath, uint32_t bookId) {
if (bookId == 0) {
LOG_ERR("BFS", "Refusing to save book_id=0 for %s", epubPath);
return false;
}

char sidecarPath[64];
buildSidecarPath(epubPath, sidecarPath, sizeof(sidecarPath));

Storage.mkdir("/.crosspoint");

JsonDocument doc;
doc["book_id"] = bookId;

String json;
serializeJson(doc, json);

const bool ok = Storage.writeFile(sidecarPath, json);
if (ok) {
LOG_DBG("BFS", "Saved book_id=%lu for %s", (unsigned long)bookId, epubPath);
} else {
LOG_ERR("BFS", "Failed to save sidecar: %s", sidecarPath);
}
return ok;
}
24 changes: 24 additions & 0 deletions lib/BookFusionSync/BookFusionBookIdStore.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
#include <cstddef>
#include <cstdint>

/**
* Per-book sidecar for BookFusion book IDs.
*
* Each EPUB that was downloaded from BookFusion has an associated sidecar file
* at /.crosspoint/bookfusion_<md5_of_epub_path>.json containing its numeric book_id.
*
* Returns 0 from loadBookId() when no sidecar exists — 0 is never a valid BookFusion ID.
*/
class BookFusionBookIdStore {
public:
// Load book_id for the given epub path. Returns 0 if not a BookFusion book.
static uint32_t loadBookId(const char* epubPath);

// Save book_id for an epub path. Returns false on I/O error or if id == 0.
static bool saveBookId(const char* epubPath, uint32_t bookId);

private:
// Derives /.crosspoint/bookfusion_<32hexchars>.json from the epub path.
static void buildSidecarPath(const char* epubPath, char* outPath, size_t maxLen);
};
Loading
Loading