Integra la tua domotica con il tuo modello AI

Ciao a tutti,

oggi sono qui a scrivere un articolo riguardo ad un progetto interessante, che unisce il mondo dell’AI con quello della domotica.

Qualche settimana fa ho voluto iniziare ad esplorare il mondo dei server MCP e ne ho parlato anche in questo articolo, l’intento era quello di vedere come integrare un’applicazione .NET 9 con l’assistente AI Claude, che uso ormai da diverso tempo.

Nell’articolo ho spiegato le basi per creare un server MCP locale, ipotizzando di scrivere una serie di Tool per leggere vari dati dalla domotica di casa. Con questo articolo ho voluto fare un passo in più, ho creato un server MCP locale per comandare alcuni dispositivi Shelly tramite le API di Shelly Cloud, il risultato è stato questo:

Incredibile vero? Ora il mio Claude è in grado di interagire con l’ecosistema Shelly e di dirmi se ho lasciato le luci accese, spegnerle o informarmi sui consumi e sulle temperature.

Procediamo un passo alla volta e vediamo cosa ci serve per realizzare tutto questo.

Obiettivo

Integrare la domotica con Claude (o Copilot/ChatGPT/Gemini..)

Cosa ci serve

Tempo fa avevo scritto due articoli su questo blog dove spiegavo come avevo reso smart tre ventole per combattere il pericoloso gas radon a casa e come avevo reso smart una lavatrice per sapere quando finiva il ciclo di lavaggio (cliccate per leggerli). In Entrambi gli articoli ho utilizzato i dispositivi Shelly, li utilizzo da diversi anni e mi trovo molto bene in quanto sono affidabili, sicuri e molto piccoli, ottimi per essere inseriti in prese elettriche con poco spazio.

Non ho nessuna affiliazione commerciale con Shelly, mi sono sempre trovato molto bene e vi riporto la mia esperienza

Obiettivo di questo articolo

Non voglio dilungarmi in una spiegazione troppo dettagliata su come ho scritto l’applicazione in quanto vorrei concentrarmi maggiormente sull’organizzazione del progetto e di come funziona architetturalmente. Il progetto è disponibile sul mio repository gitlab, potete scaricarlo, provarlo e magari anche estenderlo.

Architettura

Prima di partire capiamo come funziona il processo nella sua completezza:

  • I Dispositivi Shelly (con cloud abilitato) inviano il proprio “stato” al Cloud Shelly
  • La nostra applicazione (Server MCP) serve ad “estendere” le capacità del nostro assistente AI (nel mio caso è Claude ma potrebbe essere anche Gemini, ChatGpt, Copilot, ecc), ogni volta che gli faremo una domanda lui cercherà di capire se deve utilizzare questi strumenti oppure ci deve rispondere come al solito.

Possiamo rappresentare il workflow in questo modo:

Il progetto .NET

Nel momento in cui sto scrivendo questo articolo ho implementato solamente la chiamata alla parte Cloud delle API messe a disposizione dal team Shelly, manca tutta la parte di integrazione API per i dispositivi shelly in rete locale. Le API Cloud sono meno “fornite” e documentate ma permettono di integrare un modello anche quando si è fuori dalla propria rete locale.

ATTENZIONE il progetto sfrutta le API Cloud V2 che attualmente sono in versione Beta, potrebbero quindi cambiare o aggiornarsi nel corso del tempo.

Architettura

Ho suddiviso nel seguente modo i progetti:

  • Shelly.MCPServer
    • Qui ci sono i veri e propri tool esposti al nostro modello AI.
  • Shelly.Models
    • Questo progetto contiene le classi utilizzate all’interno del progetto
  • Shelly.Services
    • Qui ho inserito i servizi e le loro interfacce, è qui che vengono effettuate le chiamate API ed è qui che ci sono i servizi iniettati nel primo progetto tramite dependency injection. Valuterò più avanti con il crescere del progetto se spezzare questo progetto in  Services ed Infrastructure, utilizando la clean architecture.

Vediamo ora, progetto per progetto le parti principali.

Shelly.McpServer

Program.cs
using Asg.MCP.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Shelly.Models.Cloud;
using Shelly.Services.Services;

var builder = Host.CreateApplicationBuilder(args);

#region init appsettings

//Initialize arguments
builder.Configuration
    .AddCommandLine(args);

//get "--settingPath"
var settingsPath = builder.Configuration["settingsPath"];

