Il y a déjà un certain temps nous avions annoncé une librairie Universal Windows Platorm en beta, aujourd'hui nous publions officiellement cette nouvelle librairie et par la même occasion nous vous expliquons comment l'utiliser. Nous allons écrire une petite application Windows Universal qui va afficher la valeur d'un capteur de température.
Nous partons du principe que vous savez déjà ce qu'est "Universal Windows Platform", si ce n'est pas le cas vous pouvez commencer par lire la documentation de d'UWP sur le site de Microsoft. Nous supposons aussi que vous avez quelques connaissances de C# car l'application va être écrite dans ce langage.
De la même manière, si vous n'êtes pas familier avec les modules Yoctopuce, nous vous recommandons de commencer par la lecture des précédents articles de cette série, en particulier celui sur la structure logique des modules Yoctopuce.
Installation de l'API
Afin de pouvoir utiliser la librairie Yoctopuce "Universal Windows Platform" dans votre projet, il faut tout d'abord la télécharger et l'installer quelque part sur votre disque. La librairie peut être téléchargée sur notre site depuis cette page. C'est un fichier zip qui contient la librairie, quelques exemples d'utilisation et la documentation. La partie importante est le répertoire "Sources", qui contient les fichiers sources en C# de la librairie. Copiez ce répertoire quelque part sur votre disque et l'installation est terminée. Notez qu'il est aussi possible de télécharger la librairie depuis GitHub si vous préférer utiliser Git.
Création du projet Visual Studio
Après avoir lancé Visual Studio, créez un nouveau projet C# de type Blank App (Universal Windows).
Créez un nouveau projet C# de type Blank App (Universal Windows)
Il faut ensuite indiquer à Visual Studio qu'il doit compiler et utiliser la librairie Yoctopuce. Afin de garder un projet bien organisé, commençons par créer un nouveau répertoire "yoctopuce" dans le projet. Faites un clic droit sur votre projet dans le panneau "Solution Explorer", et choisissez: Add > New Folder. Ensuite, on va inclure tous les fichiers sources de la librairie dans ce répertoire.
Toujours dans le panneau "Solutions Explorer", faites un clic droit sur le répertoire que nous venons de créer et choisissez: Add > Existing Item.... Sélectionnez tous les fichiers du répertoire Sources de la librairie UWP et cliquer sur le bouton "Add". Notez qu'il est aussi possible d'ajouter uniquement une référence à ces fichiers à l'aide du bouton "Add as link", ce qui permet d'utiliser les fichiers depuis leur endroit original plutôt que de les copier dans votre projet. Cette dernière option est très pratique si vous avez plusieurs projets qui utilisent la librairie Yoctopuce.
Le fichier Package.appxmanifest
Tout comme sous Android, par défaut, une application Universal Windows n'a pas le droit d’accéder aux ports USB. Si l'on désire accéder à un périphérique USB, il faut impérativement le déclarer dans le fichier Package.appxmanifest.
Malheureusement, la fenêtre d'édition de ce fichier ne permet pas cette opération et il faut modifier le fichier Package.appxmanifest à la main. Dans le panneau "Solutions Explorer", faites un clic droit sur le fichier Package.appxmanifest et sélectionner "View Code".
Dans ce fichier XML, il faut rajouter un nœud DeviceCapability dans le nœud Capabilities. Ce nœud doit avoir un attribut "Name" qui vaut "humaninterfacedevice".
A l’intérieur de ce nœud, il faut déclarer tous les modules qui peuvent être utilisés. Concrètement, pour chaque module, il faut ajouter un nœud "Device" avec un attribut "Id" dont la valeur est une chaîne de caractères "vidpid:USB_VENDORID USB_DEVICE_ID". Le USB_VENDORID de Yoctopuce est 24e0 et le USB_DEVICE_ID de chaque module Yoctopuce peut être trouvé dans la documentation dans la section "Caractéristiques". Pour finir, le nœud "Device" doit contenir un nœud "Function" avec l'attribut "Type" dont la valeur est "usage:ff00 0001".
Pour le Yocto-Temperature voici ce qu'il faut ajouter dans le nœud "Capabilities":
<!-- Yocto-Temperature -->
<Device Id="vidpid:24e0 000b">
<Function Type="usage:ff00 0001" />
</Device>
</DeviceCapability>
Malheureusement, il n'est pas possible d'écrire un règle qui autorise tous les modules Yoctopuce, par conséquent il faut impérativement ajouter chaque module que l'on désire utiliser.
L'interface utilisateur
On va insérer deux TextBlock: le premier pour indiquer le nom du capteur utilisé. Le second pour indiquer la température.
Pour ce faire, on ouvre le fichier MainPage.xaml et on ajoute deux tags TextBlock en leur attribuant les nom "HwIdTextBox" et "TempTextBox". On va aussi en profiter pour enregistrer la méthode "OnLoad" qui sera appelée lors du chargement de l'application, pour ce faire on ajoute l’attribut Loaded dans le tag Page.
Au final le fichier MainPage.xaml devrait ressembler à ceci:
x:Class="DemoApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DemoApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Loaded="OnLoad"
>
<StackPanel Padding="16" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock HorizontalAlignment="Left" Text="TextBlock"
TextWrapping="Wrap" VerticalAlignment="Top"
FontSize="24" Name="HwIdTextBox" />
<TextBlock HorizontalAlignment="Left" Text="TextBlock"
TextWrapping="Wrap" VerticalAlignment="Top"
FontSize="48" FontWeight="Bold" Name="TempTextBox"/>
</StackPanel>
On ajoute deux TextBox
Initialisation de l'API
Maintenant que l'on a réalisé notre interface, il faut implémenter la méthode OnLoad() qui se charger d'initialiser la librairie UWP. Pour ce faire, il faut ouvrir le fichier MainPage.xaml et rajouter la méthode OnLoad() qui va appeler la méthode YAPI.RegisterHub. On en profite aussi pour initialiser les deux TextBox.
{
try {
await YAPI.RegisterHub("usb");
} catch (YAPI_Exception ex) {
var dialog = new MessageDialog("Init error:" + ex.Message);
await dialog.ShowAsync();
return;
}
HwIdTextBox.Text = "No sensor detected";
TempTextBox.Text = "N/A";
}
Mettre à jour l'interface avec la température
Contrairement à une application console, on ne peut pas se permette de créer une boucle sans fin pour lire le capteur en permanence. C'est pourquoi on va utiliser un DispatcherTimer qui appelle une fonction à une fréquence de 10Hz. C'est cette fonction qui va se charger de:
- Appeler YAPI.HandleEvents assez régulièrement. En fait ce n'est pas 100% nécessaire dans notre cas, mais c'est une bonne pratique de donner le contrôle à l'API Yoctopuce de temps en temps.
- Appeler YAPI.UpdateDeviceList une fois toutes les deux secondes, ce qui forcera l'API à re-détecter le branchement de nouveaux modules. C'est un processus assez lourd, il faut éviter de le faire trop souvent d'où les deux secondes entre chaque appel.
- Détecter la première fonctionnalité YTemperature disponible à l'aide de YTemperature.FirstTemperature()
- Si cette fonctionnalité est disponible, lire la température et mettre à jour l'interface.
{
private NonReentrantDispatcherTimer timer;
private int hardwaredetect = 0;
private YTemperature sensor;
private async void OnLoad(object sender, RoutedEventArgs e)
{
HwIdTextBox.Text = "No sensor detected";
TempTextBox.Text = "N/A";
try {
await YAPI.RegisterHub("usb");
} catch (YAPI_Exception ex) {
var dialog = new MessageDialog("Init error:" + ex.Message);
await dialog.ShowAsync();
return;
}
timer = new NonReentrantDispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
timer.TickTask = async () => { await dispatcherTimer_Tick(); };
timer.Start();
}
async Task dispatcherTimer_Tick()
{
try {
if (hardwaredetect == 0) {
await YAPI.UpdateDeviceList();
}
hardwaredetect = (hardwaredetect + 1) % 20;
await YAPI.HandleEvents();
if (sensor == null)
sensor = YTemperature.FirstTemperature();
if (sensor != null) {
HwIdTextBox.Text = await sensor.get_friendlyName();
TempTextBox.Text = await sensor.get_currentValue()
+ await sensor.get_unit();
}
} catch (YAPI_Exception ex) {
HwIdTextBox.Text = "Sensor is offline";
TempTextBox.Text = "OFFLINE";
sensor = null;
}
}
}
public class NonReentrantDispatcherTimer : DispatcherTimer
{
private bool IsRunning;
public NonReentrantDispatcherTimer()
{
base.Tick += SmartDispatcherTimer_Tick;
}
async void SmartDispatcherTimer_Tick(object sender, object e)
{
if (TickTask == null || IsRunning) {
return;
}
try {
IsRunning = true;
await TickTask.Invoke();
} finally {
IsRunning = false;
}
}
public Func<Task> TickTask { get; set; }
}
Et une fois qu'on compile tout ça, on obtient le résultat attendu, une fenêtre Windows qui affiche la température dès qu'un capteur de température Yoctopuce est branché:
Ça marche!
Note: le code complet de l'application est disponible sur GitHub:https://github.com/yoctopuce-examples/uwp_beginners
Une petite amélioration
Cet exemple ne gère que les capteurs de température, mais en changeant juste deux lignes, on peut le faire marcher avec n'importe quel senseur Yoctopuce: il suffit d'utiliser la classe YSensor à la place de la classe YTemperature:
{
private NonReentrantDispatcherTimer timer;
private int hardwaredetect = 0;
private YSensor sensor;
private async void OnLoad(object sender, RoutedEventArgs e)
{
HwIdTextBox.Text = "No sensor detected";
TempTextBox.Text = "N/A";
try {
await YAPI.RegisterHub("usb");
} catch (YAPI_Exception ex) {
var dialog = new MessageDialog("Init error:" + ex.Message);
await dialog.ShowAsync();
return;
}
timer = new NonReentrantDispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
timer.TickTask = async () => { await dispatcherTimer_Tick(); };
timer.Start();
}
async Task dispatcherTimer_Tick()
{
try {
if (hardwaredetect == 0) {
await YAPI.UpdateDeviceList();
}
hardwaredetect = (hardwaredetect + 1) % 20;
await YAPI.HandleEvents();
if (sensor == null)
sensor = YSensor.FirstSensor();
if (sensor != null) {
HwIdTextBox.Text = await sensor.get_friendlyName();
TempTextBox.Text = await sensor.get_currentValue()
+ await sensor.get_unit();
}
} catch (YAPI_Exception ex) {
HwIdTextBox.Text = "Sensor is offline";
TempTextBox.Text = "OFFLINE";
sensor = null;
}
}
}
public class NonReentrantDispatcherTimer : DispatcherTimer
{
private bool IsRunning;
public NonReentrantDispatcherTimer()
{
base.Tick += SmartDispatcherTimer_Tick;
}
async void SmartDispatcherTimer_Tick(object sender, object e)
{
if (TickTask == null || IsRunning) {
return;
}
try {
IsRunning = true;
await TickTask.Invoke();
} finally {
IsRunning = false;
}
}
public Func<Task> TickTask { get; set; }
}
Maintenant, l'application marche aussi avec un capteur de lumière, un voltmètre ou n'importe quel capteur Yoctopuce.
Ça marche aussi avec les autres capteurs
Ce "tour de magie" fonctionne car toutes les classes Yoctopuce correspondant à des fonctionnalités de type senseur héritent de la classe YSensor. Vous pouvez donc utiliser la classe YSensor pour accéder aux fonctions de base d'un senseur comme obtenir la valeur courante ou l'unité de mesure.
Conclusion
Nous avons volontairement gardé cet exemple très simple, par exemple, il ne gère qu'un seul capteur branché par USB. Bien évidement, la librairie permet de gérer plusieurs capteurs, qu'ils soient connectés en USB ou connectés sur un YoctoHub distant. Maintenant que nous avons vu les bases, il ne vous reste plus qu'à écrire vos propres applications en vous aidant de la documentation de référence.