65.9K
CodeProject 正在变化。 阅读更多。
Home

CloudCooker - 物联网温度控制器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (21投票s)

2015年3月20日

CPOL

21分钟阅读

viewsIcon

77405

downloadIcon

723

一项基于 Arduino PID 控制器的 Azure 驱动的服务,用于烧烤炉和其他烹饪设备的控制和监控。

注意:本文和代码的早期版本使用了 Azure Mobile Services。Azure Mobile Service 已于 2015 年 3 月 24 日被 Azure App Service 的 Mobile App 组件取代。本文和代码已更新,包含新的说明。

引言

我的爱好之一是使用我的陶瓷卡马多烤架。我曾烹饪/熏制过排骨、牛腩、手撕猪肉、鸡肉、烤肉以及我过去 4 次感恩节火鸡。

与烤架相比,使用这类烤架的一个主要优点是它们能够保持稳定的温度。烤架底部的可调节通风口控制着允许进入的空气量。随着通风口的打开,更多的 O2 可以到达木炭,使其燃烧得更热(也更快),从而提高烤架的温度。关闭通风口则有相反的效果。

当然,由于我们处理的是各种尺寸的木炭/木材、不同的天气条件和其他因素,因此保持正确的温度可能需要频繁调整通风口。

手动调节通风口在准备汉堡、牛排或其他短时间烧烤的食物时效果很好。然而,慢熏和慢烤可能需要很长时间,手动控制通风口会很累人。例如,熏制一块猪肩肉做手撕猪肉可能需要长达 18 小时才能煮熟。温度的升高或下降可能会毁掉整顿饭。除非我们想整夜不睡地调整通风口,否则我们需要一个自动化解决方案。

监控与控制

幸运的是,像这样的系统中的恒温可以通过 PID 控制来解决。PID 控制器(比例-积分-微分)旨在持续计算过程变量 (PV) 和设定点 (SP) 之间的差值,即误差 (e)。然后,它通过操纵变量来调整过程,以最小化误差。

在此项目中,一个 PWM 风扇连接到通风口。风扇的速度,因此进入烤架的空气流量,是我们的操纵变量。

(左图所示的风扇和附件是来自 Stoker 系统的商业设计。)

市面上有一些产品实现了这些烤架控制器,但它们可能很昂贵,我想自己制作一个。我决定使用一个 Arduino Uno 连接到 热电偶,它可以监测烤架的温度以及食物的温度。

使用 Arduino 创建 PID 控制器需要用 C 实现 PID 算法。鉴于我有一段时间没有进行过这种数值计算了,最初这似乎令人望而生畏。

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

幸运的是,Arduino 社区已经实现了这个算法。我能够使用 Brett Beauregard 的 PID Library 提供的代码,并进行了一些改编。

我的第一个原型工作得还算可以,但控制和功能有限。更改目标温度或检查食物温度很麻烦。我没有包含第一次尝试的照片,因为我的焊接技术很糟糕,会让我丢掉工作。

为什么要连接到云?

为了增强设备的功能,我可以添加额外的物理控件和显示器,但这总是会将我限制在 Arduino 处理器及其连接硬件的功能范围内。相反,我选择实施一个物联网解决方案,将设备连接到 Azure 云进行控制、监控和报告。其优点是

  1. Arduino 设备本身可以更简单,只需基本的 LED 指示灯和控件,因为它将通过云服务进行配置。
  2. 云服务可以轻松维护当前烹饪的历史记录,并维护先前烹饪的历史记录以供参考。
  3. 估算完成时间、绘制带有趋势线的温度图、计划、数据共享以及一般工作流程都可以在完整的 .NET 平台上相对轻松地实现。
  4. 我希望用户界面比简单的 Arduino 托管网页更强大。Azure App Services 将允许轻松创建用于控制和监控的移动/通用应用程序。

术语

为了理解代码和 API,我概述了定义对象和 API 时使用的一些术语。您自己的表和 API 当然应该基于您项目的目标。

设备是指 PID 控制器或其他用于控制烤架、烤箱或其他烹饪设备的温度控制器。设备通过配对过程添加到云系统中。设备向云提供温度和状态的更新。

用户是指可以登录应用程序的人。他们可以注册并拥有设备,查看共享设备的状态,以及创建烹饪记录。

