分类 IoT 平台软件 下的文章

ThingsBoard 官方已经发布一些 OAuth2 支持的例子,https://thingsboard.io/docs/user-guide/oauth-2-support/,有 Google,Auth0,其他比如 GitHub 也比较简单。这里介绍的是 Keycloak 的配置。

1、前提条件

1.1、ThingsBoard Oauth2 支持

首先确保你的 ThingsBoard 服务器版本支持 OAuth2,比如 V3.3.*,以 sysadmin 登录即可。

1.2、Keycloak 服务器

安装

过程比较简单,可根据自身需求选择安装方式;测试的时候可以用 docker ,方便快捷。

阅读手册

找到你能读懂的文档,简单熟悉配置方法。

2、配置

2.1、Keycloak 配置

新建 realm

keycloak2.png

复制 secret

keycloak3.png

增加 user

keycloak4.png

设置 user 密码

keycloak5.png

2.2、ThingsBoard 配置

keycloak1.png

尤其注意 一系列 URI,其中 YiSERVER 换成你的 真实 realm 即可。

访问令牌URI:

http://localhost:8081/realms/YiSERVER/protocol/openid-connect/token

授权URI:

http://localhost:8081/realms/YiSERVER/protocol/openid-connect/auth

JSON Web Key URI:

http://localhost:8081/realms/YiSERVER/protocol/openid-connect/certs

用户信息URI:

http://localhost:8081/realms/YiSERVER/protocol/openid-connect/userinfo

3、验证

3.1、登录 ThingsBoard

keycloak6.png

3.2、跳转到 Keycloak

keycloak7.png

3.3、完成 OAuth2 登录集成

keycloak8.png

3.4、视频预览

此二次开发主要是基于 ThingsBoard UI 界面上的 js 脚本开发,熟悉 js 开发的朋友应该很容易理解。
从 Dashboard 导出 csv 其实有好几种情况,1是 telemetry 数据导出,2是 attributes 数据导出。

准备工作

  • Chrome 浏览器
  • javascript 基础知识
  • ThingsBoard UI 二次开发基础知识
  • Dashbaord 开发经验

新建Dashboard

Telemetry 数据导出

1.png

导出csv文件内容:

timestamp,GasCon
2022-3-21 21:29:0,800
2022-3-21 21:44:0,800
2022-3-25 19:59:0,0

自定义 action,js脚本:

let $injector = widgetContext.$scope.$injector;
let attributeService = $injector.get(widgetContext
    .servicesMap.get('attributeService'));
let deviceService = $injector.get(widgetContext.servicesMap
    .get('deviceService'));

console.log(widgetContext);
let data = widgetContext.data;

let dataKeys = [];
// get all data keys to array
widgetContext.datasources[0].dataKeys.forEach(function(
    item) {
    dataKeys.push(item.name);
});

let fileTitle = "export-from-list-" + entityName;

function timeConverter(UNIX_timestamp) {
    var a = new Date(UNIX_timestamp);
    var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
    ];
    var year = a.getFullYear();
    var month = a.getMonth()+1;
    var date = a.getDate();
    var hour = a.getHours();
    var min = a.getMinutes();
    var sec = a.getSeconds();
    var time = year + '-' + month + '-' + date + ' ' +
        hour + ':' + min + ':' + sec;
    return time;
}

function exportCSV() {
    // first line: header
    var allLine = "";
    dataKeys.unshift("timestamp");
    allLine += dataKeys;
    allLine += "\n";

    var key_length = data.length; // 字段数目
    var limit = data[0]["data"].length; // 数据行数
    var line = new Array(limit + 1); // 包括首行
    for (var l = 0; l < limit; l++)
        line[l] = "";
    data.forEach(
        (item, idx) => {
            var item_data = item["data"];
            var dataKey = item["dataKey"];
            for (var j = 0; j < limit; j++) {
                if (idx == 0) {

                    line[j] += timeConverter(item_data[
                        j][0]);
                    line[j] += ",";
                }
                line[j] += item_data[j][1];
                if (idx < key_length - 1)
                    line[j] += ",";
            }
            line[j] += "\n";
        }
    );
    for (var k = 0; k < limit; k++) {
        allLine += line[k];
        allLine += "\n";
    }

    var exportedFilenmae = fileTitle + '.csv' ||
        'export.csv';

    var blob = new Blob([allLine], {
        type: 'text/csv;charset=utf-8;'
    });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob,
            exportedFilenmae);
    } else {
        var link = document.createElement("a");
        if (link.download !==
            undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download",
                exportedFilenmae);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
}

exportCSV();

Attributes 数据导出

2.png

导出csv文件内容:

timestamp,name,detector_tag,area,floor,location,department,equipment_name,gas,setup_date,sensor_date,cal_date,cal_result,ip,hub_port,sub_hub_port,maintenance
2022-3-28 21:25:9,CL96-06,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD013-HF,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD033-C-NH3,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD033-D-NH3,,,,,,,,,,,,,,,
2022-3-28 21:25:9,F-CL96-SRD037-CLF3,,,,,,,,,,,,,,,

自定义 action,js 脚本:

let $injector = widgetContext.$scope.$injector;
let attributeService = $injector.get(widgetContext
    .servicesMap.get('attributeService'));
let deviceService = $injector.get(widgetContext.servicesMap
    .get('deviceService'));

let data = widgetContext.data;
console.log(data);

let dataKeys = [];
// get all data keys to array
widgetContext.datasources[0].dataKeys.forEach(function(
    item) {
    dataKeys.push(item.name);
});

let fileTitle = "export-device-list";

function timeConverter(UNIX_timestamp) {
    var a = new Date(UNIX_timestamp);
    var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
    ];
    var year = a.getFullYear();
    var month = a.getMonth()+1;
    var date = a.getDate();
    var hour = a.getHours();
    var min = a.getMinutes();
    var sec = a.getSeconds();
    var time = year + '-' + month + '-' + date + ' ' +
        hour + ':' + min + ':' + sec;
    return time;
}

function exportCSV() {
    // first line: header
    var allLine = "";
    var key_length = dataKeys
        .length; // csv字段数目, 不含timestamp
    dataKeys.unshift("timestamp");
    allLine += dataKeys;
    // allLine += "\n";

    var limit = data.length / key_length; // csv数据行数
    var line = new Array(limit + 1); // csv总行数,包括首行
    for (var l = 0; l < limit; l++)
        line[l] = "";

    //var j = 0;
    data.forEach(
        (item, idx) => {
            var item_data = item["data"];

            if (idx % key_length == 0) {
                allLine += "\n"; // 换行
                allLine += timeConverter(item_data[0][0]); // timestamp
                allLine += ",";
            }

            allLine += item_data[0][1]; // value/值

            if ((idx+1) % key_length != 0)
                allLine += ",";
        }
    );
    
    allLine += "\n";
    // for (var k = 0; k < key_length; k++) {
    //     allLine += line[k];
    //     allLine += "\n";
    // }

    var exportedFilenmae = fileTitle + '.csv' ||
        'export.csv';

    var blob = new Blob([allLine], {
        type: 'text/csv;charset=utf-8;'
    });
    if (navigator.msSaveBlob) { // IE 10+
        navigator.msSaveBlob(blob,
            exportedFilenmae);
    } else {
        var link = document.createElement("a");
        if (link.download !==
            undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download",
                exportedFilenmae);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
}

exportCSV();

2021.8.14 第一时间将 TB 3.3 的 OTA 功能进行源码分析,基本思路就是 应用了telemetry 和 attributes ,不复杂,自己实现的话也是要这样来做。这里,只是分析了 CoAP 协议层,其他类似。

1、CoAP 路径:

按照官方文档里面的路径是错误的,需要改成如下url路径才能获取固件:coap://localhost/fw/$access_token?title=$title&version=$versoin

具体文件位置:
/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

2、checksum算法

默认支持 SHA256,如果设备端不支持或有其他算法请自取 enum 里面内容。
/common/data/src/main/java/org/thingsboard/server/common/data/ota/ChecksumAlgorithm.java
public enum ChecksumAlgorithm {

MD5,
SHA256,
SHA384,
SHA512,
CRC32,
MURMUR3_32,
MURMUR3_128

}

3、OTA Dashboard

自带了两个不错的查看 OTA 的dashboard,赶紧收藏起来。
firmware:
/application/src/main/data/json/demo/dashboards/firmware.json
software:
/application/src/main/data/json/demo/dashboards/software.json
firmware-dashboard.png

4、OTA 固件存储

firmware / software 文件使用 oid 格式存储,隐藏列,无法查看,具体详细内容请自行查看postgresql oid数据类型。

5、测试脚本

https://thingsboard.io/docs/user-guide/resources/firmware/ 目录下
http_firmware_client.py
mqtt_firmware_client.py
coap_firmware_client.py
基本可以用,但是脚步写的不是很好,有能力的可以自己改。

5、升级步骤

1、服务有一个属性,标记是不是要升级,设备定期去获取这个属性,有的话就下载,提交服务器“下载中”;
2、固定地址下载:包括固件名字和版本号,下载完成:提交服务器“已下载”;
3、设备验证固件包完整性,提交服务器“已验证”;
4、设备开始更新前,提交服务器“更新中”;
5、设备更新完成后,成功提交“已更新”,失败提交“失败”;