Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

CloudCooker - IoT Temperature Controller

4.98/5 (23 votes)
11 Feb 2020CPOL21 min read 59.7K   660  
An Azure-driven service for Arduino based PID controllers designed for control and monitoring of grills and other cooking devices
The CloudCooker system is a hobbyist project to interface cooking devices to the cloud using a Microsoft Mobile App in an Azure App Service. The system will include Arduino-based controller devices, web services, web apps, and mobile/universal applications. This article discusses the creation of an Azure Mobile App backend for mobile apps, and the integration of an Arduino-based IoT device to the service.

Note: Previous versions of this article and code utilized Azure Mobile Services. The Azure Mobile Service offering was replaced by the Mobile App component of the Azure App Service on 3/24/2015. The article and code have been updated with new instructions.

Introduction

One of my hobbies is using my ceramic kamado grill. I've cooked and/or smoked ribs, briskets, pulled pork, chickens, roasts and my past 4 Thanksgiving turkeys.

A key benefit of using these types of grills vs. others is their ability to maintain a stable temperature. An adjustable vent on the bottom of the grill controls how much air is allowed inside. As the vent is opened, more O2 can reach the charcoal, causing it to burn hotter (and faster), and increasing the grill's temperature. Closing the vent will have the opposite effect.

Of course, since we're dealing with charcoal/wood of various sizes, different weather conditions, and other factors, maintaining the correct temperature can require frequent adjustments to the vent.

Adjusting the vent manually works fine when preparing hamburgers, steaks or other items which are on the grill for a short time. However, slow smoking and roasting can go on for quite a while, making manual control of the vent tiresome. For example, smoking a pork shoulder for pulled pork may take up to 18 hours to cook. A temperature spike or drop can ruin the entire meal. Unless we want to stay awake all night tweaking the vent, we need an automated solution.

Monitoring and Control

Fortunately, maintaining a constant temperature in a system like this can be solved through PID control. A PID Controller (proportional, integral, derivative) is designed to continuously calculate the difference between the process variable (PV) and setpoint (SP) as an error (e). It then adjusts the process through a manipulated variable to order to minimize the error.

For this project, a PWM fan is attached to the vent. The speed of the fan, and therefore the airflow pushed into the grill, is our manipulated variable.

(The fan and attachment pictured to the left is a commercial design from the Stoker system.)

There are products that implement these controllers for grills, but, they can be expensive and I wanted to build my own. I decided to use an Arduino Uno connected to thermocouples that could monitor the temperature of the grill along with the temperature of the food.

Creating a PID Controller using an Arduino requires implementing the PID algorithm in C. Given that I hadn't done this sort of numerical computing in some time, it was initially dauting.

$u(t)=MV(t)=K_{p}e(t)+K_{i}\int_{0}^{t}e(\tau)d\tau+K_{d}\frac{d}{dt}e(t)$

Luckily, the Arduino community had already implemented this algorithm. I was able to use the code provided by Brett Beauregard's PID Library, with some adaptation.

My first prototype worked adequately, but had limited controls and functionality. Changing the target temperature or checking the temperature of the food was tedious. I did not include a picture of this first attempt, because my soldering ability is atrocious and would professionally discredit me.

Why Connect to the Cloud?

In order to increase the capabilities of the device, I could have added additional physical controls and displays, but this would always limit me to the capabilities of the Arduino processor and the hardware connected to it. Instead, I chose to implement an IoT solution that connects the device to the Azure cloud for control, monitoring, and reporting. The advantages are:

  1. The Arduino device itself can be much simpler, only requiring basic LED indicators and controls, since it will be configured via cloud services.
  2. Cloud services can easily maintain a running history of the current cook, and maintain a history of previous cooks for reference.
  3. Estimating completion times, graphing temperatures with trend lines, scheduling, data sharing, and general workflow are all relatively simple to implement in a full .NET platform.
  4. I wanted the user interface to be more robust than a simple Arduino hosted web page. Azure App Services will allow the easy creation of mobile/universal applications for control and monitoring.

Terminology

