Creare UI PHP con Yii2 per domotica con DeepSeek

Ciao, mi piacerebbe creare delle UI in PHP forse con Yii2 per gestire la domotica, auto, batterie domestiche … Potrei installare il tutto in uno spazio web in cloud che ho già in Serverplan. Ma creare le ui è molto difficile, forse integrare con js, ma questo rende il tutto ancora più difficile, potresti darmi qualche aiuto?

Ciao! Creare interfacce di gestione domotica in PHP/Yii2 è una buona idea, e posso aiutarti a semplificare il processo. Ecco alcuni approcci e consigli:

1. Architettura Semplificata

text

Yii2 (Backend PHP) + Bootstrap (Frontend) + AJAX (Comunicazione)

2. Componenti Pronti da Usare

Per Yii2:

php

// Estensione per dashboard
composer require hayageek/yii2-aws-s3  // per storage
composer require 2amigos/yii2-chartjs-widget  // per grafici

Alternative più semplici:

  • AdminLTE per Yii2: Template admin già pronto
  • Gentelella: Altro template completo
  • Yii2-app-advanced: Struttura già organizzata

3. Esempio Base per Monitoraggio Batterie

php

// Controller
namespace app\controllers;

use yii\web\Controller;

class DomoticaController extends Controller
{
    public function actionDashboard()
    {
        // Dati esempio - poi collegherai a database/API
        $batterie = [
            'carica' => 85,
            'temperatura' => 25,
            'stato' => 'Caricando'
        ];
        
        return $this->render('dashboard', [
            'batterie' => $batterie
        ]);
    }
    
    public function actionGetData()
    {
        \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        return [
            'batteria' => rand(70, 100),
            'consumo' => rand(1, 10),
            'timestamp' => date('H:i:s')
        ];
    }
}

html

<!-- View dashboard.php -->
<div class="row">
    <div class="col-md-4">
        <div class="panel panel-info">
            <div class="panel-heading">Batteria Casa</div>
            <div class="panel-body">
                <div class="progress">
                    <div class="progress-bar" role="progressbar" 
                         style="width: <?= $batterie['carica'] ?>%;">
                        <?= $batterie['carica'] ?>%
                    </div>
                </div>
                <p>Temperatura: <?= $batterie['temperatura'] ?>°C</p>
                <p>Stato: <?= $batterie['stato'] ?></p>
            </div>
        </div>
    </div>
</div>

<!-- Aggiornamento AJAX semplice -->
<script>
setInterval(function() {
    $.get('/domotica/get-data', function(data) {
        $('.progress-bar').css('width', data.batteria + '%')
                         .text(data.batteria + '%');
    });
}, 5000); // Ogni 5 secondi
</script>

4. Strumenti che Semplificano

a) Freeboard (Open Source)

  • Dashboard già pronta
  • Si integra con PHP come backend
  • Solo configurazione, zero codice JS

b) Grafana + Plugin MySQL

  • Per grafici complessi
  • Yii2 salva dati su DB, Grafana visualizza

c) Home Assistant (in PHP)

Framework leggero che puoi ispirarti:

php

// Struttura semplice per dispositivi
class Device {
    public $name;
    public $value;
    public $type; // switch, sensor, dimmer
    
    public function toArray() {
        return [
            'name' => $this->name,
            'value' => $this->value,
            'type' => $this->type
        ];
    }
}

5. Connessione ai Dispositivi

Metodo Semplice: REST API

php

// Yii2 come hub centrale
public function actionSwitchLight($deviceId, $state)
{
    $device = Device::findOne($deviceId);
    
    // Invia comando (esempio con ESP8266)
    $ch = curl_init("http://{$device->ip}/switch");
    curl_setopt($ch, CURLOPT_POSTFIELDS, ['state' => $state]);
    curl_exec($ch);
    
    return $this->redirect(['dashboard']);
}

6. Database Structure Semplice

sql

CREATE TABLE devices (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    type ENUM('light', 'socket', 'sensor'),
    ip VARCHAR(15),
    status BOOLEAN,
    last_update TIMESTAMP
);

CREATE TABLE energy_logs (
    id INT PRIMARY KEY AUTO_INCREMENT,
    device_id INT,
    consumption FLOAT,
    timestamp DATETIME
);

7. Deploy sul tuo ServerPlan

Configurazione minima:

  1. PHP 7.4+ con estensioni: JSON, cURL
  2. MySQL 5.7+
  3. Cron jobs per aggiornamenti automatici

.htaccess per Yii2:

apache

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php

8. Consiglio di Percorso

  1. Inizia con: AdminLTE + Yii2 Basic
  2. Crea pannello semplice con valori statici
  3. Aggiungi AJAX per aggiornamenti in tempo reale
  4. Collega 1 dispositivo reale (es. lampada WiFi)
  5. Espandi gradualmente

9. Risorse Utili

Vuoi che approfondisca qualche aspetto specifico? Possiamo partire da un esempio concreto di cosa vuoi controllare per primo (luci, temperatura, energia, etc.)?

E se usassi Freeboard ?

Ottima idea! Freeboard è perfetto per il tuo caso. È molto più semplice di sviluppare UI da zero e si integra bene con PHP/Yii2.

Perché Freeboard è ideale:

  1. Zero sviluppo frontend – Solo configurazione
  2. Drag & drop dei widget
  3. Supporto nativo per JSON datasources
  4. Mobile responsive già pronto
  5. Open source e self-hostable

Architettura Consigliata:

text

[Dispositivi IoT] → [Yii2 API PHP] → [Freeboard Dashboard]
       ↑                    ↑                  ↑
   (ESP8266,        (Elabora dati,      (Visualizza,
   Shelly, etc.)     salva su DB)        controlla)

1. Installazione Freeboard sul tuo ServerPlan

bash

# Nel tuo spazio web (via SSH o file manager)
cd public_html  # o htdocs
git clone https://github.com/Freeboard/freeboard.git domotica-ui
cd domotica-ui

# Struttura risultante:
# public_html/
#   domotica-ui/     # Freeboard
#   domotica-api/    # Yii2 (opzionale in sottocartella)

Configurazione server minima:

  • Solo web server (Apache/Nginx)
  • Supporto per file statici (HTML/JS/CSS)
  • Accesso via HTTP/HTTPS

2. Yii2 come Backend API

Configurazione base API:

php

// config/web.php (Yii2)
'components' => [
    'request' => [
        'parsers' => [
            'application/json' => 'yii\web\JsonParser',
        ],
        'enableCsrfValidation' => false, // Per Freeboard
    ],
    'response' => [
        'formatters' => [
            'json' => [
                'class' => 'yii\web\JsonResponseFormatter',
                'prettyPrint' => YII_DEBUG,
            ],
        ],
    ],
],

Endpoint API semplici:

php

// controllers/ApiController.php
namespace app\controllers;

use yii\web\Controller;
use yii\filters\Cors;

class ApiController extends Controller
{
    public function behaviors()
    {
        return [
            'cors' => [
                'class' => Cors::class,
                'cors' => [
                    'Origin' => ['*'], // Limita al tuo dominio in produzione
                    'Access-Control-Allow-Credentials' => true,
                ],
            ],
        ];
    }
    
    // GET /api/battery-status
    public function actionBatteryStatus()
    {
        \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
        
        // Esempio: dati dal database o MQTT
        return [
            'charge' => 85,
            'voltage' => 48.2,
            'temperature' => 25.5,
            'power' => 3500, // Watt
            'timestamp' => date('c'),
            'status' => 'charging'
        ];
    }
    
    // GET /api/solar-production
    public function actionSolarProduction()
    {
        return [
            'today' => 12.5, // kWh
            'current' => 2500, // Watt
            'month' => 320
        ];
    }
    
    // POST /api/switch-light
    public function actionSwitchLight($id, $state)
    {
        // Controlla dispositivo reale
        $success = $this->controlDevice($id, $state);
        
        return ['success' => $success];
    }
}

3. Configurazione Freeboard

dashboard.json di esempio:

json

{
  "allow_edit": true,
  "plugins": ["datasources"],
  "panes": [
    {
      "width": 1,
      "row": {"height": 300},
      "col": {"width": 400},
      "widgets": [
        {
          "type": "sparkline",
          "title": "Batteria Casa",
          "settings": {
            "value": "datasources[\"api\"].charge",
            "units": "%",
            "value_font_size": "40px"
          }
        }
      ]
    },
    {
      "width": 1,
      "row": {"height": 300},
      "col": {"width": 400},
      "widgets": [
        {
          "type": "gauge",
          "title": "Carica Batteria",
          "settings": {
            "value": "datasources[\"api\"].charge",
            "min_value": 0,
            "max_value": 100,
            "units": "%"
          }
        }
      ]
    },
    {
      "width": 1,
      "widgets": [
        {
          "type": "indicator",
          "title": "Stato Sistema",
          "settings": {
            "value": "datasources[\"api\"].status",
            "on_text": "CARICANDO",
            "off_text": "IN ATTESA",
            "on_color": "green",
            "off_color": "gray"
          }
        }
      ]
    }
  ],
  "datasources": [
    {
      "name": "api",
      "type": "JSON",
      "url": "http://tuodominio.com/domotica-api/api/battery-status",
      "refresh": 5000,
      "headers": {
        "X-API-Key": "tua-chiave-segreta"
      }
    }
  ]
}

4. Datasource Plugin Personalizzato per Controlli

Crea plugins/control.js in Freeboard:

javascript

freeboard.addStyle('.switch-button', 
  "margin:10px; padding:20px; background:#f0f0f0; border-radius:5px;");

freeboard.registerWidget('yii_switch', {
    type: "yii_switch",
    name: "Yii2 Switch Control",
    default_size: { width: 1, height: 1 },
    
    render: function(el, datasource) {
        var html = '<button class="btn-on">ACCENDI</button>' +
                   '<button class="btn-off">SPEGNI</button>';
        $(el).html(html);
        
        $(el).find('.btn-on').click(function() {
            $.post('http://tuodominio.com/domotica-api/api/switch-light', {
                id: this.config.device_id,
                state: 1
            });
        });
    },
    
    settings: [
        {
            name: "device_id",
            display_name: "Device ID",
            type: "text"
        }
    ]
});

