# embassy-st7789v-plot

Moteur de tracé de graphiques cartésiens (X, Y) adaptatifs et configurables pour écrans TFT LCD **ST7789V 240×320**, construit au-dessus de [`embassy-st7789v`](https://crates.io/crates/embassy-st7789v).

## 🎯 Caractéristiques

- ✅ **`#![no_std]` + `#![forbid(unsafe_code)]`** — Entièrement sûr et embarqué
- ✅ **Zéro allocation heap** — Buffers statiques uniquement (ring buffer fixe)
- ✅ **Ring buffer circulaire** — Jusqu'à 240 points (limite physique écran 240px)
- ✅ **Axes configurables** — Graduations statiques avec pas personnalisable
- ✅ **Grille adaptative** — Grille horizontale/verticale avec labels
- ✅ **API asynchrone** — Intégration complète Embassy pour non-bloquant
- ✅ **Protection des bordures** — Labels de graduations restent toujours visibles
- ✅ **Rendu ligne Bresenham** — Courbes lisses entre points de données

## 📦 Installation

```toml
[dependencies]
embassy-st7789v-plot = "0.1"
embassy-st7789v = "0.1"
embedded-hal = "1.0"
embedded-hal-async = "1.0"
```

## 🚀 Utilisation rapide

### Exemple basique — Single plot

```rust
use embassy_st7789v::{Color, St7789v, NoPin};
use embassy_st7789v_plot::{Graphics, AxisConfig, PlotConfig, LineChart};

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    // Initialiser le display
    let mut display: St7789v<_, _, NoPin> = /* ... */;

    // Créer les axes
    let x_axis = AxisConfig::new(0.0, 10.0, 2.0, b"Time (s)");
    let y_axis = AxisConfig::new(0.0, 100.0, 20.0, b"Temperature (C)");

    // Configurer le graphique
    let config = PlotConfig {
        x: 10,
        y: 10,
        width: 220,
        height: 200,
        margin_left: 40,      // Pour labels Y
        margin_right: 10,
        margin_top: 10,
        margin_bottom: 30,    // Pour labels X
        x_axis,
        y_axis,
        bg_color: Color::BLACK,
        line_color: Color::GREEN,
        axis_color: Color::WHITE,
        grid_color: Color::from_rgb(64, 64, 64),
        text_color: Color::WHITE,
        label_color: Color::CYAN,
    };

    // Créer le gestionnaire de graphique (100 points max)
    let mut chart: LineChart<100> = LineChart::new(config);

    // Ajouter des données
    chart.push(45.2);
    chart.push(47.8);
    chart.push(52.1);
    chart.push(48.9);

    // Afficher
    let mut gfx = Graphics::new_no_rst(&mut display);
    chart.render(&mut gfx).await;
}
```

### Configuration des axes

```rust
// Axe X : temps de 0 à 60s, graduations tous les 10s
let time_axis = AxisConfig::new(0.0, 60.0, 10.0, b"Time (s)");

// Axe Y : température -10 à +50°C, graduations tous les 10°C
let temp_axis = AxisConfig::new(-10.0, 50.0, 10.0, b"Temp (C)");

// Axe Y : tensions 0 à 3.3V, graduations tous les 0.5V
let voltage_axis = AxisConfig::new(0.0, 3.3, 0.5, b"U (V)");

// Nombre de graduations affiché
assert_eq!(time_axis.tick_count(), 7);  // 0, 10, 20, 30, 40, 50, 60
```

### Validation de configuration

```rust
let axis = AxisConfig::new(0.0, 10.0, 0.5, b"X");
assert!(axis.is_valid());  // step > 0 && end > start

let invalid = AxisConfig::new(10.0, 0.0, 1.0, b"X");
assert!(!invalid.is_valid());  // end <= start
```

## 🎨 Schéma de positionnement

```
┌─────────────────────────────────┐  y
│ (x, y)                          │
│ ┌──────────────────────────┐    │
│ │  Label Y (amplitude)     │    │ margin_top
│ │  ┌────────────────────┐  │    │
│ │  │  100  ┼─────────┐  │  │    │
│ │  │   80  │  •     │  │  │    │
│ │  │   60  │    •   │  │  │    │
│ │  │   40  │      •–┤  │  │    │
│ │  │   20  │        │  │  │    │
│ │  │    0  └────────┘  │  │    │
│ │  │       0  2  4  6  8 10    │ margin_bottom
│ │  │       Time (s)            │
│ │  └────────────────────────   │
│ └──────────────────────────────┘
  ↑                              ↑
margin_left              margin_right
```

## 📊 Structure de données

### Ring buffer (historique circulaire)

```
Stockage interne : array[100]
head = 3, count = 100 (buffer plein)

data[0] = valeur 97e
data[1] = valeur 98e
data[2] = valeur 99e  ← head (prochaine écriture)
data[3] = valeur 1ère  ← oldest (plus ancienne)
data[4] = valeur 2e
...
```

### Workflow de rendu

1. **Fond** → Rectangle de fond couleur (bg_color)
2. **Grille Y** → Lignes horizontales + labels Y
3. **Grille X** → Lignes verticales + labels X
4. **Labels des axes** → Texte des titres
5. **Bordures** → Cadre (axis_color)
6. **Courbe** → Lignes Bresenham reliant les points

## 🔧 Cas d'usage

### Oscilloscope 1 canal

```rust
let config = PlotConfig {
    x: 0, y: 0, width: 240, height: 320,
    margin_left: 40, margin_right: 10,
    margin_top: 10, margin_bottom: 30,
    x_axis: AxisConfig::new(0.0, 240.0, 30.0, b"Samples"),
    y_axis: AxisConfig::new(-5.0, 5.0, 1.0, b"Voltage (V)"),
    // ... colors ...
};
let mut oscilloscope: LineChart<240> = LineChart::new(config);
```

### Moniteur de température

```rust
let config = PlotConfig {
    x: 10, y: 10, width: 220, height: 150,
    margin_left: 45, margin_right: 15,
    margin_top: 15, margin_bottom: 30,
    x_axis: AxisConfig::new(0.0, 120.0, 20.0, b"Time (min)"),
    y_axis: AxisConfig::new(15.0, 35.0, 5.0, b"T (°C)"),
    // ... colors ...
};
let mut temp_chart: LineChart<120> = LineChart::new(config);
```

### Graphique de pression

```rust
let config = PlotConfig {
    x: 5, y: 5, width: 230, height: 310,
    margin_left: 35, margin_right: 5,
    margin_top: 5, margin_bottom: 30,
    x_axis: AxisConfig::new(0.0, 1000.0, 100.0, b"Pa"),
    y_axis: AxisConfig::new(900.0, 1050.0, 30.0, b"P (hPa)"),
    // ... colors ...
};
let mut pressure_chart: LineChart<100> = LineChart::new(config);
```

## 🛠️ API complète

### `LineChart<N>`

| Méthode | Description |
|---------|-------------|
| `new(config)` | Crée un nouveau graphique |
| `push(value)` | Ajoute une valeur à l'historique |
| `clear()` | Efface l'historique |
| `config()` | Retourne la configuration |
| `render(gfx)` | Affiche le graphique (async) |

### `AxisConfig`

| Méthode | Description |
|---------|-------------|
| `new(start, end, step, label)` | Crée une config d'axe |
| `is_valid()` | Vérifie cohérence (step > 0 && end > start) |
| `tick_count()` | Nombre de graduations affiché |

### `Graphics<'a, SPI, DC, RST>`

| Méthode | Description |
|---------|-------------|
| `new(display)` | Crée contexte avec RST |
| `new_no_rst(display)` | Crée contexte sans RST |
| `pixel(x, y, color)` | Trace pixel (async) |

### Fonction globale

| Fonction | Description |
|----------|-------------|
| `line(gfx, x0, y0, x1, y1, color)` | Trace ligne Bresenham (async) |

## ⚠️ Limitations

- **Nombre de points** : Maximum 240 (largeur écran ST7789V)
- **Précision** : Axes en virgule flottante, pixel en entier
- **Pas adaptatif** : Les graduations sont statiques (pas de zoom automatique)
- **Labels** : Seulement texte ASCII sur Y-axis, valeurs flottantes sur graduations

## 📝 Exemple complet avec boucle acquisition

```rust
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let mut display = /* init ST7789V */;
    
    let config = PlotConfig { /* ... */ };
    let mut chart: LineChart<200> = LineChart::new(config);
    let mut gfx = Graphics::new_no_rst(&mut display);

    // Boucle d'acquisition
    loop {
        // Lire capteur
        let value = sensor.read().await;
        chart.push(value);

        // Afficher tous les 100ms
        Timer::after(Duration::from_millis(100)).await;
        chart.render(&mut gfx).await;
    }
}
```

## 📜 License

GPL-2.0-or-later 

**Copyright (C) 2026 Jorge Andre Castro**

## 🔗 Dépendances

- [`embassy-st7789v`](https://crates.io/crates/embassy-st7789v) — Driver ST7789V
- [`embedded-hal`](https://crates.io/crates/embedded-hal) — Traits HAL
- [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) — Traits async HAL


