对接小萨塔吊

This commit is contained in:
guoshengxiong 2025-06-30 15:09:10 +08:00
parent 2e475dcf9f
commit 65338aecc9
5 changed files with 413 additions and 7 deletions

View File

@ -266,13 +266,13 @@ public class Tower implements Serializable {
*/
@Excel(name = "纬度", width = 15)
@ApiModelProperty(value = "纬度")
private java.lang.Integer latitude;
private java.lang.String latitude;
/**
* 经度
*/
@Excel(name = "经度", width = 15)
@ApiModelProperty(value = "经度")
private java.lang.Integer longtitude;
private java.lang.String longtitude;
/**
* 设备标识码
*/

View File

@ -105,10 +105,10 @@ public class TowerCurrentData implements Serializable {
@ApiModelProperty(value = "幅度 必传参数")
private java.lang.String ranger;
/**
* 高度 必传参数
* 吊钩运行高度 必传参数
*/
@Excel(name = "高度 必传参数", width = 15)
@ApiModelProperty(value = "高度 必传参数")
@Excel(name = "吊钩运行高度 必传参数", width = 15)
@ApiModelProperty(value = "吊钩运行高度 必传参数")
private java.lang.String height;
/**
* 风速(m/s)

View File

@ -283,6 +283,8 @@ public class TowerWorkCycle implements Serializable {
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty(value = "模拟生成时间")
private java.util.Date mockTime;
@ApiModelProperty(value = "小萨id")
private java.lang.String xiaoSaId;
/**
* 工作循环吊程
*/

View File

@ -338,7 +338,14 @@ public class Project implements Serializable {
private Integer enableWorkerSafeWatch;
@ApiModelProperty(value = "安全履职报警推送时间")
private String workerSafeWatchTime;
@ApiModelProperty(value = "1开启小萨塔吊")
private Integer enableXiaosa;
@ApiModelProperty(value = "小萨项目id")
private String xiaosaProjectId;
@ApiModelProperty(value = "小萨appSecret")
private String xiaosaAppKey;
@ApiModelProperty(value = "小萨appSecret")
private String xiaosaAppSecret;
/**
* runde平台token

View File

@ -5,6 +5,10 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -28,13 +32,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Component
@ -331,5 +336,397 @@ public class TowerTask {
}
}
/**
* 获取塔吊数据(小萨) 每5分钟触发任务
*/
@SchedulerLock(name = "getXiaosaTowerData", lockAtMostFor = 1000 * 60 * 5, lockAtLeastFor = 1000 * 60 * 3)
@Scheduled(cron = "0 0/5 * * * ?")
@RequestMapping("getXiaosaTowerData")
public void getXiaosaTowerData() {
try {
log.info("获取塔吊数据(小萨)开始任务");
List<Project> projects = projectMapper.selectList(new LambdaQueryWrapper<Project>()
.eq(Project::getEnableXiaosa, 1)
);
for (Project project : projects) {
// 获取设备列表
List<Tower> devList = towerMapper.selectList(new LambdaQueryWrapper<Tower>()
.eq(Tower::getProjectSn, project.getProjectSn()));
Map<String, Tower> devMap = devList.stream().collect(Collectors.toMap(Tower::getDevSn, Function.identity(), (o1, o2) -> o1));
if (CollUtil.isEmpty(devList)) {
continue;
}
JSONObject body = new JSONObject();
body.put("appKey", project.getXiaosaAppKey());
body.put("appSecret", project.getXiaosaAppSecret());
String url = "https://api.open.ixiaosa.com/auth/app_access_token";
log.info("获取塔吊数据(小萨)的token的url:{}body:{}", url, JSON.toJSONString(body));
String result = HttpRequest.post(url)
.body(JSON.toJSONString(body))
.timeout(2000)
.execute()
.body();
log.info("获取塔吊数据(小萨)的token的result:{}", result);
if (StrUtil.isBlank(result)) {
continue;
}
com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result);
if (!Objects.equals(rootJo.getBoolean("success"), true)) {
continue;
}
String tokenHeader = "Bearer " + rootJo.getJSONObject("data").getString("appAccessToken");
String xiaosaProjectId = project.getXiaosaProjectId();
url = "https://api.device.open.ixiaosa.com/data/list_crane_param_data/" + xiaosaProjectId;
log.info("萨达 查询项目下塔机参数数据url:{}", url);
result = HttpRequest.get(url)
.header("Authorization", tokenHeader)
.execute().body();
log.info("萨达 查询项目下塔机参数数据result:{}", result);
Map<String, String> projectCraneId2DevSnMap = new HashMap<>();
rootJo = JSON.parseObject(result);
if (!Objects.equals(rootJo.getBoolean("success"), true)) {
continue;
}
com.gexin.fastjson.JSONArray jsonArray = rootJo.getJSONArray("data");
for (int i = 0; i < jsonArray.size(); i++) {
com.gexin.fastjson.JSONObject jsonObject = jsonArray.getJSONObject(i);
Tower tower = devMap.get(jsonObject.getString("deviceSn"));
if (tower == null) {
continue;
}
projectCraneId2DevSnMap.put(jsonObject.getString("projectCraneId"), tower.getDevSn());
//修改塔吊设备信息
//projectId 项目 ID String
//projectCraneId 项目塔机 ID String
//projectCraneNum 塔机名称/编号 String
//craneType 塔机类型 String FlatArm:平臂,SwingArm:动臂,Tower:塔头
//jibArmLength 起重臂长 Float m
//balanceArmLength 平衡臂长 Float m
//craneHeight 塔机高度 Float m
//craneCapHeight 塔机塔帽高 Float m
//longitude 经度 Double °
//latitude 纬度 Double °
// x 相对坐标 X Float
// y 相对坐标 Y Float
//standardArmLength 标准臂长 Float m
//maxRadiusLoad 最大幅度起重量 Float t
//ratedLoad 额定吊重 Float t
tower.setForearmLength(jsonObject.getDouble("jibArmLength"));
tower.setPosteriorArmLength(jsonObject.getDouble("balanceArmLength"));
tower.setLongtitude(jsonObject.getString("longitude"));
tower.setLatitude(jsonObject.getString("latitude"));
tower.setRelatedX(jsonObject.getString("x"));
tower.setRelatedY(jsonObject.getString("y"));
towerMapper.updateById(tower);
}
if (CollUtil.isEmpty(projectCraneId2DevSnMap)) {
continue;
}
//保存塔吊实时数据
saveTowerCurrentDataForXiaoSa(tokenHeader, xiaosaProjectId, devMap, projectCraneId2DevSnMap);
//保存塔吊循环数据
saveTowerWorkCycleForXiaoSa(tokenHeader, xiaosaProjectId, project.getProjectSn(),devMap, projectCraneId2DevSnMap);
}
} catch (HttpException e) {
log.error("获取塔吊数据(小萨)开始任务err"+e);
}
}
/**
* 保存塔吊循环数据小萨
*
* @param tokenHeader
* @param xiaosaProjectId
* @param projectSn
* @param projectCraneId2DevSnMap
*/
private void saveTowerWorkCycleForXiaoSa(String tokenHeader, String xiaosaProjectId, String projectSn, Map<String, Tower> devMap, Map<String, String> projectCraneId2DevSnMap) {
String url = "https://api.device.open.ixiaosa.com/data/list_crane_work_data/" + xiaosaProjectId;
log.info("萨达 查询项目下塔机实时吊装循环数据url:{}", url);
String result = HttpRequest.get(url)
.header("Authorization", tokenHeader)
.execute().body();
log.info("萨达 查询项目下塔机实时吊装循环数据result:{}", result);
com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result);
if (!Objects.equals(rootJo.getBoolean("success"), true)) {
return;
}
com.gexin.fastjson.JSONArray jsonArray1 = rootJo.getJSONArray("data");
List<TowerWorkCycle> datas = new ArrayList<>();
if (CollUtil.isNotEmpty(jsonArray1)) {
for (int i = 0; i < jsonArray1.size(); i++) {
com.gexin.fastjson.JSONObject json = jsonArray1.getJSONObject(i);
String xiansaId = json.getString("id");
int count = towerWorkCycleService.count(new LambdaQueryWrapper<TowerWorkCycle>()
.eq(TowerWorkCycle::getXiaoSaId, xiansaId));
if (count > 0) {
continue;
}
TowerWorkCycle data = new TowerWorkCycle();
// 1. projectCraneId devSn设备识别码
data.setDevSn(projectCraneId2DevSnMap.get(json.getString("projectCraneId")));
Tower tower = devMap.get(data.getDevSn());
data.setDevName(tower.getDevName());
// 2. projectCraneNum项目塔机编号/名称无直接对应字段可存储到额外字段或忽略
// 如果 towerWorkCycle 有类似 craneName 的字段可以设置
// t.setCraneName(json.getString("projectCraneNum"));
// 3. workLoadingType吊装类型2:有吊重无直接对应字段可用于判断是否设置吊重
// int workLoadingType = json.getInt("workLoadingType");
// if (workLoadingType == 2) {
// 有吊重时设置相关参数
// }
data.setLoading(String.valueOf(Double.parseDouble(json.getString("maxLoadWeight")) * 1000)); // t kg
// 4. upTime startTime起吊时间
data.setStartTime(DateUtil.parseDateTime(json.getString("upTime")));
// 5. upAngle slingStartRotation起吊回转角度
data.setSlingStartRotation(json.getString("upAngle"));
// 6. upRadius slingStartRange起吊幅度
data.setSlingStartRange(json.getString("upRadius"));
// 7. upHookHeight slingStartHeight起吊吊钩高度
data.setSlingStartHeight(json.getString("upHookHeight"));
// 8. upLoadWeight起吊吊重无直接对应字段可结合 maxLoadWeight 使用
// 9. upSafeLoad起吊安全吊重无直接对应字段
// 10. upTorquePercent起吊力矩百分比无直接对应字段
// 11. upFalls workMultiple起吊吊绳倍率
data.setWorkMultiple(json.getInteger("upFalls"));
// 12. upElevationAngle起吊俯仰角度无直接对应字段
// 13. upWindSpeed起吊风速用于 maxWindSpeed 计算
double upWindSpeed = json.containsKey("upWindSpeed") ? json.getDouble("upWindSpeed") : 0;
// 14. maxTime力矩最大点记录时间无直接对应字段
// 15. maxAngle用于计算 maxAngle/minAngle
double maxAngle = json.getDouble("maxAngle");
// 16. maxRadius用于计算 maxRange/minRange
double maxRadius = json.getDouble("maxRadius");
// 17. maxHookHeight用于计算 maxHeight/minHeight
double maxHookHeight = json.getDouble("maxHookHeight");
// 19. maxSafeLoad最大安全吊重无直接对应字段
// 20. maxTorquePercent peakLoad最大力矩百分比
data.setPeakLoad(json.getString("maxTorquePercent"));
// 21. maxFalls最大吊绳倍率无直接对应字段
// 22. maxElevationAngle最大俯仰角度无直接对应字段
// 23. maxWindSpeed用于计算 maxWindSpeed
double maxWindSpeed = json.containsKey("maxWindSpeed") ? json.getDouble("maxWindSpeed") : 0;
// 24. downTime endTime落吊时间
data.setEndTime(DateUtil.parseDateTime(json.getString("downTime")));
// 25. downAngle slingEndRotation落吊回转角度
data.setSlingEndRotation(json.getString("downAngle"));
// 26. downRadius slingEndRange落吊幅度
data.setSlingEndRange(json.getString("downRadius"));
// 27. downHookHeight slingEndHeight落吊吊钩高度
data.setSlingEndHeight(json.getString("downHookHeight"));
// 28. downLoadWeight落吊吊重无直接对应字段
// 29. downSafeLoad落吊安全吊重无直接对应字段
// 30. downTorquePercent落吊力矩百分比无直接对应字段
// 31. downFalls落吊吊绳倍率无直接对应字段
// 32. downElevationAngle落吊俯仰角度无直接对应字段
// 33. downWindSpeed用于计算 maxWindSpeed
double downWindSpeed = json.containsKey("downWindSpeed") ? json.getDouble("downWindSpeed") : 0;
// 34. loadTime workTime起吊时长单位 s
data.setWorkTime(String.valueOf(DateUtil.between(data.getStartTime(), data.getEndTime(), DateUnit.SECOND)));
// 35. realLoadWeight实际吊重无直接对应字段
// 计算最大/最小角度幅度高度
double upAngle = json.getDouble("upAngle");
double downAngle = json.getDouble("downAngle");
data.setMaxAngle(String.valueOf(Math.max(upAngle, Math.max(maxAngle, downAngle))));
data.setMinAngle(String.valueOf(Math.min(upAngle, Math.min(maxAngle, downAngle))));
double upRadius = json.getDouble("upRadius");
double downRadius = json.getDouble("downRadius");
data.setMaxRange(String.valueOf(Math.max(upRadius, Math.max(maxRadius, downRadius))));
data.setMinRange(String.valueOf(Math.min(upRadius, Math.min(maxRadius, downRadius))));
double upHookHeight = json.getDouble("upHookHeight");
double downHookHeight = json.getDouble("downHookHeight");
data.setMaxHeight(String.valueOf(Math.max(upHookHeight, Math.max(maxHookHeight, downHookHeight))));
data.setMinHeight(String.valueOf(Math.min(upHookHeight, Math.min(maxHookHeight, downHookHeight))));
// 计算最大风速
data.setMaxWindSpeed(String.valueOf(Math.max(upWindSpeed, Math.max(maxWindSpeed, downWindSpeed))));
data.setProjectSn(projectSn);
data.setAddTime(new Date());
data.setXiaoSaId(xiansaId);
datas.add(data);
}
}
if (CollUtil.isNotEmpty(datas)) {
towerWorkCycleService.saveBatch(datas);
}
}
/**
* 保存塔吊实时数据小萨
*
* @param tokenHeader
* @param xiaosaProjectId
* @param devMap
* @param projectCraneId2DevSnMap
*/
private void saveTowerCurrentDataForXiaoSa(String tokenHeader, String xiaosaProjectId, Map<String, Tower> devMap, Map<String, String> projectCraneId2DevSnMap) {
String url = "https://api.device.open.ixiaosa.com/data/list_crane_real_time_data/" + xiaosaProjectId;
log.info("萨达 查询项目下塔机实时数据url:{}", url);
String result = HttpRequest.get(url)
.header("Authorization", tokenHeader)
.execute().body();
log.info("萨达 查询项目下塔机实时数据result:{}", result);
com.gexin.fastjson.JSONObject rootJo = JSON.parseObject(result);
if (!Objects.equals(rootJo.getBoolean("success"), true)) {
return;
}
com.gexin.fastjson.JSONArray jsonArray1 = rootJo.getJSONArray("data");
List<TowerCurrentData> datas = new ArrayList<>();
if (CollUtil.isNotEmpty(jsonArray1)) {
for (int i = 0; i < jsonArray1.size(); i++) {
//实时数据
// "time": "2025-06-27 17:56:52",
// "projectCraneId": "1928738212753227778",
// "currentCraneHeight": 0.0,
// "radius": 23.94,
// "hookHeight": 37.64,
// "angle": 307.46,
// "loadWeight": 0.67,
// "safeLoad": 6.0,
// "torque": 16.0398,
// "torquePercent": 11.24,
// "falls": 2,
// "elevationAngle": 0.0,
// "windSpeed": 0.0,
// "infractionTypeList": "",
// "craneDriverName": "",
// "craneDriverIdCard": "",
// "projectCraneNum": "1#"
com.gexin.fastjson.JSONObject json = jsonArray1.getJSONObject(i);
TowerCurrentData data = new TowerCurrentData();
data.setReciveTime(DateUtil.parseDateTime(json.getString("time")));
data.setStartTime(data.getReciveTime());
data.setDevSn(projectCraneId2DevSnMap.get(json.getString("projectCraneId")));
Tower tower = devMap.get(data.getDevSn());
data.setDevName(tower.getDevName());
data.setHeight(json.getString("hookHeight"));
data.setRanger(json.getString("radius"));
data.setAngle(json.getString("angle"));
data.setLoading(Optional.ofNullable(json.getDouble("loadWeight")).map(m -> m * 1000 + "").orElse(null));
if (json.getDouble("loadWeight") != null && json.getDouble("safeLoad") != null && json.getDouble("safeLoad").compareTo(0D) != 0) {
data.setLoadRatio(NumberUtil.roundStr(json.getDouble("loadWeight") / json.getDouble("safeLoad"), 4));
}
data.setTorque(json.getString("torque"));
data.setTorqueRatio(json.getString("torquePercent"));
data.setRate(json.getString("falls"));
data.setObliguity(json.getString("elevationAngle"));
data.setWindspeed(json.getString("windSpeed"));
data.setDriverName(json.getString("craneDriverName"));
data.setDriverIdCard(json.getString("craneDriverIdCard"));
data.setRealFlag("1");
data.setNoError(1);
String infractionTypeList = json.getString("infractionTypeList");
Integer noAlarm = 1;
if (StrUtil.isNotBlank(infractionTypeList)) {
for (String alarmKey : StrUtil.split(infractionTypeList, ",")) {
switch (alarmKey) {
case "Collision_LeftRotationAlert":
data.setMultiNegAlarm(1);
break;
case "Collision_RightRotationAlert":
data.setMultiPosAlarm(1);
break;
case "Collision_RadiusForthAlert":
data.setMultiOutAlarm(1);
break;
case "Collision_RadiusBackAlert":
data.setMultiBackAlarm(1);
break;
case "RestrictedArea_LeftRotationAlert":
data.setForbidEntryNegAlarm(1);
break;
case "RestrictedArea_RightRotationAlert":
data.setForbidEntryPosAlarm(1);
break;
case "RestrictedArea_RadiusForthAlert":
data.setForbidSuspend4OutAlarm(1);
break;
case "RestrictedArea_RadiusBackAlert":
data.setForbidSuspend4BackAlarm(1);
break;
case "AngleRestrictedArea_LeftRotationAlert":
data.setForbidSuspend2NegAlarm(1);
break;
case "AngleRestrictedArea_RightRotationAlert":
data.setForbidSuspend2PosAlarm(1);
break;
case "Limit_LeftRotationAlert":
data.setNegAngleAlarm(1);
break;
case "Limit_RightRotationAlert":
data.setPosAngleAlarm(1);
break;
case "Limit_RadiusForthAlert":
data.setMaxRangeAlarm(1);
break;
case "Limit_RadiusBackAlert":
data.setMinRangeAlarm(1);
break;
case "Limit_OfHeightTopAlert":
data.setHeightAlarm(1);
break;
case "Limit_HeightBottomAlert":
data.setHeightLowerAlarm(1);
break;
case "Limit_LoadAlert":
data.setMomentAlarm(1);
break;
case "Limit_TorqueAlert":
data.setMomentAlarm(1);
break;
case "Limit_WindSpeedAlert":
data.setWindSpeedAlarm(1);
break;
}
}
noAlarm = 0;
}
data.setNoAlarm(noAlarm);
data.setProjectSn(tower.getProjectSn());
datas.add(data);
}
}
if (CollUtil.isNotEmpty(datas)) {
towerCurrentDataService.saveBatch(datas);
}
}
}