阿里雲物聯網 .NET Core 客戶端 | CZGL.AliIoTClient:4. 裝置上報屬性

痴者工良發表於2019-06-09

文件目錄:


 

裝置自身 CPU 溫度、電源輸入電壓、記憶體使用率等,以及接入到裝置的感測器如溫度感測器、光敏感測器等,這些硬體的資料輸出即是 屬性 。
裝置將這些硬體的資料上傳到阿里雲物聯網平臺,實時顯示這些裝置的狀態和實測資料,這個過程是 上傳裝置屬性 。


1)定義物模型

在阿里雲物聯網控制檯,點選 產品 -> 功能定義 -> 新增自定義功能
填入一下內容:

功能型別:屬性  
功能名稱: CPU溫度  
識別符號: cpu_temperature  
資料型別: float (單精度浮點型)  
取值範圍:0-120  
步長:    0.1
單位:  攝氏度 / °C  
讀寫型別:只讀  

再定義一個屬性:

功能型別:屬性  
功能名稱: 格力空調溫度  
識別符號: gree_temperature  
資料型別: float (單精度浮點型)  
取值範圍:0-35  
步長:    0.1
單位:  攝氏度 / °C  
讀寫型別:讀寫

注意的是,表示符是區分大小寫的,相當於 C# 中的變數,筆者這裡建議統一使用小寫,具體原因後面說明。
注意:讀寫型別,一個只讀、一個讀寫。


2)編寫模型

前面說過, Alink json 是阿里雲定義具有一定格式的 Json ,
因此這些屬性資料是以 Json 形式上傳。在 C# 中,可以通過 類 快速生成 Json 。

引數型別說明
id string 訊息ID號,在這個裝置的生涯中,ID應當是唯一的。可以使用時間戳或guid
version string 協議版本號,目前協議版本號為1.0。固定 "1.0" 即可
params Object 屬性資料,裡面包含多個屬性物件,每個屬性物件包含上報時間(time)和上報的值(value)。
time long 屬性上報時間。
value object 上報的屬性值。
method string 固定取值 thing.event.property.post

那麼,我們要編寫一個類,儲存資訊,然後轉為 Alink json 上傳到阿里雲物聯網伺服器。在編寫這個模型前,預覽要生成的 Alink json :

{
  "id": "123456789",
  "version": "1.0",
  "params": {
    "cpu_temperature": {
      "value": 58.6,
      "time": 1524448722000
    },
    "gree_temperature": {
      "value": 26.6,
      "time": 1524448722000
    }
  },
  "method": "thing.event.property.post"
}

我們只需關注 params 部分的編寫即可。

在控制檯程式中,新建一個類 TestModel

   public class TestModel
    {
        public string id { get { return DateTime.Now.Ticks.ToString(); } set { } }
        public string version { get { return "1.0"; } set { } }
        public Params @params { get; set; }
        public class Params
        {
            /*
             * 
             */
        }
        public string @method { get { return "thing.event.property.post"; } set { } }
    }

這樣定義後,我們使用時,只需定義 params 部分即可, id、version等,不需要自己動態取值,做重複勞動。
上面有個 @params ,這是因為 params 是 C# 的關鍵字,命名欄位時為了取消衝突所以加個 @

根據我們在阿里雲物聯網控制檯定義的 屬性 ,繼續補充內容:

   public class TestModel
    {
        public string id { get { return DateTime.Now.Ticks.ToString(); } set { } }
        public string version { get { return "1.0"; } set { } }
        public Params @params { get; set; }
        public class Params
        {
            public Cpu_temperature cpu_temperature { get; set; }
            public Gree_temperature gree_temperature { get; set; }
            public class Cpu_temperature
            {
                public float value{ get; set; }
                public long time { get; set; }
            }
            public class Gree_temperature
            {
                public float value { get; set; }
                public long time { get; set; }
            }

        }
        public string @method { get { return "thing.event.property.post"; } set { } }
    }