烹饪记录是指实际准备和烹饪食物的事件。用户可以创建烹饪记录并将其设备分配给它们。用户可以通过云更新他们的烹饪记录以更改温度和其他设置。

入门

第一步是创建一个 Azure App Service 项目。在 注册 Azure 后,您可以直接从新的 Azure 管理门户创建 App Service,网址为 https://portal.azure.com。您需要 Visual Studio 2013 Update 6。由于我们使用的是 Azure App Services(于 2015 年 3 月 24 日推出),您需要通过 Microsoft Web Platform Installer 安装 Azure SDK 2.5.1 或更高版本。

在 Azure 管理门户中,选择“新建”,然后选择“Web + 移动”,再选择“移动应用”。您将为服务输入一个唯一的名称,然后通过所需设置进行操作。

包设置”下的 UserDatabase 部分配置您的移动服务的数据库。为您的新数据库输入名称、用户名和密码。请注意,这些不是您的服务将用于连接数据库的凭据,而是您用于直接管理数据库的 db_owner 账户的个人凭据。请务必选择适合您项目的数据库定价层。

App Service Plan 是将包含您的 Mobile App 以及 Azure App Service 提供的任何其他服务或应用程序的整体 App Service。您可以使用与 Mobile App 名称相同的名称。

创建服务
配置数据库

在“移动应用”部分,选择“定价层”。通过点击“查看全部”,您可以选择免费定价层,这适合测试和学习。点击“创建”以创建您的新移动应用。

新服务将需要一两分钟来实例化。完成后,您可以查看移动服务的快速入门页面。从那里,您可以下载项目的模板。

服务快速入门页面

选择您计划开发第一个移动应用程序的平台,然后选择“创建新应用”。您将看到一个下载链接,用于下载您的移动服务解决方案,以及一个下载链接,用于下载您所选平台的预配置应用程序。

注意:本文讨论的是移动服务和设备代码,不涉及应用程序的具体细节。由于 Azure Mobile Services 设计用于与主要应用程序平台和 HTML/JavaScript 配合使用,因此您可以在自己选择的平台上创建应用程序。

下载代码后,第一步是删除所有示例代码。应删除所有“待办事项”列表的引用。这包括

  1. 删除 DataObject 和 _Controller_ 文件夹下的 _.cs_ 文件
  2. 删除 _mobileservicecontext.cs_ 中 DbSet 的声明
  3. 删除 _webapiconfig.cs_ 中的 Seed 代码

此时,项目已准备好供您创建数据模型和接口。

创建表和控制器

使移动服务数据库准备就绪的最快方法是使用 Entity Framework 的 Code First 方法。我通常谨慎对待将完全控制权交给 ORM 系统,但对于这样的项目,它效果很好。

Code First 方法需要创建数据对象类来表示系统中的数据实体。实体对象是数据库中的一个表,类的属性是表的列。Entity Framework 本身将负责创建表、标识列、外键以及任何必需的连接/链接表。

您可以通过右键单击解决方案中的 _DataObjects_ 文件夹,然后选择“添加 -> 类”来创建一个实体对象。当提示输入类型时,选择标准的“”类型,并将 _.cs_ 文件命名为数据对象名称。您应该记住,Entity Framework 的命名约定使用“单数”对象名称。例如,您的类的名称应该是“Widget”,而不是“Widgets”。

添加类后,创建移动服务的 DataObject 需要三个步骤

  1. 通过添加 Microsoft.Azure.Mobile.Serverusing 指令来添加移动服务命名空间
  2. 使类继承自 Azure 移动服务的 EntityData
  3. 创建类的属性

此处以 Device 类为例。

Device.cs

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; }
    }
}

Device 对象包含序列号和名称等属性,这些属性被简单地创建为数据库列。该类还包含对单个 DeviceType 和作为设备所有者的单个用户的引用。这些属性被创建为带有相应表外键的列。ICollection 属性实现了与 UsersCooks 的多对多关系,因为一个 Device 可以被多个 Users 访问,并且随着时间的推移在多个 Cooks 中使用。这些属性在数据库中被实现为链接表。

关于 Entity Framework Code First 的一个很棒的教程可以在 Microsoft 的 Data Developer Center 中找到。在 CodeProject 和网络上还有其他优秀的指南。

