Fermer

décembre 10, 2024

Heure d’énergie solaire Blazor

Heure d’énergie solaire Blazor


Découvrez comment utiliser l’énergie solaire avec le flux de données MQTT pour alimenter une interface utilisateur Telerik pour l’application Web Blazor ASP.NET Core.

Re-bonjour à tous ! Pour l’article d’aujourd’hui, je partagerai un résumé de ce qu’Ed Charbeneau, Justin Rice et moi avons fait lors d’une Série diffusée en direct en 12 parties pour Blazor Power Hour. Au cours de la série, nous avons commencé avec Fichier > Nouveau et avons fini avec un tableau de bord de surveillance de l’énergie solaire prêt pour la production. Le résultat est une application Blazor qui consomme les données de streaming en temps réel d’un courtier MQTT et restitue ces données de manière très rapide et intuitive.

Je souhaitais également apporter un savoir-faire général en abordant certains fondamentaux de l’énergie solaire ainsi que du MQTT. Cela vous aidera à créer la même chose, quelle que soit la provenance de vos données MQTT.

Code source

Avant de commencer, visitez le projet sur GitHub à l’adresse EdCharbeneau/BlazorSolarPowerHour. Voici une capture d’écran de la page principale, qui contient à la fois des valeurs individuelles en direct et des données graphiques historiques.

Tableau de bord Blazor Solar Power Hour avec graphiques

La configuration de l’énergie solaire

Avant de parler des données en direct, nous souhaitons comprendre le jargon de base de l’énergie solaire pour établir une base. Pour comprendre ce que signifie chaque terme, il est bon de voir un diagramme.

Diagramme avec cinq éléments. Les panneaux solaires indiquent l'onduleur. L'onduleur pointe vers la charge (maison, voiture, etc.) et échange avec la batterie et le réseau électrique

  • Panneaux solaires : Ce sont généralement des panneaux de 58 V.
  • Onduleur : Un système de gestion de l’énergie.
  • Charge : La chose qui consomme de l’énergie.
  • Batterie : stockage d’énergie, généralement un système de batterie au lithium de 48 V.
  • Réseau : Votre entreprise de services publics et/ou un générateur.

Tout d’abord, parlons de la charge. Une maison typique est connectée à un compteur électrique alimenté par « le réseau ». Selon l’endroit où vous habitez ou l’appareil que vous alimentez, la plupart des maisons nécessitent également une source de 240 V. Pour éviter de compliquer cet article, je ferai simplement référence à « 120 V » comme exigence de charge. L’équipement de votre maison nécessite ce niveau de tension pour alimenter les prises, les prises de lumière et le câblage direct.

Même si un panneau solaire produit de l’électricité, il ne peut pas être directement connecté au câblage électrique d’une maison. En effet, il génère une alimentation CC de 58 V (comme une batterie de téléphone), mais votre maison a besoin d’une alimentation CA. Ainsi, un appareil appelé onduleur prend l’alimentation CC des panneaux solaires, crée un signal d’alimentation CA et double/quadruple la tension jusqu’à 120 V.

Parlons maintenant du côté batterie. Les systèmes de batteries modernes sont généralement des piles lithium-ion de 48 V CC. L’onduleur peut à la fois charger et décharger ces batteries. Selon votre configuration, la recharge peut utiliser soit les panneaux solaires, soit le réseau. Dans ma configuration, j’utilise uniquement l’énergie solaire pour charger les batteries.

Le côté réseau est comme le côté batterie dans la mesure où l’onduleur peut à la fois recevoir et envoyer de l’énergie du réseau. L’onduleur est suffisamment intelligent pour connaître la quantité d’énergie générée par les panneaux et la quantité contenue dans les batteries ; si la charge est supérieure à ce qui est disponible, l’onduleur prendra de l’énergie sur le réseau. Alternativement, si vous produisez un excédent d’électricité, vous pouvez également revendre de l’électricité au réseau.

Cela a plus de sens lorsque vous le voyez en action dans trois scénarios : ensoleillé, nocturne et sans charge/charge élevée. Passons-les en revue.