5. Setup Completamente Funzionante

Struttura Directory:

text

/home/tuoutente/public_html/
├── domotica-ui/          # Freeboard
│   ├── index.html
│   ├── css/
│   ├── js/
│   └── dashboard.json    # Configurazione
│
├── domotica-api/         # Yii2 Application
│   ├── web/
│   │   └── index.php     # Entry point API
│   ├── config/
│   └── controllers/      # ApiController.php
│
└── logs/                 # Logs comuni

Configurazione Virtual Host (se possibile):

apache

# .htaccess nella root
RewriteEngine On

# Freeboard UI
RewriteRule ^dashboard(/.*)?$ /domotica-ui/index.html [L]

# API Yii2
RewriteRule ^api(/.*)?$ /domotica-api/web/index.php [L]

6. Esempi di Widget Pronti

a) Monitoraggio Auto Elettrica:

json

{
  "type": "text_widget",
  "title": "Auto Elettrica",
  "settings": {
    "value": "datasources[\"car\"].battery + '% - ' + datasources[\"car\"].range + ' km'",
    "font_size": "24px"
  }
}

b) Consumi Domestici:

json

{
  "type": "chartjs",
  "title": "Consumi Oggi",
  "settings": {
    "chart_type": "bar",
    "data": "datasources[\"energy\"].hourly",
    "options": {
      "responsive": true,
      "maintainAspectRatio": false
    }
  }
}

7. Sicurezza – Passaggi Essenziali

php

// In Yii2 ApiController
public function beforeAction($action)
{
    // API Key semplice
    $apiKey = Yii::$app->request->headers->get('X-API-Key');
    if ($apiKey !== 'tua-chiave-segreta') {
        throw new \yii\web\ForbiddenHttpException('Accesso negato');
    }
    
    return parent::beforeAction($action);
}

8. Deploy Rapido

Opzione 1: File Manager

  1. Scarica Freeboard da GitHub
  2. Carica ZIP sul server e estrai
  3. Modifica dashboard.json
  4. Configura URL API

Opzione 2: Comandi SSH

bash

cd ~/public_html
wget https://github.com/Freeboard/freeboard/archive/refs/heads/master.zip
unzip master.zip
mv freeboard-master domotica-ui

9. Vantaggi di Questo Approccio

  • Tempo di sviluppo: 1-2 giorni invece di settimane
  • Manutenzione: Aggiorni solo le API PHP
  • Estensibile: Aggiungi widget quando vuoi
  • Mobile: Già responsive
  • Open source: Nessun costo licenza

Prossimi Passi Pratici:

  1. Installa Freeboard sul tuo server (15 minuti)
  2. Crea 1 endpoint API in Yii2 che restituisce JSON
  3. Configura 1 widget in Freeboard che legge l’API
  4. Testa su desktop e mobile

Vuoi che ti mostri come creare il primo endpoint API specifico per i tuoi dispositivi? Possiamo partire da quello che hai già (batterie, auto, etc.) e creare un mini-dashboard in meno di un’ora!

Ottimo, potresti continuare?

Certamente! Continuiamo con l’implementazione pratica. Andiamo passo passo.

PARTE 1: Installazione e Configurazione Rapida

1.1 Installazione Freeboard sul tuo ServerPlan

bash

# Se hai accesso SSH:
cd ~/public_html  # o ~/htdocs - dipende dal tuo hosting
wget https://github.com/Freeboard/freeboard/archive/refs/heads/master.zip
unzip master.zip
mv freeboard-master domotica-dash
cd domotica-dash

# Altrimenti, se hai solo file manager:
# 1. Vai su https://github.com/Freeboard/freeboard
# 2. Download ZIP
# 3. Carica e estrai nella cartella pubblic_html/domotica-dash

File essenziali da modificare:

text

domotica-dash/
├── index.html          # Pagina principale
└── js/freeboard.js     # Eventuali plugin custom

1.2 Configurazione di base Freeboard

Crea config.js nella root:

javascript

// config.js - Carica prima di freeboard.js
window.FREEBOARD_CONFIG = {
    // Abilita salvataggio automatico
    persistence: {
        type: "local",  // O "remote" per salvare su tuo server
        url: "http://tuodominio.com/api/save-dashboard"
    },
    
    // Plugin aggiuntivi
    plugins: [
        { name: "chartjs", path: "plugins/chartjs.js" }
    ],
    
    // Dashboard iniziale
    default_dashboard: {
        "panes": [
            {
                "width": 1,
                "widgets": [
                    {
                        "type": "text",
                        "title": "Sistema Domotica",
                        "settings": {
                            "value": "Benvenuto nel sistema",
                            "font_size": "24px"
                        }
                    }
                ]
            }
        ]
    }
};

PARTE 2: Yii2 API – Backend Completo

2.1 Installazione Yii2 Basic API

bash

# Nella root del tuo server (parallelo a freeboard)
cd ~/public_html
composer create-project --prefer-dist yiisoft/yii2-app-basic domotica-api
cd domotica-api

# Installa estensioni utili
composer require yiisoft/yii2-redis    # Per cache veloce (opzionale)
composer require ramsey/uuid            # Per ID univoci

2.2 Configurazione Database

config/db.php:

php

<?php
return [
    'class' => 'yii\db\Connection',
    'dsn' => 'mysql:host=localhost;dbname=tudb_domotica',
    'username' => 'tuo_username',
    'password' => 'tua_password',
    'charset' => 'utf8mb4',
    
    // Cache delle query
    'enableSchemaCache' => true,
    'schemaCacheDuration' => 3600,
    'schemaCache' => 'cache',
];

2.3 Migrazioni Database

migrations/m231214_000001_create_devices_table.php:

php

<?php
use yii\db\Migration;

class m231214_000001_create_devices_table extends Migration
{
    public function safeUp()
    {
        // Dispositivi
        $this->createTable('device', [
            'id' => $this->primaryKey(),
            'name' => $this->string(100)->notNull(),
            'type' => $this->string(50)->notNull()->comment('light, switch, sensor, battery'),
            'ip_address' => $this->string(15),
            'mqtt_topic' => $this->string(100),
            'status' => $this->boolean()->defaultValue(false),
            'last_value' => $this->decimal(10,2),
            'last_update' => $this->timestamp(),
            'config' => $this->json(), // Per parametri specifici
        ]);

        // Log dei valori
        $this->createTable('device_log', [
            'id' => $this->bigPrimaryKey(),
            'device_id' => $this->integer()->notNull(),
            'value' => $this->decimal(10,2),
            'unit' => $this->string(20),
            'timestamp' => $this->timestamp()->defaultExpression('CURRENT_TIMESTAMP'),
        ]);

        // Index per performance
        $this->createIndex('idx_device_log_device', 'device_log', ['device_id', 'timestamp']);
        
        // Dati di esempio
        $this->batchInsert('device', 
            ['name', 'type', 'ip_address', 'status'],
            [
                ['Batteria Casa', 'battery', '192.168.1.100', true],
                ['Pannello Solare', 'sensor', '192.168.1.101', true],
                ['Auto Elettrica', 'vehicle', null, true],
                ['Luce Salotto', 'light', '192.168.1.102', false],
            ]
        );
    }

    public function safeDown()
    {
        $this->dropTable('device_log');
        $this->dropTable('device');
    }
}

Esegui la migrazione:

bash

cd domotica-api
./yii migrate

2.4 Modelli Yii2

models/Device.php:

php

<?php
namespace app\models;

use Yii;
use yii\db\ActiveRecord;

class Device extends ActiveRecord
{
    const TYPE_BATTERY = 'battery';
    const TYPE_SOLAR = 'solar';
    const TYPE_VEHICLE = 'vehicle';
    const TYPE_LIGHT = 'light';
    
    public static function tableName()
    {
        return 'device';
    }
    
    public function rules()
    {
        return [
            [['name', 'type'], 'required'],
            ['type', 'in', 'range' => ['battery', 'solar', 'vehicle', 'light', 'switch', 'sensor']],
            ['ip_address', 'ip'],
            ['status', 'boolean'],
            ['config', 'safe'],
        ];
    }
    
    public function getLogs($limit = 100)
    {
        return $this->hasMany(DeviceLog::class, ['device_id' => 'id'])
                    ->orderBy(['timestamp' => SORT_DESC])
                    ->limit($limit);
    }
    
    public function getLastLog()
    {
        return $this->hasOne(DeviceLog::class, ['device_id' => 'id'])
                    ->orderBy(['timestamp' => SORT_DESC]);
    }
    
    // Metodo per controllare dispositivo
    public function sendCommand($command, $value = null)
    {
        switch($this->type) {
            case self::TYPE_LIGHT:
                return $this->controlLight($command);
            case self::TYPE_BATTERY:
                return $this->readBatteryStatus();
            // ... altri tipi
        }
    }
    
    private function controlLight($state)
    {
        // Esempio con ESP8266 HTTP
        $url = "http://{$this->ip_address}/relay";
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, ['state' => $state ? 1 : 0]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 3);
        
        $response = curl_exec($ch);
        curl_close($ch);
        
        return $response === 'OK';
    }
}

models/DeviceLog.php:

php

<?php
namespace app\models;

use yii\db\ActiveRecord;

class DeviceLog extends ActiveRecord
{
    public static function tableName()
    {
        return 'device_log';
    }
    
    public function getDevice()
    {
        return $this->hasOne(Device::class, ['id' => 'device_id']);
    }
}

2.5 Controller API Completo

controllers/ApiController.php:

php

<?php
namespace app\controllers;