下一步是为每个数据对象创建控制器类。Visual Studio 使这个过程相对轻松。在解决方案中,右键单击“_Controllers_”文件夹,然后选择“新建生成的项”。您将看到“添加生成项”界面。选择“Microsoft Azure Apps 表控制器”。如果您没有看到此选项,请确保已安装最新的 Azure SDK。

添加生成对话框

接下来,您将看到 **添加控制器** 对话框。选择一个类,选择数据上下文(应该只有一个选项),并使用默认名称。

添加控制器对话框

点击 **确定** 后,控制器类即被创建。这会自动为每个表创建标准的 GET/PATCH/POST/DELETE 方法,并将其添加到移动服务中。

此时,Mobile App Server 已准备就绪,可以在 Debug 模式下本地运行或发布到 Azure。您应用程序所需的所有与数据交互的接口都已可用。

总而言之,对于一个可扩展的 Web 服务,它内置了消息总线、单一登录功能和推送通知支持,这项工作量非常小。手动构建的此类项目通常会从一个简单的 Web API 开始,然后在后期遇到扩展性问题。由于所有 Azure App Service 项都是从头开始构建消息总线的,因此可以轻松地将其从“免费层”迁移出来,并配置为自动缩放以支持大量用户,而无需进行额外的架构工作。

填充数据库(可选)

除非您的数据模型依赖于表中存在的数据,否则技术上不需要添加种子数据。但是,我强烈建议使用 Seed 方法来添加启动/测试数据,以便于调试和配置。在初始开发过程中,您多次调整实体模型,这通常会重建您的数据库,而拥有种子数据将允许您继续处理应用程序,而无需不断手动重新填充数据库。

作为一项附加好处,您的 Seed 代码可以立即暴露实体建模中的问题,充当您系统的测试代码。

Seed 方法位于 _App_Start_ 下的 _WebApiConfig.cs_ 中。您的 Seed 代码应该使用您之前定义的 dataobject 类创建对象,并将它们添加到移动服务上下文中。下面提供了一个示例。

WebApiConfig.cs - Seed 方法(部分)

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);

为您的移动应用程序创建的 Web 应用程序包含一个方便的网页,用于使用和测试您的 API。您将在 Debug 模式下看到此页面,或者通过访问您已部署的云服务 servicename-code.azurewebsites.net 来查看。

移动服务主屏幕

在 Debug 模式下,点击“试用”即可查看您的 API。如果您正在访问 Azure 上已发布的 à;服务,系统将提示您进行 HTTP 基本身份验证。要查看您的 API,请输入任何用户名,并将您的应用程序密钥作为密码。此密钥可在门户中找到。

API 文档屏幕

从这个页面,您可以选择任何一个 API,查看示例 JSON 对象,并通过基于 Web 的交互式界面实际地对站点进行 GET/POST/PATCH/DELETE 操作。我发现这是一个非常有价值的工具,用于调试请求、测试查询以及验证响应是否按预期返回。

Azure 和物联网设备的安全性

Azure App Services 支持 Google、Facebook、Microsoft 和 Azure Active Directory 的身份验证,以及 自定义身份验证。身份验证的实现可以像使用身份提供者注册、获取相应的密钥,并在门户的“身份”选项卡下进行配置一样简单。

网上有很多关于这方面的优秀指南,包括 Microsoft 的官方指南。如果您选择在 Azure App Service 或您的应用程序中实现授权,则可以将 [AuthorizeLevel(AuthorizationLevel.User)] 属性添加到您需要限制的表控制器类或方法。

CookController.cs(部分)

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);
        }

包含的身份验证模型适用于直接支持 Azure 移动服务的应用程序和网站。但是,我们不希望要求基于 Arduino 的设备使用 Microsoft 或 Google 帐户登录,并且让 Arduino 处理 cookie 或传递令牌会非常复杂。

另一方面,将表控制器直接对匿名访问开放存在明显的安全隐患。

为本项目实现的解决方案要求设备传输其序列号和共享密钥才能向云服务发出请求。这既用于识别设备,也用于验证设备。当用户将新的 IoT 控制器设备添加到 CloudCooker 系统时,他们将启动一个配对过程来注册设备。

例如,用户可以在应用程序中输入设备的序列号,然后在设备本身上点击物理“配对”按钮。当设备请求配对时,Web 服务将验证用户是否已添加该序列号,在应用程序 UI 中确认配对,如果批准,将生成一个共享密钥并返回给 Arduino 设备,以便在将来的请求中使用。