In order to understand the code and APIs, I have outlined some of the terminology I used to define my objects and APIs. Your own tables and APIs should obviously be based on the goals of your project.

Devices are PID controllers or other temperature controllers used to control a grill, oven or other cooking instrument. Devices are added to the cloud system via a pairing process. Devices provide updates on temperature and status to the cloud.

Users are people who can log into the application. They can register and own Devices, view the status of shared Devices, and create Cooks.

Cooks are the events where food is actually prepared and cooked. Users can create Cooks and assign their Devices to them. Users can provide updates to their cooks through the cloud to change temperature and other settings.

Getting Started

The first step is to create an Azure App Service project. After signing up for Azure, you can create the App Service directly from the new Azure Management Portal at https://portal.azure.com. You will need Visual Studio 2013 Update 6. Since we are using the Azure App Services, which became available on March 24th, 2015, you will need to install the Azure SDK 2.5.1 or greater though the Microsoft Web Platform Installer.

From the Azure Management Portal, select "New" then "Web + Mobile", then "Mobile App". You will enter a unique name for your service, and then navigate through the required settings.

The UserDatabase section under "Package Settings" configures the database for your mobile service. Enter a name, username and password for your new database. Please note that these are not the credentials that your service will use to connect to the database, but your personal credentials for a db_owner account used for directly managing the database. Make sure to select the database pricing tier appropriate for your project.

The App Service Plan is the overall App Service that will contain your Mobile App, as well as any other service or App available through the Azure App Service. You can use the same name as your Mobile App name.

Image 3 Image 4
Create Service
Configure Database

From the Mobile App section, select "Pricing Tier". By clicking "View All", you can select the Free pricing tier, which is appropriate for testing and learning. Click "Create" to create your new Mobile App.

The new service will take a minute or two to instantiate. After this is complete, you can view the mobile service's Quick Start page. From there, you can download templates for your project.

Image 5

Service QuickStart Page

Select the platform you plan to develop your first mobile app for, then choose "Create a New App". You will be presented with a download link for your mobile service solution, and a link to download a pre-configured app for the platform you selected.

Note: This article discusses the mobile service and device code, and does not address app specifics. Since Azure Mobile Services is designed to work with major app platforms and HTML/JavaScript, you can create an app on your platform of choice.

After downloading the code, the first step is to strip out all the sample code. All of the "ToDo" list references should be removed. This includes:

  1. removing DataObject and Controller folder .cs files
  2. removing declaration of DbSet in mobileservicecontext.cs
  3. removing Seed code from webapiconfig.cs

At this point, the project is ready for you to create your data model and interfaces.

Creating Tables and Controllers

The quickest way to get the mobile service database ready is to use the Entity Framework code-first approach. I am generally wary of giving up total control to ORM systems, but for a project like this, it worked out well.

A code-first approach requires the creation of data object classes that represent data entities in the system. The entity object is a table in the database, and the properties of the class are the columns of the table. The Entity Framework itself will handle the creation of tables, identity columns, foreign keys and any junction/linking tables required.

You can create a entity object by right clicking on the DataObjects folder in your solution, and selecting "Add->Class". When prompted for the type, select the standard "Class" type, and name the .cs file as the name of your data object. You should keep in mind that Entity Framework naming conventions use "singular" object names. For example, the name of your class should be "Widget" and not "Widgets".

After adding the class, there are three steps to creating the DataObject for the mobile service:

  1. Add the mobile services namespace by adding a using directive for Microsoft.Azure.Mobile.Server
  2. Have the class inherit from the Azure mobile services EntityData
  3. Create properties of the class

The Device class is presented here as an example.

Device.cs

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Azure.Mobile.Server

namespace cloudcookerService.DataObjects
{
    /// A Device is a PID controller or other monitor that acts as an IoT device 
    /// to report information about the smoker, oven, sous vide, etc.
    public class Device : EntityData
    {

        public string SerialNumber { get; set; }
        public string SharedSecret { get; set; }
        public string Name { get; set; }

        public string DeviceTypeID { get; set; }
        public virtual DeviceType DeviceType { get; set; }

