Mon Tutoriel de la voiture connectée Azure Sphere – Partie 3

Dans mes précédents articles, je démarrais la présentation du matériel nécessaire et de l’installation que j’ai faite pour réaliser ma petite voiture connectée et téléguidée. J’ai ensuite décrit les différentes étapes permettant de contrôler les moteurs de la voiture. Si vous arrivez là par hasard, n’hésitez-pas à revenir sur les pages précédentes afin de mieux comprendre où nous en sommes :

Je vous propose maintenant de regarder comment j’ai réaliser le téléguidage par la voix avec Azure IoT Hub, Direct Device Method, Azure Function et Google Actions pour Google Home (et oui ca fait du monde tout çà !!)

Azure IoT Hub et Direct Device Method

Je vais aller très vite sur la connexion à IoT Hub, en effet, j’ai déjà réalisé un article sur ce sujet (c’était d’ailleurs pendant ma préparation sur ces travaux pratiques…. Vous pourrez trouver l’article dédié à ce sujet sur : Comment connecter Azure Sphere avec IoT Hub

Cependant, je n’avais pas encore abordé le sujet des Direct Device Method que j’ai utilisé sur ce projet.

Direct Device Method

Les méthodes d’appareils directes permettent depuis le cloud d’interagir avec un appareil connecté sur Azure IoT Hub de manière Synchrone. En effet, lorsque vous utilisez cette fonctionnalité, ceci vous permet de déclencher des méthodes sur l’appareil et d’avoir un retour de l’exécution de celle-ci sur le cloud.

Comme je l’ai dit, ceci ne fonctionne que lorsque l’appareil est connecté, il ne s’agit donc pas uniquement d’un message asynchrone qui sera utilisé lorsque l’appareil se connectera. Ce type d’action est assez utile par exemple pour obtenir en temps réel des informations sur l’appareil (valeurs en mémoire, ….) ou d’y effectuer des opérations (redémarrages, …).

Dans mon cas j’ai réalisé un handler de méthodes directes permettant de déclencher les actions sur les moteurs de la voiture :

/// <summary>
///     Callback invoked when a Direct Method is received from Azure IoT Hub.
/// </summary>
static int DeviceMethodCallback(const char *methodName, const unsigned char *payload,
                                size_t payloadSize, unsigned char **response, size_t *responseSize,
                                void *userContextCallback)
{
    // All methods are ignored
    int result = 0;
    char *responseString = "{\"result\":\"OK\"}";

    Log_Debug("Received Device Method callback: Method name %s.\n", methodName);

    // if 'response' is non-NULL, the Azure IoT library frees it after use, so copy it to heap
    *responseSize = strlen(responseString);
    *response = malloc(*responseSize);
    memcpy(*response, responseString, *responseSize);

    if (strcmp(methodName, "GoForward") == 0)
    {
        JoyitCar_GoForward();
    }
    else if (strcmp(methodName, "GoBackward") == 0)
    {
        JoyitCar_GoBackward();
    }
    else if (strcmp(methodName, "Break") == 0)
    {
        JoyitCar_Break();
    }
    else if (strcmp(methodName, "TurnRight") == 0)
    {
        JoyitCar_TurnRight();
    }
    else if (strcmp(methodName, "TurnLeft") == 0)
    {
        JoyitCar_TurnLeft();
    }
    else if (strcmp(methodName, "StartDemo") == 0)
    {
        JoyitCar_StartDemo();
    }
    else
    {
        responseString = "{\"result\":\"NotFound\"}";
        result = -1;
    }

    return result;
}

L’abonnement à ces méthodes se fait alors une fois la connexion à IoT Hub réalisé via le code suivant :

    IoTHubDeviceClient_LL_SetDeviceMethodCallback(iothubClientHandle, DeviceMethodCallback, NULL);

Une fois le code exécuté sur l’appareil, vous pouvez le tester rapidement depuis le portail Azure :

Déclenchement d’une méthode
Validation du déclenchement de la méthode directe

Google Action & Google Home

Maintenant que nous pouvons déclencher des méthodes directes sur l’appareil, nous pouvons mettre en œuvre les éléments permettant à notre Google Home de les déclencher.

Pour ce faire, j’ai réalisé une Azure Function interrogée par Google Action.

Vous pourrez trouver en détails l’intégration utilisée entre Google Actions et Azure Function sur un autre article que j’ai réalisé : Actions Google Home et Azure Functions.

Dans le cadre de ce projet, j’ai donc réalisé une action prenant en charge les différentes intentions recherchées (correspondants aux mouvements que je voulais prendre en charge).

Intentions Google Home

Azure Function Facade

Enfin pour déclencher mes méthodes sur Azure IoT Hub depuis Google Actions, j’ai réalisé une fonction Azure .NET Core 5 et C#. En utilisant la librairie .NET de Microsoft.Azure.Devices nous pouvons créer les éléments nécessaires pour se connecter à IoT Hub :

        private static void ConfigureServices(HostBuilderContext context, IServiceCollection services)
        {
            var ioTHubConnectionString = context.Configuration.GetValue<string>("IoTHubConnectionString");

            services.AddScoped<VehicleService>();
            
            services.AddSingleton(sp => ServiceClient.CreateFromConnectionString(ioTHubConnectionString));
            services.AddSingleton(sp => RegistryManager.CreateFromConnectionString(ioTHubConnectionString));
        }

Puis maintenant le service permettant de déclencher les méthodes directes sur l’appareil via Azure IoT Hub :

   public class VehicleService
    {
        private readonly IConfiguration configuration;

        private readonly RegistryManager registryManager;

        private readonly ServiceClient serviceClient;

        private Device device;

        public VehicleService(IConfiguration configuration, RegistryManager registryManager, ServiceClient serviceClient)
        {
            this.configuration = configuration;
            this.registryManager = registryManager;
            this.serviceClient = serviceClient;
        }

        private void EnsureIsConnected()
        {
            if (device == null)
            {
                throw new InvalidProgramException("Service not initialized. Please call ConnectToDeviceAsync method before!");

            }

            if (device.ConnectionState == DeviceConnectionState.Disconnected)
            {
                throw new InvalidOperationException("Device is not connected!");
            }
        }

        public async Task ConnectToDeviceAsync()
        {
            device = await registryManager.GetDeviceAsync(this.configuration.GetValue<string>("DEVICE_ID"));

            if (device == null)
            {
                throw new InvalidOperationException("Device is not present in the registry!");
            }

            if (device.ConnectionState == DeviceConnectionState.Disconnected)
            {
                throw new InvalidOperationException("Device is not connected!");
            }
        }

        public async Task GoForward()
        {
            await this.SendMethod(nameof(GoForward));
        }

        public async Task GoBackward()
        {
            await this.SendMethod(nameof(GoBackward));
        }

        public async Task Break()
        {
            await this.SendMethod(nameof(Break));
        }

        public async Task TurnLeft()
        {
            await this.SendMethod(nameof(TurnLeft));
        }

        public async Task TurnRight()
        {
            await this.SendMethod(nameof(TurnRight));
        }

        private async Task SendMethod(string methodName)
        {
            this.EnsureIsConnected();
            
            var response = await this.serviceClient.InvokeDeviceMethodAsync(
                deviceId: this.device.Id, 
                new CloudToDeviceMethod(methodName));
        }
    }

Le code de la fonction correspond alors à transcrire les intentions en déclenchement des méthodes du service que j’ai connecté :

        [Function("joyit_car_facade")]
        public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
            FunctionContext executionContext)
        {
            ILogger log = executionContext.GetLogger("joyit_car_facade");

            using (var reader = new StreamReader(req.Body))
            {
                var data = await reader.ReadToEndAsync();
                var query = JsonConvert.DeserializeObject<GoogleDialogFlowQuery>(data);

                var dialogResponse = GoogleDialogFlowResponse.FromQuery(query);

                await vehicleService.ConnectToDeviceAsync();

                switch (query.Intent.Name)
                {
                    case "actions.intent.MAIN":
                        break;
                    case "GoForward":
                        await vehicleService.GoForward();
                        break;
                    case "GoBackward":
                        await vehicleService.GoBackward();
                        break;
                    case "TurnLeft":
                        await vehicleService.TurnLeft();
                        break;
                    case "TurnRight":
                        await vehicleService.TurnRight();
                        break;
                    case "Break":
                        await vehicleService.Break();
                        break;
                    default:                
                        var response = req.CreateResponse(HttpStatusCode.NotFound);

                        await response.WriteStringAsync($"Intent {query.Intent.Name} is not handled!");

                        return response;
                }

                dialogResponse.Prompt.FirstSimple = new Simple
                {
                    Speech = "Ok"
                };

                dialogResponse.Prompt.Override = false;

                var httpResponse = req.CreateResponse(HttpStatusCode.OK);
                await httpResponse.WriteAsJsonAsync(dialogResponse);

                return httpResponse;
            }
        }

Conclusion

J’ai maintenant réussi à déclencher des actions sur ma voiture en utilisant mon Google Home avec une commande simple :

  • « Parler avec Mini voiture »
  • « Avancer »
  • « Reculer »
  • « Tourner à gauche »
  • ….

Après pour être très franc avec vous, ne vous attendez pas à avoir des déclenchements instantanées. Dans mes tests, les méthodes directes mettent à elles toutes-seules plusieurs secondes pour se déclencher sur l’appareil (5-20 sec). Je crois savoir que la connexion de l’appareil sur une Gateway transparente et le déclenchement des Device Method sur la gateway offre une réduction substantielle du délai de déclenchement et offrirai donc une réactivité plus accrue, mais j’avoue ne pas l’avoir testé et ne peux donc pas le confirmer.

Cependant, ce n’est pas très grave puisque pour que mon POC, j’ai préféré l’utilisation d’une télécommande BLE sur téléphone afin de contrôler au plus près la voiture.

Nous allons voir cela dans le prochain article :

https://kbeaugrandblog.wordpress.com/2021/05/08/tutoriel-de-la-voiture-connectee-azure-sphere-partie-4

3 commentaires sur “Mon Tutoriel de la voiture connectée Azure Sphere – Partie 3

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s