use Yii;
use yii\web\Controller;
use yii\filters\Cors;
use yii\filters\ContentNegotiator;
use yii\web\Response;
use app\models\Device;
use app\models\DeviceLog;

class ApiController extends Controller
{
    public $enableCsrfValidation = false;
    
    public function behaviors()
    {
        return [
            'cors' => [
                'class' => Cors::class,
                'cors' => [
                    'Origin' => ['http://localhost', 'https://tudominio.com'],
                    'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
                    'Access-Control-Request-Headers' => ['*'],
                    'Access-Control-Allow-Credentials' => null,
                    'Access-Control-Max-Age' => 86400,
                    'Access-Control-Expose-Headers' => [],
                ],
            ],
            'contentNegotiator' => [
                'class' => ContentNegotiator::class,
                'formats' => [
                    'application/json' => Response::FORMAT_JSON,
                ],
            ],
        ];
    }
    
    /**
     * GET /api/dashboard-data
     * Tutti i dati per la dashboard principale
     */
    public function actionDashboardData()
    {
        $data = [
            'timestamp' => date('c'),
            'devices' => [],
            'summary' => [],
        ];
        
        // Batterie
        $batteries = Device::find()
            ->where(['type' => 'battery'])
            ->with(['lastLog'])
            ->all();
            
        foreach ($batteries as $battery) {
            $data['devices']['batteries'][] = [
                'id' => $battery->id,
                'name' => $battery->name,
                'charge' => $battery->lastLog ? $battery->lastLog->value : 0,
                'status' => $battery->status,
                'last_update' => $battery->last_update,
            ];
        }
        
        // Produzione solare
        $solar = Device::find()
            ->where(['type' => 'solar'])
            ->with(['lastLog'])
            ->one();
            
        if ($solar) {
            $data['devices']['solar'] = [
                'production' => $solar->lastLog ? $solar->lastLog->value : 0,
                'unit' => 'W',
                'today' => $this->getTodayProduction($solar->id),
            ];
        }
        
        // Auto elettrica
        $vehicle = Device::find()
            ->where(['type' => 'vehicle'])
            ->with(['lastLog'])
            ->one();
            
        if ($vehicle) {
            $data['devices']['vehicle'] = [
                'battery' => $vehicle->lastLog ? $vehicle->lastLog->value : 0,
                'range' => $this->calculateRange($vehicle->lastLog ? $vehicle->lastLog->value : 0),
                'charging' => $vehicle->status,
            ];
        }
        
        // Riepilogo energetico
        $data['summary'] = $this->calculateEnergySummary();
        
        return $data;
    }
    
    /**
     * GET /api/device/:id
     * Dettaglio dispositivo con storico
     */
    public function actionDevice($id, $hours = 24)
    {
        $device = Device::findOne($id);
        
        if (!$device) {
            Yii::$app->response->statusCode = 404;
            return ['error' => 'Dispositivo non trovato'];
        }
        
        // Storico ultime N ore
        $since = date('Y-m-d H:i:s', strtotime("-$hours hours"));
        $logs = DeviceLog::find()
            ->where(['device_id' => $id])
            ->andWhere(['>=', 'timestamp', $since])
            ->orderBy(['timestamp' => SORT_ASC])
            ->all();
        
        return [
            'device' => [
                'id' => $device->id,
                'name' => $device->name,
                'type' => $device->type,
                'status' => $device->status,
                'config' => json_decode($device->config, true) ?: [],
            ],
            'current' => $device->lastLog ? $device->lastLog->value : null,
            'history' => array_map(function($log) {
                return [
                    'value' => (float)$log->value,
                    'timestamp' => $log->timestamp,
                ];
            }, $logs),
        ];
    }
    
    /**
     * POST /api/device/:id/control
     * Controlla un dispositivo
     */
    public function actionControl($id)
    {
        $device = Device::findOne($id);
        
        if (!$device) {
            Yii::$app->response->statusCode = 404;
            return ['error' => 'Dispositivo non trovato'];
        }
        
        $command = Yii::$app->request->post('command');
        $value = Yii::$app->request->post('value');
        
        try {
            $success = $device->sendCommand($command, $value);
            
            if ($success) {
                // Salva nel log
                $log = new DeviceLog();
                $log->device_id = $device->id;
                $log->value = $value;
                $log->unit = 'state';
                $log->save();
                
                // Aggiorna ultimo stato
                $device->last_value = $value;
                $device->last_update = date('Y-m-d H:i:s');
                $device->save();
                
                return ['success' => true, 'message' => 'Comando eseguito'];
            } else {
                return ['success' => false, 'message' => 'Errore nell\'esecuzione'];
            }
        } catch (\Exception $e) {
            Yii::error($e->getMessage(), 'api');
            return ['success' => false, 'message' => 'Errore interno'];
        }
    }
    
    /**
     * GET /api/energy/today
     * Consumi e produzione di oggi
     */
    public function actionEnergyToday()
    {
        $today = date('Y-m-d');
        
        $production = DeviceLog::find()
            ->select(['HOUR(timestamp) as hour', 'SUM(value) as total'])
            ->innerJoinWith(['device'])
            ->where(['device.type' => 'solar'])
            ->andWhere(['DATE(timestamp)' => $today])
            ->groupBy(['HOUR(timestamp)'])
            ->indexBy('hour')
            ->all();
        
        $consumption = DeviceLog::find()
            ->select(['HOUR(timestamp) as hour', 'SUM(value) as total'])
            ->innerJoinWith(['device'])
            ->where(['device.type' => 'consumption'])
            ->andWhere(['DATE(timestamp)' => $today])
            ->groupBy(['HOUR(timestamp)'])
            ->indexBy('hour')
            ->all();
        
        // Formatta per grafico
        $hours = range(0, 23);
        $chartData = [
            'labels' => array_map(function($h) { return sprintf('%02d:00', $h); }, $hours),
            'production' => [],
            'consumption' => [],
        ];
        
        foreach ($hours as $hour) {
            $chartData['production'][] = isset($production[$hour]) ? (float)$production[$hour]->total : 0;
            $chartData['consumption'][] = isset($consumption[$hour]) ? (float)$consumption[$hour]->total : 0;
        }
        
        return $chartData;
    }
    
    /**
     * POST /api/webhook/device-update
     * Webhook per aggiornamenti automatici da dispositivi
     */
    public function actionWebhookDeviceUpdate()
    {
        $secret = Yii::$app->request->headers->get('X-Webhook-Secret');
        if ($secret !== Yii::$app->params['webhookSecret']) {
            Yii::$app->response->statusCode = 403;
            return ['error' => 'Accesso non autorizzato'];
        }
        
        $data = Yii::$app->request->post();
        
        $device = Device::find()
            ->where(['ip_address' => $data['ip']])
            ->orWhere(['mqtt_topic' => $data['topic']])
            ->one();
        
        if ($device) {
            $log = new DeviceLog();
            $log->device_id = $device->id;
            $log->value = $data['value'];
            $log->unit = $data['unit'] ?? 'unknown';
            
            if ($log->save()) {
                $device->last_value = $data['value'];
                $device->last_update = date('Y-m-d H:i:s');
                $device->save();
                
                return ['success' => true];
            }
        }
        
        return ['success' => false];
    }
    
    // Metodi helper privati
    private function getTodayProduction($deviceId)
    {
        $today = date('Y-m-d');
        return DeviceLog::find()
            ->where(['device_id' => $deviceId])
            ->andWhere(['DATE(timestamp)' => $today])
            ->sum('value') ?: 0;
    }
    
    private function calculateRange($batteryPercent)
    {
        // Esempio: 500km range completo
        return round(($batteryPercent / 100) * 500, 1);
    }
    
    private function calculateEnergySummary()
    {
        $today = date('Y-m-d');
        
        return [
            'production_today' => DeviceLog::find()
                ->innerJoinWith(['device'])
                ->where(['device.type' => 'solar'])
                ->andWhere(['DATE(timestamp)' => $today])
                ->sum('value') ?: 0,
                
            'consumption_today' => DeviceLog::find()
                ->innerJoinWith(['device'])
                ->where(['device.type' => 'consumption'])
                ->andWhere(['DATE(timestamp)' => $today])
                ->sum('value') ?: 0,
                
            'battery_stored' => Device::find()
                ->where(['type' => 'battery'])
                ->sum('last_value') ?: 0,
        ];
    }
}

2.6 Configurazione URL

config/web.php:

php

'components' => [
    'urlManager' => [
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'rules' => [
            // API routes
            'GET api/dashboard-data' => 'api/dashboard-data',
            'GET api/device/<id>' => 'api/device',
            'POST api/device/<id>/control' => 'api/control',
            'GET api/energy/today' => 'api/energy-today',
            'POST api/webhook/device-update' => 'api/webhook-device-update',
            
            // Fallback
            '<controller:\w+>/<action:\w+>' => '<controller>/<action>',
        ],
    ],
],

PARTE 3: Dashboard Freeboard Personalizzata

3.1 Dashboard JSON Completa

Salva come dashboard.json in Freeboard:

json

