sundial
by Itstommy10
README.md
# Sundial — le luci iCUE seguono il sole
Webapp locale che al **tramonto** applica automaticamente un preset di
illuminazione ai dispositivi Corsair tramite l'SDK ufficiale di iCUE, e
all'**alba** restituisce il controllo a iCUE (o spegne, o applica un altro
preset). Tramonto e alba sono calcolati ogni giorno per la posizione
configurata, quindi l'orario segue le stagioni senza doverlo mai aggiornare.

> Gli screenshot sono presi in modalità dimostrativa (`MOCK=1`), con
> dispositivi finti e la posizione impostata su Roma.
## Cosa fa
- **Segue il sole, non l'orologio.** `astral` calcola alba e tramonto per le
coordinate scelte; `APScheduler` programma i due eventi e li ricalcola ogni
notte alle 00:05.
- **Anticipo/ritardo configurabile.** Un offset in minuti (negativo = prima
del tramonto) sposta l'evento rispetto al sole.
- **Preset di luce.** Colore + luminosità, applicabili a tutti i dispositivi
o solo ad alcuni, fino al singolo LED.
- **Tre azioni per ogni evento:** applicare un preset, spegnere le luci, o
restituire il controllo a iCUE.
- **Prova ora.** Simula il tramonto all'istante per verificare la
configurazione senza aspettare sera.
- **Widget per [gethomepage](https://gethomepage.dev)** e **controllo via MCP**
da un assistente AI (entrambi più sotto).
## Requisiti
- **Windows** con **iCUE 4 o 5** installato e in esecuzione
- In iCUE: **Impostazioni → abilita "SDK"** (senza questo non funziona nulla)
- **Python 3.11+**
Sundial deve girare sullo stesso PC di iCUE, perché parla con l'SDK in locale.
## Installazione ed esecuzione
```bat
git clone https://github.com/Itstommy10/sundial.git
cd sundial
pip install -r requirements.txt
python app.py
```
Poi apri **http://localhost:8420**. Funziona anche dal telefono usando l'IP
del PC sulla rete locale (es. `http://192.168.1.x:8420`).
La porta si cambia con la variabile d'ambiente `PORT`:
```bat
set PORT=9000
python app.py
```
### Provare senza iCUE
Per vedere l'interfaccia su un computer senza iCUE (o senza hardware
Corsair), la modalità mock simula due dispositivi:
```bat
set MOCK=1
python app.py
```
### Primo avvio
Al primo avvio viene creato `config.json` accanto ad `app.py` con la
configurazione di default. Il file **non è nel repository** (è personale:
contiene le tue coordinate) e non va committato. Dall'interfaccia:
1. In **Posizione**, imposta la tua città — il pulsante *"Usa la mia
posizione"* la ricava dal browser e rileva il fuso orario da solo.
2. In **Preset luce**, crea il preset da usare al tramonto.
3. In **Automazione**, scegli le azioni per tramonto e alba e salva.
## L'interfaccia
**Automazione** — l'interruttore generale, le azioni dei due eventi e
l'anticipo/ritardo in minuti:

**Preset luce** — ogni preset porta colore, luminosità e il bersaglio
(tutti i dispositivi o una selezione):

**Dispositivi e LED** — la mappa dei LED indirizzabili di ogni dispositivo,
in ordine di posizione fisica; da qui si scelgono i singoli LED:

## Widget per gethomepage
`GET /api/homepage` restituisce un riepilogo già formattato (solo valori
scalari, come vuole Homepage). Da incollare in `services.yaml`:
```yaml
- Sundial:
href: http://192.168.1.x:8420
description: Luci iCUE al tramonto
widget:
type: customapi
url: http://192.168.1.x:8420/api/homepage
refreshInterval: 60000
mappings:
- field: prossimo
label: Prossimo evento
- field: tramonto
label: Tramonto
- field: azione_tramonto
label: Al tramonto
- field: stato
label: Automazione
```
Homepage mostra al massimo 4 campi per riga: gli altri disponibili sono
`alba`, `icue` (`Connesso`/`Mock`/`Assente`), `dispositivi` (quanti rilevati)
e `attivo` (se c'è un preset attualmente applicato). La chiamata parte dal
server di Homepage, quindi l'IP dev'essere quello del PC con Sundial —
`localhost` funziona solo se girano sulla stessa macchina.
## Controllo da un assistente (MCP)
`mcp_server.py` espone Sundial come strumenti MCP, così puoi pilotarlo a voce
o in chat da Claude Code / Claude Desktop ("crea un preset che spegne i LED 1
e 3 del tappetino all'alba"). È un **client dell'API HTTP**: `app.py` deve
essere in esecuzione, perché la sessione dell'SDK iCUE va aperta da un solo
processo.
```bat
pip install mcp
claude mcp add sundial -- python C:\percorso\sundial\mcp_server.py
```
In alternativa copia `.mcp.json.example` in `.mcp.json` e correggi il
percorso: aprendo la cartella con Claude Code il server viene proposto in
automatico (va approvato una volta).
Strumenti disponibili: `sundial_status`, `sundial_devices`, `sundial_presets`,
`sundial_apply_preset`, `sundial_create_preset`, `sundial_update_preset`,
`sundial_delete_preset`, `sundial_set_automation`, `sundial_set_location`,
`sundial_turn_off`, `sundial_release`, `sundial_test_sunset`.
Dispositivi e preset si indicano **per nome** (`"tappetino"`, `"mm700"`,
`"scheda madre"`, `"Tramonto caldo"`), e i LED con i numeri `1..N` mostrati
sulla mappa nell'interfaccia web. Se un nome è ambiguo, lo strumento lo dice
elencando le alternative invece di scegliere a caso.
## API HTTP
Documentazione interattiva (Swagger) su **http://localhost:8420/api/docs**.
| Metodo | Endpoint | Descrizione |
| -------- | ---------------------------- | --------------------------------------------- |
| `GET` | `/api/status` | Stato completo: sole, job, dispositivi, config |
| `GET` | `/api/homepage` | Riepilogo piatto per il widget customapi |
| `PUT` | `/api/location` | Imposta posizione e fuso orario |
| `PUT` | `/api/automation` | Imposta azioni, offset e interruttore generale |
| `POST` | `/api/presets` | Crea un preset |
| `PUT` | `/api/presets/{id}` | Modifica un preset |
| `DELETE` | `/api/presets/{id}` | Elimina un preset |
| `POST` | `/api/apply/{id}` | Applica subito un preset |
| `GET` | `/api/devices/{id}/leds` | Mappa dei LED di un dispositivo |
| `POST` | `/api/off` | Spegne tutto (o solo i LED indicati) |
| `POST` | `/api/release` | Restituisce il controllo a iCUE |
| `POST` | `/api/test-sunset` | Simula subito l'evento tramonto |
## Avvio automatico con Windows
Nel repository c'è già `sundial.vbs`, che avvia l'app senza finestra a
console. Per farlo partire a ogni accesso a Windows basta mettere un
collegamento allo script nella cartella Esecuzione automatica
(`Win+R` → `shell:startup`), oppure crearlo da PowerShell:
```powershell
$sh = New-Object -ComObject WScript.Shell
$s = $sh.CreateShortcut((Join-Path ([Environment]::GetFolderPath('Startup')) 'Sundial.lnk'))
$s.TargetPath = 'wscript.exe'
$s.Arguments = '"C:\percorso\sundial\sundial.vbs"'
$s.WorkingDirectory = 'C:\percorso\sundial'
$s.Save()
```
Lo script usa `python.exe` con la finestra nascosta, **non** `pythonw.exe`:
senza console `sys.stderr` è `None` e uvicorn fallisce in silenzio mentre
configura il logging, quindi l'app non parte affatto. Se ti serve un
interprete specifico, il percorso si imposta nella variabile `py` dentro
`sundial.vbs`.
## Icona nella tray
All'avvio Sundial mette un sole arancione nell'area di notifica. Il menu
(tasto destro) mostra il prossimo evento in programma e permette di:
- **Apri Sundial** — apre l'interfaccia nel browser (anche col doppio clic)
- **Prova ora il tramonto** — simula subito l'evento
- **Spegni le luci** / **Ridai il controllo a iCUE**
- **Esci** — ferma davvero l'app (server e scheduler compresi)
L'icona richiede `pystray` e `pillow`, già in `requirements.txt`: se mancano,
l'app parte comunque senza icona. Per disattivarla di proposito:
```bat
set SUNDIAL_TRAY=0
python app.py
```
## Note e limiti
- Se il PC è spento al tramonto, l'evento non scatta (riavviando Sundial
dopo, riprogramma automaticamente il prossimo evento utile).
- Dopo una scrittura dell'SDK, iCUE **smette di ridisegnare** quel
dispositivo: i LED restano all'ultimo colore finché non si rilascia il
controllo. Il solo `release_control` non basta (risponde `CE_Success` ma
con un profilo statico iCUE non ridipinge): `release()` fa un ciclo
*richiesta controllo esclusivo → rilascio* che forza iCUE a riprendersi il
dispositivo. Conseguenza pratica: se un preset tocca solo alcuni LED, gli
altri dello stesso dispositivo restano congelati fino al rilascio.
- Spegnere un LED RGB significa scrivergli `(0,0,0)`: l'SDK non ha un
interruttore per singolo LED. I preset con `mode: "off"` fanno questo.
- Verificato con `cuesdk` 4.0.84: `request_control(device_id, access_level)`
vuole **due** argomenti, `release_control(None)` fa **crashare** il
processo (va chiamata per dispositivo) e `sdk.disconnect()` a sua volta
crasha, quindi la sessione non va chiusa a runtime.
- L'API non ha autenticazione: pensata per la rete locale, non esporla su
Internet.
## Licenza
MIT — vedi [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues