本示例用于展示使用C语言开发设备端如何接入物联网平台。
# 主要流程:
向平台请求设备接入资源
获取并保存设备接入资源配置信息
连接平台发送和接收消息
# 示例代码环境
centos8
# 前置开源库/组件
git
make
gcc
openssl-devel
# 开源paho-mqtt-c编译集成
git clone https://github.com/eclipse/paho.mqtt.c.git
进入根目录
make
sudo make install
# 核心步骤
# 签名生成
signatrue的生成主要依赖了开源libcurl以及openssl的组件,其中base64代码段使用的paho-mqtt-client.c的Base64.c的部分源码
libcurl安装
https://curl.se/download.html
wget https://curl.se/download/curl-7.75.0.tar.gz
解压后进入根目录
./configure
make
make install
#include <stdio.h>
#include <string.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <curl/curl.h>
/** type for size of a buffer, it saves passing around @p size_t (unsigned long long or unsigned long int) */
typedef unsigned int b64_size_t;
/** type for raw base64 data */
typedef unsigned char b64_data_t;
static b64_size_t Base64_encodeDecode(
char *out, b64_size_t out_len, const char *in, b64_size_t in_len, int encode )
{
b64_size_t ret = 0u;
if ( in_len > 0u )
{
int rv;
BIO *bio, *b64, *b_in, *b_out;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bio);
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); /* ignore new-lines */
if ( encode )
{
b_in = bio;
b_out = b64;
}
else
{
b_in = b64;
b_out = bio;
}
rv = BIO_write(b_out, in, (int)in_len);
BIO_flush(b_out); /* indicate end of encoding */
if ( rv > 0 )
{
rv = BIO_read(b_in, out, (int)out_len);
if ( rv > 0 )
{
ret = (b64_size_t)rv;
if ( out_len > ret )
out[ret] = '\0';
}
}
BIO_free_all(b64); /* free all used memory */
}
return ret;
}
b64_size_t Base64_decode( b64_data_t *out, b64_size_t out_len, const char *in, b64_size_t in_len )
{
return Base64_encodeDecode( (char*)out, out_len, in, in_len, 0 );
}
b64_size_t Base64_encode( char *out, b64_size_t out_len, const b64_data_t *in, b64_size_t in_len )
{
return Base64_encodeDecode( out, out_len, (const char*)in, in_len, 1 );
}
b64_size_t Base64_decodeLength( const char *in, b64_size_t in_len )
{
b64_size_t pad = 0u;
if ( in && in_len > 1u )
pad += ( in[in_len - 2u] == '=' ? 1u : 0u );
if ( in && in_len > 0u )
pad += ( in[in_len - 1u] == '=' ? 1u : 0u );
return (in_len / 4u * 3u) - pad;
}
b64_size_t Base64_encodeLength( const b64_data_t *in, b64_size_t in_len )
{
return ((4u * in_len / 3u) + 3u) & ~0x3;
}
char* generate_signature(const char *url, unsigned long expire_time, const char *body, const char *device_secret)
{
if(body == NULL){
body = "{}";
}
char raw[2048] = {0};
sprintf(raw, "%s\n%ld\n%s", url, expire_time, body);
printf("raw = \n%s\n", raw);
unsigned char *result = NULL;
unsigned int result_len = -1;
result = HMAC(EVP_sha256(), device_secret, strlen(device_secret), raw, strlen(raw), result, &result_len);
b64_size_t encoded_length = Base64_encodeLength((b64_data_t*) result, result_len);
char encode_out[256] = {0};
b64_size_t ret = Base64_encode(encode_out, encoded_length, (b64_data_t*) result, result_len);
CURL *curl = curl_easy_init();
if(curl) {
char *output = curl_easy_escape(curl, encode_out, 0);
if(output) {
printf("URL encoded signatrue = %s\n", output);
return output;
}
}
return NULL;
}
# 调用设备鉴权接口激活设备
编译产出时需要保证signature.c同目录
gcc {origin_file} -o {output_file} -lcrypto -lcurl
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <curl/curl.h>
#include "signature.c"
#define ADDRESS "http://yourip:port"
#define INSTANCE_ID "yourinstanceid"
#define PRODUCT_KEY "yourproductkey"
#define DEVICE_NAME "yourdevicename"
#define DEVICE_SECRET "yourdevicesecret"
int main()
{
char url_format[128] = "/v1/devices/%s/%s/%s/auth"; // your api path (exclude ip:port)
char url[256] = {0};
sprintf(url, url_format, INSTANCE_ID, PRODUCT_KEY ,DEVICE_NAME);
printf("url = %s\n", url);
struct timeval tv;
gettimeofday(&tv, NULL);
unsigned long expire_time=tv.tv_sec*1000 + tv.tv_usec/1000;
expire_time = expire_time / 60 / 1000;
printf("expire_time = %ld\n", expire_time); // minutes
char *sig = generate_signature(url, expire_time, NULL, DEVICE_SECRET);
printf("\nsig = %s\n", sig);
if (sig)
{
CURL *curl;
CURLcode res;
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if(curl) {
char full_url[512] = {0};
sprintf(full_url, "%s%s", ADDRESS, url);
printf("\nfull_url = %s\n", full_url);
struct curl_slist *headers = NULL;
char sig_header[128] = {};
sprintf(sig_header, "Signature:%s", sig);
printf("\nsig_header = %s\n", sig_header);
char time_header[128] = {};
sprintf(time_header, "ExpiryTime:%ld", expire_time);
printf("\ntime_header = %s\n", time_header);
headers = curl_slist_append(headers, sig_header);
headers = curl_slist_append(headers, time_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
/* First set the URL that is about to receive our POST. This URL can
just as well be a https:// URL if that is what should receive the
data. */
curl_easy_setopt(curl, CURLOPT_URL, full_url);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
curl_easy_setopt(curl, CURLOPT_POST, 1);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
}
curl_global_cleanup();
free(sig);
return 0;
}
free(sig);
return -1;
}
# 调用设备状态接口更新设备状态为上线
编译产出时需要保证signature.c同目录
同时需要cJSON开源库
git clone https://github.com/DaveGamble/cJSON.git
进入根目录
mkdir build
cd build
cmake .. -DENABLE_CJSON_UTILS=On -DENABLE_CJSON_TEST=Off -DCMAKE_INSTALL_PREFIX=/usr
make
make DESTDIR=$pkgdir install
编译产出
gcc {origin_file} -o {output_file} -lcrypto -lcurl -lcjson
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>
#include "signature.c"
#define ADDRESS "http://yourip:port"
#define INSTANCE_ID "yourinstanceid"
#define PRODUCT_KEY "yourproductkey"
#define DEVICE_NAME "yourdevicename"
#define DEVICE_SECRET "yourdevicesecret"
int main()
{
char url_format[128] = "/v1/devices/%s/%s/%s/states"; // your api path (exclude ip:port)
char url[256] = {0};
sprintf(url, url_format, INSTANCE_ID, PRODUCT_KEY ,DEVICE_NAME);
printf("url = %s\n", url);
struct timeval tv;
gettimeofday(&tv, NULL);
unsigned long expire_time=tv.tv_sec*1000 + tv.tv_usec/1000;
expire_time = expire_time / 60 / 1000;
printf("expire_time = %ld\n", expire_time); // minutes
struct cJSON *monitor = cJSON_CreateObject();
cJSON_AddTrueToObject(monitor, "state");
cJSON_AddStringToObject(monitor, "type", "ONLINE_STATE");
char *body = cJSON_PrintUnformatted(monitor);
char *sig = generate_signature(url, expire_time, body, DEVICE_SECRET);
printf("\nsig = %s\n", sig);
cJSON_Delete(monitor);
if (sig)
{
CURL *curl;
CURLcode res;
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if(curl) {
char full_url[512] = {0};
sprintf(full_url, "%s%s", ADDRESS, url);
printf("\nfull_url = %s\n", full_url);
struct curl_slist *headers = NULL;
char sig_header[128] = {};
sprintf(sig_header, "Signature:%s", sig);
printf("\nsig_header = %s\n", sig_header);
char time_header[128] = {};
sprintf(time_header, "ExpiryTime:%ld", expire_time);
printf("\ntime_header = %s\n", time_header);
headers = curl_slist_append(headers, sig_header);
headers = curl_slist_append(headers, time_header);
headers = curl_slist_append(headers, "Content-Type:application/json;charset:utf-8");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
/* First set the URL that is about to receive our POST. This URL can
just as well be a https:// URL if that is what should receive the
data. */
curl_easy_setopt(curl, CURLOPT_URL, full_url);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
/* always cleanup */
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
}
curl_global_cleanup();
free(sig);
cJSON_free(body);
return 0;
}
free(sig);
cJSON_free(body);
return -1;
}
# 调用接口获取MQTT连接信息
编译产出时需要保证signature.c同目录
编译产出
gcc {origin_file} -o {output_file} -lcrypto -lcurl -lcjson
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>
#include "signature.c"
#define ADDRESS "http://yourip:port"
#define INSTANCE_ID "yourinstanceid"
#define PRODUCT_KEY "yourproductkey"
#define DEVICE_NAME "yourdevicename"
#define DEVICE_SECRET "yourdevicesecret"
struct MemoryStruct {
char *memory;
size_t size;
};
size_t callback_func(void *contents, size_t size, size_t nmemb, void *stream)
{
/* contents - your string variable.
stream - data chuck you received */
size_t real_size = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)stream;
char *ptr = realloc(mem->memory, mem->size + real_size + 1);
if(ptr == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, real_size);
mem->size += real_size;
mem->memory[mem->size] = 0;
return real_size;
}
int main()
{
char url_format[128] = "/v1/devices/%s/%s/%s/resources"; // your api path (exclude ip:port)
char url[256] = {0};
sprintf(url, url_format, INSTANCE_ID, PRODUCT_KEY ,DEVICE_NAME);
printf("url = %s\n", url);
struct timeval tv;
gettimeofday(&tv, NULL);
unsigned long expire_time=tv.tv_sec*1000 + tv.tv_usec/1000;
expire_time = expire_time / 60 / 1000;
printf("expire_time = %ld\n", expire_time); // minutes
struct cJSON *monitor = cJSON_CreateObject();
cJSON_AddStringToObject(monitor, "resourceType", "MQTT");
char *body = cJSON_PrintUnformatted(monitor);
char *sig = generate_signature(url, expire_time, body, DEVICE_SECRET);
printf("\nsig = %s\n", sig);
cJSON_Delete(monitor);
if (sig)
{
CURL *curl;
CURLcode res;
/* In windows, this will init the winsock stuff */
curl_global_init(CURL_GLOBAL_ALL);
/* get a curl handle */
curl = curl_easy_init();
if(curl) {
char full_url[512] = {0};
sprintf(full_url, "%s%s", ADDRESS, url);
printf("\nfull_url = %s\n", full_url);
struct curl_slist *headers = NULL;
char sig_header[128] = {};
sprintf(sig_header, "Signature:%s", sig);
printf("\nsig_header = %s\n", sig_header);
char time_header[128] = {};
sprintf(time_header, "ExpiryTime:%ld", expire_time);
printf("\ntime_header = %s\n", time_header);
headers = curl_slist_append(headers, sig_header);
headers = curl_slist_append(headers, time_header);
headers = curl_slist_append(headers, "Content-Type:application/json;charset:utf-8");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
/* First set the URL that is about to receive our POST. This URL can
just as well be a https:// URL if that is what should receive the
data. */
curl_easy_setopt(curl, CURLOPT_URL, full_url);
/* Now specify the POST data */
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_POST, 1);
struct MemoryStruct chunk;
chunk.memory = malloc(1); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, callback_func);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
/* Check for errors */
if(res != CURLE_OK)
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
else{
/*
* Now, our chunk.memory points to a memory block that is chunk.size
* bytes big and contains the remote file.
*
* Do something nice with it!
*/
printf("\n%lu bytes retrieved\n%s", (unsigned long)chunk.size, chunk.memory);
printf("\nMqtt info:\n");
monitor = cJSON_Parse(chunk.memory);
struct cJSON *content = cJSON_GetObjectItemCaseSensitive(monitor, "content");
struct cJSON *username = cJSON_GetObjectItemCaseSensitive(content, "username");
if (cJSON_IsString(username) && (username->valuestring != NULL))
{
printf("username: %s\n", username->valuestring);
}
struct cJSON *password = cJSON_GetObjectItemCaseSensitive(content, "password");
if (cJSON_IsString(password) && (password->valuestring != NULL))
{
printf("password: %s\n", password->valuestring);
}
struct cJSON *broker = cJSON_GetObjectItemCaseSensitive(content, "broker");
if (cJSON_IsString(broker) && (broker->valuestring != NULL))
{
printf("broker: %s\n", broker->valuestring);
}
struct cJSON *clientId = cJSON_GetObjectItemCaseSensitive(content, "clientId");
if (cJSON_IsString(clientId) && (clientId->valuestring != NULL))
{
printf("clientId: %s\n", clientId->valuestring);
}
cJSON_Delete(monitor);
}
/* always cleanup */
curl_easy_cleanup(curl);
curl_slist_free_all(headers);
free(chunk.memory);
}
curl_global_cleanup();
free(sig);
cJSON_free(body);
return 0;
}
free(sig);
cJSON_free(body);
return -1;
}
# mqtt publish示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#define ADDRESS "tcp://yourip:port"
#define CLIENTID "ExamplePublish"
#define TOPIC "$iot/yourdevicename/msg"
#define PAYLOAD "Hello world"
#define QOS 0
#define TIMEOUT 10000L
int main(int argc, char* argv[])
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;
if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to create client, return code %d\n", rc);
exit(EXIT_FAILURE);
}
conn_opts.username = "yourusername";
conn_opts.password = "yourpassword";
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
exit(EXIT_FAILURE);
}
pubmsg.payload = PAYLOAD;
pubmsg.payloadlen = (int)strlen(PAYLOAD);
pubmsg.qos = QOS;
pubmsg.retained = 0;
if ((rc = MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to publish message, return code %d\n", rc);
exit(EXIT_FAILURE);
}
printf("Waiting for up to %d seconds for publication of %s\n"
"on topic %s for client with ClientID: %s\n",
(int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID);
rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
printf("Message with delivery token %d delivered\n", token);
if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)
printf("Failed to disconnect, return code %d\n", rc);
MQTTClient_destroy(&client);
return rc;
}
# mqtt subscribe示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MQTTClient.h"
#define ADDRESS "tcp://yourip:port"
#define CLIENTID "ExampleSubscribe"
#define TOPIC "$iot/yourdevicename/events"
#define QOS 0
#define TIMEOUT 10000L
volatile MQTTClient_deliveryToken deliveredtoken;
void delivered(void *context, MQTTClient_deliveryToken dt)
{
printf("Message with token value %d delivery confirmed\n", dt);
deliveredtoken = dt;
}
int msgarrvd(void *context, char *topicName, int topicLen, MQTTClient_message *message)
{
printf("Message arrived\n");
printf(" topic: %s\n", topicName);
printf(" message: %.*s\n", message->payloadlen, (char*)message->payload);
MQTTClient_freeMessage(&message);
MQTTClient_free(topicName);
return 1;
}
void connlost(void *context, char *cause)
{
printf("\nConnection lost\n");
printf(" cause: %s\n", cause);
}
int main(int argc, char* argv[])
{
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
int rc;
if ((rc = MQTTClient_create(&client, ADDRESS, CLIENTID,
MQTTCLIENT_PERSISTENCE_NONE, NULL)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to create client, return code %d\n", rc);
rc = EXIT_FAILURE;
goto exit;
}
if ((rc = MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to set callbacks, return code %d\n", rc);
rc = EXIT_FAILURE;
goto destroy_exit;
}
conn_opts.username = "yourusername";
conn_opts.password = "yourpassword";
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to connect, return code %d\n", rc);
rc = EXIT_FAILURE;
goto destroy_exit;
}
printf("Subscribing to topic %s\nfor client %s using QoS%d\n\n"
"Press Q<Enter> to quit\n\n", TOPIC, CLIENTID, QOS);
if ((rc = MQTTClient_subscribe(client, TOPIC, QOS)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to subscribe, return code %d\n", rc);
rc = EXIT_FAILURE;
}
else
{
int ch;
do
{
ch = getchar();
} while (ch!='Q' && ch != 'q');
if ((rc = MQTTClient_unsubscribe(client, TOPIC)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to unsubscribe, return code %d\n", rc);
rc = EXIT_FAILURE;
}
}
if ((rc = MQTTClient_disconnect(client, 10000)) != MQTTCLIENT_SUCCESS)
{
printf("Failed to disconnect, return code %d\n", rc);
rc = EXIT_FAILURE;
}
destroy_exit:
MQTTClient_destroy(&client);
exit:
return rc;
}
# 编译产出
gcc -o ${output_file} ${src_file} -lpaho-mqtt3c -lpaho-mqtt3a
直接执行${output_file}
上一篇: 查询设备历史数据 下一篇: 网关如何接入物联网平台