問題是,這樣寫還不行,因為還沒有給 TestModel 裡的類進行例項化。
我們可以利用 建構函式 對裡面的引用型別進行例項化,當然亦可編寫依賴注入容器。。

   public class TestModel
    {
        public string id { get { return DateTime.Now.Ticks.ToString(); } set { } }
        public string version { get { return "1.0"; } set { } }
        public Params @params { get; set; }

        public TestModel()
        {
            @params = new Params();
        }
        public class Params
        {
            public Cpu_temperature cpu_temperature { get; set; }
            public Gree_temperature gree_temperature { get; set; }

            public Params()
            {
                cpu_temperature = new Cpu_temperature();
                gree_temperature = new Gree_temperature();
            }
            public class Cpu_temperature
            {
                public float value{ get; set; }
                public long time { get; set; }
            }
            public class Gree_temperature
            {
                public float value { get; set; }
                public long time { get; set; }
            }

        }
        public string method { get { return "thing.event.property.post"; } set { } }
    }

3)上傳裝置屬性資料

編寫控制檯程式,引入 CZGL.AliIoTClient ,編寫基礎程式碼(請替換 DeviceOptions 的資訊):

        static AliIoTClientJson client;
        static void Main(string[] args)
        {
            // 建立客戶端
            client = new AliIoTClientJson(new DeviceOptions
            {
                ProductKey = "a1A6VVt72pD",
                DeviceName = "json",
                DeviceSecret = "7QrjTptQYCdepjbQvSoqkuygic2051zM",
                RegionId = "cn-shanghai"
            });

            // 設定要訂閱的Topic、執行接收內容的Topic
            string[] topics = new string[] { client.CombineHeadTopic("get") };
            // 使用預設事件
            client.UseDefaultEventHandler();
            // 連線伺服器
            client.ConnectIoT(topics,null,60);
            ToServer();    // 自定義方法,後面說明
            Console.ReadKey();
        }

再 Program 類中,編寫一個方法用來收集屬性資料、上傳屬性資料:

        public static void ToServer()
        {
            // 例項化模型
            TestModel model = new TestModel();

            // 設定屬性值
            model.@params.cpu_temperature.value = 56.5F;
            model.@params.cpu_temperature.time =AliIoTClientJson.GetUnixTime();
            // 低碳環境、節約資源,從你我做起,夏天空調不低於 26°
            model.@params.gree_temperature.value=26.0F;
            model.@params.gree_temperature.time=AliIoTClientJson.GetUnixTime();

            // 上傳屬性資料
            client.Thing_Property_Post<TestModel>(model,false);
        }

啟動控制檯應用,在阿里雲物聯網控制檯,開啟裝置,點選 執行狀態 ,即可看到上傳的屬性資料。 文章後面會詳細說明 CZGL.AliIoTClient 關於屬性上傳的具體情況。

當然,這樣的資料只是固定賦值的,這裡只是演示,具體資料需要開發者採集。下面給出一些模擬資料的方法。


4)模擬資料

筆者編寫了三個資料模擬方法:
不需要理會裡面是怎麼寫的,僅是個模擬資料的工具而已,你也可以自己編寫相應的模擬資料方法。 裡面有四個引數,對應:原始值、最小值、最大值、波動範圍。

    /// <summary>
    /// 模擬資料
    /// </summary>
    public static class DeviceSimulate
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="original">原始資料</param>
        /// <param name="range">波動範圍</param>
        /// <param name="min">最小值</param>
        /// <param name="max">最大值</param>
        /// <returns></returns>
        public static int Property(ref int original, int min, int max, int range)
        {
            int num = (new Random()).Next(0, range + 1);
            bool addorrm;
            if (original + num > max || original > max)
                addorrm = false;
            else if (original < min || original - num < min)
                addorrm = true;
            else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

            if (addorrm == true)
                original += num;
            else
                original -= num;
            return original;
        }

        public static float Property(ref float original, float min, float max, int range = 8)
        {
            original = float.Parse(original.ToString("#0.00"));
            float num = float.Parse(((new Random()).NextDouble() / range).ToString("#0.00"));
            bool addorrm;
            if (original + num > max || original > max)
                addorrm = false;
            else if (original < min || original - num < min)
                addorrm = true;
            else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

            if (addorrm == true)
                original += num;
            else
                original -= num;
            original = float.Parse(original.ToString("#0.00"));
            return original;
        }

        public static double Property(ref double original, double min, double max, int range = 8)
        {
            original = double.Parse(original.ToString("#0.0000"));
            double num = double.Parse(((new Random()).NextDouble() / range).ToString("#0.0000"));
            bool addorrm;
            if (original + num > max || original > max)
                addorrm = false;
            else if (original < min || original - num < min)
                addorrm = true;
            else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

            if (addorrm == true)
                original += num;
            else original -= num;
            original = double.Parse(original.ToString("#0.0000"));
            return original;
        }
    }