Ensoleillé

  • Une maison est équipée de cinq ampoules de 100 watts branchées ; la charge est actuellement de 500 watts.
  • C’est une journée très ensoleillée ; les panneaux solaires génèrent 1 500 watts de puissance.

Cela signifie que l’onduleur envoie les 1 000 watts supplémentaires pour charger la batterie (et les revend au réseau lorsque la batterie est pleine).

Même disposition du diagramme. Les panneaux solaires 1500 watts comportent deux flèches. On passe par l'inverse pour charger avec 500 watts. L'autre passe par onduleur et se transforme en batterie avec +1000 watts

Nuageux/Nuit

Supposons maintenant que les panneaux ne produisent pas d’électricité : soit le soleil s’est couché, soit le temps est mauvais. La maison a encore besoin de 500 watts de puissance, l’onduleur utilise donc l’énergie stockée dans les batteries.

Les panneaux solaires ont 0 watts. Batterie -500 watts pointe sa flèche à travers l'onduleur pour charger 500 watts

Pas de frais ou charge élevée

Alors, que se passe-t-il si l’onduleur ne peut pas fournir suffisamment d’énergie à partir des batteries et de l’énergie solaire ? Ou que se passe-t-il si quelqu’un allume un appareil très demandé qui consomme plus de puissance que ce que la batterie et l’énergie solaire peuvent fournir ? Dans cette situation, l’onduleur utilisera automatiquement le réseau pour ce montant supplémentaire.

Les panneaux solaires ont 0 watts. La batterie -1 000 watts pointe sa flèche à travers l'onduleur vers la charge, et le réseau électrique 1 500 pointe sa flèche à travers l'onduleur vers la charge. La charge totalise 2 500 watts

Note: Il existe plusieurs façons de configurer votre système. Certaines personnes ont leurs systèmes réglés sur le mode « Solaire+Grid d’abord » pour la maison ; il ne passe en mode batterie que si l’alimentation du réseau est coupée. Tandis que d’autres, comme moi, utilisent toujours le mode « Solaire + Batterie d’abord » et n’obtiennent de l’électricité du réseau que lorsque cela est nécessaire.

Données en direct

Sachant tout cela, vous souhaiterez peut-être disposer d’une sorte d’application ou de tableau de bord pour voir ce qui se passe en temps réel. Vous souhaiterez également pouvoir consulter des données historiques sur la quantité d’énergie solaire que vous avez générée, la quantité de batterie que vous avez utilisée, le nombre de fois où vous avez dû vous connecter au réseau pour obtenir de l’électricité, etc.

Par exemple, voici une période de 32 heures provenant d’un de mes systèmes :

graphique linéaire montrant les écarts de charge, de réseau et d'énergie solaire

Remarquez que le 20 novembre, la journée n’était pas très ensoleillée (cela devrait être un grand arc lisse et non irrégulier), donc la batterie n’était pas complètement chargée. Au cours de cette nuit-là, la batterie s’est épuisée quelques heures avant le lever du soleil. C’est pourquoi vous voyez que le réseau est un peu utilisé pour que l’énergie solaire recommence à produire.

Toutes ces données sont formidables, mais comment les intégrer dans notre application ? Cela dépend du fabricant de votre onduleur. Dans mon cas, j’ai un Raspberry Pi, installé Assistant Solaire dessus, connecté le Pi à l’onduleur et activé le courtier MQTT. Ce service MQTT est ce qui est essentiel et sur lequel nous pouvons nous appuyer !

Remarque : il ne s’agit pas d’une approbation officielle de Progress Software pour Solar Assistant ; c’est mon expérience personnelle parce que j’aime le système. Vous pouvez utiliser le courtier MQTT ou le système de surveillance solaire de votre choix.

MQTT

Alors, qu’est-ce que MQTT ? En résumé, il s’agit d’un protocole de messagerie standard pour l’IoT et constitue un extrêmement transport de messagerie léger « pub-sub » idéal pour connecter des appareils distants avec une faible empreinte de code et une bande passante réseau minimale. Aborder ce sujet complètement sort du cadre de cet article de blog. Voir MQTT – La norme pour la messagerie IoT documents pour plus de détails.

