工业物联网框架(基于三星 Artik 10 和 Temboo):水资源管理案例研究





5.00/5 (8投票s)
介绍强大的工业 Temboo Choreos,以扩展工业物联网框架和生态系统
概述
物联网是目前最热门的领域之一,也是本年度最大的技术热点。由于物联网的核心本质上是嵌入式系统,它被视为创客的领域,并吸引了创客和 DIY 社区的关注。许多人误以为物联网仅仅是硬件连接到某些互联网服务,因此复杂的硬件(甚至是一个黑客改造)才是物联网系统的核心。然而,物联网是一个框架,它提供了一套连接物理设备与计算实体(包括边缘和核心)的工具。工业物联网(Industrial IoT)由于历史上创客们对工业自动化不像机器人和家庭自动化那样感兴趣,因此讨论较少。
工业自动化或监控系统远远超出了业余项目的范畴。整个原理取决于监控设备的强大且可扩展的架构。任何工业系统的核心在于克服数据泄露的能力,提供高精度连接,以及更重要的——强大的 API 和分析引擎框架。实时数据分析与数据收集同等重要。
那么,工业物联网(有时称为 IIoT)与家庭物联网有何不同?举例来说,如果我们考虑水管理系统,家用水箱可能只有一个单元,水箱容量在 1000 升到 3000 升之间,每天最多加水 2-3 次。现在考虑一个拥有铸造、模塑、轧制、研磨、搅拌、压平等单元的钢厂。这些单元中的每一个都需要自己的水管理。每分钟都有数百万加仑的水供应到这些单元。水的流动直接与生产目标相关联。因此,工业物联网架构必须能够处理如此大量的数据交换,跨越如此多的设备,分布在如此大的地理区域(供水单元、泵房和分销中心通常位于不同的地理位置)。因此,工业物联网需要以可扩展性、可维护性以及当然还有分析能力为目标来理解。
你可以想象,当需要在不同供应商制造的、位于不同地点的不同机器上更新固件,而又不中断当前的监控过程时,所需付出的努力是多么巨大。这与消费者物联网设备(例如智能继电器)有显著不同。
在本文中,我将向您介绍最值得信赖和最有前景的 IIoT 板之一——三星 Artik,并向您解释使用 Artik 和 Temboo 的过程。我们将以水资源管理为例。由于在工业中部署解决方案超出了本文的范围,我将带您了解系统的运行过程,并详细说明当前的模拟原型如何在工业环境中应用。
引言
这是我与三星 Artik 10 进行的一项高级实验。我们在“三星创客抗旱挑战赛”中获得了该设备。我们获得了三星 Artik 10,所以这是对我用该主板所做工作的致敬。我们将构建一个简单的物联网应用程序并开始使用它。
您可以通过 视频 查看该解决方案。
背景
开始时遇到了意想不到的麻烦,因为我们通过该主板时遇到了真正的困难,这完全是我的经验,我们通过了物联网应用程序。
我们的开始方式...
首先,我只是开箱了我的 Artik 10,并弄清楚它有什么功能。于是,我们打开了盒子。Artik 10 主板很坚固。
盒子
主板
首先
我们没有更新映像,因为我们希望使用的其中一个库可能与旧映像配合得最好,因此我们将它保持原样,直到项目完成。
访问 Wi-Fi
我们使用的是 Windows 系统,所以第一件事就是如何让设备连接到 Wi-Fi 或互联网。首先,Artik 中安装的操作系统是 Linux,因此一个解决方案必须仅通过安装的 PuTTY 来交互。连接 Artik 10 主板,使用提供的电源适配器通电,并通过 PuTTY 进入 Artik 10 设备。
Artik 10 中的 Wi-Fi 配置
我们需要遵循 三星 提供的设置指南。
现在,在我们登录 Artik 10 主板后,我们将使用 `dir` 命令检查 Artik 10 主板上的文件列表。
我们将有三个文件
- llvm-arm.conf
- mariadb-ar.conf
- temboo.conf
检查 Wi-Fi 状态
ifconfig
它将提供所有详细信息。
现在我们可以开始使用 Wi-Fi 了。
Temboo:魔法的代名词
由于我们计划使用一个对 Artik 10 来说简单而强大的云平台,我们发现 Temboo 是显而易见的答案,因为它是一个云平台,可以在几分钟内生成生产级的物联网代码。我们可以利用许多 API 来实现我们的目的。
API == Choreos
Temboo 中的每个 API 都被命名为 Choreos。有许多不同的 Choreos 可供您利用。使用方法很简单,因为掌握它的学习曲线只需要遵循几个步骤。
Temboo 的平台
如果我们想利用 Temboo 的硬件功能,我们发现有三个选项
- 德州仪器 (Texas Instruments) 主板
- Arduino 主板
- 三星 Artik 10 (Arduino 认证)
我们可以选择其中任何一个平台进行工作。由于我们手边有 Artik 10 主板,我们选择了三星。
Temboo 的 SDK
我们有多种选项可以使用 SDK。我们将在 Processing 上展示。
首先,我们需要下载 Temboo 的 Processing 库。
只需点击“运行你的第一个 Processing Choreo”。在下一页,它将显示 SDK 和下载选项。
当我们点击 Processing 时,下一步将显示关于如何开始使用的步骤。
首先,让我们下载 Temboo 的 Processing 库。
解压库文件,并将其复制到 Processing 的库文件夹中。
将生成的代码复制到 Processing IDE 中。
import com.temboo.core.*;
import com.temboo.Library.Google.Geocoding.*;
// Create a session using your Temboo account application details
TembooSession session = new TembooSession
("abhigeek81", "myFirstApp", "XvaH06Qhpq9zAqDedc9UqsjMljvvu47b");
void setup() {
// Run the GeocodeByAddress Choreo function
runGeocodeByAddressChoreo();
}
void runGeocodeByAddressChoreo() {
// Create the Choreo object using your Temboo session
GeocodeByAddress geocodeByAddressChoreo = new GeocodeByAddress(session);
// Set inputs
geocodeByAddressChoreo.setAddress("1600 Amphitheatre Parkway, Mountain View, CA");
// Run the Choreo and store the results
GeocodeByAddressResultSet geocodeByAddressResults = geocodeByAddressChoreo.run();
// Print results
println(geocodeByAddressResults.getLatitude());
println(geocodeByAddressResults.getLongitude());
println(geocodeByAddressResults.getResponse());
}
现在是输出。
我们已经看到了使用 Choreo 并在 Processing 上运行它的简便性。
现成的物联网应用,您可以立即部署
我们有大量现成的、易于使用的物联网应用程序。我们可以使用其中任何一个部署在我们的三星 Artik 上。
这些是 Temboo 上可用的现成物联网应用程序选项。
选项有:
- 楼宇管理
- 能量
- 环境
- 农业
- Logistics
- 制造业
- 智慧城市
- 水资源管理
Artik 10 的工作流程
让我们分解一下流程。
- 我们需要为设备通电,连接 USB 线,并按照三星 Artik 10 中的说明进行配置。
- 由于我们使用的是 Windows 计算机,而 Artik 10 运行的是 Fedora Core,因此我们必须通过 PuTTY 进行通信。
- 创建一个 Temboo 账户。按照现成的物联网应用程序说明运行代码。
一个技巧:Temboo 有一个很棒的功能,即在可视化信息并遵循步骤后,整个代码会被转换为 C 语言,因此我们可以根据自己的喜好组合 Choreos。 - 现在会有一个 zip 文件,我们需要通过 PuTTY 将其推送到 Artik 10 板上。
- 您的物联网应用程序已准备就绪。
让我们为 Artik 10 创建一个 Choreo
当我们进入仪表板时,我们将选择三星。
当我们点击“**入门**”时,它将带我们到复制库配置文件进行使用的页面。
下一步,我们输入我们的地址,以便 Yahoo 天气提供天气状况,然后我们就可以准备好部署在 Artik 10 上的代码了。
生成的代码是 C 语言代码。
头文件和 C 文件
/*
* IMPORTANT NOTE about TembooAccount.h
*
* TembooAccount.h contains your Temboo account information and must be included
* alongside main.c.
*/
#ifndef TEMBOOACCOUNT_H_
#define TEMBOOACCOUNT_H_
#define TEMBOO_ACCOUNT "abhigeek81" // Your Temboo account name
#define TEMBOO_APP_KEY_NAME "myFirstApp" // Your Temboo app name
#define TEMBOO_APP_KEY "XvaH06Qhpq9zAqDedc9UqsjMljvvu47b" // Your Temboo app key
#endif /* TEMBOOACCOUNT_H_ */
/*
* The same TembooAccount.h file settings can be used for all of your Temboo programs.
* Keeping your account information in a separate file means you can share the
* main.c file without worrying that you forgot to delete your credentials.
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <temboo.h>
#include <temboosession.h>
#include <temboonetworkclient.h>
#include "TembooAccount.h"
// SocketConnection is a struct containing data needed
// for communicating with the network interface
SocketConnection theSocket;
// There should only be one TembooSession per device. It represents
// the connection to Temboo
TembooSession theSession;
// Limit the number of times the Choreo is to be run. This avoids
// inadvertently using up your monthly Choreo limit
int currentRun = 0;
const int MAX_RUNS = 10;
// Defines a time in seconds for how long the Choreo
// has to complete before timing
const int CHOREO_TIMEOUT = 300;
// How long to wait between Choreo execution requests in seconds.
// Note that some services will block access if you
// hit them too frequently
const uint32_t choreoInterval = 30;
uint32_t lastChoreoRunTime = 0;
TembooError setup() {
// We have to initialize the TembooSession struct exactly once.
TembooError returnCode = TEMBOO_SUCCESS;
#ifndef USE_SSL
returnCode = initTembooSession(
&theSession,
TEMBOO_ACCOUNT,
TEMBOO_APP_KEY_NAME,
TEMBOO_APP_KEY,
&theSocket);
#else
printf("Enabling TLS...\n");
returnCode = initTembooSessionSSL(
&theSession,
TEMBOO_ACCOUNT,
TEMBOO_APP_KEY_NAME,
TEMBOO_APP_KEY,
&theSocket,
"/opt/iothub/artik/temboo/temboo_artik_library/lib/temboo.pem",
NULL);
#endif
return returnCode;
}
// Call a Temboo Choreo
void runGetWeatherByAddress(TembooSession* session) {
printf("\nRunning GetWeatherByAddress\n");
// Initialize Choreo data structure
TembooChoreo choreo;
const char choreoName[] = "/Library/Yahoo/Weather/GetWeatherByAddress";
initChoreo(&choreo, choreoName);
// Set Choreo inputs
ChoreoInput AddressIn;
AddressIn.name = "Address";
AddressIn.value = "kolkata";
addChoreoInput(&choreo, &AddressIn);
int returnCode = runChoreo(&choreo, session, CHOREO_TIMEOUT);
if (returnCode != 0) {
printf("runChoreo failed. Error: %d\n", returnCode);
}
// Print the response received from Temboo
while (tembooClientAvailable(session->connectionData)) {
printf("%c", readChoreoResult(&choreo, session));
}
// When we're done, close the connection
tembooClientStop(session->connectionData);
}
int main(void) {
if (setup() != TEMBOO_SUCCESS) {
return EXIT_FAILURE;
}
uint32_t now = time(NULL);
lastChoreoRunTime = now - choreoInterval;
while(currentRun < MAX_RUNS){
now = time(NULL);
if ((now - lastChoreoRunTime >= choreoInterval)){
lastChoreoRunTime = now;
currentRun++;
runGetWeatherByAddress(&theSession);
}
usleep(1000);
}
#ifdef USE_SSL
// Free the SSL context and and set Temboo connections to no TLS
endTembooSessionSSL(&theSession);
#endif
return EXIT_SUCCESS;
}
将代码推送到 Artik 10
在这里,我选择使用虚拟机/我创建了一个 Ubuntu VM 14.10 并在该操作系统上下载了代码。我们打开终端来查看下载位置,因为我们已将代码下载到 _downloads_ 文件夹中。
现在,我们需要使用安全的复制命令 (`scp`) 将在 Ubuntu OS 中下载的文件复制到 Artik 10 板上。我们使用 `ifconfig` 获取 Artik 10 板的 IP 地址,然后我们需要使用 `scp` 命令。
scp getweatheraddress.zip root@192.168.0.3:/hostname
输入密码 root one 并开始复制文件。成功后,您将看到文件已复制。
我们解压文件。
[root@localhost ~]$ unzip getweatherbyaddress.zip
我们首先编译代码。
[root@localhost ~]$ gcc -L/opt/iothub/artik/temboo/temboo_artik_library/lib
-ltemboo -I/opt/iothub/artik/temboo/temboo_artik_library/include getweatherbyaddress.c
-o getweatherbyaddress
现在我们运行它。
[root@localhost ~]$ ./getweatherbyaddress
来自主板的输出。
我们现在将重新创建用于工业物联网的水管理系统。
工业物联网水管理系统
大型生产企业或工业中的水管理,因为它有助于通过适当的使用来节省大量的水。水非常宝贵,很难使用,我们需要对水进行适当的渠道化。有些地方我们看到水被浪费,有些地方则不足。基本问题是如何正确利用水资源。妥善规划用水至关重要。在需要时使用水,不要浪费。物联网在水资源管理中发挥着重要作用,这个工业物联网项目是 Temboo 中提到的水管理系统的复制品。
头文件如下。
/*
* IMPORTANT NOTE about TembooAccount.h
*
* TembooAccount.h contains your Temboo account information and must be included
* alongside main.c.
*/
#ifndef TEMBOOACCOUNT_H_
#define TEMBOOACCOUNT_H_
#define TEMBOO_ACCOUNT "abhigeek81" // Your Temboo account name
#define TEMBOO_APP_KEY_NAME "WaterManagementApp" // Your Temboo app name
#define TEMBOO_APP_KEY "9Vj52NWvhrMPWFsOA2UYUlpamVyPyqKf" // Your Temboo app key
#endif /* TEMBOOACCOUNT_H_ */
/*
* The same TembooAccount.h file settings can be used for all of your Temboo programs.
* Keeping your account information in a separate file means you can share the
* main.c file without worrying that you forgot to delete your credentials.
*/
主 C 文件如下。
#define _BSD_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <temboo.h>
#include <temboosession.h>
#include <temboonetworkclient.h>
#include "TembooAccount.h"
#define HIGH 1
#define LOW 0
#define INPUT 1
#define OUTPUT 0
// SocketConnection is a user defined struct containing whatever
// data is needed for communicating with the network interface.
// In this demo, it is defind in demo.h and contains a socket handle.
SocketConnection theSocket;
// There should only be one TembooSession per device. It represents
// the connection to the Temboo system.
TembooSession theSession;
// Defines a time in seconds for how long the Choreo
// has to complete before timing
const int CHOREO_TIMEOUT = 300;
// Initialize a boolean to track the state of the pump
bool pumpStatus = false;
// Initialize a minimum water level threshold
// (NOTE: our sensor values go up when the water goes down.
// This should be adjusted based on your sensor calibration)
const int triggerAlertThreshold = 2100;
// Initialize a water level threshold for the water pump to stop filling
const int pumpOffThreshold = 2030;
// Initialize Pins
const int waterLevelSensor = 2;
const int motorPin = 22;
// Initialize a variable to hold the sensorValue
int waterLevel = 0;
bool digitalPinMode(int pin, int dir) {
FILE * fd;
char fName[32];
// Exporting the pin to be used
if(( fd = fopen("/sys/class/gpio/export", "w")) == NULL) {
printf("Error: unable to export pin\n");
return false;
}
fprintf(fd, "%d\n", pin);
fclose(fd);
// Setting direction of the pin
sprintf(fName, "/sys/class/gpio/gpio%d/direction", pin);
if((fd = fopen(fName, "w")) == NULL) {
printf("Error: can't open pin direction\n");
return false;
}
if(dir == OUTPUT) {
fprintf(fd, "out\n");
} else {
fprintf(fd, "in\n");
}
fclose(fd);
return true;
}
int digitalRead(int pin) {
FILE * fd;
char fName[32];
char val[2];
// Open pin value file
sprintf(fName, "/sys/class/gpio/gpio%d/value", pin);
if((fd = fopen(fName, "r")) == NULL) {
printf("Error: can't open pin value\n");
return false;
}
fgets(val, 2, fd);
fclose(fd);
return atoi(val);
}
bool digitalWrite(int pin, int val) {
FILE * fd;
char fName[32];
// Open value file
sprintf(fName, "/sys/class/gpio/gpio%d/value", pin);
if((fd = fopen(fName, "w")) == NULL) {
printf("Error: can't open pin value\n");
return false;
}
if(val == HIGH) {
fprintf(fd, "1\n");
} else {
fprintf(fd, "0\n");
}
fclose(fd);
return true;
}
int analogRead(int pin) {
FILE * fd;
char fName[64];
char val[8];
// Open value file
sprintf(fName, "/sys/devices/12d10000.adc/iio:device0/in_voltage%d_raw", pin);
if((fd = fopen(fName, "r")) == NULL) {
printf("Error: can't open analog voltage value\n");
return 0;
}
fgets(val, 8, fd);
fclose(fd);
return atoi(val);
}
TembooError setup() {
// We have to initialize the TembooSession struct exactly once
// The TEMBOO_ACCOUNT, TEMBOO_APP_KEY_NAME, and TEMBOO_APP_KEY
// values are your Temboo account credentials.
// We have to initialize the TembooSession struct exactly once.
TembooError returnCode = TEMBOO_SUCCESS;
#ifndef USE_SSL
returnCode = initTembooSession(
&theSession,
TEMBOO_ACCOUNT,
TEMBOO_APP_KEY_NAME,
TEMBOO_APP_KEY,
&theSocket);
#else
printf("Enabling TLS...\n");
returnCode = initTembooSessionSSL(
&theSession,
TEMBOO_ACCOUNT,
TEMBOO_APP_KEY_NAME,
TEMBOO_APP_KEY,
&theSocket,
"/opt/iothub/artik/temboo/temboo_artik_library/
lib/temboo.pem",
NULL);
#endif
digitalPinMode(motorPin, OUTPUT);
return returnCode;
}
void runGetWeatherByAddress(TembooSession* session, char* msg) {
// Initialize Choreo data structure
TembooChoreo choreo;
const char choreoName[] = "/Library/Yahoo/Weather/GetWeatherByAddress";
initChoreo(&choreo, choreoName);
// Set profile to use for execution
// This profile contains input data for Address
const char profileName[] = "ser";
setChoreoProfile(&choreo, profileName);
ChoreoInput TmbAppSrcIn;
TmbAppSrcIn.name = "TmbAppSrc";
TmbAppSrcIn.value = "WaterManagementApp";
addChoreoInput(&choreo, &TmbAppSrcIn);
// Output filter to get the forecast for tomorrow
ChoreoOutput filterTomorrow;
filterTomorrow.name = "tomorrow";
filterTomorrow.xpath = "/rss/channel/item/yweather:forecast[2]/@text";
filterTomorrow.variable = "Response";
// Output filter to get the forecast for today
ChoreoOutput filterToday;
filterToday.name = "today";
filterToday.xpath = "/rss/channel/item/yweather:forecast[1]/@text";
filterToday.variable = "Response";
addChoreoOutput(&choreo, &filterTomorrow);
addChoreoOutput(&choreo, &filterToday);
int returnCode = runChoreo(&choreo, session, CHOREO_TIMEOUT);
if (returnCode == 0) {
while (tembooClientAvailable(session->connectionData)) {
char name[64];
char value[64];
memset(name, 0, sizeof(name));
memset(value, 0, sizeof(value));
choreoResultReadStringUntil(session->connectionData, name, sizeof(name), '\x1F');
// Parsing the result and adding the forecast to the message being sent to the phone call
if (0 == strcmp(name, "tomorrow")) {
if (choreoResultReadStringUntil(session->connectionData, value, sizeof(value), '\x1E') == -1) {
printf("Error: char array is not large enough to store the string\n");
} else {
strcat(msg, "Tomorrow's forecast is ");
strcat(msg, value);
strcat(msg,". ");
}
} else if (0 == strcmp(name, "today")) {
if (choreoResultReadStringUntil(session->connectionData,
value, sizeof(value), '\x1E') == -1) {
printf("Error: char array is not large enough to store the string\n");
} else {
strcat(msg, "Today's forecast is ");
strcat(msg, value);
strcat(msg, ". ");
}
}
else {
choreoResultFind(session->connectionData, "\x1E");
}
}
}
// When we're done, close the connection
tembooClientStop(session->connectionData);
}
void runCaptureTextToSpeechPromptChoreo(TembooSession* session) {
// Create our message buffer. Make sure it is big enough to fit the full message
char msg[256] = "Alert, your tank is running low.";
runGetWeatherByAddress(session, msg);
// Append the text for which button to press to turn on the pump
strcat(msg, "Press 1 to turn on the pump.");
// Initialize Choreo data structure
TembooChoreo choreo;
const char choreoName[] = "/Library/Nexmo/Voice/CaptureTextToSpeechPrompt";
initChoreo(&choreo, choreoName);
ChoreoInput TextIn;
TextIn.name = "Text";
TextIn.value = msg;
addChoreoInput(&choreo, &TextIn);
ChoreoInput TmbAppSrcIn;
TmbAppSrcIn.name = "TmbAppSrc";
TmbAppSrcIn.value = "WaterManagementApp";
addChoreoInput(&choreo, &TmbAppSrcIn);
// Set profile to use for execution
// This profile contains input data for:
// APIKey, APISecret, ByeText, MaxDigits, and To
const char profileName[] = "abhishek22";
setChoreoProfile(&choreo, profileName);
int returnCode = runChoreo(&choreo, session, CHOREO_TIMEOUT);
if (returnCode == 0) {
while(tembooClientAvailable(session->connectionData)) {
char nameString[64];
char dataString[32];
memset(nameString,0,sizeof(nameString));
memset(dataString,0,sizeof(dataString));
choreoResultReadStringUntil(session->connectionData,
nameString, sizeof(nameString), '\x1F');
if(!strcmp(nameString, "Digits")) {
if(choreoResultReadStringUntil(session->connectionData,
dataString, sizeof(dataString), '\x1E') == -1) {
printf("Error: char array is not large enough to store the string\n");
} else {
// If the number 1 is pressed on the phone keypad, turn on the pump
if(!strcmp(dataString, "1")){
digitalWrite(motorPin, HIGH);
// Set the motor state boolean to ON
pumpStatus = true;
}
}
} else {
choreoResultFind(session->connectionData, "\x1E");
}
}
}
// When we're done, close the connection
tembooClientStop(session->connectionData);
}
int main(void) {
if (setup() != TEMBOO_SUCCESS) {
return EXIT_FAILURE;
}
while(1){
int i = 0;
// Averaging the last 10 values to smooth the water level
waterLevel = 0;
for(i = 0; i <10; i++) {
waterLevel = waterLevel + analogRead(waterLevelSensor);
}
waterLevel *= .1;
printf("The water level is %i\n", waterLevel);
// If water is above this value, make a call to see if the pump should be turned on
if (waterLevel > triggerAlertThreshold && !pumpStatus){
runCaptureTextToSpeechPromptChoreo(&theSession);
}
// If water level is below this value, turn the pump off
if(waterLevel < pumpOffThreshold && pumpStatus) {
digitalWrite(motorPin, LOW);
pumpStatus = false;
}
usleep(5000);
}
#ifdef USE_SSL
// Free the SSL context and and set Temboo connections to no TLS
endTembooSessionSSL(&theSession);
#endif
return EXIT_SUCCESS;
}
水资源管理案例研究
让我们开始这个过程。
我们进行了基于加利福尼亚州最缺水地区的实验。我们的用例是加利福尼亚州贝克斯菲尔德。
现在,我们有一个场景,需要弄清楚水位何时低以及水泵何时启动。
物联网系统的工作方式是:它使用 Yahoo 天气 Choreo,当阈值较低时,会触发一个警报来检查水位(这是由水位指示器完成的)。
我们使用了 Nexmo Choreo 来自动生成手机上的切换开关,其中 1 触发水泵开启,0 触发水泵关闭。
所需硬件
- 水位传感器
- 水泵
- TP120 晶体管
- 面包板
- 三星 Artik 10 设备
- 蓄水池
- 水箱
设置
带水位传感器的蓄水池。
在 Artik 10 上运行代码后,由于水位为空,会反映出相关数值。
现在,借助 Nexmo 服务,将生成一个电话呼叫,该呼叫将触发切换水泵的选项。
随着水位变化,水位指示器也会随之变化。
水泵开始工作。
完整的解决方案正在运行。
结论
我们已经看到了 Artik 10 如何用作工业物联网解决方案(尽管模拟很简单)。我们充分利用了 Temboo 的强大功能。Temboo Choreos 的强大功能及其自动代码生成能力使我们能够非常快速地完成解决方案。我们还充分利用了 Yahoo 天气 Choreo。Nexmo 用于拨打电话。您可以尝试使用其他 choreos,并选择最适合您工厂的。
关注点
获取 Artik 10 主板并使用它非常有趣,是一次很好的学习经历。
历史
- 2016 年 8 月 4 日:初版