{
  "version": "1.0",
  "allow_edit": true,
  "plugins": [
    "datasources",
    "freeboard/widgets/knob.js",
    "freeboard/plugins/chartjs.js"
  ],
  "datasources": [
    {
      "name": "domotica_api",
      "type": "JSON",
      "url": "../domotica-api/api/dashboard-data",
      "refresh": 10000,
      "headers": {
        "X-API-Key": "tua-chiave-api"
      }
    },
    {
      "name": "energy_chart",
      "type": "JSON",
      "url": "../domotica-api/api/energy/today",
      "refresh": 300000
    }
  ],
  "panes": [
    {
      "id": "pane_energia",
      "title": "Energia",
      "width": 2,
      "col": {"width": 600},
      "row": {"height": 400},
      "widgets": [
        {
          "type": "gauge",
          "title": "Batteria Casa",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.batteries[0].charge",
            "min_value": 0,
            "max_value": 100,
            "units": "%",
            "gauge_width": 50,
            "donut": true,
            "level_colors": ["#ff0000", "#ffff00", "#00ff00"]
          }
        },
        {
          "type": "indicator",
          "title": "Stato Carica",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.batteries[0].status",
            "on_text": "CARICANDO",
            "off_text": "IN ATTESA",
            "on_color": "#00ff00",
            "off_color": "#cccccc"
          }
        }
      ]
    },
    {
      "id": "pane_solare",
      "title": "Pannelli Solari",
      "width": 2,
      "widgets": [
        {
          "type": "sparkline",
          "title": "Produzione Attuale",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.solar.production",
            "units": "W",
            "value_font_size": "32px",
            "refresh": 5000
          }
        },
        {
          "type": "text",
          "settings": {
            "value": "Oggi: " + 
                     "datasources[\"domotica_api\"].devices.solar.today + ' kWh'",
            "font_size": "18px"
          }
        }
      ]
    },
    {
      "id": "pane_auto",
      "title": "Auto Elettrica",
      "width": 2,
      "widgets": [
        {
          "type": "gauge",
          "title": "Batteria Auto",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.vehicle.battery",
            "min_value": 0,
            "max_value": 100,
            "units": "%"
          }
        },
        {
          "type": "text",
          "title": "Autonomia",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.vehicle.range + ' km'",
            "font_size": "24px",
            "font_weight": "bold"
          }
        },
        {
          "type": "indicator",
          "title": "Carica",
          "settings": {
            "value": "datasources[\"domotica_api\"].devices.vehicle.charging",
            "on_text": "IN CARICA",
            "off_text": "FERMA",
            "on_color": "#ff9900",
            "off_color": "#666666"
          }
        }
      ]
    },
    {
      "id": "pane_grafico",
      "title": "Consumi Oggi",
      "width": 4,
      "height": 2,
      "widgets": [
        {
          "type": "chartjs",
          "settings": {
            "chart_type": "line",
            "data": {
              "labels": "datasources[\"energy_chart\"].labels",
              "datasets": [
                {
                  "label": "Produzione",
                  "data": "datasources[\"energy_chart\"].production",
                  "borderColor": "#4CAF50",
                  "backgroundColor": "rgba(76, 175, 80, 0.1)"
                },
                {
                  "label": "Consumo",
                  "data": "datasources[\"energy_chart\"].consumption",
                  "borderColor": "#F44336",
                  "backgroundColor": "rgba(244, 67, 54, 0.1)"
                }
              ]
            },
            "options": {
              "responsive": true,
              "maintainAspectRatio": false,
              "scales": {
                "y": {
                  "beginAtZero": true,
                  "title": {
                    "display": true,
                    "text": "Watt"
                  }
                }
              }
            }
          }
        }
      ]
    },
    {
      "id": "pane_controlli",
      "title": "Controlli Rapidi",
      "width": 2,
      "widgets": [
        {
          "type": "html",
          "settings": {
            "html": "<div style='padding:10px;'>" +
                    "<button class='btn-control' data-device='1' data-command='on'>" +
                    "   Accendi Luce Salotto" +
                    "</button>" +
                    "<br><br>" +
                    "<button class='btn-control' data-device='2' data-command='off'>" +
                    "   Spegni Presa Cucina" +
                    "</button>" +
                    "</div>"
          }
        }
      ]
    }
  ]
}

3.2 JavaScript per Controlli Interattivi

Aggiungi in index.html di Freeboard (dopo freeboard.js):

html

<script>
$(document).ready(function() {
    // Inizializza Freeboard con la nostra dashboard
    freeboard.initialize(true, function() {
        // Dopo il caricamento
        setupControls();
        setupRealTimeUpdates();
    });
    
    function setupControls() {
        $(document).on('click', '.btn-control', function() {
            var deviceId = $(this).data('device');
            var command = $(this).data('command');
            
            $.ajax({
                url: '../domotica-api/api/device/' + deviceId + '/control',
                method: 'POST',
                data: {
                    command: command,
                    value: command === 'on' ? 1 : 0
                },
                headers: {
                    'X-API-Key': 'tua-chiave-api'
                },
                success: function(response) {
                    if (response.success) {
                        alert('Comando eseguito!');
                        // Aggiorna i dati
                        freeboard.loadDatasource('domotica_api');
                    } else {
                        alert('Errore: ' + response.message);
                    }
                }
            });
        });
    }
    
    function setupRealTimeUpdates() {
        // WebSocket per aggiornamenti in tempo reale (opzionale)
        if (typeof WebSocket !== 'undefined') {
            var ws = new WebSocket('ws://tuodominio.com:8080');
            
            ws.onmessage = function(event) {
                var data = JSON.parse(event.data);
                
                // Aggiorna widget specifici in base al tipo di dato
                switch(data.type) {
                    case 'battery_update':
                        updateBatteryWidget(data.value);
                        break;
                    case 'solar_update':
                        updateSolarWidget(data.value);
                        break;
                }
            };
        }
    }
    
    // Stili per i bottoni
    $('<style>').text(`
        .btn-control {
            background: linear-gradient(to bottom, #4CAF50, #45a049);
            border: none;
            color: white;
            padding: 12px 24px;
            text-align: center;
            text-decoration: none;
            display: inline-block;
            font-size: 16px;
            margin: 4px 2px;
            cursor: pointer;
            border-radius: 4px;
            width: 100%;
            transition: background 0.3s;
        }
        .btn-control:hover {
            background: linear-gradient(to bottom, #45a049, #3d8b40);
        }
        .btn-control:active {
            transform: translateY(1px);
        }
    `).appendTo('head');
});
</script>

PARTE 4: Script di Supporto

4.1 Script per Simulazione Dati (se non hai dispositivi reali)

Crea domotica-api/commands/SimulateDataController.php:

php

<?php
namespace app\commands;

use yii\console\Controller;
use app\models\Device;
use app\models\DeviceLog;

class SimulateDataController extends Controller
{
    public function actionRun()
    {
        echo "Simulazione dati domotica...\n";
        
        while (true) {
            $this->updateBattery();
            $this->updateSolar();
            $this->updateVehicle();
            
            echo "Dati aggiornati: " . date('H:i:s') . "\n";
            sleep(10); // Aggiorna ogni 10 secondi
        }
    }
    
    private function updateBattery()
    {
        $device = Device::findOne(['type' => 'battery']);
        if ($device) {
            // Simula carica che oscilla tra 70% e 100%
            $charge = 85 + (sin(time() / 100) * 15);
            $charge = max(70, min(100, $charge));
            
            $log = new DeviceLog();
            $log->device_id = $device->id;
            $log->value = round($charge, 1);
            $log->unit = '%';
            $log->save();
            
            $device->last_value = $charge;
            $device->last_update = date('Y-m-d H:i:s');
            $device->save();
        }
    }
    
    private function updateSolar()
    {
        $device = Device::findOne(['type' => 'solar']);
        if ($device) {
            // Simula produzione solare (0 di notte, picco a mezzogiorno)
            $hour = date('H');
            $production = 0;
            
            if ($hour >= 6 && $hour <= 18) {
                // Curva a campana per la produzione
                $normalized = ($hour - 6) / 12;
                $production = 3000 * sin($normalized * pi());
            }
            
            // Aggiungi un po' di rumore
            $production += rand(-100, 100);
            $production = max(0, $production);
            
            $log = new DeviceLog();
            $log->device_id = $device->id;
            $log->value = round($production);
            $log->unit = 'W';
            $log->save();
            
            $device->last_value = $production;
            $device->last_update = date('Y-m-d H:i:s');
            $device->save();
        }
    }
}

Esegui (via SSH o cron):

bash

cd domotica-api
./yii simulate-data/run > /dev/null 2>&1 &

4.2 Cron Job per Pulizia e Report

Crea domotica-api/commands/CronController.php:

php

<?php
namespace app\commands;

use yii\console\Controller;
use yii\db\Expression;

class CronController extends Controller
{
    // Esegui ogni giorno a mezzanotte
    public function actionDaily()
    {
        // Pulisci log vecchi di 30 giorni
        $thirtyDaysAgo = date('Y-m-d H:i:s', strtotime('-30 days'));
        \Yii::$app->db->createCommand()
            ->delete('device_log', ['<', 'timestamp', $thirtyDaysAgo])
            ->execute();
            
        echo "Log vecchi puliti\n";
        
        // Crea report giornaliero
        $this->createDailyReport();
    }
    
    private function createDailyReport()
    {
        $yesterday = date('Y-m-d', strtotime('-1 day'));
        
        $report = [
            'date' => $yesterday,
            'total_production' => 0,
            'total_consumption' => 0,
            'devices' => [],
        ];
        
        // Salva su file o database
        $filename = \Yii::getAlias('@app/runtime/reports/') . $yesterday . '.json';
        file_put_contents($filename, json_encode($report, JSON_PRETTY_PRINT));
        
        echo "Report creato: $filename\n";
    }
}

Configura cron su ServerPlan:

bash

# Aggiungi al crontab (crontab -e)
0 0 * * * cd /home/tuoutente/public_html/domotica-api && /usr/bin/php yii cron/daily
*/5 * * * * cd /home/tuoutente/public_html/domotica-api && /usr/bin/php yii simulate-data/run --oneshot

PARTE 5: Sicurezza Avanzata

5.1 Middleware di Autenticazione

Crea components/ApiAuth.php:

php

<?php
namespace app\components;

use Yii;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;

class ApiAuth extends HttpBearerAuth
{
    public function authenticate($user, $request, $response)
    {
        // API Key in header o query string
        $apiKey = $request->headers->get('X-API-Key') 
                ?: $request->get('api_key');
        
        if ($apiKey && $this->validateApiKey($apiKey)) {
            // Crea utente fittizio per Yii2
            $identity = new \yii\web\User();
            $identity->id = 'api_user';
            $user->switchIdentity($identity);
            
            return $identity;
        }
        
        return null;
    }
    