int 模擬資料
range 是指每次生成 [0,range] 範圍的增/減量,
例如 初始值 56 , range = 2 ,那麼可能 56±0 或 56±1 或 56±2 , 是增還是減,是隨機的。但是設定 min 、 max 後,最後生成的值會在此範圍內波動。

float、double 模擬資料
對應 float、double,range 的值越大,波動範圍越小。預設 range = 8,大概就是每次 0.1 的波動範圍。
其中,float 小數保留兩位, double 小數保留 4 位,
需要更高或減少小數位數,修改一下 ...ToString("#0.0000")

模擬屬性資料
接下來我們模擬一下兩個屬性的資料。

在 Program 中定義兩個變數儲存 cpu 和 空調 資料。

        static float cpu_temperature = 50.0F;
        static float gree_temperature = 26.0F;

修改 ToServer() 方法

        public static void ToServer()
        {
            // 例項化模型
            TestModel model = new TestModel();

            // 設定屬性值
            model.@params.cpu_temperature.value = DeviceSimulate.Property(ref cpu_temperature, 40, 60, 8);
            model.@params.cpu_temperature.time = AliIoTClientJson.GetUnixTime();
            // 低碳環境、節約資源,從你我做起,夏天空調不低於 26°
            model.@params.gree_temperature.value = DeviceSimulate.Property(ref gree_temperature, 40, 60, 8); ;
            model.@params.gree_temperature.time = AliIoTClientJson.GetUnixTime();

            // 上傳屬性資料
            client.Thing_Property_Post<TestModel>(model, false);
        }

在 Main() 方法裡增加程式碼:

            // 定時上傳資料
            while (true)
            {
                ToServer();
                Thread.Sleep(1000);
            }

至此,已經基本完成。