对于此原型,使用了预定义的共享密钥,并直接将其添加到 Seed 代码和设备的 EEPROM 中。

另一个安全问题是 Arduino 设备没有足够的处理能力进行 SSL 加密。因此,必须通过标准 HTTP 进行到服务器的连接。我考虑过实现一个不太复杂的加密,甚至对数据进行混淆,但结论是这从纯粹的安全角度来看毫无意义。

最终,我接受了降低安全性的工程权衡,以使用成本更低的设备。抛开软件最佳实践——该设备传输的是火鸡温度,而不是信用卡号。对于这个原型来说,升级硬件是不值得的。

当然,您支持的安全模型和加密级别将取决于您的物联网设备的功能以及您可接受的风险水平。

物联网设备的自定义 API

Azure App Services 支持我们将用于实现设备接口的自定义 API。在此示例中,我们将从直接更新云服务的接口开始。Arduino 设备将使用此接口来更新烤架和食物的当前温度值。

第一步是创建数据传输对象的类。请注意,DirectUpdate继承自 Azure Mobile Server 的实体数据类,因为它不打算成为数据库中的表。

对于此 API,创建了两个类。DirectUpdate 是用于从设备接收数据的类,而 DirectUpdateResponse 用于向设备返回更新数据(如果需要)。

DirectUpdate.cs

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; }
    }
}

接下来,创建了一个控制器来接受来自 Arduino 设备的 POST 请求。虽然 Azure services 框架自动处理了我们的表控制器,但自定义 API 需要更多手动工作。DirectUpdate POST 请求实现了以下逻辑:

  1. 验证设备是否已注册并具有有效的序列号和共享密钥,如果未经授权则返回 HTTP 状态码 403。
  2. 检查以确保设备正在报告正确的 CookCookConfiguration 数据。如果是,则使用来自设备的新信息更新数据库。
  3. 如果设备使用正确的配置,则返回状态码 201。
  4. 如果设备没有当前配置,并且不应在使用中,则返回状态码 204。
  5. 如果设备需要更改其配置(例如,如果烤架温度需要改变),则返回一个新的 DataUpdateResponse 和 HTTP 状态码 250。

返回非标准的 HTTP 状态码可能看起来是一个不寻常的决定,但与每次处理完整的 JSON 响应相比,它是一种更快地告诉设备需要做什么的方法。

DirectUpdateController.cs

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>

Arduino 与 Azure App Services 的连接

Arduino C 开发与 .NET 开发或任何旨在运行在标准服务器或 PC 上的托管语言截然不同。Arduino Uno 中发现的 ATMega328 的计算能力只有 Intel 处理器的一小部分,SRAM 也只有 2k。这意味着如果代码请求的内存超过 2k,您将遇到内存分配错误或意外行为。即使您熟悉 C 风格的内存管理,仅在需要时使用 malloc()free() 来处理指针,内存碎片也会累积,并且 2k 的限制会比您想象的更快达到……

在大多数 Arduino 草图中,像迭代器这样的小型局部变量在芯片寄存器中创建,使用起来完全没问题。只有当我们创建像 HTTP 请求那样的大型字符串时,内存分配才会成为问题。

为了解决这个问题,我们必须“打破”您可能已经习惯在高级语言中工作的规则。例如,我们不会按需即时声明字符串,而是创建一个全局字符串缓冲区并重复使用它。内存将在启动时预留,并使用可预测的内存量。

注意:一种节省 SRAM 的方法是将 const string 变量存储在为程序代码本身预留的空间(PROGMEM)中。我特意没有利用这一点,以便文章的代码更简单。当我重新添加 PID 和温度监控支持时,我将使用它。有关将变量存储在 PROGMEM 空间中的更多信息,可以在 此处找到。

有各种可用的 Arduino 库可以处理 JSON、http 客户端请求和其他操作,但这些库会占用大量内存,包括程序本身和运行时内存。手动创建请求是最精简的实现方法。

创建 Azure HTTP 请求

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);

如果您在创建 HTTP 请求时遇到问题,我建议使用上面提到的内置 Azure App 移动服务器测试站点来检查您的工作。通过使用任何浏览器的开发者/F12 工具,您可以运行您的任何 API,并查看原始 HTTP 请求和响应,并确保您的代码遵循相同的格式。

