mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-19 23:57:41 +03:00
## Summary
- **What is the goal of this PR?**
Implements wireless EPUB file management via a built-in web server,
enabling users to upload, browse, organize, and delete EPUB files from
any device on the same WiFi network without needing a computer cable
connection.
- **What changes are included?**
- **New Web Server**
([`CrossPointWebServer.cpp`](src/CrossPointWebServer.cpp),
[`CrossPointWebServer.h`](src/CrossPointWebServer.h)):
- HTTP server on port 80 with a responsive HTML/CSS interface
- Home page showing device status (version, IP, free memory)
- File Manager with folder navigation and breadcrumb support
- EPUB file upload with progress tracking
- Folder creation and file/folder deletion
- XSS protection via HTML escaping
- Hidden system folders (`.` prefixed, "System Volume Information",
"XTCache")
- **WiFi Screen** ([`WifiScreen.cpp`](src/screens/WifiScreen.cpp),
[`WifiScreen.h`](src/screens/WifiScreen.h)):
- Network scanning with signal strength indicators
- Visual indicators for encrypted (`*`) and saved (`+`) networks
- State machine managing: scanning, network selection, password entry,
connecting, save/forget prompts
- 15-second connection timeout handling
- Integration with web server (starts on connect, stops on exit)
- **WiFi Credential Storage**
([`WifiCredentialStore.cpp`](src/WifiCredentialStore.cpp),
[`WifiCredentialStore.h`](src/WifiCredentialStore.h)):
- Persistent storage in `/sd/.crosspoint/wifi.bin`
- XOR obfuscation for stored passwords (basic protection against casual
reading)
- Up to 8 saved networks with add/remove/update operations
- **On-Screen Keyboard**
([`OnScreenKeyboard.cpp`](src/screens/OnScreenKeyboard.cpp),
[`OnScreenKeyboard.h`](src/screens/OnScreenKeyboard.h)):
- Reusable QWERTY keyboard component with shift support
- Special keys: Shift, Space, Backspace, Done
- Support for password masking mode
- **Settings Screen Integration**
([`SettingsScreen.h`](src/screens/SettingsScreen.h)):
- Added WiFi action to navigate to the new WiFi screen
- **Documentation** ([`docs/webserver.md`](docs/webserver.md)):
- Comprehensive user guide covering WiFi setup, web interface usage,
file management, troubleshooting, and security notes
- See this for more screenshots!
- Working "displays the right way in GitHub" on my repo:
https://github.com/olearycrew/crosspoint-reader/blob/feature/connect-to-wifi/docs/webserver.md
**Video demo**
https://github.com/user-attachments/assets/283e32dc-2d9f-4ae2-848e-01f41166a731
## Additional Context
- **Security considerations**: The web server has no
authentication—anyone on the same WiFi network can access files. This is
documented as a limitation, recommending use only on trusted private
networks. Password obfuscation in the credential store is XOR-based, not
cryptographically secure.
- **Memory implications**: The web server and WiFi stack consume
significant memory. The implementation properly cleans up (stops server,
disconnects WiFi, sets `WIFI_OFF` mode) when exiting the WiFi screen to
free resources.
- **Async operations**: Network scanning and connection use async
patterns with FreeRTOS tasks to prevent blocking the UI. The display
task handles rendering on a dedicated thread with mutex protection.
- **Browser compatibility**: The web interface uses standard
HTML5/CSS3/JavaScript and is tested to work with all modern browsers on
desktop and mobile.
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
import os
|
|
import re
|
|
|
|
SRC_DIR = "src"
|
|
|
|
def minify_html(html: str) -> str:
|
|
# Tags where whitespace should be preserved
|
|
preserve_tags = ['pre', 'code', 'textarea', 'script', 'style']
|
|
preserve_regex = '|'.join(preserve_tags)
|
|
|
|
# Protect preserve blocks with placeholders
|
|
preserve_blocks = []
|
|
def preserve(match):
|
|
preserve_blocks.append(match.group(0))
|
|
return f"__PRESERVE_BLOCK_{len(preserve_blocks)-1}__"
|
|
|
|
html = re.sub(rf'<({preserve_regex})[\s\S]*?</\1>', preserve, html, flags=re.IGNORECASE)
|
|
|
|
# Remove HTML comments
|
|
html = re.sub(r'<!--.*?-->', '', html, flags=re.DOTALL)
|
|
|
|
# Collapse all whitespace between tags
|
|
html = re.sub(r'>\s+<', '><', html)
|
|
|
|
# Collapse multiple spaces inside tags
|
|
html = re.sub(r'\s+', ' ', html)
|
|
|
|
# Restore preserved blocks
|
|
for i, block in enumerate(preserve_blocks):
|
|
html = html.replace(f"__PRESERVE_BLOCK_{i}__", block)
|
|
|
|
return html.strip()
|
|
|
|
for root, _, files in os.walk(SRC_DIR):
|
|
for file in files:
|
|
if file.endswith(".html"):
|
|
html_path = os.path.join(root, file)
|
|
with open(html_path, "r", encoding="utf-8") as f:
|
|
html_content = f.read()
|
|
|
|
# minified = regex.sub("\g<1>", html_content)
|
|
minified = minify_html(html_content)
|
|
base_name = f"{os.path.splitext(file)[0]}Html"
|
|
header_path = os.path.join(root, f"{base_name}.generated.h")
|
|
|
|
with open(header_path, "w", encoding="utf-8") as h:
|
|
h.write(f"// THIS FILE IS AUTOGENERATED, DO NOT EDIT MANUALLY\n\n")
|
|
h.write(f"#pragma once\n")
|
|
h.write(f'constexpr char {base_name}[] PROGMEM = R"rawliteral({minified})rawliteral";\n')
|
|
|
|
print(f"Generated: {header_path}")
|