Maintenant, pour une meilleure nouvelle : il existe une excellente bibliothèque .NET disponible => dotnet/MQTTnet. MQTTnet est une bibliothèque .NET hautes performances pour la communication basée sur MQTT. Cela signifie que nous pouvons lancer un projet .NET et nous connecter au firehose en direct !

Présentation du projet

Nous avons commencé avec votre standard « Fichier > Nouveau » et avons ajouté les packages requis pour MQTTnet. Notez que la bibliothèque est .NET Standard 2.0, nous pouvons donc l’utiliser pour n’importe quelle application .NET 9, mais nous avons choisi ASP.NET Blazor et Progress Interface utilisateur Telerik pour Blazor pour avoir un bon parcours d’apprentissage sur l’utilisation des services de dépendance (en arrière-plan, singleton et scope), l’utilisation de la base de données pour les statistiques à long terme et une superbe interface utilisateur avec des graphiques.

Nous devions réfléchir à la manière d’écrire le code du service MQTT. Oui, nous aurions pu l’intégrer dans une page code-behind, mais le service n’est alors valable que pour le cycle de vie de cette page : aucune donnée à long terme à interroger. Ce que nous voulons vraiment, c’est un service d’arrière-plan qui reste connecté et enregistre les données en direct : une classe MqttService (voir terminé).

public class MqttService(IConfiguration config, IServiceProvider serviceProvider) : BackgroundService, IAsyncDisposable
{ 
}

L’interface BackgroundService nécessite l’implémentation de ExecuteAsync, qui est déclenché lors de l’initialisation du service et c’est là que nous nous connecterons au courtier MQTT.

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    // Phase 1 - Connect to the MQTT broker
    this.mqttFactory = new MqttFactory();
    this.mqttClient = mqttFactory?.CreateMqttClient();

    var clientOptions = new MqttClientOptionsBuilder()
        .WithTcpServer("ip address", 1883).Build();

    mqttClient.ApplicationMessageReceivedAsync += GotMessage;

    // We're connected, but not listening for topics yet
    await mqttClient!.ConnectAsync(clientOptions, CancellationToken.None);

    // Phase 2 - Subscribe to a topic
    var subscriptionOptions = mqttFactory?.CreateSubscribeOptionsBuilder()
        .WithTopicFilter(f => { f.WithTopic("solar_assistant/#"); })
        .Build();
        
    // Start listening for messages
    await mqttClient.SubscribeAsync(subscriptionOptions, CancellationToken.None);
}

Cet exemple est simplifié pour transmettre les parties importantes :

  • L’adresse de l’hôte. Il peut s’agir d’une adresse IP ou d’un nom de domaine complet.
  • Le port que le courtier utilise pour MQTT (la valeur par défaut est 1883).
  • Nous nous sommes abonnés à l’événement ApplicationMessageReceivedAsync.
  • Nous nous sommes abonnés à un sujet nommé « solar_assistant/# » (l’octothorp est un joker).

Comme vous pouvez le voir, la bibliothèque utilise le modèle de générateur, nous transmettons tous les bits requis, puis nous pouvons nous asseoir et regarder les messages arriver dans le gestionnaire d’événements GotMessage :

private async Task GotMessage(MqttApplicationMessageReceivedEventArgs e)
{
    // The topic is a string
    var topic = e.ApplicationMessage.Topic;

    // The value comes in as an ArraySegment, so we'll decode that for visual output
    ArraySegment<byte> bytes = e.ApplicationMessage.PayloadSegment;

    string value = Encoding.ASCII.GetString(bytes.ToArray());

    Console.WriteLine($"{topic}: {value}");
}

Le code complet peut être consulté ici, mais l’idée générale est que cette classe de service se connectera au courtier MQTT et recevra tous les sujets ayant un préfixe « solar_assistant/ ». Comment pouvons-nous le démarrer et l’utiliser ?

En tant que service d’arrière-plan, nous souhaitons l’enregistrer d’une manière légèrement différente. Dans Program.cs, il doit d’abord être instancié en tant que singleton… mais il doit ensuite être enregistré en tant que HostedService.