完整程式碼如下:

    class Program
    {
        static AliIoTClientJson client;
        static void Main(string[] args)
        {
            // 建立客戶端
            client = new AliIoTClientJson(new DeviceOptions
            {
                ProductKey = "a1A6VVt72pD",
                DeviceName = "json",
                DeviceSecret = "7QrjTptQYCdepjbQvSoqkuygic2051zM",
                RegionId = "cn-shanghai"
            });

            // 設定要訂閱的Topic、執行接收內容的Topic
            string[] topics = new string[] { client.CombineHeadTopic("get") };
            // 使用預設事件
            client.UseDefaultEventHandler();
            // 連線伺服器
            client.ConnectIoT(topics, null, 60);
            // 定時上傳資料
            while (true)
            {
                ToServer();
                Thread.Sleep(1000);
            }

            Console.ReadKey();
        }
        static float cpu_temperature = 50.0F;
        static float gree_temperature = 26.0F;
        public static void ToServer()
        {
            // 例項化模型
            TestModel model = new TestModel();

            // 設定屬性值
            model.@params.cpu_temperature.value = DeviceSimulate.Property(ref cpu_temperature, 40, 60, 8);
            model.@params.cpu_temperature.time = AliIoTClientJson.GetUnixTime();
            // 低碳環境、節約資源,從你我做起,夏天空調不低於 26°
            model.@params.gree_temperature.value = DeviceSimulate.Property(ref gree_temperature, 40, 60, 8); ;
            model.@params.gree_temperature.time = AliIoTClientJson.GetUnixTime();

            // 上傳屬性資料
            client.Thing_Property_Post<TestModel>(model, false);
        }

        /// <summary>
        /// 模擬資料
        /// </summary>
        public static class DeviceSimulate
        {
            /// <summary>
            /// 
            /// </summary>
            /// <param name="original">原始資料</param>
            /// <param name="range">波動範圍</param>
            /// <param name="min">最小值</param>
            /// <param name="max">最大值</param>
            /// <returns></returns>
            public static int Property(ref int original, int min, int max, int range)
            {
                int num = (new Random()).Next(0, range + 1);
                bool addorrm;
                if (original + num > max || original > max)
                    addorrm = false;
                else if (original < min || original - num < min)
                    addorrm = true;
                else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

                if (addorrm == true)
                    original += num;
                else
                    original -= num;
                return original;
            }

            public static float Property(ref float original, float min, float max, int range = 8)
            {
                original = float.Parse(original.ToString("#0.00"));
                float num = float.Parse(((new Random()).NextDouble() / range).ToString("#0.00"));
                bool addorrm;
                if (original + num > max || original > max)
                    addorrm = false;
                else if (original < min || original - num < min)
                    addorrm = true;
                else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

                if (addorrm == true)
                    original += num;
                else
                    original -= num;
                original = float.Parse(original.ToString("#0.00"));
                return original;
            }

            public static double Property(ref double original, double min, double max, int range = 8)
            {
                original = double.Parse(original.ToString("#0.0000"));
                double num = double.Parse(((new Random()).NextDouble() / range).ToString("#0.0000"));
                bool addorrm;
                if (original + num > max || original > max)
                    addorrm = false;
                else if (original < min || original - num < min)
                    addorrm = true;
                else addorrm = ((new Random()).Next(1, 3) > 1) ? true : false;

                if (addorrm == true)
                    original += num;
                else original -= num;
                original = double.Parse(original.ToString("#0.0000"));
                return original;
            }
        }

    }

執行控制檯程式,然後開啟阿里雲物聯網控制檯,檢視裝置的執行狀態,開啟 自動重新整理 ,檢視資料變化。

如果你覺得每次波動得範圍太大,可以把 range 改大一些,如果你覺得資料不穩定,
可以把 min - max 的範圍改小一些,模擬的資料值將在此範圍波動。


5)裝置屬性 - CZGL.AliIoTClient

首先要說明,產品建立前,需要設定為 Alinkjson/透傳 產品,
因此 CZGL.AliIoTClient 設定了兩個客戶端類。

類名說明
AliIoTClientJson 以Alink json形式上傳資料
AliIoTClientBinary 以透傳形式上傳資料

這兩個類,僅在 屬性、事件、服務 三個功能中資料上傳形式有差別,連線伺服器、普通Topic等其它資料的使用是完全一致的。
一個產品只能定義一種上傳資料的形式。

CZGL.AliIoTClient 中上傳屬性的方法(Alink json):

// 不需要SDK處理任何中間過程,直接把資料上傳。
// 那你需要先將資料儲存到json中,在轉成byte[],由SDK傳送。
public int Thing_Property_Post(byte[] json)

// 由SDK幫你傳送原始的json,是否需要將json轉為小寫再傳送,預設 true
public int Thing_Property_Post(string json, 
                               [bool isToLwer = True])

// 設定要傳送的json;是否轉為小寫;設定編碼格式,為空則為UTF8
public int Thing_Property_Post(string json, 
                               [bool isToLwer = True], 
                               [System.Text.Encoding encoding = null])

// 直接傳入模型,什麼都不需要管,SDK轉換後上傳
public int Thing_Property_Post<TModel>(TModel model, 
                               [bool isToLower = True])

獲取 UNIX 時間: 由於阿里雲要求上傳的屬性資料等,要帶上 Unix 時間,所以筆者一併寫在 CZGL.AliIoTClient 了。

public static long GetUnixTime()

使用示例參考上面的過程。

透傳
如果你想使用透傳,則使用 AliIoTClientBinary 類,

// 裝置上傳屬性--透傳
public int Thing_Property_UpRaw(byte[] bytes)

// 裝置上傳屬性--透傳,轉為 Base 64位加密後上傳
public int Thing_Property_UpRawToBase64(byte[] bytes, 
                        [System.Text.Encoding encoding = null])

