使用C语言开发设备端示例代码
更新时间: 2023-04-10

本示例用于展示使用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}