    private function validateApiKey($key)
    {
        // Verifica contro lista di chiavi valide
        $validKeys = Yii::$app->params['apiKeys'] ?? [];
        return in_array($key, $validKeys);
    }
}

5.2 Rate Limiting

Aggiungi in ApiController:

php

use yii\filters\RateLimiter;

public function behaviors()
{
    $behaviors = parent::behaviors();
    
    $behaviors['rateLimiter'] = [
        'class' => RateLimiter::class,
        'enableRateLimitHeaders' => true,
        'errorMessage' => 'Troppe richieste',
    ];
    
    return $behaviors;
}

PARTE 6: Deploy e Test

6.1 Controllo Finale

Struttura completa:

text

public_html/
├── domotica-dash/                 # Freeboard UI
│   ├── index.html
│   ├── dashboard.json
│   ├── js/
│   └── css/
│
├── domotica-api/                  # Yii2 Backend
│   ├── web/
│   │   └── index.php
│   ├── controllers/
│   │   └── ApiController.php
│   ├── models/
│   ├── migrations/
│   └── config/
│
└── domotica-data/                 # Dati persistenti (opzionale)
    ├── logs/
    └── reports/

6.2 Test Endpoint API

bash

# Test API con curl
curl -H "X-API-Key: tua-chiave" \
     https://tudominio.com/domotica-api/api/dashboard-data

# Risposta attesa:
{
  "timestamp": "2023-12-14T10:30:00+01:00",
  "devices": {
    "batteries": [
      {
        "id": 1,
        "name": "Batteria Casa",
        "charge": 85.5,
        "status": true
      }
    ],
    "solar": {
      "production": 2450,
      "unit": "W",
      "today": 12.3
    }
  }
}

6.3 Accesso alla Dashboard

URL della dashboard:

text

https://tudominio.com/domotica-dash/

URL API:

text

https://tudominio.com/dom

ottimo grazie. Osservo che è molto articolato e senza il tuoi aiuto qualsiasi modifica sarebbe impossibile…

Hai ragione, è diventato troppo complesso! Semplifichiamo radicalmente. Ti propongo un approccio molto più semplice che puoi gestire da solo.

VERSIONE SUPER SEMPLICE – 3 File Totali!

1. Struttura Minimalista

text

public_html/
├── dashboard.php      # UI + Logica tutto insieme
├── config.php         # Configurazione
└── dati.json          # Dati dei dispositivi (file JSON)

2. File 1: config.php

php

<?php
// CONFIGURAZIONE SEMPLICE
define('PASSWORD', 'domotica123');  // Cambia questa!
define('DATA_FILE', __DIR__ . '/dati.json');

// Dispositivi preconfigurati (puoi modificare)
$dispositivi = [
    'batteria_casa' => [
        'nome' => 'Batteria Casa',
        'tipo' => 'batteria',
        'valore' => 85,
        'unita' => '%',
        'colore' => 'success'
    ],
    'pannelli_solari' => [
        'nome' => 'Pannelli Solari',
        'tipo' => 'produzione',
        'valore' => 2500,
        'unita' => 'W',
        'colore' => 'warning'
    ],
    'auto_elettrica' => [
        'nome' => 'Auto Elettrica',
        'tipo' => 'veicolo',
        'valore' => 72,
        'unita' => '%',
        'colore' => 'info'
    ],
    'luce_salotto' => [
        'nome' => 'Luce Salotto',
        'tipo' => 'interruttore',
        'valore' => 0,  // 0=spento, 1=acceso
        'unita' => '',
        'colore' => 'secondary'
    ]
];
?>

3. File 2: dashboard.php (TUTTO IN UNO!)

php

<?php
include 'config.php';

// Controlla password semplice
session_start();
if (!isset($_SESSION['loggato'])) {
    if ($_POST['password'] === PASSWORD) {
        $_SESSION['loggato'] = true;
    } elseif (isset($_POST['password'])) {
        $errore = "Password errata!";
    }
}

if (!isset($_SESSION['loggato'])) {
    // MOSTRA PAGINA DI LOGIN
    ?>
    <!DOCTYPE html>
    <html lang="it">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Accesso Domotica</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <style>
            body { background: #f5f5f5; padding-top: 100px; }
            .login-box { max-width: 400px; margin: auto; background: white; padding: 30px; border-radius: 10px; }
        </style>
    </head>
    <body>
        <div class="login-box">
            <h3 class="text-center mb-4">🔐 Sistema Domotica</h3>
            <?php if(isset($errore)) echo "<div class='alert alert-danger'>$errore</div>"; ?>
            <form method="post">
                <div class="mb-3">
                    <input type="password" name="password" class="form-control" placeholder="Inserisci password" required>
                </div>
                <button type="submit" class="btn btn-primary w-100">Accedi</button>
            </form>
        </div>
    </body>
    </html>
    <?php exit;
}

// LEGGI DATI ESISTENTI
if (file_exists(DATA_FILE)) {
    $dati = json_decode(file_get_contents(DATA_FILE), true);
} else {
    $dati = $dispositivi; // Usa quelli predefiniti
}

// GESTISCI COMANDI
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_POST['aggiorna'])) {
        // Aggiorna valori casuali (simulazione)
        foreach ($dati as $key => &$disp) {
            if ($disp['tipo'] === 'batteria') {
                $disp['valore'] = max(0, min(100, $disp['valore'] + rand(-2, 2)));
            } elseif ($disp['tipo'] === 'produzione') {
                // Produzione solare: alta di giorno, bassa di notte
                $ora = date('H');
                if ($ora >= 6 && $ora <= 18) {
                    $disp['valore'] = rand(2000, 3500);
                } else {
                    $disp['valore'] = rand(0, 100);
                }
            }
        }
        
        // Salva su file
        file_put_contents(DATA_FILE, json_encode($dati, JSON_PRETTY_PRINT));
    }
    
    if (isset($_POST['interruttore'])) {
        $device = $_POST['device'];
        $dati[$device]['valore'] = $dati[$device]['valore'] ? 0 : 1;
        file_put_contents(DATA_FILE, json_encode($dati, JSON_PRETTY_PRINT));
    }
}

// CALCOLA STATISTICHE
$totale_batteria = 0;
$conta_batteria = 0;
$produzione_attuale = 0;

foreach ($dati as $disp) {
    if ($disp['tipo'] === 'batteria') {
        $totale_batteria += $disp['valore'];
        $conta_batteria++;
    }
    if ($disp['tipo'] === 'produzione') {
        $produzione_attuale = $disp['valore'];
    }
}
$media_batteria = $conta_batteria > 0 ? round($totale_batteria / $conta_batteria, 1) : 0;
?>