        public string OwnerID { get; set; }
        public virtual User Owner { get; set; }

        public string imageurl { get; set; }

        public ICollection<User> Users { get; set; }

        public ICollection<Cook> Cooks { get; set; }
    }
}

The Device object contains properties such as a serial number, and name, which are simply created as database columns. The class also contains a reference to a single DeviceType, and to a single User as the device's owner. These properties are created as columns with foreign keys to the appropriate tables. The ICollection properties implement many to many relationships with Users and Cooks, since a Device can be accessed by many Users and used in many Cooks over time. These properties are implemented as linking tables in the database.

A great tutorial on Entity Frameworks Code First is available from Microsoft from their Data Developer Center. There are other excellent guides on CodeProject and on the web.

The next step is to create controller classes for each of your data objects. Visual Studio makes this a relatively painless process. From your solution, right click on the "Controllers" folder and select "New Scaffolded Item". You will be presented with the "Add Scaffold" interface. Select "Microsoft Azure Apps Table Controller". If you do not see this option, ensure you have the latest Azure SDK installed.

Image 6

Add Scaffold Dialog

Next, you will be presented with the Add Controller Dialog. Select one of your classes, select the data context (there should only be one option) and use the default name.

Image 7

Add Controller Dialog

After clicking OK, the controller class is created. This automatically creates the standard GET/PATCH/POST/DELETE methods for each of your tables, and adds it to the mobile service.

At this point, the Mobile App Server is ready for use and can be run locally in Debug mode or published to Azure. All of the interfaces needed to interact with data in your application are available for use.

All in all, this is a very small amount of work for a scalable web service with a message bus, single-sign on capabilities, and push notification support built in. A project of this type built manually would typically start out as a simple web API, and then run into scaling problems down the road. Since all Azure App Service items are built with a message bus from the ground up, it can be easily transitioned off of the "free tier", and configured for auto-scaling to support a very large number of users without additional architecture work.

Seeding the Database (Optional)

Adding seed data is not technically required unless your data model depends on data to be present in a table. However, I would strongly recommend using the seed method to add startup/test data for ease in debugging and configuration. During initial development, you will tweak your entity model several times which will often rebuild your database, and having seed data will allow you to continue working on your applications without constantly repopulating the database manually.

As a side benefit, your seed code can immediately expose problems in your entity modeling, acting as test code for your system.

The Seed method is located in WebApiConfig.cs under App_Start. Your seed code should create objects using your dataobject classes defined earlier, and add them to the mobile service context. A sample is included below.

WebApiConfig.cs - Seed Method (Partial)

