Skip to main content
Glama
Itstommy10

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.

![L'interfaccia di Sundial](docs/screenshot-app.png)

> 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:

![Pannello automazione](docs/screenshot-auto.png)

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

![Preset luce](docs/screenshot-presets.png)

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

![Dispositivi e LED](docs/screenshot-devices.png)

## 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).