From d7683613c7efa817939527bc868e97a95e385300 Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Tue, 6 Sep 2022 18:41:54 -0700 Subject: [PATCH 1/2] Adding disclaimers on Apple Silicon --- 2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md | 2 ++ 2-farm/lessons/5-migrate-application-to-the-cloud/README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md index a9569aa7..ec7f3d6f 100644 --- a/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md +++ b/2-farm/lessons/4-migrate-your-plant-to-the-cloud/README.md @@ -344,6 +344,8 @@ For now, you won't be updating your server code. Instead you can use the Azure C The contents of the `payload` will match the message sent by your IoT device. + > At the time of writing, the `az iot` extension is not fully working on Apple Silicon. If you are using an Apple Silicon device, you will need to monitor the messages a different way, such as using the [Azure IoT Tools for Visual Studio Code](https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-vscode-iot-toolkit-cloud-device-messaging). + 1. These messages have a number of properties attached to them automatically, such as the timestamp they were sent. These are known as *annotations*. To view all the message annotations, use the following command: ```sh diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md index 028cef72..9785c78b 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md @@ -68,6 +68,8 @@ Functions apps consist of one or more *triggers* - functions that respond to eve ### Task - install the Azure Functions tooling +> At the time of writing, the Azure Functions code tools are not fully working on Apple Silicon with Python projects. You will need to use am Intel-based Mac, Windows PC, or Linux PC instead. + One great feature of Azure Functions is that you can run them locally. The same runtime that is used in the cloud can be run on your computer, allowing you to write code that responds to IoT messages and run it locally. You can even debug your code as events are handled. Once you are happy with your code, it can be deployed to the cloud. The Azure Functions tooling is available as a CLI, known as the Azure Functions Core Tools. From 6b5e82f01b679cdfe2c9adca6a14ce49a7427a7e Mon Sep 17 00:00:00 2001 From: Jim Bennett Date: Wed, 7 Sep 2022 14:20:41 -0700 Subject: [PATCH 2/2] Adding workarounds for broken func templates --- .../README.md | 72 ++++++++++++------- .../iot-hub-trigger/__init__.py | 5 +- .../iot-hub-trigger/function.json | 6 +- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md index 9785c78b..a5e634e7 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/README.md +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/README.md @@ -292,21 +292,19 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- This file will contain the following code: ```python - from typing import List import logging + import azure.functions as func - - def main(events: List[func.EventHubEvent]): - for event in events: - logging.info('Python EventHub trigger processed an event: %s', - event.get_body().decode('utf-8')) - ``` - The core of the trigger is the `main` function. It is this function that is called with the events from the IoT Hub. This function has a parameter called `events` that contains a list of `EventHubEvent`. Each event in this list is a message sent to IoT Hub, along with properties that are the same as the annotations you saw in the last lesson. - This trigger processes a list of events, rather than individual events. When you first run the trigger it wil process any unprocessed events on the IoT Hub (remember that messages are stored for a while so they are not lost if your application code is offline). After this it will generally process a list containing only one event, unless a lot of events are sent to the Hub in a short space of time. + def main(event: func.EventHubEvent): + logging.info('Python EventHub trigger processed an event: %s', + event.get_body().decode('utf-8')) + ``` + + The core of the trigger is the `main` function. It is this function that is called with the events from the IoT Hub. This function has a parameter called `event` that contains an `EventHubEvent`. Every time a message is sent to IoT Hub, this function is called passing that message as the `event`, along with properties that are the same as the annotations you saw in the last lesson. - The core of this function loops through the list and logs the events. + The core of this function logs the event. * `function.json` - this contains configuration for the trigger. The main configuration is in a section called `bindings`. A binding is the term for a connection between Azure Functions and other Azure services. This function has an input binding to an event hub - it connects to an event hub and receives data. @@ -323,6 +321,12 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- > 💁 The connection string cannot be stored in the `function.json` file, it has to be read from the settings. This is to stop you accidentally exposing your connection string. +1. Due to [a bug in the Azure Functions template](https://github.com/Azure/azure-functions-templates/issues/1250), the `function.json` has an incorrect value for the `cardinality` field. Update this field from `many` to `one`: + + ```json + "cardinality": "one", + ``` + 1. Update the value of `"connection"` in the `function.json` file to point to the new value you added to the `local.settings.json` file: ```json @@ -331,6 +335,12 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- > 💁 Remember - this needs to point to the setting, not contain the actual connection string. +1. The connection string contains the `eventHubName` value, so the value for this in the `function.json` file needs to be cleared. Update this value to an empty string: + + ```json + "eventHubName": "", + ``` + ### Task - run the event trigger 1. Make sure you are not running the IoT Hub event monitor. If this is running at the same time as the functions app, the functions app will not be able to connect and consume events. @@ -368,20 +378,38 @@ This will create a folder inside the `soil-moisture-trigger` folder called `iot- Each call to the function will be surrounded by a `Executing 'Functions.iot-hub-trigger'`/`Executed 'Functions.iot-hub-trigger'` block in the output, so you can how many messages were processed in each function call. - > If you get the following error: - - ```output - The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused. - ``` - - Then check Azurite is running and you have set the `AzureWebJobsStorage` in the `local.settings.json` file to `UseDevelopmentStorage=true`. - 1. Make sure your IoT device is running, You will see new soil moisture messages appearing in the Functions app. 1. Stop and restart the Functions app. You will see that it won't process messages previous messages again, it will only process new messages. > 💁 VS Code also supports debugging your Functions. You can set break points by clicking on the border by the start of each line of code, or putting the cursor on a line of code and selecting *Run -> Toggle breakpoint*, or pressing `F9`. You can launch the debugger by selecting *Run -> Start debugging*, pressing `F5`, or selecting the *Run and debug* pane and selecting the **Start debugging** button. By doing this you can see the details of the events being processed. +#### Troubleshooting + +* If you get the following error: + + ```output + The listener for function 'Functions.iot-hub-trigger' was unable to start. Microsoft.WindowsAzure.Storage: Connection refused. System.Net.Http: Connection refused. System.Private.CoreLib: Connection refused. + ``` + + Check Azurite is running and you have set the `AzureWebJobsStorage` in the `local.settings.json` file to `UseDevelopmentStorage=true`. + +* If you get the following error: + + ```output + System.Private.CoreLib: Exception while executing function: Functions.iot-hub-trigger. System.Private.CoreLib: Result: Failure Exception: AttributeError: 'list' object has no attribute 'get_body' + ``` + + Check that you have set the `cardinality` in the `function.json` file to `one`. + +* If you get the following error: + + ```output + Azure.Messaging.EventHubs: The path to an Event Hub may be specified as part of the connection string or as a separate value, but not both. Please verify that your connection string does not have the `EntityPath` token if you are passing an explicit Event Hub name. (Parameter 'connectionString'). + ``` + + Check that you have set the `eventHubName` in the `function.json` file to an empty string. + ## Send direct method requests from serverless code So far your Functions app is listening to messages from the IoT Hub using the Event Hub compatible end point. You now need to send commands to the IoT device. This is done by using a different connection to the IoT Hub via the *Registry Manager*. The Registry Manager is a tool that allows you to see what devices are registered with the IoT Hub, and communicate with those devices by sending cloud to device messages, direct method requests or updating the device twin. You can also use it to register, update or delete IoT devices from the IoT Hub. @@ -439,13 +467,7 @@ To connect to the Registry Manager, you need a connection string. 1. Remove the code from inside the `main` method, but keep the method itself. -1. When multiple messages are received, it only makes sense to process the last one as this is the current soil moisture. It makes no sense to process messages from before. Add the following code to get the last message from the `events` parameter: - - ```python - event = events[-1] - ``` - -1. Below this, add the following code: +1. In the `main` method, add the following code: ```python body = json.loads(event.get_body().decode('utf-8')) diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py index 8d1f5865..77eabe1d 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/__init__.py @@ -1,4 +1,3 @@ -from typing import List import logging import azure.functions as func @@ -8,9 +7,7 @@ import os from azure.iot.hub import IoTHubRegistryManager from azure.iot.hub.models import CloudToDeviceMethod -def main(events: List[func.EventHubEvent]): - event = events[-1] - +def main(event: func.EventHubEvent): body = json.loads(event.get_body().decode('utf-8')) device_id = event.iothub_metadata['connection-device-id'] diff --git a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json index 0117bdf5..cda6466f 100644 --- a/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json +++ b/2-farm/lessons/5-migrate-application-to-the-cloud/code/functions/soil-moisture-trigger/iot-hub-trigger/function.json @@ -3,11 +3,11 @@ "bindings": [ { "type": "eventHubTrigger", - "name": "events", + "name": "event", "direction": "in", - "eventHubName": "samples-workitems", + "eventHubName": "", "connection": "IOT_HUB_CONNECTION_STRING", - "cardinality": "many", + "cardinality": "one", "consumerGroup": "$Default", "dataType": "binary" }