6)關於透傳

透傳以二進位制報文形式上傳,例如 0x020000007b00 ,這裡是 16 進位制,每兩位一個位元組。
如果是 2進位制 ,則是 8位 一個位元組。

透傳需要在阿里雲物聯網控制檯建立 透傳 產品後,設定指令碼,將透傳資料 轉為 Alink json。
透傳資料是自定義的,以位元組為單位,其中有5個位元組為特定位元組,以位元組位進行拆分的。

記住,是以位元組為單位。

透傳資料格式標準:

欄位位元組數
幀型別 1位元組
請求ID 4位元組
屬性資料 N個位元組

幀型別:

值(16進位制)說明
0x00 屬性上報
0x01 屬性設定
0x02 上報資料返回結果
0x03 屬性設定裝置返回結果
0xff 未知的命令

**舉例說明 **

很多人是直接把 10進位制 或 16進位制 直接轉換成 2進位制 。
例如 0x020000007b00,轉為 2進位制 :100000000000000000000000000111101100000000。 但是這樣是錯誤的。

以上面 cpu 和 空調溫度 舉例,要上傳屬性資料,幀型別為 0x00。

屬性10進位制16進位制2進位制劃一下2進位制
cpu_temperature 56 38 00111000 00 11 10 00
gree_temperature 26 1a 00011010 00 01 10 10

應當這樣拆分和設定值:

位元組類轉位元組數16進位制2進位制
進製表示 0x
幀型別 1位元組 00 00000000
ID 4位元組 00 00 00 7b 00000000 00000000 00000000 01111011
cpu_temperature 1 位元組 38 00111000
gree_temperature 1 位元組 1a 00011010

16進位制資料:
0x000000007b381a

2進位制資料:
00000000000000000000000000000000011110110011100000011010

將 16進位制 或 2進位制 的資料儲存到 byte[] 變數中,切記要強制轉換。 儲存時,一個 byte 為一個位元組,M個位元組,則 byte[M]。

儲存:
使用 16進位制 儲存透傳資料,2進位制弄不來的。 :joy: :joy: :joy:
有些同學非要用 2進位制 儲存,反正我是弄不來,用 二進位制 數值 儲存,這個觸發我的知識盲區了。

示例(僅對 AliIoTClientBinary 客戶端有效):

            // 儲存透傳資料
            byte[] b = new byte[7];
            b[0] = 0x00;
            b[1] = 0x00;
            b[2] = 0x00;
            b[3] = 0x00;
            b[4] = 0x7b;
            b[5] = 0x38;
            b[6] = 0x1a;
            
            // 上傳透傳資料
            client.Thing_Property_UpRaw(b);

如果上報屬性,要求 請輸入二進位制資料Base64編碼後的字串,可以使用

            byte[] b = new byte[7];
            b[0] = 0x00;
            b[1] = 0x00;
            b[2] = 0x00;
            b[3] = 0x00;
            b[4] = 0x7b;
            b[5] = 0x38;
            b[6] = 0x1a;

            // client.Thing_Property_UpRaw(b);
            client.Thing_Property_UpRawToBase64(b);

透傳資料的坑很多,這裡 CZGL.AliIoTClient 只提供如何處理資料和上傳資料,雲端的指令碼解析請參考
https://help.aliyun.com/document_detail/114621.html?spm=a2c4g.11186623.2.13.209b65b9Q9z0Nx#concept-185365


7)後續說明

其實,每次上傳伺服器都會作出響應,CZGL.AliIoTClient 預設不接收這些響應資訊。
你可以使用 OpenPropertyPostReply() 接收裝置屬性上傳後伺服器的響應,應當在連線伺服器前使用此方法
使用 Close.PropertyPostReply() 取消接收裝置屬性上傳後伺服器的響應。

示例:

            // 。。。
            client.ClosePropertyPostReply();

            // 連線伺服器
            client.ConnectIoT(topics, null, 60);

上傳屬性資料,可以分開上傳,不需要每次都要上傳全部的屬性。需要更新哪個屬性,就上傳這個屬性。

相關文章