// Using MqttService as a background service
builder.Services.AddSingleton<MqttService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<MqttService>());

Pour plus d’informations, consultez le Services hébergés documentation.

Stockage des données

Excellent, maintenant que nous obtenons des données en direct dans ce service en arrière-plan, qu’en faisons-nous ? Bien que nous puissions injecter ce singleton dans une vue rasoir, cela ne serait bon que pour les valeurs de données en direct. Nous souhaitons qu’il soit stocké afin de pouvoir créer des graphiques avec des données historiques.

Pour cela, nous pouvons utiliser Entity Framework Core et le Fournisseur SQLite pour enregistrer les valeurs entrantes dans une base de données. Il s’agit de la configuration standard dans Program.cs et DbContext. Pour le DbSet, j’ai créé une classe pour MqttDataIem avec quatre propriétés simples :

public class MqttDataItem
{
    [Key]
    public Guid Id { get; set; }

    // String representation of the MQTT topic.
    public string? Topic { get; set; }

    // Value of the MQTT message.
    public string? Value { get; set; }

    // Time stamp of when the MQTT message was received.
    public DateTime Timestamp { get; set; }
}

Normalement, nous pourrions nous arrêter là et injecter le DbContext dans la classe MqttService, mais nous voulons également un service que le reste de l’application peut utiliser pour charger ces données à afficher dans l’interface utilisateur. Pour ce faire, j’ai enveloppé DbContext dans une classe de service « MessagesDbService » facilement consommable.

public class MessagesDbService(MeasurementsDbContext dbContext)
{
    public async Task<List<MqttDataItem>> GetAllMeasurementsAsync() { }
    public async Task<List<MqttDataItem>> GetMeasurementsAsync(DateTime start, DateTime end) { }
    public async Task<DataSourceResult> GetMeasurementsRequestAsync(DateTime start, DateTime end, DataSourceRequest dataSourceRequest) { }
    public async Task<MqttDataItem> AddMeasurementAsync(MqttDataItem dataItem) { }
    public async Task UpdateMeasurementAsync(MqttDataItem updatedItem) { }
    public async Task DeleteMeasurementAsync(MqttDataItem item) { }
}

Maintenant, nous pouvons revenir au MqttService et mettre à jour la tâche GotMessage en enregistrant chaque message MQTT.

private async Task GotMessage(MqttApplicationMessageReceivedEventArgs e)
{
    // Get the value from the payload
    var decodedPayload = e.ApplicationMessage.PayloadSegment.GetTopicValue();

    // Important: Create a temporary scope in order to access the DbService in a background operation and add the item.
    using var scope = serviceProvider.CreateScope();
    var dbService = scope.ServiceProvider.GetRequiredService<MessagesDbService>();
    
    await dbService.AddMeasurementAsync(new MqttDataItem
    {
        Topic = e.ApplicationMessage.Topic,
        Value = decodedPayload,
        Timestamp = DateTime.Now
    });
}

Graphiques et jauges

Vient maintenant la partie amusante ! Nous utiliserons Telerik UI pour Blazor pour restituer certaines de ces données en direct dans des jauges et quelques graphiques. Il faudrait trop de temps pour parcourir toutes les parties du code : vous pouvez consulter les pages d’accueil et d’historique dans l’application réelle pour connaître toutes les parties.

Ici, laissez-moi vous montrer ce qui se passe à un niveau élevé. Nous interrogeons la base de données toutes les quelques secondes pour obtenir les dernières valeurs en direct et mettre à jour les graphiques et les jauges, la jauge en direct du niveau de la batterie et l’historique de l’alimentation.

Sur la page, nous avons une liste déroulante qui sélectionne jusqu’où nous voulons lire la base de données. Nous disposons également d’une fonction de minuterie qui met continuellement à jour la page toutes les quelques secondes.

Le « tick » de la minuterie appelle le Obtenir des valeurs tâche, qui effectue les opérations suivantes :

