package com.ruoyi.device.service.impl; import com.alibaba.fastjson2.JSON; import com.ruoyi.device.api.domain.PayloadVO; import com.ruoyi.device.api.enums.PayloadStatusEnum; import com.ruoyi.device.domain.api.*; import com.ruoyi.device.domain.model.*; import com.ruoyi.device.domain.model.thingsboard.AttributeMap; import com.ruoyi.device.domain.model.thingsboard.TelemetryMap; import com.ruoyi.device.domain.model.thingsboard.constants.DeviceAttributes; import com.ruoyi.device.domain.model.thingsboard.constants.DeviceTelemetry; import com.ruoyi.device.domain.model.weather.Weather; import com.ruoyi.device.service.api.IBufferDeviceService; import com.ruoyi.device.service.dto.AircraftDTO; import com.ruoyi.device.service.dto.AircraftDetailDTO; import com.ruoyi.device.service.dto.DockDetailDTO; import com.ruoyi.device.api.enums.AircraftStatusEnum; import com.ruoyi.device.api.enums.DockStatusEnum; import com.ruoyi.device.api.enums.NetworkTypeEnum; import com.ruoyi.device.api.enums.AirConditionerStatusEnum; import com.ruoyi.device.api.enums.CabinDoorStatusEnum; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 设备缓冲服务实现 * 整合数据库数据和ThingsBoard数据 * * @author ruoyi * @date 2026-01-20 */ @Service @Slf4j public class BufferDeviceImpl implements IBufferDeviceService { @Autowired private IDockDomain dockDomain; @Autowired private IDeviceDomain deviceDomain; @Autowired private IAircraftDomain aircraftDomain; @Autowired private IAircraftPayloadDomain aircraftPayloadDomain; @Autowired private IDockAircraftDomain dockAircraftDomain; @Autowired private IThingsBoardDomain thingsBoardDomain; @Autowired private IPayloadDomain payloadDomain; @Autowired private IWeatherDomain iWeatherDomain; @Override public DockDetailDTO getDockDetailById(Long dockId) { Dock dock = dockDomain.selectDockByDockId(dockId); log.info("selectDockByDockId {}",JSON.toJSONString(dock)); if (dock == null) { return null; } Device device = deviceDomain.selectDeviceByDeviceId(dock.getDeviceId()); log.info("selectDeviceByDeviceId {}",JSON.toJSONString(device)); DockDetailDTO dto = getChangeAbleDockDetailDTO(device.getIotDeviceId()); dto.setDockId(dock.getDockId()); dto.setDockName(dock.getDockName()); dto.setDockLocation(dock.getDockLocation()); dto.setDockIotId(device.getIotDeviceId()); dto.setSnNumber(device.getDeviceSn()); dto.setBindTime(device.getCreateTime().getTime()); List aircrafts = dockAircraftDomain.selectDockAircraftByDockId(dockId); if(!CollectionUtils.isEmpty(aircrafts)){ DockAircraft dockAircraft = aircrafts.get(0); Aircraft aircraft = aircraftDomain.selectAircraftByAircraftId(dockAircraft.getAircraftId()); dto.setAircraftId(aircraft.getAircraftId()); dto.setAircraftName(aircraft.getAircraftName()); Device airDevice = deviceDomain.selectDeviceByDeviceId(aircraft.getDeviceId()); dto.setAircraftIotId(airDevice.getIotDeviceId()); List aircraftPayloads = aircraftPayloadDomain.selectAircraftPayloadByAircraftId(aircraft.getAircraftId()); if(!CollectionUtils.isEmpty(aircraftPayloads)){ List payloads = new ArrayList<>(aircraftPayloads.size()); for(AircraftPayload aircraftPayload : aircraftPayloads){ Payload payload = payloadDomain.selectPayloadByPayloadId(aircraftPayload.getPayloadId()); PayloadVO payloadVO = new PayloadVO(); payloadVO.setPayloadId(aircraftPayload.getPayloadId()); payloadVO.setPayloadName(payload.getPayloadName()); payloadVO.setPayloadType(payload.getPayloadType()); payloadVO.setPayloadStatus(PayloadStatusEnum.ONLINE.getCode()); payloads.add(payloadVO); } dto.setPayloadList(payloads); } AircraftDetailDTO aircraftDetailDTO = getChangeAbleAirDetailDTO(airDevice.getIotDeviceId(), device.getIotDeviceId()); /** * 设置无人机状态 */ dto.setAircraftStatus(aircraftDetailDTO.getAircraftStatus()); /** * 设置作业架次 */ dto.setMissionCount(aircraftDetailDTO.getMissionCount()); } return dto; } @Override public AircraftDetailDTO getAircraftDetailById(Long aircraftId) { Aircraft aircraft = aircraftDomain.selectAircraftByAircraftId(aircraftId); log.info("selectAircraftByAircraftId {}",JSON.toJSONString(aircraft)); if (aircraft == null) { return null; } Device device = deviceDomain.selectDeviceByDeviceId(aircraft.getDeviceId()); log.info("selectDeviceByDeviceId {}",JSON.toJSONString(device)); // 查找无人机关联的机场 List dockAircrafts = dockAircraftDomain.selectDockAircraftByAircraftId(aircraftId); String dockDeviceIotId = null; if (!CollectionUtils.isEmpty(dockAircrafts)) { DockAircraft dockAircraft = dockAircrafts.get(0); Dock dock = dockDomain.selectDockByDockId(dockAircraft.getDockId()); if (dock != null) { Device dockDevice = deviceDomain.selectDeviceByDeviceId(dock.getDeviceId()); if (dockDevice != null) { dockDeviceIotId = dockDevice.getIotDeviceId(); } } } AircraftDetailDTO dto = getChangeAbleAirDetailDTO(device.getIotDeviceId(), dockDeviceIotId); dto.setAircraftId(aircraft.getAircraftId()); dto.setAircraftName(aircraft.getAircraftName()); dto.setSnNumber(device.getDeviceSn()); dto.setBindTime(device.getCreateTime().getTime()); List aircraftPayloads = aircraftPayloadDomain.selectAircraftPayloadByAircraftId(aircraft.getAircraftId()); if(!CollectionUtils.isEmpty(aircraftPayloads)){ List payloads = new ArrayList<>(aircraftPayloads.size()); for(AircraftPayload aircraftPayload : aircraftPayloads){ Payload payload = payloadDomain.selectPayloadByPayloadId(aircraftPayload.getPayloadId()); PayloadVO payloadVO = new PayloadVO(); payloadVO.setPayloadId(aircraftPayload.getPayloadId()); payloadVO.setPayloadName(payload.getPayloadName()); payloadVO.setPayloadType(payload.getPayloadType()); payloadVO.setPayloadStatus(PayloadStatusEnum.ONLINE.getCode()); payloads.add(payloadVO); } dto.setPayloadList(payloads); } // 获取电池SN号 - 从 batteries 数组里面的第一个的 SN号 TelemetryMap telemetryMap = thingsBoardDomain.getPredefinedDeviceTelemetry(device.getIotDeviceId()); telemetryMap.get(DeviceTelemetry.BATTERY) .ifPresent(telemetryValue -> { var batteryData = telemetryValue.getValue(); if (batteryData != null && !CollectionUtils.isEmpty(batteryData.getBatteries())) { // log.info("batteries {}",JSON.toJSONString(batteryData)); String batterySn = batteryData.getBatteries().get(0).getSn(); dto.setVoltage(batteryData.getBatteries().get(0).getVoltage()); dto.setBatteryLevel(batteryData.getBatteries().get(0).getCapacityPercent()); dto.setBatteryTemperature(batteryData.getBatteries().get(0).getTemperature()); dto.setBatteryLevel(batteryData.getBatteries().get(0).getCapacityPercent()); dto.setCycleCount(batteryData.getBatteries().get(0).getLoopTimes()); dto.setFlightTimeRemaining(batteryData.getRemainFlightTime()); dto.setBatterySn(batterySn); } }); // 飞行时长 - 从 total_flight_time 获取秒数并转换为天 telemetryMap.get(DeviceTelemetry.Total_Flight_Time) .ifPresent(telemetryValue -> { Integer seconds = telemetryValue.getValue(); if (seconds != null) { // 将秒转换为天:秒 / (60 * 60 * 24) Integer days = seconds / (60 * 60 * 24); dto.setFlightDuration(days); } }); return dto; } private DockDetailDTO getChangeAbleDockDetailDTO(String dockerDeviceIotId) { DockDetailDTO dto = new DockDetailDTO(); AttributeMap attributeMap = thingsBoardDomain.getPredefinedDeviceAttributes(dockerDeviceIotId); TelemetryMap telemetryMap = thingsBoardDomain.getPredefinedDeviceTelemetry(dockerDeviceIotId); // 根据 ACTIVE 属性和 MODE_CODE 设置机场状态 // 先检查 ACTIVE 属性判断是否离线,只有在线时才通过 MODE_CODE 判断具体状态 // MODE_CODE 定义: {"0":"空闲中","1":"现场调试","2":"远程调试","3":"固件升级中","4":"作业中","5":"待标定"} Boolean isActive = attributeMap.get(DeviceAttributes.ACTIVE).orElse(false); if (!isActive) { // 设备离线 dto.setDockStatus(DockStatusEnum.OFFLINE.getCode()); } else { // 设备在线,根据 MODE_CODE 判断具体状态 attributeMap.get(DeviceAttributes.MODE_CODE).ifPresent(modeCode -> { String dockStatus = mapModeCodeToDockStatus(modeCode); dto.setDockStatus(dockStatus); }); } // 设置备降点坐标数据 // 备降点经度 telemetryMap.get(DeviceTelemetry.Alternate_land_point_Longitude) .ifPresent(telemetryValue -> dto.setBackupLongitude(telemetryValue.getValue())); // 备降点纬度 telemetryMap.get(DeviceTelemetry.Alternate_land_point_Latitude) .ifPresent(telemetryValue -> dto.setBackupLatitude(telemetryValue.getValue())); // 从 acc_time 获取运行时间,转换为天为单位 // acc_time 单位是秒,需要转换为天:秒 / (60 * 60 * 24) telemetryMap.get(DeviceTelemetry.Acc_Time) .ifPresent(telemetryValue -> { Integer seconds = telemetryValue.getValue(); if (seconds != null) { // 将秒转换为天 Integer days = seconds / (60 * 60 * 24); dto.setRunningDuration(days); } }); // 设置环境数据 // 风速(单位:米每秒) telemetryMap.get(DeviceTelemetry.Wind_Speed) .ifPresent(telemetryValue -> dto.setWindSpeed(telemetryValue.getValue())); // 降雨量(枚举值:0-无雨,1-小雨,2-中雨,3-大雨) telemetryMap.get(DeviceTelemetry.Rainfall) .ifPresent(telemetryValue -> { Integer rainfallCode = telemetryValue.getValue(); if (rainfallCode != null) { dto.setRainfall(rainfallCode.doubleValue()); } }); // 环境温度(环境温度) telemetryMap.get(DeviceTelemetry.Environment_Temperature) .ifPresent(telemetryValue -> dto.setEnvironmentTemperature(telemetryValue.getValue())); // 设置网络类型和网络速率 // 网络类型(枚举值:1-4G,2-以太网) telemetryMap.get(DeviceTelemetry.Network_State_Type) .ifPresent(telemetryValue -> { Integer networkTypeCode = telemetryValue.getValue(); String networkType = mapNetworkTypeCodeToEnum(networkTypeCode); dto.setNetworkType(networkType); }); // 网络速率(单位:千字节每秒 KB/s) telemetryMap.get(DeviceTelemetry.Network_State_Rate) .ifPresent(telemetryValue -> { Double rate = telemetryValue.getValue(); if (rate != null) { // 将速率转换为网络延迟(假设延迟与速率成反比) // 这里可以根据实际业务逻辑调整 dto.setNetworkDelay(rate.intValue()); } }); // 设置硬件状态数据 // 空调状态(枚举值:0-空闲模式,1-制冷模式,2-制热模式,3-除湿模式等) telemetryMap.get(DeviceTelemetry.AIR_CONDITIONER_STATE) .ifPresent(telemetryValue -> { Integer stateCode = telemetryValue.getValue(); if (stateCode != null) { dto.setAirConditionerStatus(mapAirConditionerState(stateCode)); } }); // 舱门状态(枚举值:0-关闭,1-打开,2-半开,3-舱盖状态异常) telemetryMap.get(DeviceTelemetry.Cover_State) .ifPresent(telemetryValue -> { Integer coverState = telemetryValue.getValue(); if (coverState != null) { dto.setCabinDoorStatus(mapCabinDoorState(coverState)); } }); // 舱内温度 telemetryMap.get(DeviceTelemetry.TEMPERATURE) .ifPresent(telemetryValue -> dto.setCabinTemperature(telemetryValue.getValue())); // 舱内湿度 telemetryMap.get(DeviceTelemetry.HUMIDITY) .ifPresent(telemetryValue -> dto.setCabinHumidity(telemetryValue.getValue())); // 赋予经纬度 // log.info("开始获取经纬度数据,dockerDeviceIotId: {}", dockerDeviceIotId); // 纬度 attributeMap.get(DeviceAttributes.Latitude) .ifPresentOrElse( latitude -> { // log.info("获取到纬度数据: {}", latitude); dto.setLatitude(latitude); }, () -> log.warn("未获取到纬度数据,dockerDeviceIotId: {}", dockerDeviceIotId) ); // 经度 attributeMap.get(DeviceAttributes.Longitude) .ifPresentOrElse( longitude -> { log.info("获取到经度数据: {}", longitude); dto.setLongitude(longitude); }, () -> log.warn("未获取到经度数据,dockerDeviceIotId: {}", dockerDeviceIotId) ); Weather weather =iWeatherDomain.weatherInfo(dto.getLatitude().toString(), dto.getLongitude().toString()); if (weather != null) { dto.setWindSpeed(weather.getWindSpeed()); dto.setRainfall(weather.getRainfall()); dto.setEnvironmentTemperature(weather.getEnvironmentTemperature()); dto.setEnvironmentHumidity(weather.getEnvironmentHumidity()); // log.info("weather {}",weather); } // 无人机充电状态(枚举值:0-空闲,1-充电中) telemetryMap.get(DeviceTelemetry.Drone_Charge_State_State) .ifPresent(telemetryValue -> { Integer chargeState = telemetryValue.getValue(); if (chargeState != null) { String chargingStatus = mapChargeStateToStatus(chargeState); dto.setChargingStatus(chargingStatus); } }); // 电量百分比 telemetryMap.get(DeviceTelemetry.Drone_Charge_State_Capacity_Percent) .ifPresent(telemetryValue -> { Integer capacityPercent = telemetryValue.getValue(); dto.setCapacity_percent(capacityPercent); }); return dto; } /** * 将 MODE_CODE 映射到机场状态 * @param modeCode 模式代码 * @return 机场状态字符串 */ private String mapModeCodeToDockStatus(Integer modeCode) { switch (modeCode) { case 0: return DockStatusEnum.IDLE.getCode(); case 1: case 2: return DockStatusEnum.Debugging.getCode(); case 3: return DockStatusEnum.FIRMWARE_UPGRADING.getCode(); case 4: return DockStatusEnum.WORKING.getCode(); case 5: default: return DockStatusEnum.UNKNOWN.getCode(); } } /** * 将网络类型代码映射到 NetworkTypeEnum * @param networkTypeCode 网络类型代码(1-4G,2-以太网) * @return 网络类型枚举的 code */ private String mapNetworkTypeCodeToEnum(Integer networkTypeCode) { if (networkTypeCode == null) { return null; } switch (networkTypeCode) { case 1: return NetworkTypeEnum.FOUR_G.getCode(); case 2: return NetworkTypeEnum.ETHERNET.getCode(); default: return null; } } /** * 将充电状态代码映射到充电状态字符串 * @param chargeState 充电状态代码(0-空闲,1-充电中) * @return 充电状态字符串 */ private String mapChargeStateToStatus(Integer chargeState) { if (chargeState == null) { return "未知"; } switch (chargeState) { case 0: return "FREE"; case 1: return "CHARGING"; default: return "UNKNOWN"; } } /** * 将空调状态代码映射到 AirConditionerStatusEnum * @param stateCode 空调状态代码 * @return 空调状态枚举的 code */ private String mapAirConditionerState(Integer stateCode) { if (stateCode == null) { return null; } // 只映射主要的四种模式,其他状态返回 null switch (stateCode) { case 0: return AirConditionerStatusEnum.IDLE.getCode(); case 1: return AirConditionerStatusEnum.COOLING.getCode(); case 2: return AirConditionerStatusEnum.HEATING.getCode(); case 3: return AirConditionerStatusEnum.DEHUMIDIFYING.getCode(); default: // 其他状态(准备模式、退出模式等)不映射 return null; } } /** * 将舱门状态代码映射到 CabinDoorStatusEnum * @param coverState 舱门状态代码(0-关闭,1-打开,2-半开,3-舱盖状态异常) * @return 舱门状态枚举的 code */ private String mapCabinDoorState(Integer coverState) { if (coverState == null) { return null; } switch (coverState) { case 0: return CabinDoorStatusEnum.CLOSED.getCode(); case 1: return CabinDoorStatusEnum.OPEN.getCode(); case 2: return CabinDoorStatusEnum.HALF_OPEN.getCode(); case 3: return CabinDoorStatusEnum.ABNORMAL.getCode(); default: return null; } } private AircraftDetailDTO getChangeAbleAirDetailDTO(String aircraftDeviceIotId, String dockDeviceIotId) { AircraftDetailDTO dto = new AircraftDetailDTO(); AttributeMap attributeMap = thingsBoardDomain.getPredefinedDeviceAttributes(aircraftDeviceIotId); TelemetryMap telemetryMap = thingsBoardDomain.getPredefinedDeviceTelemetry(aircraftDeviceIotId); // AttributeMap attributeDockMap = thingsBoardDomain.getPredefinedDeviceAttributes(dockDeviceIotId); TelemetryMap telemetryDockMap = thingsBoardDomain.getPredefinedDeviceTelemetry(dockDeviceIotId); //设置aircraftStatus // drone_in_dock 表示是否在舱外 // device_online_status 表示是否开机 // 先判断机场的状态是否是 // 任务中 和 调试, 这两个状态用机场的, 暂时不要用离线状态 // String aircraftStatus = determineAircraftStatus(attributeMap, telemetryMap,telemetryDockMap); dto.setAircraftStatus(aircraftStatus); // 从 TelemetryMap 中获取 total_flight_sorties 赋予 missionCount telemetryMap.get(DeviceTelemetry.Total_Flight_Sorties) .ifPresent(telemetryValue -> dto.setMissionCount(telemetryValue.getValue())); // RTK 信号 - 从 position_state.rtk_number 获取 RTK 搜星数量 telemetryMap.get(DeviceTelemetry.Position_State_Rtk_Number) .ifPresent(telemetryValue -> { Integer rtkNumber = telemetryValue.getValue(); if (rtkNumber != null) { dto.setRtkSignal(rtkNumber); } }); telemetryMap.get(DeviceTelemetry.Position_State_GPS_Number) .ifPresent(telemetryValue -> { Integer gpsNumber = telemetryValue.getValue(); if (gpsNumber != null) { dto.setGpsSignal(gpsNumber); } }); // 限高 telemetryMap.get(DeviceTelemetry.Height_Limit) .ifPresent(telemetryValue -> dto.setMaxAltitude(telemetryValue.getValue())); // 限远 telemetryMap.get(DeviceTelemetry.Distance_Limit_Status_Distance_Limit) .ifPresent(telemetryValue -> dto.setMaxDistance(telemetryValue.getValue())); return dto; } /** * 根据设备属性和遥测数据确定无人机状态 * * @param attributeMap 无人机 属性映射 * @param telemetryMap 机场 遥测数据映射 * @return 无人机状态字符串 */ private String determineAircraftStatus(AttributeMap attributeMap,TelemetryMap telemetryAirportMap,TelemetryMap telemetryMap) { // 先检查 MODE_CODE,判断是否处于任务中或调试状态 // MODE_CODE 定义: {"0":"待机","1":"起飞准备","2":"起飞准备完毕","3":"手动飞行","4":"自动起飞", // "5":"航线飞行","6":"全景拍照","7":"智能跟随","8":"ADS-B 躲避","9":"自动返航", // "10":"自动降落","11":"强制降落","12":"三桨叶降落","13":"升级中","14":"未连接", // "15":"APAS","16":"虚拟摇杆状态","17":"指令飞行","18":"空中 RTK 收敛模式", // "19":"机场选址中","20":"POI环绕"} Integer modeCode = attributeMap.get(DeviceAttributes.MODE_CODE).orElse(null); if (modeCode != null) { // 14 = 未连接,直接返回离线状态 if (modeCode == 14) { return AircraftStatusEnum.OFFLINE.getCode(); } // 判断是否在任务中 (飞行相关状态: 1-12, 15-20) if (modeCode >= 1 && modeCode <= 12 || modeCode >= 15 && modeCode <= 20) { return AircraftStatusEnum.IN_MISSION.getCode(); } } // {"0":"关闭","1":"打开","2":"半开","3":"舱盖状态异常"} Integer cover_state = telemetryMap.get(DeviceTelemetry.Cover_State) .map(telemetryValue -> telemetryValue.getValue()) .orElse(null); Integer droneInDock = 1; if (cover_state != null && cover_state == 0) { droneInDock = 1; }else { droneInDock = 0; } // // 获取无人机是否在舱内: {"0":"舱外","1":"舱内"} // Integer droneInDock = telemetryMap.get(DeviceTelemetry.Drone_In_Dock) // .map(telemetryValue -> telemetryValue.getValue()) // .orElse(null); // 从 telemetryAirportMap 中取 total_flight_time, 如果时间是在一分钟以内更新的,则为开机,否则为关机 // 获取无人机是否开机: {"0":"关机","1":"开机"} Integer deviceOnlineStatus = telemetryMap.get(DeviceTelemetry.Sub_Device_Online_Status) .map(telemetryValue -> telemetryValue.getValue()) .orElse(null); // 根据舱内/舱外和开机/关机状态确定无人机状态 boolean isInCabin = droneInDock != null && droneInDock == 1; boolean isPowerOn = deviceOnlineStatus != null && deviceOnlineStatus == 1; if (isInCabin) { // 舱内 if (isPowerOn) { return AircraftStatusEnum.POWER_ON_IN_CABIN.getCode(); } else { return AircraftStatusEnum.POWER_OFF_IN_CABIN.getCode(); } } else { // 舱外 if (isPowerOn) { return AircraftStatusEnum.POWER_ON_OUT_CABIN.getCode(); } else { return AircraftStatusEnum.POWER_OFF_OUT_CABIN.getCode(); } } } @Override public Map getDockDetailsByIds(List dockIds) { if (CollectionUtils.isEmpty(dockIds)) { return new HashMap<>(); } Map resultMap = new HashMap<>(dockIds.size()); // 批量查询所有机场基础信息 for (Long dockId : dockIds) { try { DockDetailDTO dto = getDockDetailById(dockId); if (dto != null) { resultMap.put(dockId, dto); } } catch (Exception e) { log.error("获取机场详情失败, dockId: {}", dockId, e); } } return resultMap; } @Override public Map getAircraftDetailsByIds(List aircraftIds) { if (CollectionUtils.isEmpty(aircraftIds)) { return new HashMap<>(); } Map resultMap = new HashMap<>(aircraftIds.size()); // 批量查询所有无人机基础信息 for (Long aircraftId : aircraftIds) { try { AircraftDetailDTO dto = getAircraftDetailById(aircraftId); if (dto != null) { resultMap.put(aircraftId, dto); } } catch (Exception e) { log.error("获取无人机详情失败, aircraftId: {}", aircraftId, e); } } return resultMap; } }