<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard Domotica Semplice</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
    <style>
        body { background: #f8f9fa; }
        .card { 
            border-radius: 15px; 
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            margin-bottom: 20px;
            transition: transform 0.3s;
        }
        .card:hover { transform: translateY(-5px); }
        .progress { height: 25px; border-radius: 10px; }
        .btn-action { width: 100%; padding: 10px; }
        .stat-box { 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 20px;
        }
        .device-icon { font-size: 2rem; margin-bottom: 10px; }
    </style>
</head>
<body>
    <!-- NAVBAR -->
    <nav class="navbar navbar-dark bg-dark">
        <div class="container-fluid">
            <span class="navbar-brand">
                <i class="bi bi-house"></i> Domotica Casa
            </span>
            <form method="post" class="d-flex">
                <button name="aggiorna" class="btn btn-outline-light me-2">
                    <i class="bi bi-arrow-clockwise"></i> Aggiorna
                </button>
                <a href="?logout" class="btn btn-danger">Esci</a>
            </form>
        </div>
    </nav>

    <div class="container mt-4">
        <!-- STATISTICHE -->
        <div class="row">
            <div class="col-md-4">
                <div class="stat-box text-center">
                    <i class="bi bi-battery-charging device-icon"></i>
                    <h3><?= $media_batteria ?>%</h3>
                    <p>Batteria Media</p>
                </div>
            </div>
            <div class="col-md-4">
                <div class="stat-box text-center" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
                    <i class="bi bi-sun device-icon"></i>
                    <h3><?= $produzione_attuale ?>W</h3>
                    <p>Produzione Solare</p>
                </div>
            </div>
            <div class="col-md-4">
                <div class="stat-box text-center" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
                    <i class="bi bi-car-front device-icon"></i>
                    <h3><?= $dati['auto_elettrica']['valore'] ?>%</h3>
                    <p>Auto Elettrica</p>
                </div>
            </div>
        </div>

        <!-- DISPOSITIVI -->
        <div class="row">
            <?php foreach ($dati as $key => $dispositivo): ?>
            <div class="col-md-3 col-sm-6">
                <div class="card">
                    <div class="card-body text-center">
                        <!-- Icona -->
                        <?php
                        $icone = [
                            'batteria' => 'bi-battery-charging',
                            'produzione' => 'bi-sun',
                            'veicolo' => 'bi-car-front',
                            'interruttore' => 'bi-lightbulb'
                        ];
                        $icona = $icone[$dispositivo['tipo']] ?? 'bi-device-hdd';
                        ?>
                        <i class="bi <?= $icona ?> text-<?= $dispositivo['colore'] ?>" style="font-size: 2.5rem;"></i>
                        
                        <h5 class="card-title mt-2"><?= $dispositivo['nome'] ?></h5>
                        
                        <!-- Visualizzazione Valore -->
                        <?php if ($dispositivo['tipo'] === 'batteria' || $dispositivo['tipo'] === 'veicolo'): ?>
                            <div class="progress mt-3">
                                <div class="progress-bar bg-<?= $dispositivo['colore'] ?>" 
                                     role="progressbar" 
                                     style="width: <?= $dispositivo['valore'] ?>%">
                                    <?= $dispositivo['valore'] ?>%
                                </div>
                            </div>
                        <?php elseif ($dispositivo['tipo'] === 'produzione'): ?>
                            <h2 class="text-<?= $dispositivo['colore'] ?>">
                                <?= number_format($dispositivo['valore']) ?>
                                <small class="fs-6"><?= $dispositivo['unita'] ?></small>
                            </h2>
                        <?php elseif ($dispositivo['tipo'] === 'interruttore'): ?>
                            <form method="post" class="mt-3">
                                <input type="hidden" name="device" value="<?= $key ?>">
                                <button name="interruttore" 
                                        class="btn btn-<?= $dispositivo['valore'] ? 'success' : 'secondary' ?> btn-action">
                                    <i class="bi bi-power"></i>
                                    <?= $dispositivo['valore'] ? 'ACCESO' : 'SPENTO' ?>
                                </button>
                            </form>
                        <?php endif; ?>
                        
                        <!-- Timestamp -->
                        <small class="text-muted d-block mt-2">
                            <i class="bi bi-clock"></i> Ultimo aggiornamento: <?= date('H:i') ?>
                        </small>
                    </div>
                </div>
            </div>
            <?php endforeach; ?>
        </div>

        <!-- PANNELLO CONTROLLI -->
        <div class="row mt-4">
            <div class="col-12">
                <div class="card">
                    <div class="card-header">
                        <h5><i class="bi bi-sliders"></i> Controlli Rapidi</h5>
                    </div>
                    <div class="card-body">
                        <div class="row">
                            <div class="col-md-3">
                                <button class="btn btn-outline-primary w-100 mb-2">
                                    <i class="bi bi-sunrise"></i> Modalità Giorno
                                </button>
                            </div>
                            <div class="col-md-3">
                                <button class="btn btn-outline-secondary w-100 mb-2">
                                    <i class="bi bi-moon"></i> Modalità Notte
                                </button>
                            </div>
                            <div class="col-md-3">
                                <button class="btn btn-outline-success w-100 mb-2">
                                    <i class="bi bi-lightning-charge"></i> Risparmio Energetico
                                </button>
                            </div>
                            <div class="col-md-3">
                                <button class="btn btn-outline-danger w-100 mb-2">
                                    <i class="bi bi-shield-exclamation"></i> Tutto Spento
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- LOG ATTIVITÀ -->
        <div class="row mt-4">
            <div class="col-12">
                <div class="card">
                    <div class="card-header">
                        <h5><i class="bi bi-list-check"></i> Ultime Attività</h5>
                    </div>
                    <div class="card-body">
                        <ul class="list-group">
                            <li class="list-group-item">
                                <i class="bi bi-check-circle text-success"></i>
                                Sistema aggiornato alle <?= date('H:i:s') ?>
                            </li>
                            <li class="list-group-item">
                                <i class="bi bi-battery text-info"></i>
                                Batteria casa: <?= $dati['batteria_casa']['valore'] ?>%
                            </li>
                            <li class="list-group-item">
                                <i class="bi bi-sun text-warning"></i>
                                Produzione solare: <?= $dati['pannelli_solari']['valore'] ?>W
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- JavaScript per aggiornamento automatico -->
    <script>
    // Aggiorna la pagina ogni 30 secondi
    setTimeout(function() {
        document.querySelector('[name="aggiorna"]').click();
    }, 30000);

    // Animazioni semplici
    document.querySelectorAll('.card').forEach(card => {
        card.addEventListener('click', function() {
            this.style.transform = 'scale(0.98)';
            setTimeout(() => {
                this.style.transform = '';
            }, 200);
        });
    });
    </script>
</body>
</html>
<?php
// Logout
if (isset($_GET['logout'])) {
    session_destroy();
    header('Location: dashboard.php');
    exit;
}
?>

4. File 3: .htaccess (per sicurezza)

apache

# Blocca accesso diretto al file dati
<Files "dati.json">
    Order Allow,Deny
    Deny from all
</Files>

# Forza HTTPS se disponibile
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

COME USARE:

  1. Carica i 3 file sul tuo ServerPlan
  2. Modifica config.php:
    • Cambia la password domotica123
    • Modifica i dispositivi come vuoi
  3. Visita https://tuodominio.com/dashboard.php
  4. Login con la password che hai scelto

FUNZIONALITÀ PRONTE:

✅ Dashboard responsive (funziona su telefono)
✅ Password di accesso
✅ Interruttori ON/OFF
✅ Grafici a barre per batterie
✅ Aggiornamento automatico ogni 30 secondi
✅ Statistiche in tempo reale
✅ Nessun database – tutto su file JSON
✅ Design moderno con Bootstrap 5

COME MODIFICARE:

Aggiungere un nuovo dispositivo:

php

// In config.php, aggiungi nell'array $dispositivi:
'termosifone' => [
    'nome' => 'Termosifone Sala',
    'tipo' => 'interruttore',  // oppure 'termostato'
    'valore' => 0,
    'unita' => '',
    'colore' => 'primary'
]

Cambiare colori:

Usa i colori Bootstrap: primarysuccesswarningdangerinfosecondary

Cambiare l’icona:

Vedi https://icons.getbootstrap.com/ – usa bi-nome-icona

PROSSIMI PASSI (quando ti senti pronto):

  1. Aggiungi dispositivi reali – modificando le funzioni di controllo
  2. Aggiungi grafici – con Chart.js (1 file in più)
  3. Notifiche – con Telegram o email
  4. Cron job – per salvare dati giornalieri

VANTAGGI DI QUESTA VERSIONE:

🚀 Installazione: 5 minuti
🔧 Manutenzione: Modifichi 1 file e capisci tutto
📱 Mobile: Già responsive
🔒 Sicuro: Con password
💾 Nessun database: Più semplice da gestire

Questo lo puoi gestire completamente da solo! Parti da qui e quando ti senti sicuro, espandi gradualmente.

Vuoi che ti mostri come aggiungere il primo dispositivo reale (es. una presa WiFi)?

si grazie

Perfetto! Aggiungiamo dispositivi reali passo dopo passo. Cominciamo con i dispositivi più comuni e semplici.

1. PRESA/SWITCH WiFi (TP-Link Kasa, Sonoff, Shelly)

Configurazione per Shelly (il più semplice):

Prima modifica config.php:

php

// Aggiungi dopo l'array $dispositivi
$dispositivi_reali = [
    'shelly_salotto' => [
        'nome' => 'Luce Salotto',
        'tipo' => 'interruttore',
        'ip' => '192.168.1.100',  // IP del tuo Shelly
        'model' => 'shelly1',
        'valore' => 0
    ],
    'tplink_cucina' => [
        'nome' => 'Presa Cucina',
        'tipo' => 'interruttore', 
        'ip' => '192.168.1.101',
        'model' => 'tplink',
        'username' => 'admin',      // Se richiesto
        'password' => 'password'    // Se richiesto
    ]
];

File aggiuntivo: dispositivi_reali.php

php

<?php
// FUNZIONI PER CONTROLLARE DISPOSITIVI REALI

class DispositiviReali {
    
    // CONTROLLA SHELLY (HTTP semplice)
    public static function controllaShelly($ip, $relay = 0, $state = null) {
        if ($state !== null) {
            // Imposta stato
            $url = "http://$ip/relay/$relay?turn=" . ($state ? "on" : "off");
            $response = file_get_contents($url);
            return strpos($response, '"ison":true') !== false;
        } else {
            // Leggi stato
            $url = "http://$ip/status";
            $response = file_get_contents($url);
            $data = json_decode($response, true);
            return $data['relays'][$relay]['ison'] ?? false;
        }
    }
    
    // CONTROLLA TP-LINK KASA
    public static function controllaTPLink($ip, $state = null) {
        // TP-Link usa protocollo proprietario, ma c'è una libreria PHP
        require_once 'tplink.class.php'; // Scarica da GitHub
        $plug = new TPLinkSmartPlug($ip);
        
        if ($state !== null) {
            return $state ? $plug->powerOn() : $plug->powerOff();
        } else {
            return $plug->getRelayState();
        }
    }
    
    // CONTROLLA SONOFF BASIC (Tasmota)
    public static function controllaSonoff($ip, $state = null) {
        if ($state !== null) {
            $url = "http://$ip/cm?cmnd=Power%20" . ($state ? "On" : "Off");
            $response = file_get_contents($url);
            $data = json_decode($response, true);
            return $data['POWER'] === "ON";
        } else {
            $url = "http://$ip/cm?cmnd=status";
            $response = file_get_contents($url);
            $data = json_decode($response, true);
            return $data['Status']['Power'] === 1;
        }
    }
    
    // LEGGI SENSORE DI TEMPERATURA SHELLY
    public static function leggiTemperaturaShelly($ip) {
        $url = "http://$ip/status";
        $response = file_get_contents($url);
        $data = json_decode($response, true);
        
        if (isset($data['tmp']['tC'])) {
            return $data['tmp']['tC'];  // Temperatura in Celsius
        } elseif (isset($data['temperature'])) {
            return $data['temperature'];
        }
        
        return null;
    }
    
    // LEGGI CONSUMO ENERGETICO (Shelly EM/3EM)
    public static function leggiConsumoShelly($ip) {
        $url = "http://$ip/status";
        $response = @file_get_contents($url);
        if ($response === false) return null;
        
        $data = json_decode($response, true);
        
        // Shelly EM misura consumo
        if (isset($data['emeters'][0]['power'])) {
            return $data['emeters'][0]['power'];  // Watt
        }
        
        return null;
    }
}
?>

Integrazione nella dashboard:

Modifica dashboard.php – aggiungi all’inizio:

php

// ... dopo include 'config.php'
include 'dispositivi_reali.php';

// LEGGI STATO DISPOSITIVI REALI
if (file_exists(DATA_FILE)) {
    $dati = json_decode(file_get_contents(DATA_FILE), true);
    
    // Aggiorna valori da dispositivi reali
    foreach ($dispositivi_reali as $key => $device) {
        if (isset($dati[$key])) {
            try {
                switch($device['model']) {
                    case 'shelly1':
                        $stato = DispositiviReali::controllaShelly($device['ip']);
                        $dati[$key]['valore'] = $stato ? 1 : 0;
                        // Se ha sensore temperatura
                        $temp = DispositiviReali::leggiTemperaturaShelly($device['ip']);
                        if ($temp !== null) {
                            $dati[$key]['temperatura'] = $temp;
                        }
                        break;
                        
                    case 'tplink':
                        $stato = DispositiviReali::controllaTPLink($device['ip']);
                        $dati[$key]['valore'] = $stato ? 1 : 0;
                        break;
                }
            } catch (Exception $e) {
                // Dispositivo non raggiungibile
                $dati[$key]['offline'] = true;
            }
        }
    }
} else {
    $dati = array_merge($dispositivi, $dispositivi_reali);
}

Modifica la sezione interruttori nella dashboard:

php

<?php elseif ($dispositivo['tipo'] === 'interruttore'): ?>
    <?php 
    $offline = isset($dispositivo['offline']) && $dispositivo['offline'];
    $stato_reale = $dispositivo['valore'];
    ?>
    
    <div class="mb-3">
        <?php if($offline): ?>
            <div class="alert alert-warning">
                <i class="bi bi-wifi-off"></i> Dispositivo offline
            </div>
        <?php endif; ?>
        
        <form method="post" class="d-flex gap-2">
            <input type="hidden" name="device" value="<?= $key ?>">
            
            <button name="accendi" 
                    class="btn btn-<?= $stato_reale ? 'success' : 'outline-success' ?> flex-fill"
                    <?= $offline ? 'disabled' : '' ?>>
                <i class="bi bi-power"></i> ON
            </button>
            
            <button name="spegni" 
                    class="btn btn-<?= !$stato_reale ? 'danger' : 'outline-danger' ?> flex-fill"
                    <?= $offline ? 'disabled' : '' ?>>
                <i class="bi bi-power"></i> OFF
            </button>
        </form>
        
        <?php if(isset($dispositivo['temperatura'])): ?>
            <div class="mt-2 text-center">
                <small>
                    <i class="bi bi-thermometer"></i>
                    <?= $dispositivo['temperatura'] ?>°C
                </small>
            </div>
        <?php endif; ?>
    </div>
<?php endif; ?>

Gestisci i comandi in cima al file:

php

// GESTISCI COMANDI PER DISPOSITIVI REALI
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // ... codice esistente ...
    
    if (isset($_POST['accendi']) || isset($_POST['spegni'])) {
        $device_key = $_POST['device'];
        $state = isset($_POST['accendi']) ? 1 : 0;
        
        if (isset($dispositivi_reali[$device_key])) {
            $device = $dispositivi_reali[$device_key];
            
            try {
                switch($device['model']) {
                    case 'shelly1':
                        $success = DispositiviReali::controllaShelly($device['ip'], 0, $state);
                        break;
                    case 'tplink':
                        $success = DispositiviReali::controllaTPLink($device['ip'], $state);
                        break;
                    case 'sonoff':
                        $success = DispositiviReali::controllaSonoff($device['ip'], $state);
                        break;
                    default:
                        $success = false;
                }
                
                if ($success) {
                    $dati[$device_key]['valore'] = $state;
                    $dati[$device_key]['ultimo_comando'] = date('H:i:s');
                    $_SESSION['messaggio'] = "Comando eseguito!";
                }
            } catch (Exception $e) {
                $_SESSION['errore'] = "Errore: " . $e->getMessage();
            }
            
            file_put_contents(DATA_FILE, json_encode($dati, JSON_PRETTY_PRINT));
        }
    }
}