if (!string.IsNullOrEmpty(settingsPath) && File.Exists(settingsPath))
{
    builder.Configuration
        .AddJsonFile(settingsPath, optional: false, reloadOnChange: true)
        .AddUserSecrets<Program>(); ;
}

else {
    builder.Configuration
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
        .AddUserSecrets<Program>(); ;
}

#endregion

//Init log on console
builder.Logging.AddConsole(consoleLogOptions => {
    // Configure all logs to go to stderr
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});



#region Dependency Injection

//DI of my services
builder.Services.AddHttpClient<ShellyCloudService>("PhoneProviderClient");
builder.Services.AddSingleton<ShellyCloudDeviceStore>();
builder.Services.AddSingleton<IShellyCloudService, ShellyCloudService>();

//MCP dependency injection
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

#endregion

var app = builder.Build();
await app.RunAsync();

Nella prima parte ho abilitato il command line, in modo tale da ricevere il percorso dell’appsettings.json come parametro in fase di avvio dell’applicazione. Questo è utile perché l’applicazione compilata la posizioneremo in un luogo differente rispetto al nostro progetto .NET

Nella seconda parte invece è configurata la Dependency Injection dei due servizi, che approfondiremo più avanti.

Shelly.Models

Essendo solamente un progetto di DTO non c”è nessuna logica particolare da spiegare. La cartella “Cloud” contiene le classi per mappare i payload e le risposte dal Cloud. Se un domani implementassi anche le API dirette verso i dispositivi allora li andrei ad organizzare in una directory differente.

La classe GenericDeviceStatusModel è una classe generica con le sole informazioni che ci interessano, l’idea è di sfruttarla anche per le API dei dispositivi.

Shelly.Services

Qui abbiamo un po’ più di parole da spendere, procediamo con odine.

Interfaces

La directory contiene le interfacce dei servizi, utilizzate nel progetto Shelly.McpServer per inizializzare la dependency injection

Mapper

Contiene una classe per mappare le informazioni dagli oggetti di risposta delle API all’oggetto generico GenericDeviceStatusModel. Alcune informazioni (es. se il dispositivo è acceso oppure no) è disponibile in punti differenti a seconda della tipologia di device (relays, switch,ecc).

Services

ShellyCloudService.cs che contiene i metodi e le chiamate API al Cloud Shelly. Attenzione che ad oggi c’è un limite di 1 richiesta al secondo. Per gestire gli errori ho utilizzato la libreria Polly (le policy sono salvate nella directory Utils), che permette di gestire in modo standard e strutturato eventuali fallimenti alle chiamate (con un delay di 1 secondo tra un tentativo e l’altro).

Ad oggi purtroppo non esiste nessuna API da richiamare per farsi ritornare la lista di tutti i dispositivi associati al proprio account, bisogna quindi censirli manualmente all’interno all’interno della classe ShellyCloudDeviceStore.cs. Gli id dei dispositivi si ottengono dall’app Shelly, nelle informazioni del singolo dispositivo.

Get device Id from shelly app
Device ID dall’app Shelly

Sempre in questa classe ho aggiunto un array di “friendly name”, qui andremo a censire tutti i possibili nomi che noi usiamo per chiedere al nostro assistente AI lo stato del dispositivo.

Il campo “Channel” serve per diversificare gli switch di tutti quei dispositivi che ne supportano più di uno, come lo Shelly Plus 2PM: A questi dispositivi potremmo attaccare due punti luci per esempio, ognuno con lo stesso id ma con ChannelId differente.

ShellyCloudDeviceStore.cs
using Shelly.Models.Cloud;

namespace Shelly.Services.Services
{
    public class ShellyCloudDeviceStore
    {
        public readonly IEnumerable<DeviceNameMappingStoreItem> Store = 
            new List<DeviceNameMappingStoreItem>()
            {
                new DeviceNameMappingStoreItem()
                {
                    DeviceId = "xxxxxxxxx",
                    ChannelId = 0,
                    FriendlyNames = ["kitchen", "kitchen light", "luce cucina", "cucina"],
                    DeviceType = "switch"
                },

                new DeviceNameMappingStoreItem()
                {
                    DeviceId = "cccccccccc",
                    ChannelId = 0,
                    FriendlyNames = ["garage", "basculante", "porta garage", "garage door"],
                    DeviceType = "sensor"
                },
            };
    }
}

