分类 IoT 平台软件 下的文章
YiAPP 移动客户端集成 OAuth2 认证服务器,实现统一登录
移动客户端通过 OAuth2 登录,实现账号统一管理。
准备工作
- YiAPP 客户端;
- OAuth2 服务器;
- IoT 应用服务器;
工作原理
- 打开 YiAPP 客户端软件;
- 使用“第三方登录”,选择 YiCLOUD 或 Github;
- 登录 第三方账号;
- 登录成功后即可完成统一登录;
视频演示
ThingsBoard 集成 Keycloak 自定义 OAuth2 认证服务器
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
复制 secret
增加 user
设置 user 密码
2.2、ThingsBoard 配置
尤其注意 一系列 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
3.2、跳转到 Keycloak
3.3、完成 OAuth2 登录集成
3.4、视频预览
ThingsBoard 二次开发:导出数据到 csv/excel
此二次开发主要是基于 ThingsBoard UI 界面上的 js 脚本开发,熟悉 js 开发的朋友应该很容易理解。
从 Dashboard 导出 csv 其实有好几种情况,1是 telemetry 数据导出,2是 attributes 数据导出。
准备工作
- Chrome 浏览器
- javascript 基础知识
- ThingsBoard UI 二次开发基础知识
- Dashbaord 开发经验
新建Dashboard
Telemetry 数据导出
导出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 数据导出
导出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();
ThingsBoard 3.3 版本 OTA 源码分析
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
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、设备更新完成后,成功提交“已更新”,失败提交“失败”;