Building an IoT solution with PeakUp to improve management of poultry houses
PeakUp, together with Microsoft, developed a “connected chicken” IoT solution using Azure services that will enable central monitoring and better control of conditions in poultry farms.
Technologies used:
- Azure IoT Hub
- Azure Stream Analytics
- Azure DocumentDB
- Azure Storage
- Azure Event Hubs
- Azure Functions
- Azure App Service/Web Apps
- Power BI Embedded
Core team:
- Faruk Celik – Senior Technical Evangelist, Microsoft
- Kadircan Toprakci – Chief Operations Officer, PeakUp
- Fatih Dogan – Cloud Application Developer, PeakUp
- Yasin Cilekci – Cloud UI Developer, PeakUp
- Fatih Mehmet Can – Cloud Application Developer, PeakUp
Customer profile
PeakUp Information Technologies is a Microsoft Partner based in Istanbul, Turkey, that offers private and public cloud solutions within the scope of Microsoft cloud technologies. While providing end-to-end cloud-based solutions from infrastructure to application on its consultancy side, PeakUp also offers IT-based technical training and role-based IT recruitment processes for companies.
Problem statement
We learned from PeakUp that they are working with one of the biggest poultry products manufacturers in Turkey: Banvit. Banvit was already using an “automation” system to derive environmental data for monitoring and triggering action items, but they knew that creating more intelligent systems backed with legacy data would help them increase work efficiency and service quality, and give them a deeper understanding of chicken nurturing.
In addition, that system was not interconnected with Banvit’s other poultry houses, and Banvit wants to centrally manage and see its poultry houses around the region. The project would include adding new sensors for measuring carbon dioxide and luminosity, which would enable Banvit to get much more information from their poultry houses. This can help them prevent chicken infertility due to problems such as CO2 levels and maintain the luminosity level in the poultry houses, which can affect productivity.
Proposed solution
After discussing all the details within the team, we decided to go with the architecture shown in the following image. In the technical delivery, we followed this architecture and successfully implemented it.
Technical delivery
PeakUp had started designing a solution for Banvit to modernize its old automation system, but they hadn’t yet finished the project. They had already decided on the devices to place in the poultry houses:
We started working together and got more details from Banvit. We learned that a poultry house is about 100 meters x 16 meters in size, as shown below:
We also learned from Banvit that measuring the CO2 level from one point would be sufficient for a poultry house. Together with engineers at PeakUp, we decided we could cover this area with 16 SensorTag devices to get readings of temperature, humidity, and light intensity from each. Because the area is big, we decided to place three Raspberry Pi 3 (RPI3) devices in a poultry house and name those devices Edge1, Center, and Edge2. The Center device would be placed in a central position and Edge1 and Edge2 would each be placed with eight SensorTag devices. The Center would not talk via Bluetooth to any SensorTag at all—it would measure the CO2 level only.
For Internet connectivity, we decided to place a 3G router with a SIM card and connect those three RPI3s to it via Wi-Fi. The SensorTag is a battery-powered device and one CR2032 button cell can run it for about one year.
After deciding on the devices in the field, we started discussing the services we’ll use in Azure:
PeakUp had already decided to run Raspbian on RPI3 devices because they want to run their code in a “Node-RED Flow.” In Microsoft Azure IoT SDK, connecting to Azure IoT Hub using Node-RED had already been documented here. Also, we found a ready-to-use “node” named node-red-node-sensortag in Node-RED’s Flows repository.
We ran some tests with Node-RED and managed to send data from a SensorTag to Azure IoT hub with no issues:
We plugged an Azure Stream Analytics job into IoT hub and then connected an output of Stream Analytics to their Power BI tenant to see the telemetry in real time. We also defined an Azure Blob storage as an output in Stream Analytics to have all the telemetry stored/archived for future data analytics operations.
The following is a sample Node-RED flow that we worked on:
The next step was to add another query in Stream Analytics to see if a value was exceeding the limits we defined. To achieve this, we used the Reference Data Join feature of Stream Analytics and defined another container in Blob storage to feed our telemetry thresholds from a CSV file into Stream Analytics as an input. We wrote the query and defined its output alias as “Alerts.”
with
[Stream] as (
select
deviceId as DeviceId,
id as SubDeviceId,
temperature.object as Temperature_Object,
temperature.ambient as Temperature_Ambient,
accelerometer.x as Accelerometer_X,
accelerometer.y as Accelerometer_Y,
accelerometer.z as Accelerometer_Z,
humidity.temperature as Humidity_Temperature,
humidity.value as Humidity_Value,
pressure as Pressure,
gyroscope.x as Gyroscope_X,
gyroscope.y as Gyroscope_Y,
gyroscope.z as Gyroscope_Z,
magnetometer.x as Magnetometer_X,
magnetometer.y as Magnetometer_Y,
magnetometer.z as Magnetometer_Z,
luxometer as Luxometer,
keys.one as Keys_One,
keys.two as Keys_Two,
ADC.A0 as ADC_A0,
System.Timestamp as CreatedAt
from [Hub]
where [ObjectType] IS NULL -- Filter out device info and command responses
),
[FilteredAlerts] as (
select
s.DeviceId,
s.SubDeviceId,
avg(s.Temperature_Object) as Temperature_Object,
avg(s.Temperature_Ambient) as Temperature_Ambient,
avg(t.Temperature_Ambient_Min) as Temperature_Ambient_Min,
avg(t.Temperature_Ambient_Max) as Temperature_Ambient_Max,
avg(s.Humidity_Temperature) as Humidity_Temperature,
avg(s.Humidity_Value) as Humidity_Value,
avg(s.Pressure) as Pressure,
avg(s.Luxometer) as Luxometer,
avg(t.Luxometer_Min) as Luxometer_Min,
avg(t.Luxometer_Max) as Luxometer_Max,
avg(s.ADC_A0) as ADC_A0,
avg(t.ADC_A0_Min) as ADC_A0_Min,
avg(t.ADC_A0_Max) as ADC_A0_Max,
System.Timestamp as CreatedAt
from Stream as s
join Thresholds as t
on s.DeviceId = t.DeviceId and s.SubDeviceId = t.SubDeviceId
where s.Temperature_Ambient < t.Temperature_Ambient_Min or s.Temperature_Ambient > t.Temperature_Ambient_Max
or s.Luxometer < t.Luxometer_Min or s.Luxometer > t.Luxometer_Max
or s.ADC_A0 < t.ADC_A0_Min or s.ADC_A0 > t.ADC_A0_Max
group by s.DeviceId, s.SubDeviceId, tumblingwindow(second,15)
)
select * into PowerBI from Stream
select * into Storage from Stream
select * into Alerts from FilteredAlerts
select * into [Storage-Alerts] from FilteredAlerts
This “Alerts” output from Stream Analytics would need to trigger a code somewhere so the system could take the necessary actions (such as turning on air conditioning to remove poor-quality air and bring fresh air into the poultry house, as well as send an email or an SMS message to those in charge of the poultry house). We selected Azure Functions for this purpose. This “serverless” approach would save money because we would only pay for the execution of our code when an alert situation occurred.
Azure Functions could be triggered by an Azure Service Bus/Event Hub. We decided to take data from Stream Analytics and pass it to an event hub and then the event hub would trigger our code in Azure Functions. We created an Azure Functions app and wrote the code that would send a message back to the device through the IoT hub in a cloud-to-device (C2D) direction:
#r "Microsoft.Azure.WebJobs.Extensions.SendGrid"
#r "PeakUp.ConnectedChicken.Context.dll"
#r "PeakUp.ConnectedChicken.Entities.dll"
#load "EHMessage.cs"
#load "CCMessage.csx"
#load "CCTakeAction.csx"
#load "CCTakeActionNode.csx"
using System;
using System.Text;
using System.Configuration;
using SendGrid.Helpers.Mail;
using Microsoft.Azure.Devices;
using Newtonsoft.Json;
using PeakUp.ConnectedChicken.Context;
public static void Run(string myEventHubMessage,TraceWriter log, out Mail mail)
{
string iotHubConnectionString = ConfigurationManager.AppSettings["IoTHubConnectionString"];
if(string.IsNullOrEmpty(iotHubConnectionString))
{
throw new NullReferenceException("IoTHubConnectionString");
}
EHMessage ehMessage = JsonConvert.DeserializeObject<List<EHMessage>>(myEventHubMessage)[0];
log.Info(JsonConvert.SerializeObject(ehMessage));
log.Info(iotHubConnectionString);
CCMessage<CCTakeAction> ccMessage = new CCMessage<CCTakeAction>("TakeAction");
ccMessage.DeviceID = ehMessage.deviceid;
ccMessage.SubDeviceID = ehMessage.subdeviceid;
ccMessage.Data = new CCTakeAction();
if(ehMessage.adc_a0 > 3000)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[CO2](High)", Value = ehMessage.adc_a0 });
}
if(ehMessage.luxometer < 5)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[Lux]{Stud}(Low)", Value = ehMessage.luxometer});
}
if(ehMessage.luxometer > 80)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[Lux]{Stud}(High)", Value = ehMessage.luxometer});
}
if(ehMessage.luxometer < 30)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[Lux]{Broiler}(Low)", Value = ehMessage.luxometer});
}
if(ehMessage.luxometer > 40)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[Lux]{Broiler}(High)", Value = ehMessage.luxometer});
}
if(ehMessage.temperature_ambient > 35)
{
ccMessage.Data.About.Add(new CCTakeActionNode { Name = "[Temperature](High)", Value = ehMessage.temperature_ambient });
}
ServiceClient serviceClient = ServiceClient.CreateFromConnectionString(iotHubConnectionString);
log.Info((serviceClient == null).ToString());
Message iotC2DMessage = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(ccMessage)));
var personalization = new Personalization();
personalization.AddTo(new Email("foo@bar.baz"));
var mailContent = new Content("text/html", JsonConvert.SerializeObject(ccMessage));
mail = new Mail();
mail.AddContent(mailContent);
mail.AddPersonalization(personalization);
var task = serviceClient.SendAsync(ehMessage.deviceid ?? "cc-peakup-1", iotC2DMessage);
task.Wait();
}
We needed to create a UI for Banvit users to interact with the system. We chose the Web Apps feature of Azure App Service to run/host it. We created an ASP.NET MVC app and deployed it to our Azure web app. We chose Azure DocumentDB as our device metadata storage because we need to store information for the poultry houses (name, location, longitude, latitude) and RPI3 devices (deviceID, connected sensortag MACIDs) so we can show it in the web app.
DocumentDB was the perfect match for that purpose because we needed to go with a NoSQL solution. This web UI also allows admin users to replace a broken SensorTag. We achieved this by sending the necessary configuration change information back to the RPI3 devices in a C2D direction. The configuration change message is received by the Node.JS/Node-RED
code running in RPI3 and the same Node.JS code puts the new information (such as new SensorTag MACID) into Node-RED Flow, which is a JSON file as well. The following is a mock-up for the UI:
Security
The SensorTag and RPI3 devices that we used in this system are all placed inside a poultry house that is already physically secured. For “connectivity security,” we are placing a 3G/Wi-Fi router in each poultry house and the access point is secured with WPS/WPS2. The telemetry data that we sent from RPI3 to Azure IoT Hub is going with AMQP over HTTPS protocol.
Conclusion
With this project, PeakUp strengthened their IoT muscles. They are planning to use this architecture design as a basis for their new IoT-focused projects.
The project has not been implemented in the poultry houses yet, but we will stay in touch with PeakUp to see the outcome of the system in the field.
General lessons
- With Azure IoT Hub, the “connected chicken” solution can cover dozens, hundreds, or even more poultry houses.
- Using Azure Functions reduced costs significantly.
- Being able to retrieve reference data from Azure Blob storage and feed it into Azure Stream Analytics is a powerful feature. Otherwise, we wouldn’t be able to let the end user set the thresholds of a telemetry type and we would need to hardcode the thresholds into the query, which would require us to recreate the Stream Analytics query and restart the Stream Analytics job.
- Connecting sensors to RPI3 took more time than we expected. We spent time connecting the CO2 sensor to the Raspberry Pi device due to issues with the ADC (Analog-to-Digital) converters used.
- If poultry house technicians/engineers need more sensors/telemetry (such as NH3), it will be easy to add to our “Center” RPI3 devices in a poultry house. We won’t need to redesign everything because our design is ready to handle much more telemetry data with no issues.
Opportunities going forward
- The current design does not cover an offline scenario, such as if Internet connectivity is lost. We are planning to implement a scenario in which we store the telemetry to the SD card of RPI3.
- This design can be implemented in many similar systems just by changing the device and sensors in the field.
- The reason we stored raw telemetry data in Azure Blob storage is for analysis. We are currently storing telemetry data in Blob storage indefinitely, which will change as PeakUp works on analyzing the data using Azure Machine Learning to find patterns and take preventive actions.
Additional resources
- Azure IoT SDK GitHub Repo
- Azure IoT Hub SDK - Node-RED “device” samples
- Azure Service Bus/Event Hubs
- Using reference data or lookup tables in a Stream Analytics input stream
- Reference Data JOIN (Azure Stream Analytics)
- Sample wiring example code showing how to connect and send data to Microsoft Azure IoT Hub with a TI CC3200 Launchpad
- Raspberry Pi 3
- Texas Instruments SensorTag
- Node-RED