// Mostra messaggi
if (isset($_SESSION['messaggio'])) {
    echo '<div class="alert alert-success alert-dismissible fade show">
            ' . $_SESSION['messaggio'] . '
            <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
          </div>';
    unset($_SESSION['messaggio']);
}

2. SENSORI TEMPERATURA/UMIDITÀ (DHT22, BME280)

Configurazione per ESP8266/ESP32 con web server:

Il microcontrollore ESP deve esporre un endpoint HTTP:

c++

// Codice Arduino/ESP8266 semplificato
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

void setup() {
    // Connessione WiFi
    WiFi.begin("SSID", "password");
    
    // Endpoint API
    server.on("/api/temperature", []() {
        float temp = readTemperature(); // Tua funzione
        float humidity = readHumidity();
        
        String json = "{";
        json += "\"temperature\":" + String(temp, 1);
        json += ",\"humidity\":" + String(humidity, 1);
        json += ",\"device\":\"sensor_camera\"";
        json += "}";
        
        server.send(200, "application/json", json);
    });
    
    server.begin();
}

Aggiungi al tuo PHP:

In dispositivi_reali.php:

php

// LEGGI SENSORE ESP8266/ESP32
public static function leggiSensoreESP($ip) {
    $url = "http://$ip/api/temperature";
    $context = stream_context_create([
        'http' => ['timeout' => 3] // 3 secondi timeout
    ]);
    
    $response = @file_get_contents($url, false, $context);
    if ($response === false) return null;
    
    return json_decode($response, true);
}

// LEGGI MULTIPLI SENSORI (array di IP)
public static function leggiTuttiSensori($sensori) {
    $results = [];
    
    foreach ($sensori as $nome => $ip) {
        $data = self::leggiSensoreESP($ip);
        if ($data) {
            $results[$nome] = $data;
        }
    }
    
    return $results;
}

Configura i sensori in config.php:

php

$sensori = [
    'camera' => [
        'nome' => 'Temperatura Camera',
        'ip' => '192.168.1.110',
        'tipo' => 'sensore',
        'unita_temp' => '°C',
        'unita_umid' => '%'
    ],
    'esterno' => [
        'nome' => 'Temperatura Esterna',
        'ip' => '192.168.1.111',
        'tipo' => 'sensore'
    ]
];

Visualizzazione nella dashboard:

php

<!-- Aggiungi dopo la sezione dispositivi -->
<div class="row mt-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5><i class="bi bi-thermometer-half"></i> Sensori Ambientali</h5>
            </div>
            <div class="card-body">
                <div class="row">
                    <?php
                    $dati_sensori = DispositiviReali::leggiTuttiSensori($sensori);
                    foreach ($dati_sensori as $nome => $sensore):
                    ?>
                    <div class="col-md-4">
                        <div class="card">
                            <div class="card-body text-center">
                                <h6><?= $sensori[$nome]['nome'] ?></h6>
                                <div class="display-4">
                                    <?= $sensore['temperature'] ?? '--' ?>
                                    <small class="fs-6">°C</small>
                                </div>
                                <div class="text-muted">
                                    <i class="bi bi-droplet"></i>
                                    Umidità: <?= $sensore['humidity'] ?? '--' ?>%
                                </div>
                                <small class="text-muted">
                                    <i class="bi bi-clock"></i> <?= date('H:i') ?>
                                </small>
                            </div>
                        </div>
                    </div>
                    <?php endforeach; ?>
                </div>
            </div>
        </div>
    </div>
</div>

3. CONTROLLO AUTOMOBILI (API Tesla, altre auto)

Per Tesla (API ufficiale):

Crea tesla_api.php:

php

<?php
class TeslaAPI {
    private $email;
    private $password;
    private $access_token;
    
    public function __construct($email, $password) {
        $this->email = $email;
        $this->password = $password;
        $this->login();
    }
    
    private function login() {
        $data = [
            'grant_type' => 'password',
            'client_id' => 'ownerapi',
            'client_secret' => 'c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3',
            'email' => $this->email,
            'password' => $this->password
        ];
        
        $response = $this->request('https://auth.tesla.com/oauth2/v3/token', $data);
        $this->access_token = $response['access_token'];
    }
    
    public function getVehicleData() {
        // Prima ottieni l'ID veicolo
        $vehicles = $this->request('https://owner-api.teslamotors.com/api/1/vehicles');
        
        if (empty($vehicles['response'])) {
            return null;
        }
        
        $vehicle_id = $vehicles['response'][0]['id_s'];
        $data = $this->request("https://owner-api.teslamotors.com/api/1/vehicles/$vehicle_id/vehicle_data");
        
        return [
            'battery' => $data['response']['charge_state']['battery_level'] ?? 0,
            'range' => $data['response']['charge_state']['battery_range'] ?? 0,
            'charging' => $data['response']['charge_state']['charging_state'] === 'Charging',
            'temperature' => $data['response']['climate_state']['inside_temp'] ?? 0
        ];
    }
    
    public function startCharging() {
        // Implementa avvio carica
    }
    
    public function setChargeLimit($percent) {
        // Imposta limite carica
    }
    
    private function request($url, $data = null) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        
        if ($data) {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        
        if ($this->access_token) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Authorization: Bearer ' . $this->access_token,
                'Content-Type: application/json'
            ]);
        }
        
        $response = curl_exec($ch);
        curl_close($ch);
        
        return json_decode($response, true);
    }
}
?>

Integrazione nella dashboard:

php

// In config.php
$auto_elettrica = [
    'tesla' => [
        'nome' => 'Tesla Model 3',
        'tipo' => 'veicolo',
        'marca' => 'tesla',
        'email' => 'tua@email.com',
        'password' => 'tua_password'
    ]
];

// In dashboard.php
try {
    require_once 'tesla_api.php';
    $tesla = new TeslaAPI(
        $auto_elettrica['tesla']['email'],
        $auto_elettrica['tesla']['password']
    );
    
    $vehicle_data = $tesla->getVehicleData();
    if ($vehicle_data) {
        $dati['auto_elettrica']['valore'] = $vehicle_data['battery'];
        $dati['auto_elettrica']['range'] = $vehicle_data['range'];
        $dati['auto_elettrica']['caricando'] = $vehicle_data['charging'];
    }
} catch (Exception $e) {
    // Gestisci errore
}

