A modern, self-hosted Flask app to control a Waveshare E-Ink display (or web mock) showing the flag of any country. Supports Google Home voice control, IFTTT, and secure or local API access.
- Clone & Install
git clone https://github.com/CHaerem/Flags.git cd Flags sudo apt update && sudo apt install python3-pip python3-venv python3-flask python3 -m venv venv source venv/bin/activate pip install -r requirements.txt
- Run the Flask server
python3 run.py [--mock] [--headless] [--port PORT]
--mock: Web preview (no hardware)--headless: Metadata only (no display)
- (Optional) Run display script directly
sudo python3 scripts/main.py [CountryName]
- Auto-start on boot
- Cron: Add to
crontab -e - systemd: See example below
- Cron: Add to
- Real or Mock Display: E-Ink hardware or browser preview
- Modern Web UI: Flag info, autocomplete, map
- Configurable: Enable/disable, mock/headless, schedule, display size
- Flexible Scheduling: Time-based intervals, update at startup
- Offline/Online: Works on your LAN or via Tailscale
- API & Voice Control: Secure or local endpoints, Google Home/IFTTT
- Voice Recognition: Use microphone to select flags using voice commands
- Display Lock: Prevents hardware conflicts
- History Tracking: View a list of previously displayed flags
The FlagPi now supports changing flags using voice commands directly through a microphone connected to the Raspberry Pi!
-
Install Dependencies
- The required packages
voskandsounddeviceare included inrequirements.txt - You'll need a microphone connected to your Raspberry Pi
- The required packages
-
Download Voice Model
- Download the small English model from Vosk:
(or use
mkdir -p models cd models wget https://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip unzip vosk-model-small-en-us-0.15.zipcurl -Oinstead ofwgetif not available)
- Download the small English model from Vosk:
-
Trigger the Voice Listener
- Make a POST request to
/voice-listenendpoint - Example with curl:
curl -X POST http://FlagPi.local/voice-listen
- Make a POST request to
-
Speak Your Command
- After triggering, FlagPi will listen for about 10 seconds
- Speak the name of a country clearly (e.g., "Change to Japan" or just "Sweden")
- The endpoint will listen until it recognizes a country or the 10 seconds expire
-
Response Format The endpoint returns a JSON response with the following format:
{ "status": "success", // success, partial_success, not_found, timeout, error "message": "Changed flag to Japan", // Human-readable message "country": "Japan", // Only present if a country was matched "transcribed_text": "change to japan" // The recognized speech text }
You can use this endpoint with Home Assistant to create a complete voice control system:
# Example configuration for Home Assistant
# First, define the REST command to trigger voice listening
rest_command:
flag_voice_listen:
url: http://192.168.1.37/voice-listen
method: POST
content_type: "application/json"
return_response: true
# Then create an automation to handle the button press and process the response
automation:
- alias: "Voice-driven flag selection"
trigger:
platform: device
type: turned_on
device_id: 6b50ed9f341615aecbbb156e37fb066f # Your button device ID
entity_id: binary_sensor.flag_button_double_press
action:
# Step 1: Announce we're listening
- service: tts.google_translate_say
data:
entity_id: media_player.living_room_speaker
message: "Listening for flag request..."
# Step 2: Call the voice listening endpoint
- service: rest_command.flag_voice_listen
response_variable: voice_response
# Step 3: Process the response with conditional actions
- choose:
# Success case
- conditions:
- condition: template
value_template: "{{ voice_response.status == 'success' }}"
sequence:
- service: tts.google_translate_say
data:
entity_id: media_player.living_room_speaker
message: "{{ voice_response.message }}"
# No country recognized case
- conditions:
- condition: template
value_template: "{{ voice_response.status == 'not_found' }}"
sequence:
- service: tts.google_translate_say
data:
entity_id: media_player.living_room_speaker
message: "Sorry, I couldn't recognize any country name in '{{ voice_response.transcribed_text }}'"
# Default case (error or timeout)
default:
- service: tts.google_translate_say
data:
entity_id: media_player.living_room_speaker
message: "{{ voice_response.message if voice_response.message is defined else 'There was an error processing your request' }}"| Endpoint | Access | Auth Required | Purpose |
|---|---|---|---|
/change-flag |
Public (Funnel) | Yes (X-Flag-Token) |
Change flag remotely |
/local-change-flag |
Local only | No | Change flag locally |
/current-flag |
Public/Local | No | Get current flag info |
Only
/change-flagis exposed via Tailscale Funnel for secure remote/IFTTT use. Local web UI/scripts use/local-change-flag(open on LAN)./current-flagis always open.
with header:
> X-Flag-Token: your-secret-token
-
Local Access
- For local scripts or web UI, use:
- No token required for local use.
- For local scripts or web UI, use:
-
Get Current Flag Info
- Anyone (local or remote) can GET:
http://FlagPi.local/current-flag https://flagpi.ts.net/current-flag
- Anyone (local or remote) can GET:
β Result:
- Google Home speaks your command
- IFTTT sends the flag update
- Tailscale securely delivers it to your Pi (with token)
- Flask accepts the request and updates the E-Ink flag display
Summary:
- Remote/public updates:
/change-flag(secure, token required) - Local updates:
/local-change-flag(open) - Flag info:
/current-flag(open)
Let me know if youβd like a diagram or script to automate this setup!
project-root/
βββ run.py # Main Flask server entry point
βββ scripts/
β βββ main.py # Flag display & metadata update logic
β βββ config_manager.py # Config and state management
β βββ ... # Other utility scripts
βββ app/ # Flask application package
β βββ __init__.py # App initialization
β βββ routes.py # API and web routes
β βββ static/ # Static assets (css, js, data)
β β βββ css/
β β βββ js/ # UI, autocomplete, map
β β βββ data/ # Country/flag JSON
β βββ templates/ # HTML templates (index, config, preview)
βββ display/ # Display drivers (e-ink, mock)
β βββ manager.py, lock.py, ...
βββ flag_cache/ # Downloaded flag images
βββ requirements.txt # Python dependencies
βββ ...
Then enable and start the service:
```bash
sudo systemctl daemon-reload
sudo systemctl enable flag-app
sudo systemctl start flag-app
```
- Access
/configin the web UI to:- Enable/disable flag updates
- Switch between physical display, mock display (web preview), or headless (metadata only)
- Set update interval (15 min, 30 min, 1h, ...)
- Choose fixed/random country or update at startup
- Adjust display width/height
- Manually trigger flag update
- See current flag info
- Use
--mockor enable mock mode in config to preview the e-ink display in the browser (/preview) - No hardware required for mock mode (great for development/testing)
- Use
--headlessor enable in config to only update metadata (no display access) - Useful for troubleshooting or running on non-Pi systems
- Country input fields use offline autocomplete with emoji flags
- The web UI shows a world map and highlights the selected country
- View past flags at
/history
POST /change-flag(JSON or form): Change displayed flagPOST /update-flag: Force update (random or config country)GET /config: View config page
- Physical e-ink display (Waveshare 7.3") supported on Raspberry Pi
- Mock display provides a web preview for any system
- Display lock ensures safe access (prevents hardware conflicts)
{
"country": "Argentina",
"info": "Capital: Buenos Aires",
"emoji": "π¦π·",
"timestamp": "2025-04-25 14:30:00",
"population": 45376763,
"region": "Americas",
"subregion": "South America",
"languages": {"spa": "Spanish"},
"currencies": {"ARS": {"symbol": "$", "name": "Argentine peso"}},
"timezones": ["UTC-03:00"]
}- NFC: Write your Pi's web URL to an NFC tag for instant access
- Google Home: (Planned feature, not yet implemented)
| Component | Offline | Source |
|---|---|---|
| E-ink display | β | Local on Raspberry Pi |
| Web Interface | β | Self-hosted Flask app |
| Google Home | π§ | (Planned, not available) |
| NFC tag | β | Static HTTPS link to page |
- Add more scheduling options
- Improve dark/light theme
- More map features
- Multi-language support
Christopher Hærem
π§ chris.haerem@gmail.com
π https://github.com/CHaerem
- See above for systemd/cron setup for auto-start
- Logs: check
flask_api.logor usejournalctl -u flag-app - For troubleshooting, try
--mockor--headlessto isolate issues - All configuration can be managed through
/configin the web UI
If the application isn't working as expected:
-
Check the service logs:
sudo journalctl -u flag-app tail -f flask_api.log
-
Try running in mock/headless mode to debug
-
Ensure all dependencies are installed (see requirements.txt)
-
For hardware issues, check display cables and power
To deploy the flag display application to your Raspberry Pi:
-
Copy all project files to your Pi (e.g.,
/home/chris/Flags/) -
Install required dependencies:
pip3 install -r requirements.txt
-
Make sure the startup script is executable:
chmod +x run.py
-
Test that the application runs correctly:
python3 run.py
-
Install the systemd service:
sudo cp config/flag-api.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable flag-api.service sudo systemctl start flag-api.service -
Check the service status:
sudo systemctl status flag-api.service
-
Access the web interface at
http://smartpi.local/from your local network.
If you need to update the application:
- Copy the updated files to the Pi
- Restart the service:
sudo systemctl restart flag-api.service
If the application isn't working as expected:
-
Check the service logs:
sudo journalctl -u flag-api.service -n 50
-
Check if the service is running:
sudo systemctl status flag-api.service
-
Ensure the country data is correctly prepared by checking if
/home/chris/Flags/app/static/data/countries.jsonexists