Gestione delle chiavi API

Per poter connetterci al cloud Shelly abbiamo bisogno di un indirizzo di cloud e di una chiave API. La prima la possiamo recuperare dalle impostazioni del nostro account

Cloud key nelle impostazioni utente

A livello di progetto la andremo a gestire questi due aspetti in due punti differenti: l’endpoint delle api lo andremo a censire nel nostro appsettings.json

appsettings.json
{
  "SHELLY_API_ENDPOINT": "shelly-12-eu.shelly.cloud"
}

Mentre per quanto riguarda la chiave API, informazione sensibile, sfrutteremo i secret di Visual Studio

User secret
{

  "SHELLY_API_KEY": "XXXXXXXXXXXXXXXXXXXXXXX"

}

ATTENZIONE In debug i secret sono aggiunti automaticamente al nostro appsettings.json, in produzione invece no. La vostra chiave API deve essere mantenuta sicura e segreta, decidete voi come gestire questa informazione: potete aggiungerla all’appsettings.json, potete aggiungerla come variabile di ambiente alla macchina oppure potete utilizzare un servizio più professionale come Azure Key Vault. Considerato che questo progetto è sul mio PC, nella mia directory dell’account con la cifratura attiva ho ritenuto “sicuro” poterla aggiungere all’appsettings.json

Deploy del server MCP

Diversamente da quanto visto nell’articolo relativo ai server MCP, non andremo a far puntare Claude direttamente al progetto ma andremo a pubblicarlo in una directory dedicata sul nostro PC. Questo ci agevola nella fase di sviluppo perché se puntassimo direttamente al progetto bisognerebbe chiudere claude ogni volta che c’è la necessità di ricompilare il progetto (questo perché viene avviata un’istanza.

Individuate una directory dove poter aggiungere tutti i vostri  server MCP, io l’ho messa in C:\Users\<nome_utente>\MCP\ShellyMcpServer\Shelly.McpServer.exe.

Una volta creata la directory spostiamoci nel nostro progetto .NET e pubblichiamo il progetto in configurazione Release.

Aggancio server MCP a Claude

Ora che i file sono pubblicati dobbiamo istruire Claude su come avviare la nostra applicazione, passando come parametro anche l’appsettings con i parametri configurativi. Apriamo Claude, andiamo nelle impostazioni e apriamo il file di configurazione dei plugin. Modifichiamolo in questo modo:

claude_desktop_config.json
{
    "mcpServers": {
        "mcp-shelly": {
            "command": "C:\\Users\\<user>\\MCP\\ShellyMcpServer\\Shelly.McpServer.exe",
            "args": [
                "--settingsPath",
                "C:\\Users\\<user>\\MCP\\ShellyMcpServer\\appsettings.json"
            ]
        }
    }
}

Chiudiamo Claude (dalla barra in basso) e lo riavviamo, dovremmo trovarci con il nostro server MCP agganciato:

Server MCP Shelly disponibile in Claude Desktop

Test applicazione

Proviamo a fare qualche test, chiedendo a Claude di interagire con i nostri dispositivi Shelly:

Conclusioni

Il progetto che abbiamo visto oggi è un naturale proseguimento del primo articolo che avevo scritto e ci ha dimostrato come integrare l’ecosistema Shelly all’interno del nostro assistente AI personale. Questo progetto, disponibile per tutti su Gitlab, è il primo passo verso la costruzione di un server MCP che dialoga con la nostra domotica, un contesto molto personale.

Un giorno ognuno di noi avrà un proprio assistente creato su misura, i server MCP sono il primo passo per questo futuro perché permettono di “estendere” e “personalizzare” un modello AI “generale” con contesti legati alle nostre vite. Ecco quindi che potremo chiedere al nostro assistente AI di gestire casa, di regolare la temperatura, di controllare cosa abbiamo nel frigor, se a casa sta piovendo, il tutto gestito con queste applicazioni di integrazione. Migliori integrazioni significheranno una migliore efficienza ed una migliore produttività.

Ho già altri progetti in mente che vedono questo progetto come uno dei tasselli fondamentali per la loro riuscita, vedrò di elaborarli e di ritornare con nuove storie da raccontare e nuove esperienze da condividere con tutti voi.

Alla prossima!

Condividi questo articolo
Shareable URL
Post precedente

Rendi smart la tua vecchia lavatrice

Leggi il prossimo articolo