| | |
| | | package gecaoji; |
| | | import baseStation.BaseStation; |
| | | import set.Setsys; |
| | | import zhuye.MowerLocationData; |
| | | import java.io.FileInputStream; |
| | | import java.io.FileOutputStream; |
| | | import java.io.IOException; |
| | | import java.io.OutputStreamWriter; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.Locale; |
| | | import java.util.Properties; |
| | | |
| | |
| | | // 卫星可数 |
| | | private String differentialAge; |
| | | // 差分时间 |
| | | private String selfCheckStatus = "-1"; |
| | | // 割草机自检状态 |
| | | private String mowerStartStatus = "-1"; |
| | | // 割草机启动状态:1开启,0熄火,-1未知 |
| | | private String mowerLightStatus = "-1"; |
| | | // 割草机灯开关状态:1开启,0关闭,-1未知 |
| | | private String mowerBladeHeight = "-1"; |
| | | // 割草机刀盘高度:-1未知 |
| | | private String mowerWidth; |
| | | // 割草机宽度,单位米 |
| | | private String mowerLength; |
| | | // 割草机长度,单位米 |
| | | private String mowingSafetyDistance; |
| | | // 割草安全距离,单位米 |
| | | |
| | | private static final double METERS_PER_DEGREE_LAT = 111320.0d; |
| | | |
| | |
| | | applyDefaults(gecaoji); |
| | | } |
| | | |
| | | /** |
| | | * 保存所有属性到device.properties文件 |
| | | */ |
| | | public void saveToProperties() { |
| | | Properties properties = new Properties(); |
| | | |
| | | // 加载现有属性(保留其他属性) |
| | | try (FileInputStream input = new FileInputStream("device.properties")) { |
| | | properties.load(input); |
| | | } catch (IOException e) { |
| | | // 如果文件不存在,继续创建新文件 |
| | | } |
| | | |
| | | // 设置所有设备属性 |
| | | if (mowerName != null) properties.setProperty("mowerName", mowerName); |
| | | if (mowerModel != null) properties.setProperty("mowerModel", mowerModel); |
| | | if (mowerNumber != null) properties.setProperty("mowerNumber", mowerNumber); |
| | | if (mowingWidth != null) properties.setProperty("mowingWidth", mowingWidth); |
| | | if (mowingHeight != null) properties.setProperty("mowingHeight", mowingHeight); |
| | | if (baseStationNumber != null) properties.setProperty("baseStationNumber", baseStationNumber); |
| | | if (baseStationCardNumber != null) properties.setProperty("baseStationCardNumber", baseStationCardNumber); |
| | | if (baseStationCoordinates != null) properties.setProperty("baseStationCoordinates", baseStationCoordinates); |
| | | if (deviceCardnumber != null) properties.setProperty("deviceCardnumber", deviceCardnumber); |
| | | if (createTime != null) properties.setProperty("createTime", createTime); |
| | | if (GupdateTime != null) properties.setProperty("GupdateTime", GupdateTime); |
| | | if (BupdateTime != null) properties.setProperty("BupdateTime", BupdateTime); |
| | | if (realtimeLatitude != null) properties.setProperty("realtimeLatitude", realtimeLatitude); |
| | | if (realtimeLongitude != null) properties.setProperty("realtimeLongitude", realtimeLongitude); |
| | | if (realtimeAltitude != null) properties.setProperty("realtimeAltitude", realtimeAltitude); |
| | | if (realtimeX != null) properties.setProperty("realtimeX", realtimeX); |
| | | if (realtimeY != null) properties.setProperty("realtimeY", realtimeY); |
| | | if (realtimeSpeed != null) properties.setProperty("realtimeSpeed", realtimeSpeed); |
| | | if (heading != null) properties.setProperty("heading", heading); |
| | | if (pitch != null) properties.setProperty("pitch", pitch); |
| | | if (battery != null) properties.setProperty("battery", battery); |
| | | if (positioningStatus != null) properties.setProperty("positioningStatus", positioningStatus); |
| | | if (satelliteCount != null) properties.setProperty("satelliteCount", satelliteCount); |
| | | if (differentialAge != null) properties.setProperty("differentialAge", differentialAge); |
| | | if (selfCheckStatus != null) properties.setProperty("selfCheckStatus", selfCheckStatus); |
| | | if (mowerStartStatus != null) properties.setProperty("mowerStartStatus", mowerStartStatus); |
| | | if (mowerLightStatus != null) properties.setProperty("mowerLightStatus", mowerLightStatus); |
| | | if (mowerBladeHeight != null) properties.setProperty("mowerBladeHeight", mowerBladeHeight); |
| | | if (mowerWidth != null) properties.setProperty("mowerWidth", mowerWidth); |
| | | if (mowerLength != null) properties.setProperty("mowerLength", mowerLength); |
| | | if (mowingSafetyDistance != null) properties.setProperty("mowingSafetyDistance", mowingSafetyDistance); |
| | | |
| | | // 保存到文件 |
| | | try (FileOutputStream output = new FileOutputStream("device.properties"); |
| | | OutputStreamWriter writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) { |
| | | properties.store(writer, "Updated device properties"); |
| | | } catch (IOException ex) { |
| | | System.err.println("无法保存 device.properties: " + ex.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void loadPropertiesInto(Device target, Properties properties) { |
| | | if (target == null) { |
| | | return; |
| | |
| | | target.positioningStatus = properties.getProperty("positioningStatus", "-1"); |
| | | target.satelliteCount = properties.getProperty("satelliteCount", "-1"); |
| | | target.differentialAge = properties.getProperty("differentialAge", "-1"); |
| | | target.selfCheckStatus = properties.getProperty("selfCheckStatus", "-1"); |
| | | target.mowerStartStatus = properties.getProperty("mowerStartStatus", "-1"); |
| | | target.mowerLightStatus = properties.getProperty("mowerLightStatus", "-1"); |
| | | target.mowerBladeHeight = properties.getProperty("mowerBladeHeight", "-1"); |
| | | target.mowerWidth = properties.getProperty("mowerWidth", "-1"); |
| | | target.mowerLength = properties.getProperty("mowerLength", "-1"); |
| | | target.mowingSafetyDistance = properties.getProperty("mowingSafetyDistance", "-1"); |
| | | } |
| | | |
| | | private void applyDefaults(Device target) { |
| | |
| | | target.positioningStatus = "-1"; |
| | | target.satelliteCount = "-1"; |
| | | target.differentialAge = "-1"; |
| | | target.selfCheckStatus = "-1"; |
| | | target.mowerStartStatus = "-1"; |
| | | target.mowerLightStatus = "-1"; |
| | | target.mowerBladeHeight = "-1"; |
| | | target.mowerWidth = "-1"; |
| | | target.mowerLength = "-1"; |
| | | target.mowingSafetyDistance = "-1"; |
| | | } |
| | | |
| | | public static synchronized Device initializeActiveDevice(String mowerId) { // 根据设备ID初始化活跃设备 |
| | |
| | | case "differentialAge": |
| | | this.differentialAge = value; |
| | | return true; |
| | | case "selfCheckStatus": |
| | | this.selfCheckStatus = value; |
| | | return true; |
| | | case "mowerStartStatus": |
| | | this.mowerStartStatus = value; |
| | | return true; |
| | | case "mowerLightStatus": |
| | | this.mowerLightStatus = value; |
| | | return true; |
| | | case "mowerBladeHeight": |
| | | this.mowerBladeHeight = value; |
| | | return true; |
| | | case "mowerWidth": |
| | | this.mowerWidth = value; |
| | | return true; |
| | | case "mowerLength": |
| | | this.mowerLength = value; |
| | | return true; |
| | | case "mowingSafetyDistance": |
| | | this.mowingSafetyDistance = value; |
| | | return true; |
| | | default: |
| | | System.err.println("未知字段: " + fieldName); |
| | | return false; |
| | |
| | | device.applyGNGGAUpdate(gnggaData, deviceId); |
| | | } |
| | | |
| | | public static synchronized void updateFromSerialGNGGA(String gnggaData) { // 串口数据更新路径(无需设备编号匹配) |
| | | Device device = gecaoji; |
| | | if (device == null) { |
| | | return; |
| | | } |
| | | device.chuankouGNGGAUpdate(gnggaData); |
| | | } |
| | | |
| | | private void applyGNGGAUpdate(String gnggaData, String deviceId) { // 执行GNGGA更新逻辑 |
| | | if (gnggaData == null) { |
| | | return; |
| | |
| | | mowerNumber = incomingDeviceId; |
| | | } |
| | | |
| | | // 检查是否正在导航预览模式 |
| | | boolean isNavigating = checkIfNavigating(); |
| | | |
| | | String latitudeValue = sanitizeField(fields, 2); |
| | | String latitudeHemisphere = sanitizeField(fields, 3); |
| | | String longitudeValue = sanitizeField(fields, 4); |
| | | String longitudeHemisphere = sanitizeField(fields, 5); |
| | | |
| | | realtimeLatitude = defaultIfEmpty(combineCoordinate(latitudeValue, latitudeHemisphere)); |
| | | realtimeLongitude = defaultIfEmpty(combineCoordinate(longitudeValue, longitudeHemisphere)); |
| | | realtimeAltitude = defaultIfEmpty(sanitizeField(fields, 9)); |
| | | // 只有在非导航预览模式下才更新位置相关数据 |
| | | if (!isNavigating) { |
| | | String combinedLatitude = combineCoordinate(latitudeValue, latitudeHemisphere); |
| | | if (hasMeaningfulValue(combinedLatitude)) { |
| | | realtimeLatitude = combinedLatitude; |
| | | } |
| | | String combinedLongitude = combineCoordinate(longitudeValue, longitudeHemisphere); |
| | | if (hasMeaningfulValue(combinedLongitude)) { |
| | | realtimeLongitude = combinedLongitude; |
| | | } |
| | | |
| | | String altitudeValue = sanitizeField(fields, 9); |
| | | if (hasMeaningfulValue(altitudeValue)) { |
| | | realtimeAltitude = altitudeValue; |
| | | } |
| | | |
| | | // 更新位置坐标 |
| | | updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere); |
| | | |
| | | // 更新航向角(位置相关) |
| | | heading = defaultIfEmpty(sanitizeField(fields, 19)); |
| | | } |
| | | |
| | | // 其他数据(电量、卫星数等)始终更新 |
| | | positioningStatus = defaultIfEmpty(sanitizeField(fields, 6)); |
| | | // 同步到绘制模块的数据源,保证往返绘制定时器能识别定位质量 |
| | | try { |
| | | MowerLocationData.updateProperty("positioningQuality", positioningStatus); |
| | | } catch (Throwable ignored) { |
| | | // 防御式:即使更新失败也不影响设备数据处理 |
| | | } |
| | | satelliteCount = defaultIfEmpty(sanitizeField(fields, 7)); |
| | | differentialAge = defaultIfEmpty(sanitizeField(fields, 13)); |
| | | battery = defaultIfEmpty(sanitizeField(fields, 16)); |
| | | pitch = defaultIfEmpty(sanitizeField(fields, 17)); |
| | | realtimeSpeed = defaultIfEmpty(sanitizeField(fields, 18)); |
| | | heading = defaultIfEmpty(sanitizeField(fields, 19)); |
| | | GupdateTime = String.valueOf(System.currentTimeMillis()); |
| | | } |
| | | |
| | | /**串口更新GNGGA数据*/ |
| | | private void chuankouGNGGAUpdate(String gnggaData) { // 执行GNGGA更新逻辑 |
| | | if (gnggaData == null) { |
| | | return; |
| | | } |
| | | |
| | | updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere); |
| | | String trimmed = gnggaData.trim(); |
| | | if (trimmed.isEmpty() || !trimmed.startsWith("$GNGGA")) { |
| | | return; |
| | | } |
| | | |
| | | String[] fields = trimmed.split(","); |
| | | if (fields.length < 15) { |
| | | System.err.println("GNGGA字段数量不足: " + fields.length); |
| | | return; |
| | | } |
| | | |
| | | // 检查是否正在导航预览模式 |
| | | boolean isNavigating = checkIfNavigating(); |
| | | |
| | | String latitudeValue = sanitizeField(fields, 2); |
| | | String latitudeHemisphere = sanitizeField(fields, 3); |
| | | String longitudeValue = sanitizeField(fields, 4); |
| | | String longitudeHemisphere = sanitizeField(fields, 5); |
| | | |
| | | // 只有在非导航预览模式下才更新位置相关数据 |
| | | if (!isNavigating) { |
| | | String combinedLatitude = combineCoordinate(latitudeValue, latitudeHemisphere); |
| | | if (hasMeaningfulValue(combinedLatitude)) { |
| | | realtimeLatitude = combinedLatitude; |
| | | } |
| | | String combinedLongitude = combineCoordinate(longitudeValue, longitudeHemisphere); |
| | | if (hasMeaningfulValue(combinedLongitude)) { |
| | | realtimeLongitude = combinedLongitude; |
| | | } |
| | | |
| | | String altitudeValue = sanitizeField(fields, 9); |
| | | if (hasMeaningfulValue(altitudeValue)) { |
| | | realtimeAltitude = altitudeValue; |
| | | } |
| | | |
| | | // 更新位置坐标 |
| | | updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere); |
| | | |
| | | // 串口收到GNGGA数据后,触发拖尾更新 |
| | | notifyMowerTrailUpdate(); |
| | | } |
| | | |
| | | // 其他数据(电量、卫星数等)始终更新 |
| | | positioningStatus = defaultIfEmpty(sanitizeField(fields, 6)); |
| | | // 同步到绘制模块的数据源,保证往返绘制定时器能识别定位质量 |
| | | try { |
| | | MowerLocationData.updateProperty("positioningQuality", positioningStatus); |
| | | } catch (Throwable ignored) { |
| | | // 防御式:即使更新失败也不影响设备数据处理 |
| | | } |
| | | satelliteCount = defaultIfEmpty(sanitizeField(fields, 7)); |
| | | differentialAge = defaultIfEmpty(sanitizeField(fields, 13)); |
| | | realtimeSpeed ="0"; |
| | | GupdateTime = String.valueOf(System.currentTimeMillis()); |
| | | } |
| | | |
| | | /** |
| | | * 通知地图渲染器更新割草机拖尾 |
| | | * 当串口收到GNGGA数据并更新位置后调用 |
| | | */ |
| | | /** |
| | | * 检查是否正在导航预览模式 |
| | | * @return 如果正在导航预览返回true,否则返回false |
| | | */ |
| | | private boolean checkIfNavigating() { |
| | | try { |
| | | dikuai.daohangyulan nav = dikuai.daohangyulan.getInstance(); |
| | | if (nav != null) { |
| | | return nav.isNavigating(); |
| | | } |
| | | } catch (Exception e) { |
| | | // 如果获取导航实例失败,返回false(不影响主要功能) |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private void notifyMowerTrailUpdate() { |
| | | try { |
| | | // 通过Shouye.getInstance()获取实例,避免循环依赖 |
| | | zhuye.Shouye shouye = zhuye.Shouye.getInstance(); |
| | | if (shouye != null) { |
| | | zhuye.MapRenderer mapRenderer = shouye.getMapRenderer(); |
| | | if (mapRenderer != null) { |
| | | // 调用更新拖尾方法 |
| | | mapRenderer.forceUpdateIdleMowerTrail(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | // 如果调用失败,静默处理(不影响主要功能) |
| | | // System.err.println("通知拖尾更新失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private void updateRelativeCoordinates(String latValue, String latHemisphere, |
| | | String lonValue, String lonHemisphere) { // 计算相对坐标 |
| | | if (!hasMeaningfulValue(latValue) || !hasMeaningfulValue(lonValue) |
| | | || !hasMeaningfulValue(latHemisphere) || !hasMeaningfulValue(lonHemisphere)) { |
| | | realtimeX = "-1"; |
| | | realtimeY = "-1"; |
| | | return; |
| | | } |
| | | |
| | | double mowerLat = toDecimalDegrees(latValue, latHemisphere); |
| | | double mowerLon = toDecimalDegrees(lonValue, lonHemisphere); |
| | | if (Double.isNaN(mowerLat) || Double.isNaN(mowerLon)) { |
| | | realtimeX = "-1"; |
| | | realtimeY = "-1"; |
| | | return; |
| | | } |
| | | |
| | | double[] baseLatLon = resolveBaseStationLatLon(); |
| | | if (baseLatLon == null) { |
| | | realtimeX = "-1"; |
| | | realtimeY = "-1"; |
| | | return; |
| | | } |
| | | |
| | |
| | | double eastMeters = deltaLonDeg * metersPerLon; |
| | | double northMeters = deltaLatDeg * METERS_PER_DEGREE_LAT; |
| | | |
| | | realtimeX = formatMeters(eastMeters); |
| | | realtimeY = formatMeters(northMeters); |
| | | if (Double.isFinite(eastMeters) && Double.isFinite(northMeters)) { |
| | | realtimeX = formatMeters(eastMeters); |
| | | realtimeY = formatMeters(northMeters); |
| | | |
| | | // 保存坐标到工具类 |
| | | lujing.SavaXyZuobiao.addCoordinate(eastMeters, northMeters); |
| | | } |
| | | } |
| | | |
| | | private double[] resolveBaseStationLatLon() { // 解析基站经纬度 |
| | |
| | | this.differentialAge = differentialAge; |
| | | } |
| | | |
| | | public String getSelfCheckStatus() { // 获取自检状态 |
| | | return selfCheckStatus; |
| | | } |
| | | |
| | | public void setSelfCheckStatus(String selfCheckStatus) { // 设置自检状态 |
| | | this.selfCheckStatus = selfCheckStatus; |
| | | } |
| | | |
| | | public String getMowerStartStatus() { // 获取割草机启动状态 |
| | | return mowerStartStatus; |
| | | } |
| | | |
| | | public void setMowerStartStatus(String mowerStartStatus) { // 设置割草机启动状态 |
| | | this.mowerStartStatus = mowerStartStatus; |
| | | } |
| | | |
| | | public String getMowerLightStatus() { // 获取割草机灯开关状态 |
| | | return mowerLightStatus; |
| | | } |
| | | |
| | | public void setMowerLightStatus(String mowerLightStatus) { // 设置割草机灯开关状态 |
| | | this.mowerLightStatus = mowerLightStatus; |
| | | } |
| | | |
| | | public String getMowerBladeHeight() { // 获取割草机刀盘高度 |
| | | return mowerBladeHeight; |
| | | } |
| | | |
| | | public void setMowerBladeHeight(String mowerBladeHeight) { // 设置割草机刀盘高度 |
| | | this.mowerBladeHeight = mowerBladeHeight; |
| | | } |
| | | |
| | | public String getMowerWidth() { // 获取割草机宽度 |
| | | return mowerWidth; |
| | | } |
| | | |
| | | public void setMowerWidth(String mowerWidth) { // 设置割草机宽度 |
| | | this.mowerWidth = mowerWidth; |
| | | } |
| | | |
| | | public String getMowerLength() { // 获取割草机长度 |
| | | return mowerLength; |
| | | } |
| | | |
| | | public void setMowerLength(String mowerLength) { // 设置割草机长度 |
| | | this.mowerLength = mowerLength; |
| | | } |
| | | |
| | | public String getMowingSafetyDistance() { // 获取割草安全距离 |
| | | return mowingSafetyDistance; |
| | | } |
| | | |
| | | public void setMowingSafetyDistance(String mowingSafetyDistance) { // 设置割草安全距离 |
| | | this.mowingSafetyDistance = mowingSafetyDistance; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { // 输出对象信息 |
| | | return "Device{" + |
| | |
| | | ", positioningStatus='" + positioningStatus + '\'' + |
| | | ", satelliteCount='" + satelliteCount + '\'' + |
| | | ", differentialAge='" + differentialAge + '\'' + |
| | | ", selfCheckStatus='" + selfCheckStatus + '\'' + |
| | | ", mowerStartStatus='" + mowerStartStatus + '\'' + |
| | | ", mowerLightStatus='" + mowerLightStatus + '\'' + |
| | | ", mowerBladeHeight='" + mowerBladeHeight + '\'' + |
| | | ", mowerWidth='" + mowerWidth + '\'' + |
| | | ", mowerLength='" + mowerLength + '\'' + |
| | | ", mowingSafetyDistance='" + mowingSafetyDistance + '\'' + |
| | | '}'; |
| | | } |
| | | } |