POST 请求的响应也是手动处理的,通过使用 TextFinder 库。此实现并非理想,因为添加新元素到响应可能会破坏值的解释,这与真正的 JSON 读取器不同。将来,我可能会编写一个简单的 JSON 读取器库,它在处理响应时提供更多的灵活性,而不会像创建 C struct 来管理数据转换的库那样占用内存。

处理 HTTP 响应

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);
}

学到的教训

我最初的意图是让实际的 PID 控制器设备尽可能简单。目标是依赖 Web 服务来通信设置和更新,设备本身只需要简单的 LED 指示灯。虽然我使用的是 Arduino Uno,但使用原型平台的最终目标应该是用最少/最便宜的组件来构建最终产品。我遇到了三个主要问题:

首先,虽然互联网上有很多关于将 Arduino 与 RGB LED 和热电偶连接的有用的指南,但添加几个这样的设备以及网络接口用完了 ATMega328 中可用的引脚。我需要使用复用器来管理多个热电偶和 LED,这增加了项目的复杂性。

其次,虽然我的原型使用了标准的 Arduino 以太网 Shield,但最终产品将使用 WiFi。为了避免在设备上硬编码设置,我需要提供一种配置 SSID 和网络密码的方法。没有硬件接口,我可以尝试让 Arduino 通过暂时将其 WiFi 接口切换到 ad-hoc 模式,读取新设置,然后切换回基础设施模式,从而与移动应用程序直接通信。这再次增加了项目的复杂性。

最后,Arduino Duo 中的 ATmega328 芯片内存有限(2k),这在我以前的项目中不是问题,但我在添加 PID 控制器、网络通信和管理 JSON 的代码时遇到了限制。我还计划内置记录和队列设备更新的功能,以便可以更频繁地读取传感器而无需每次都访问服务器,但内存限制使得这也很困难。

如果使用内存和引脚更多的芯片,如 Atmega644p 或 Atmega1284p,我将不会遇到这些问题。切换到这些芯片不会显著增加最终产品的成本,并且可以让我利用库来简化代码。话虽如此,如果任何更有经验的 Arduino 开发人员有关于如何减少我的内存使用量的建议,请告诉我。

当然,我可以忽略成本,集成 Raspberry Pi 或其他微型计算机,一次性解决所有这些问题,但那是胆小鬼的做法。

未来更新

现在服务和设备已经启动并运行,下一步将是创建用于管理和控制设备的应用程序。Azure App Services 将允许我在任何我选择的平台上实现移动或 Web 应用程序。这是一个个人困境,因为我拥有 Android 设备,而我的妻子使用 iPhone。这一直是我们之间争执的根源,但如果我需要学习 Objective-C 来维护我们的婚姻,那也没办法。

我没有开始制作原型应用程序的原因之一是 Microsoft 最近宣布 Raspberry Pi 2 将支持 Windows 10 应用程序。一个带有 5-7 英寸触摸屏运行 C# 通用应用程序的 Raspberry Pi 2 将是厨房柜台的梦想控制器。它可以显示当前状态,显示进度图,并查看过去的烹饪事件,而且我可以将其构建得足够坚固,能够处理厨房/户外使用。

与此同时,由于该系统是基于云的,并且不受限于微型微处理器的功能,因此进行高级控制和分析的机会非常广泛。我的一些初步想法是:

  • 通过电子邮件/应用内通知提供食物状态。
  • 食谱步骤。例如,以低温烹饪烤肉,直到达到目标内部温度,然后自动升高火力以形成外壳。
  • 利用历史数据并结合天气数据来预测完成时间,特别是对于像牛腩这样的慢烤肉类,它们的温度不是线性增加的。
  • 远程 PID 控制器调优,并为特定的食谱、天气条件和燃料类型保存调优预设。

我的朋友们经常开玩笑说,在我家用餐可以在下午 3 点到晚上 11 点之间任何时间。我计划允许用户猜测饭菜什么时候会做好。希望这能让他们不去想他们饿了,因为食物比我预期的要慢。

感谢您的阅读,如果您有任何评论或建议,我将不胜感激。如果大家感兴趣,我可以在应用程序上线运行后撰写后续文章。

© . All rights reserved.