4. BATTERIE DOMESTICHE (Powerwall, altre)

Per Powerwall Tesla:

php

class PowerwallAPI {
    private $ip;
    
    public function __construct($ip) {
        $this->ip = $ip;
    }
    
    public function getStatus() {
        // Powerwall API locale
        $url = "https://{$this->ip}/api/system_status/soe";
        
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Per self-signed cert
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        
        // Se richiede autenticazione
        curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
        curl_setopt($ch, CURLOPT_USERPWD, "customer:password");
        
        $response = curl_exec($ch);
        curl_close($ch);
        
        $data = json_decode($response, true);
        
        return [
            'charge' => $data['percentage'] ?? 0,
            'power' => $data['instant_power'] ?? 0, // Watt
            'grid_power' => $this->getGridPower()
        ];
    }
    
    private function getGridPower() {
        $url = "https://{$this->ip}/api/meters/aggregates";
        // ... richiesta simile
    }
}

5. AUTOMAZIONI SEMPLICI

Crea automazioni.php:

php

<?php
class Automazioni {
    
    // ACCENDI LUCI AL TRAMONTO
    public static function controlloIlluminazione() {
        $ora_attuale = date('H');
        $alba = 7;  // 7:00
        $tramonto = 19; // 19:00
        
        // Se è notte e le luci sono spente
        if ($ora_attuale >= $tramonto || $ora_attuale < $alba) {
            $luci = ['shelly_salotto', 'shelly_cucina'];
            
            foreach ($luci as $luce) {
                // Controlla se qualcuno è in casa (presenza)
                if (self::rilevaPresenza()) {
                    DispositiviReali::controllaShelly($luce, 0, true);
                }
            }
        }
    }
    
    // CONTROLLO TEMPERATURA AUTOMATICO
    public static function controlloTermostato($temperatura_attuale, $soglia = 21) {
        if ($temperatura_attuale < $soglia - 2) {
            // Accendi riscaldamento
            DispositiviReali::controllaShelly('termostato', 0, true);
        } elseif ($temperatura_attuale > $soglia + 2) {
            // Spegni riscaldamento
            DispositiviReali::controllaShelly('termostato', 0, false);
        }
    }
    
    // RILEVA PRESENZA (simulata - poi implementa con sensori)
    private static function rilevaPresenza() {
        // Per ora: sempre vero dalle 18 alle 22
        $ora = date('H');
        return ($ora >= 18 && $ora <= 22);
    }
    
    // MODALITÀ "VIA"
    public static function modalitaVia() {
        $dispositivi_da_spegnere = [
            'shelly_salotto',
            'shelly_cucina',
            'tplink_camera'
        ];
        
        foreach ($dispositivi_da_spegnere as $device) {
            DispositiviReali::controllaShelly($device, 0, false);
        }
        
        // Mantieni accesa solo la sicurezza
        DispositiviReali::controllaShelly('allarme', 0, true);
    }
}
?>

Integrazione nella dashboard:

php

<!-- Pulsanti automazioni -->
<div class="row mt-4">
    <div class="col-12">
        <div class="card">
            <div class="card-header">
                <h5><i class="bi bi-robot"></i> Automazioni</h5>
            </div>
            <div class="card-body">
                <div class="row">
                    <div class="col-md-3">
                        <form method="post">
                            <button name="automazione" value="modalita_casa" 
                                    class="btn btn-outline-primary w-100">
                                <i class="bi bi-house-door"></i> Modalità Casa
                            </button>
                        </form>
                    </div>
                    <div class="col-md-3">
                        <form method="post">
                            <button name="automazione" value="modalita_via" 
                                    class="btn btn-outline-secondary w-100">
                                <i class="bi bi-car-front"></i> Modalità Via
                            </button>
                        </form>
                    </div>
                    <div class="col-md-3">
                        <form method="post">
                            <button name="automazione" value="tutto_spento" 
                                    class="btn btn-outline-danger w-100">
                                <i class="bi bi-power"></i> Tutto Spento
                            </button>
                        </form>
                    </div>
                    <div class="col-md-3">
                        <form method="post">
                            <button name="automazione" value="risparmio_energetico" 
                                    class="btn btn-outline-success w-100">
                                <i class="bi bi-lightning-charge"></i> Risparmio
                            </button>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Gestisci comandi -->
<?php
if (isset($_POST['automazione'])) {
    require_once 'automazioni.php';
    
    switch($_POST['automazione']) {
        case 'modalita_via':
            Automazioni::modalitaVia();
            $_SESSION['messaggio'] = "Modalità 'VIA' attivata";
            break;
            
        case 'tutto_spento':
            // Spegni tutto
            foreach ($dispositivi_reali as $key => $device) {
                if ($device['tipo'] === 'interruttore') {
                    DispositiviReali::controllaShelly($device['ip'], 0, false);
                }
            }
            $_SESSION['messaggio'] = "Tutto spento";
            break;
    }
}
?>

6. SCHEDA RIASSUNTIVA PER DISPOSITIVI REALI

Crea una tabella di stato nella dashboard:

php

<!-- Tabella stato dispositivi -->
<div class="card mt-4">
    <div class="card-header">
        <h5><i class="bi bi-list-check"></i> Stato Dispositivi</h5>
    </div>
    <div class="card-body">
        <div class="table-responsive">
            <table class="table table-hover">
                <thead>
                    <tr>
                        <th>Dispositivo</th>
                        <th>Tipo</th>
                        <th>Stato</th>
                        <th>Valore</th>
                        <th>Ultimo Aggiornamento</th>
                        <th>Azioni</th>
                    </tr>
                </thead>
                <tbody>
                    <?php foreach ($dispositivi_reali as $key => $device): 
                        $stato = $dati[$key]['valore'] ?? 0;
                        $offline = isset($dati[$key]['offline']) && $dati[$key]['offline'];
                    ?>
                    <tr class="<?= $offline ? 'table-warning' : '' ?>">
                        <td>
                            <i class="bi bi-<?= $stato ? 'check-circle text-success' : 'x-circle text-secondary' ?>"></i>
                            <?= $device['nome'] ?>
                        </td>
                        <td><?= $device['model'] ?? 'N/A' ?></td>
                        <td>
                            <?php if($offline): ?>
                                <span class="badge bg-warning">Offline</span>
                            <?php else: ?>
                                <span class="badge bg-<?= $stato ? 'success' : 'secondary' ?>">
                                    <?= $stato ? 'ON' : 'OFF' ?>
                                </span>
                            <?php endif; ?>
                        </td>
                        <td>
                            <?php if(isset($dati[$key]['temperatura'])): ?>
                                <?= $dati[$key]['temperatura'] ?>°C
                            <?php else: ?>
                                <?= $stato ? 'Acceso' : 'Spento' ?>
                            <?php endif; ?>
                        </td>
                        <td><?= $dati[$key]['ultimo_comando'] ?? 'Mai' ?></td>
                        <td>
                            <div class="btn-group btn-group-sm">
                                <form method="post" class="d-inline">
                                    <input type="hidden" name="device" value="<?= $key ?>">
                                    <button name="accendi" class="btn btn-outline-success btn-sm"
                                            <?= $offline ? 'disabled' : '' ?>>
                                        ON
                                    </button>
                                    <button name="spegni" class="btn btn-outline-danger btn-sm"
                                            <?= $offline ? 'disabled' : '' ?>>
                                        OFF
                                    </button>
                                </form>
                            </div>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>
    </div>
</div>

7. CONFIGURAZIONE PASSO PASSO PER IL PRIMO DISPOSITIVO

Opzione A: Shelly 1 (il più semplice)

  1. Acquista Shelly 1 (~20€)
  2. Collegalo alla rete WiFi
  3. Ottieni l’IP dall’app Shelly
  4. Modifica config.php:

php

$dispositivi_reali = [
    'shelly1' => [
        'nome' => 'Luce Ingresso',
        'tipo' => 'interruttore',
        'ip' => '192.168.1.XXX',  // Sostituisci con IP reale
        'model' => 'shelly1'
    ]
];
  1. Testa con un browser:

text

http://192.168.1.XXX/relay/0?turn=on
http://192.168.1.XXX/relay/0?turn=off
http://192.168.1.XXX/status

Opzione B: Presa WiFi TP-Link (Kasa)

  1. Usa questa libreriahttps://github.com/clach04/python-kasa
  2. Scarica il file PHP adattato: (ti invio via email se vuoi)
  3. Configura simile a Shelly

Opzione C: ESP8266 personalizzato

  1. Programma con Arduino IDE
  2. Usa questo codice base (te lo invio completo)
  3. Esponi endpoint HTTP semplici

8. SICUREZZA IMPORTANTE

php

// In config.php
define('IP_WHITELIST', [
    '192.168.1.0/24',  // Solo rete locale
    // '85.123.456.789'  // Il tuo IP pubblico se accedi da fuori
]);

function checkIP() {
    $ip = $_SERVER['REMOTE_ADDR'];
    foreach (IP_WHITELIST as $range) {
        if (strpos($range, '/')) {
            // Controlla subnet
            list($subnet, $mask) = explode('/', $range);
            if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
                return true;
            }
        } elseif ($ip === $range) {
            return true;
        }
    }
    return false;
}

// All'inizio di dashboard.php
if (!checkIP() && $_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
    die('Accesso non autorizzato da questo IP: ' . $_SERVER['REMOTE_ADDR']);
}

RIEPILOGO FILE FINALI:

  1. config.php – Configurazione
  2. dashboard.php – Interfaccia principale
  3. dispositivi_reali.php – Funzioni controllo dispositivi
  4. automazioni.php – Automazioni (opzionale)
  5. dati.json – Dati salvati
  6. .htaccess – Sicurezza

Vuoi che ti invii il codice completo per un dispositivo specifico che hai già? (Shelly, TP-Link, ESP8266, etc.) – Posso prepararti un pacchetto pronto da caricare!