async Task GetValues()
{
    // GET DATABASE DATA
    var items = await DataService.GetMeasurementsAsync(StartDateTime, EndDateTime);

    // Update individual live values
    loadPower = items.GetNewestValue(TopicName.LoadPower_Inverter1, "0");
    pvPower = items.GetNewestValue(TopicName.PvPower_Inverter1, "0");
    gridPower = items.GetNewestValue(TopicName.GridPower_Inverter1, "0");
    batteryPower = items.GetNewestValue(TopicName.BatteryPower_Total, "0");
    batteryCharge = items.GetNewestValue(TopicName.BatteryStateOfCharge_Total, "0");
    // ... and many more values


    // HISTORICAL DATA FOR CHARTS
    foreach (var item in items)
    {
        var topicName = GetTopicName(item.Topic ?? "");

        switch (topicName)
        {
            case TopicName.LoadPower_Inverter1:
                LoadPowerData.Add(new ChartMqttDataItem { Category = topicName, CurrentValue = Convert.ToDouble(item.Value), Timestamp = item.Timestamp });
                break;
            case TopicName.PvPower_Inverter1:
                SolarPowerData.Add(new ChartMqttDataItem { Category = topicName, CurrentValue = Convert.ToDouble(item.Value), Timestamp = item.Timestamp });
                break;
            case TopicName.BatteryPower_Total:
                BatteryPowerData.Add(new ChartMqttDataItem { Category = topicName, CurrentValue = Convert.ToDouble(item.Value), Timestamp = item.Timestamp });
                break;
            case TopicName.GridPower_Inverter1:
                GridPowerData.Add(new ChartMqttDataItem { Category = topicName, CurrentValue = Convert.ToDouble(item.Value), Timestamp = item.Timestamp });
                break;
            case TopicName.BatteryStateOfCharge_Total:
                BatteryChargeData.Add(new ChartMqttDataItem { Category = topicName, CurrentValue = Convert.ToDouble(item.Value), Timestamp = item.Timestamp });
                break;
        }
    }
}

Dans l’interface utilisateur, ces valeurs remplissent divers éléments :

Tableau de bord affichant la plage de données prêtes, les valeurs en temps réel et les valeurs historiques

Grille de données

Sur la page Historique, nous ne nous soucions pas des mises à jour en temps réel, nous n’avons donc pas besoin de minuterie. Au lieu de cela, nous profitons de la capacité de Telerik Grid à utiliser automatiquement les objets DataSourceRequest et DataSourceResult en partenariat avec le service de base de données :

private async Task OnRead(GridReadEventArgs args)
{
    DataSourceResult result = await DataService.GetMeasurementsRequestAsync(StartDate, EndDate, args.Request);

    args.Data = result.Data;
    args.Total = result.Total;
}

Si nous jetons un coup d’œil à la classe du service de base de données, vous verrez que nous avons une méthode surchargée qui utilise le paramètre DataSourceRequest et renvoie le résultat à l’aide de la méthode d’extension ToDataSourceResult :

public async Task<DataSourceResult> GetMeasurementsRequestAsync(DateTime start, DateTime end, DataSourceRequest dataSourceRequest)
{
    return await dbContext.Measurements
        .Where(i => i.Timestamp > start && i.Timestamp < end)
        .ToDataSourceResultAsync(dataSourceRequest);
}

Nous pouvons désormais utiliser très facilement le filtrage, le tri et le regroupement intégrés de TelerikGrid ! Comportement puissant avec seulement quelques lignes de code grâce aux wrappers DataSource.

Conclusion

Alors, où vas-tu à partir d’ici ? L’objectif principal de la série CodeItLive et de cet article de blog était de vous montrer à quel point il peut être facile de se connecter à un service MQTT en direct dans votre application .NET et de transférer ces données vers une magnifique interface utilisateur. Il n’est pas nécessaire qu’il s’agisse d’une application Blazor : vous pouvez utiliser le même code dans .NET MAUI, WPF et même WinForms… et Telerik DevCraft a des contrôles pour chacun d’eux.

Voici quelques ressources utiles où vous pouvez en savoir plus sur ce dont nous avons discuté :

En attendant la prochaine fois, continuez à bricoler !

PS Rejoignez-nous sur CodeItLive pour plus de plaisir.




Source link