C#
protected override void Seed(cloudcookerContext context)
    {
        base.Seed(context);
         //Add users
        var Users = new List<User>
        {
             new User { Id = Guid.NewGuid().ToString("N"), 
                        FirstName = "John",   LastName = "Smith", 
                        Email="xxxxx@gmail.com" },
             new User { Id = Guid.NewGuid().ToString("N"), 
                        FirstName = "Test1",   LastName = "User1", 
                        Email="test1@cookercorp.com" },
             new User { Id = Guid.NewGuid().ToString("N"), 
                        FirstName = "Test2",   LastName = "User2", 
                        Email="test3@cookercorp.com" }
        };

        Users.ForEach(u => context.Users.AddOrUpdate(x=>x.Email,u));
  
        DeviceType TestDeviceType = new DeviceType { Id = Guid.NewGuid().ToString("N"), 
                                                   Name = "Arduino Prototype 1" };

        //Add devices
        Device BGEDevice = new Device { Id = Guid.NewGuid().ToString("N"), 
                         Name = "Big Green Egg Controller", 
                         SerialNumber = "12345ABCDE12345", 
                         SharedSecret = "2dd2ae03e96642b0b17743cd4d757f51", 
                         Owner = Users[0], DeviceType = TestDeviceType };
        BGEDevice.Users = new List<User> { };
        BGEDevice.Users.Add(Users[0]);
        context.Devices.AddOrUpdate(x=>x.SerialNumber, BGEDevice);

The Web App created for use by your mobile apps includes a handy web page for using and testing your APIs. You will see this page both in Debug mode or by navigating to your deployed cloud service at servicename-code.azurewebsites.net.

Image 8

Mobile Service Home Screen

In debug mode, click "Try it out" to see your APIs. If you are accessing your published service on Azure, you will be prompted with HTTP basic authentication. To see your APIs, enter anything for the username, and your Application Key for the password. This key is available on the portal.

Image 9

API Documentation Screen

From this page, you can select any of these APIs, view sample JSON objects, and actually GET/POST/PATCH/DELETE to the site through an interactive web-based interface. I found this an invaluable tool for debugging requests, testing queries, and verifying that responses were coming through as expected.

Security with Azure and IoT Devices

Azure App Services includes authentication support for Google, Facebook, Microsoft, and Azure Active Directory, as well as support for custom authentication. The implementation of authentication can be as simple as signing up with the identity provider, obtaining the appropriate keys, and configuring it on the portal under the Identity tab.

There are many excellent guides to this online, including Microsoft's official guides. If you chose to implement authorization with Azure App Service or your apps, you would add the [AuthorizeLevel(AuthorizationLevel.User)] attribute to the table controller classes or methods you needed to restrict.

CookControler.cs (Partial)

C#
using System.Linq;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.OData;
using Microsoft.Azure.Mobile.Server;
using Microsoft.Azure.Mobile.Security
using cloudcookerService.DataObjects;
using cloudcookerService.Models;

namespace cloudcookerService.Controllers
{
    [AuthorizeLevel(AuthorizationLevel.User)] 
    public class CookController : TableController<Cook>
    {
        protected override void Initialize(HttpControllerContext controllerContext)
        {
            base.Initialize(controllerContext);
            cloudcookerContext context = new cloudcookerContext();
            DomainManager = new EntityDomainManager<Cook>(context, Request, Services);
        }

The included authentication model works well for apps and web sites that directly support Azure app services. However, we would not want to require an Arduino-based device to sign on with a Microsoft or Google account, and it would be very complex to have an Arduino handle cookies or pass tokens.

On the other hand, there are obvious security concerns with simply opening up table controllers for direct anonymous access.

The solution implemented for this project requires the device to transmit its serial number and a shared secret to make requests to the cloud service. This is used both to identify and authenticate the device. When a user adds a new IoT controller device to the CloudCooker system, they will initiate a pairing process to register the device.

For example, the user could enter the serial number of the device in an app, and then click a physical "Pairing" button on the device itself. When the device requests pairing, the web service will verify that serial number has been added by the user, confirm the pairing in the app UI, and if approved, a shared secret will be generated and returned to the Arduino device for use in future requests.

For this prototype, a pre-defined shared secret was used, and added directly to the seed code and the device's EEPROM.

Another security concern is that Arduino devices do not have the processing power needed for SSL encryption. Connections to the server must therefore take place over standard HTTP. I considered implementing a less complex encryption or even an obfuscation of the data, but concluded that this would serve no purpose from a pure security perspective.

Ultimately, I accepted the engineering trade-off of decreased security to use a lower-cost device. Putting aside software best practices - the device is transmitting turkey temperatures, not credit card numbers. It wasn't worth upgrading the hardware for this prototype.

Of course, the security model and encryption levels you support will depend on the capabilities of your IoT device, and your acceptable risk level.

Custom APIs for IoT Device

Azure App Services supports custom APIs that we will utilize to implement the interfaces used by devices. In this example, we will start with the interface to directly update the cloud service. This will be used by the Arduino device to update the current temperature values of the grill and food.

The first step is to create the classes for the data transfer objects. Note that the DirectUpdate class does not inherit from the Azure Mobile Server entity data class, as this is not meant to be a table in the database.

For this API, two classes were created. DirectUpdate is the class used to receive data from the device, and DirectUpdateResponse is used to return update data to the device, if needed.

DirectUpdate.cs

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace cloudcookerService.DataObjects
{
    public class DirectUpdate
    {
        public string SerialNumber { get; set; }
        public string SharedSecret { get; set; }
        public string CookConfigurationID { get; set; }
        public int CurrentTemperature { get; set; }
        public int CurrentFoodTemperature { get; set; }
        public bool ControlElementActive { get; set; }

    }
    public class DirectUpdateResponse
    {
        public string CookConfigurationID { get; set; }
        public int SetpointTemperature { get; set; }
        public int TargetFoodTemperature { get; set; }
    }
}

Next, a controller was created to accept post requests from the Arduino device. While the Azure services framework handled our table controllers automatically, the custom APIs require more manual work. The DirectUpdate post request implements the following logic:

  1. Validate that the Device is registered with a valid serial number and shared secret, and return a HTTP status code of 403 if not authorized.
  2. Check to ensure that the Device is reporting data for the correct Cook and CookConfiguration. If so, update the database with new information from the device.
  3. If the Device is using the correct configuration, return a status code of 201.
  4. If the Device has no current configuration, and should not be in use, return a status code fo 204.
  5. If the Device needs to change its configuration, (for example, if the temperature of the grill needs to change), return a new DataUpdateResponse, and an HTTP status code of 250.

Returning a non-standard HTTP status code may seem like an unusual decision, but it is a quicker way of telling the device what it needs to do versus processing a complete JSON response each time.

DirectUpdateController.cs

C#
public async Task<HttpResponseMessage><httpresponsemessage> 
  PostDeviceUpdate(DirectUpdate item) {
    HttpResponseMessage r = new HttpResponseMessage();

    //Validate Device
    Device device = context.Devices.Include("Cooks.Configurations").Where
  (x => x.SerialNumber == item.SerialNumber).Where
  (x => x.SharedSecret == item.SharedSecret).FirstOrDefault();
    if (device == null) {
        r.StatusCode = (HttpStatusCode)403;
        r.Content = new StringContent("Invalid Serial Number or Device not paired.");
        return r;
    }

    //Valid Device, so:
    CookConfiguration updateCC = await context.CookConfigurations.FindAsync
                               (item.CookConfigurationID);
    if (updateCC != null) 
    {
        var newDeviceUpdate = new DeviceUpdate {
            Id = Guid.NewGuid().ToString("N"),
            CookConfiguration = updateCC,
            reportDate = DateTime.Now,
            currentTemperature = item.currentTemperature,
            currentFoodTemperature = item.currentFoodTemperature,
            ControlElementActive = item.ControlElementActive
        };
        context.DeviceUpdates.Add(newDeviceUpdate);
        context.SaveChanges();
    }

    CookConfiguration currentCC = getCookConfigurationForDevice(device);
    if (currentCC==null) {
        r.StatusCode = (HttpStatusCode)204;
        return r;
    }

    if (currentCC == updateCC) {
        r.StatusCode = (HttpStatusCode)201;
        return r;
    }
    else {
        DirectUpdateResponse directupdateresponse = new DirectUpdateResponse { 
            CookConfigurationID = currentCC.Id, 
            setpointTemperature = currentCC.setpointTemperature, 
            targetFoodTemperature = currentCC.targetFoodTemperature };
        r.StatusCode = (HttpStatusCode)250;
        r.Content = new StringContent(JsonConvert.SerializeObject(directupdateresponse));  
       return r;
    }

}
</httpresponsemessage>

Connecting Arduino with Azure App Services

Arduino C development is very different than .NET development or any managed language intended to run on standard servers or PCs. The ATMega328 found in the Arduino Uno has only a fraction of the computing power of a Intel-based processor, and only 2k of SRAM. This means that if the code requests more than 2k of memory, you will experience memory allocation errors or unexpected behavior. Even if you are comfortable with C-style memory management, using malloc() and free() on pointers only as needed, memory fragmentation will accumulate and the 2k limit will be reached faster than you think...

In most Arduino sketches, small local variables like iterators are created in the chip's register and are perfectly fine to use. It is only when we are creating large strings like those used in HTTP requests that memory allocation becomes an issue

In order to deal with this, we have to "break rules" that you may have become accustomed to working with higher-level languages. For example, rather than declaring strings on the fly as needed, we will create one global string buffer and reuse it. The memory will be reserved on startup and use a predictable amount of memory,

Note: One way to save SRAM is to store const string variables in the space reserved for the program code itself (PROGMEM). I deliberately chose not to take advantage of this so the code for the article would be simpler. When I re-add PID and temperature monitoring support back in, I will be using this. More information on storing variables in PROGMEM space can be found here.

There are a variety of libraries available for Arduino that can handle JSON, http client requests and other actions, but these libraries use a significant amount of memory both for the program itself and at runtime. Creating the request manually is the leanest method of implementation.

Creating Azure HTTP Request

C#
if (client.connect(MobileServiceHostName, 80)) 
{
    Serial.println("Connected to Azure");
    //Manually create JSON in global buffer for POST Request. This is much less expensive 
    //than using JSON Libraries.
    sprintf(buffer, "{");
    sprintf(buffer + strlen(buffer), "\"serialNumber\": \"%s\",",serialNumber);
    sprintf(buffer + strlen(buffer), "\"sharedSecret\": \"%s\",",sharedSecret);
    sprintf(buffer + strlen(buffer), "\"cookConfigurationID\": \"%s\",",
                                    currentCookConfigurationID);
    sprintf(buffer + strlen(buffer), "\"currentTemperature\": \"%d\",",currentTemperature);
    sprintf(buffer + strlen(buffer), "\"currentFoodTemperature\": \"%d\",",
                                    currentFoodTemperature);
    sprintf(buffer + strlen(buffer), "\"controlElementActive\": \"%s\"",
                                    controlElementActive ? "true" : "false");
    sprintf(buffer + strlen(buffer), "}");
    //

    // HTTP REQUEST
    client.println("POST /api/DirectUpdate HTTP/1.1");
    client.print("Host: ");
    client.println(MobileServiceHostName);      
    //ZUMO apparently stands for aZUre MObile services.
    client.print("X-ZUMO-APPLICATION: ");
    client.println(MS_ApplicationKey);

    client.println("Content-Type: application/json");
      
    // Content length
    client.print("Content-Length: ");
    client.println(strlen(buffer));
 
    // End of headers
    client.println();
 
    // Request body
    client.println(buffer);

If you run into problems creating the HTTP request, I recommend using the built-in Azure App mobile server test site referenced above to check your work. By using any browser's developer/F12 tools, you can run any of your APIs and see the raw HTTP request and response, and ensure that your code is following the same format.

The response to the POST request is also handled manually, by using the TextFinder library. This implementation is not ideal, as adding new elements to the response may break the interpretation of the values, unlike a true JSON reader. In the future, I may write a simple JSON reader library which provides more flexibility in handling responses without the memory overhead of libraries that create C structs to manage data translation.

Handling HTTP Response

C#
if (client.connect(MobileServiceHostName, 80)) 
{
    //Search for HTTP status code
    finder.find("HTTP/1.1");
    int statuscode = finder.getValue();
      
    Serial.print("Statuscode = ");
    Serial.println(statuscode);
    //Depending on status code, set the appropriate LED color and 
    //optionally continue to read the response.
    switch(statuscode)
    {
        case 200:
            //This should not happen unless the service is malfunctioning.
            setNetworkStatusLED(colorYellow);            
            client.stop();
            break;
        case 201:
            //Update received.
            setNetworkStatusLED(colorGreen);     
            client.stop();
            break;
        case 204:
            //Device is not configured for a cookconfiguration.        
            setNetworkStatusLED(colorWhite);
            currentCookConfigurationID[0]=0;
            setpointTemperature=0;
            targetFoodTemperature=0;
            client.stop();
            break;        
        case 250:
            //Device has a new configuration.
            //This uses the TextFinder library to load values from the returned JSON object.
            //This code is fragile, but has the smallest memory footprint.
            finder.find("\"CookConfigurationID\"");
            finder.getString("\"","\"",currentCookConfigurationID,200);
            setpointTemperature = finder.getValue();
            targetFoodTemperature= finder.getValue();
            client.stop();
            break;        
        case 403:
            //Unrecognized device or invalid shared secret.
            setNetworkStatusLED(colorBlue);
            currentCookConfigurationID[0]=0;
            setpointTemperature=0;
            targetFoodTemperature=0;     
            client.stop();
            break;                
        default:
             //All other responses are bad.
            setNetworkStatusLED(colorRed); 
            client.stop();
    }
} 
else 
{
    Serial.println("Connection to Azure Failed");
    setNetworkStatusLED(colorRed);
}

Lessons Learned

My original intention was to keep the actual PID controller device as simple as possible. The goal was to rely on web services to communicate settings and updates, requiring only simple led indicators on the device itself. Although I was using an Arduino Uno, the ultimate goal of using a prototyping platform should be to build a final product using the fewest/cheapest components required. There are three main issues I encountered:

First, while there are many useful guides on the internet to interface an Arudino with RGB LEDs and thermocouples, adding several of each along with networking interfaces used up the available pins in the ATMega328. I will need to use multiplexers to manage multiple thermocouples and LEDs, increasing the complexity of the project.

Secondly, while my prototype used a standard Arduino ethernet shield, the end product will use WiFi. To avoid hardcoding settings on the device, I will need to provide a method to configure the SSID and network password. Without a hardware interface, I can attempt to do this by having the Arduino directly communicate with an mobile app by switching its WiFi interface temporarily to ad-hoc mode, reading new settings, then switching back to infrastructure mode. Again, this increases the complexity of the project.

Lastly, the limited memory (2k) of the ATmega328 chip in the Arduino duo had not been a problem with my previous projects, but I struggled to add code for the PID Controller, network communication and managing JSON without hitting limits. I also planned to build in the capability to record and queue device updates so that reading sensors could be conducted more frequently without requiring server hits each time, but memory limitations made that difficult as well.

I wouldn't have run into these problems while using a more chip with more memory and pins like an Atmega644p or Atmega1284p. Switching to these chips would not have noticeably increased the cost of the final product, and would have allowed me to take advantage of libraries to simplify the code. That said, if any more experienced Arduino developers have suggestions on how I can reduce my memory usage, please let me know.

Of course, I could just ignore the costs, integrate a Raspberry Pi or some other microcomputer and solve all of these problems at once, but that is the coward's way out.

Future Updates

Now that the services and devices are up and operational, the next step(s) will be the creation of applications to manage and control the devices. Azure App Services will allow me to implement a mobile or web app on any platform I choose. This is a personal dilemma, since I own Android devices, and my wife uses an iPhone. It has been a constant rift between us, but if I need to learn objective-C to preserve our marriage, then so be it.

One of the reasons that I have not started on a prototype app is the recent announcement by Microsoft that the Raspberri Pi 2 will support Windows 10 apps. A Raspberry Pi 2 with a 5-7" touchscreen running a C# Universal app would be a dream controller for the kitchen counter. It could display current status, display a graph of progress, and review past cooking events, and I can build it to be rugged enough to handle kitchen/outdoor use.

In the meantime, since the system is cloud-based and not limited to the capabilities of a tiny microprocessor, the opportunities for advanced control and analysis are extensive. Some of my initial ideas are:

  • Email/In-App Notifications of food status.
  • Recipe steps. For example, cooking a roast at a low temp until it reaches a target internal temperature, then increasing the fire for a crust automatically.
  • Using historical data and correlating weather data to predict completion times, especially for slow-cooking meats like brisket which do not increase in temperature linearly.
  • Remote PID controller tuning, and saving tuning presets for specific recipies, weather conditions, and fuel types.

My friends often joke that dinner at my house can be served anywhere between 3pm and 11pm. I plan to allow users to take bets on when the meal will actually be done. Hopefully this will distract them from the fact they're hungry because the food is taking longer to cook than I expected...

Thanks for reading, and I'd appreciate any comments or suggestions. If people are interested, I could write a follow-up after applications are up and running.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)