一、前情提要
-
現(xiàn)有物聯(lián)網(wǎng)系統(tǒng)已經(jīng)初步接入了一些智能設備并對相應的設備進行數(shù)據(jù)收集和控制。下一步是要實現(xiàn)設備的聯(lián)動功能。
溫濕度傳感器
光照傳感器
紅外遙控器
空調(diào)
智能插座
二、要做什么?
我們要實現(xiàn)的功能是,場景自動化功能。
- 當溫度達到30℃時且濕度小于45%時 ==> 打開空調(diào),并將空調(diào)調(diào)整為制冷模式,溫度調(diào)節(jié)為20攝氏度
- 當濟南的pm2.5濃度大于23時,==> 打開關閉插座,并發(fā)送釘釘消息提醒
-
當濟南的天氣狀態(tài)為“雨天”時或室內(nèi)光照強度小于250LUX時 ==> 打開空調(diào)除濕
這些任務,均可統(tǒng)一設置執(zhí)行的時間段,并設置周幾的執(zhí)行狀態(tài)。
功能前端效果
三、怎么做?
1、難點
- 如何定義任務的數(shù)據(jù)結(jié)構(gòu),保證能夠?qū)崿F(xiàn)上面的功能。
- 如何通過代碼動態(tài)去判斷各種數(shù)據(jù)值的對比是否能夠滿足條件。
- 如何動態(tài)的去執(zhí)行各個動作,且每個動作所需的參數(shù)不同。
- 如何優(yōu)雅的實現(xiàn),必然不能每個條件都寫一大串 if - else
- 如何觸發(fā)每個任務的執(zhí)行,定義統(tǒng)一的觸發(fā)入口
- 如何保證服務的穩(wěn)定及可拓展,橙子便利現(xiàn)有140+門店,假設每家門店都有5個常規(guī)任務在執(zhí)行,進行的任務數(shù)就要再700+。
2、表結(jié)構(gòu)
CREATE TABLE `scene_automation` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`scene_name` varchar(200) DEFAULT NULL COMMENT '場景名稱',
`store_code` varchar(10) DEFAULT NULL COMMENT '門店編號',
`match_type` varchar(10) DEFAULT NULL COMMENT '條件匹配模式(ANY 任意滿足 ALL 全部滿足)',
`background` varchar(200) DEFAULT NULL COMMENT '背景',
`scene_state` varchar(10) DEFAULT NULL COMMENT '場景狀態(tài)(ON 開啟 OFF 關閉)',
`conditions` text COMMENT '條件',
`actions` text COMMENT '條件',
`preconditions` text COMMENT '前置條件',
`create_no` varchar(50) DEFAULT NULL COMMENT '創(chuàng)建人',
`create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
`update_no` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='場景自動化表';
3、數(shù)據(jù)結(jié)構(gòu)
{
"sceneName": "場景自動化測試",
"storeCode": "0009",
"matchType": "ALL",
"background": "#ffffff",
"sceneState": "ON",
"conditionMessages": [
{
"entityId": "6cb6a428adede6b580jqqb",
"entityType": "DEVICE",
"orderNum": 1,
"display": {
"code": "temperature",
"operator": "MORE",
"value": 25
}
},
{
"entityId": "6cb6a428adede6b580jqqb",
"entityType": "DEVICE",
"orderNum": 2,
"display": {
"code": "humidity",
"operator": "MORE",
"value": 30
}
}
],
"preconditionMessages": [
{
"display": {
"start": "00:00",
"end": "23:59",
"loops": "1111110"
},
"type": "TIME_CHECK"
}
],
"actionMessages": [
{
"executor": "socket",
"entityId": "6c30fb6e4c962c96b2asgo",
"property": {
"switchSocket": true
}
},
{
"executor": "sendDing",
"property": {
"message": "釘釘消息發(fā)送測試"
}
}
]
}
4、引入jexl邏輯引擎
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-jexl</artifactId>
<version>2.1.1</version>
</dependency>
Java表達式語言--Java Expression Language(JEXL),這是Apache開源的一個jar包,旨在促進在用Java編寫的應用程序和框架中,實現(xiàn)動態(tài)和腳本功能,JEXL基于JSTL表達式語言的一些擴展實現(xiàn)了表達式語言,支持shell腳本或ECMAScript(js)中的大多數(shù)構(gòu)造.
5、條件-核心邏輯封裝(關系運算符)
/**
* 關系運算符
*
* @param conditionOperationMessage
* @return boolean
*/
private static boolean relationalCal(ConditionOperationMessage conditionOperationMessage) {
JexlEngine engine = new JexlEngine();
JexlContext context = new MapContext();
// 構(gòu)建表達式
String command = " if ( a " +
conditionOperationMessage.getOperation() +
conditionOperationMessage.getValue() +
" ) { return true } else { return false } ";
// 設置變量值
context.set("a", conditionOperationMessage.getParam());
// 執(zhí)行計算結(jié)果
boolean result = (boolean) engine.createExpression(command).evaluate(context);
log.info("關系運算結(jié)果 [ {} {} {} ] --> [{}] ", conditionOperationMessage.getParam(), conditionOperationMessage.getOperation(), conditionOperationMessage.getValue(), result);
return result;
}
6、條件-邏輯運算表達式
/**
* 邏輯或運算核心方法
*
* @param conditionOperationMessageList
* @return boolean
*/
private static boolean logicOrCoreCal(List<ConditionOperationMessage> conditionOperationMessageList) {
log.info("========開始進入邏輯或運算=====");
for (ConditionOperationMessage conditionOperationMessage : conditionOperationMessageList) {
if (relationalCal(conditionOperationMessage)) {
log.info("========滿足邏輯或運算=====");
return true;
}
}
log.info("========不滿足邏輯或運算=====");
return false;
}
/**
* 邏輯或運算核心方法
*
* @param conditionOperationMessageList
* @return boolean
*/
private static boolean logicAndCoreCal(List<ConditionOperationMessage> conditionOperationMessageList) {
log.info("========開始進入邏輯且運算=====");
for (ConditionOperationMessage conditionOperationMessage : conditionOperationMessageList) {
if (!relationalCal(conditionOperationMessage)) {
log.info("========不滿足邏輯且運算=====");
return false;
}
}
log.info("========滿足邏輯且運算=====");
// 校驗是否全部為true
return true;
}
7、動作-動態(tài)方法執(zhí)行
/**
* 執(zhí)行動作
*
* @param actionMessageList
*/
public static void action(List<ActionMessage> actionMessageList) {
JexlEngine engine = new JexlEngine();
JexlContext context;
if (!CollectionUtils.isEmpty(actionMessageList)) {
for (ActionMessage actionMessage : actionMessageList) {
context = new MapContext();
String command = "ActionTool." + actionMessage.getExecutor() + "(property)";
context.set("ActionTool", ActionTool.class);
context.set("property", actionMessage.getProperty());
boolean result = (boolean) engine.createExpression(command).evaluate(context);
log.info("方法[{}]執(zhí)行結(jié)果[{}]", actionMessage.getExecutor(), result);
}
}
}
/**
* 插座控制
*
* @param property
*/
public static boolean socket(ActionPropertyMessage property) {
log.info("插座控制[{}]", property.getSwitchSocket());
autoService.testService();
return true;
}
/**
* 插座控制
*
* @param property
*/
public static boolean sendDing(ActionPropertyMessage property) {
log.info("發(fā)送釘釘消息[{}]", property.getMessage());
return true;
}
/**
* 延遲
*
* @param property
*/
public static boolean delay(ActionPropertyMessage property) {
log.info("延時控制 時[{}] 分[{}] 秒[{}] ", property.getDelayHour(), property.getDelayMinutes(), property.getDelaySeconds());
return true;
}
/**
* 空調(diào)控制
*
* @param property
*/
public static boolean airCondition(ActionPropertyMessage property) {
log.info("空調(diào)控制 類型[{}] 值[{}]", property.getAirConditionType().getStr(), property.getAirConditionValue());
return true;
}
調(diào)用入口
/**
* 調(diào)用入口
*
* @param code 觸發(fā)條件
* @return SimpleMessage
*/
@Override
public SimpleMessage imitate(String code) {
// 獲取正在開啟的活動
List<SceneAutomation> sceneAutomationList = sceneAutomationDao.getSuitableScene(code);
// 校驗是否存在該場景
if (CollectionUtils.isEmpty(sceneAutomationList)) {
return new SimpleMessage(ErrorCodeEnum.NO, "查詢不到該場景");
}
// 遍歷場景
for (SceneAutomation sceneAutomation : sceneAutomationList) {
// 判斷前置條件
List<PreconditionMessage> preconditionMessageList = JSON.parseArray(sceneAutomation.getPreconditions(), PreconditionMessage.class);
if (!PreconditionTool.judge(preconditionMessageList)) {
log.info("任務[{}] 不滿足前置條件", sceneAutomation.getSceneName());
continue;
}
// 解析條件
List<ConditionMessage> conditionMessages = JSON.parseArray(sceneAutomation.getConditions(), ConditionMessage.class);
// 條件執(zhí)行封裝列表
List<ConditionOperationMessage> conditionOperationMessageList = new ArrayList<>();
// 獲取條件值
conditionMessages.forEach(conditionMessage -> {
// 設備類型
if (ConditionEntityTypeEnum.DEVICE.equals(conditionMessage.getEntityType())) {
// 條件值
Object param = sceneAutomationDao.getDeviceParam(conditionMessage.getDisplay().getCode(), conditionMessage.getEntityId());
log.info("設備碼[{}] 類型[{}] 當前值[{}] ", conditionMessage.getEntityId(), conditionMessage.getDisplay().getCode(), param);
conditionOperationMessageList.add(ConditionOperationMessage.builder()
.param(param)
.operation(conditionMessage.getDisplay().getOperator().getStr())
.value(conditionMessage.getDisplay().getValue())
.build());
} else {
// TODO 待定
}
});
// 判斷條件是否執(zhí)行完成
if (CollectionUtils.isEmpty(conditionOperationMessageList)
|| !ConditionTool.judge(conditionOperationMessageList, sceneAutomation.getMatchType())) {
log.info("任務[{}] 不滿足條件", sceneAutomation.getSceneName());
continue;
}
// 執(zhí)行動作
List<ActionMessage> actionMessages = JSON.parseArray(sceneAutomation.getActions(), ActionMessage.class);
// 執(zhí)行
ActionTool.action(actionMessages);
}
return new SimpleMessage(ErrorCodeEnum.OK);
}
三、還有什么要補充的?
1、mysql 查詢 json格式數(shù)據(jù)
/**
* 獲取符合條件的場景
*
* @param code 條件值
* @return List<SceneAutomation>
*/
@Select("SELECT " +
" id, " +
" scene_name, " +
" store_code, " +
" match_type, " +
" background, " +
" scene_state, " +
" conditions, " +
" preconditions, " +
" actions " +
"FROM " +
" `scene_automation` " +
"WHERE " +
" scene_state = 'ON' " +
" AND JSON_CONTAINS( conditions, JSON_OBJECT( 'display', JSON_OBJECT( 'code', #{code} ) ) )")
List<SceneAutomation> getSuitableScene(@Param("code") String code);
/**
* 獲取參數(shù)值
*
* @param key key
* @param deviceUid 設備碼
* @return Object
*/
@Select(" SELECT " +
"device_detail ->> '$.${key}' as 'param' " +
"from store_devices WHERE " +
"device_uid = #{deviceUid} ")
Object getDeviceParam(@Param("key") String key, @Param("deviceUid") String deviceUid);
2、static 穿透 service 執(zhí)行
/**
* static穿透service執(zhí)行方法
*/
final
AutoService innerAutoService;
static AutoService autoService;
public ActionTool(AutoService innerAutoService) {
this.innerAutoService = innerAutoService;
}
/**
* static穿透service初始化
*/
@PostConstruct
public void init() {
autoService = innerAutoService;
}
四、這玩意兒的意義是什么?
1、我們在遇到特殊問題的時候,要打破固有的編程思想,根據(jù)自己的業(yè)務需求找到最優(yōu)的解決方案。
2、不要妥協(xié),要讓自己的程序變得優(yōu)雅。





