优化和路径规划新适配
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import '../../../../core/consts/http_api_consts.dart';
|
||||
import '../../../../core/logging/i_logger_service.dart';
|
||||
import '../models/device_task_model.dart';
|
||||
|
||||
@@ -50,7 +51,7 @@ class DeviceTaskDatasourceImpl implements DeviceTaskDatasource {
|
||||
int pageSize = 99999999,
|
||||
}) async {
|
||||
try {
|
||||
final url = 'http://1.95.137.212:59015/iot/deviceTask/deviceTaskPool';
|
||||
final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/deviceTaskPool';
|
||||
final response = await dio.get(
|
||||
url,
|
||||
queryParameters: {
|
||||
@@ -89,7 +90,7 @@ class DeviceTaskDatasourceImpl implements DeviceTaskDatasource {
|
||||
required int siteId,
|
||||
}) async {
|
||||
try {
|
||||
final url = 'http://1.95.137.212:59015/iot/deviceTask/cancelTask';
|
||||
final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/cancelTask';
|
||||
final response = await dio.post(
|
||||
url,
|
||||
data: {
|
||||
@@ -124,7 +125,7 @@ class DeviceTaskDatasourceImpl implements DeviceTaskDatasource {
|
||||
required int siteId,
|
||||
}) async {
|
||||
try {
|
||||
final url = 'http://1.95.137.212:59015/iot/deviceTask/pauseTask';
|
||||
final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/pauseTask';
|
||||
final response = await dio.post(
|
||||
url,
|
||||
data: {
|
||||
@@ -159,7 +160,7 @@ class DeviceTaskDatasourceImpl implements DeviceTaskDatasource {
|
||||
required int siteId,
|
||||
}) async {
|
||||
try {
|
||||
final url = 'http://1.95.137.212:59015/iot/deviceTask/recoveryTask';
|
||||
final url = '${HttpApiConsts.baseUrl}/iot/deviceTask/recoveryTask';
|
||||
final response = await dio.post(
|
||||
url,
|
||||
data: {
|
||||
|
||||
@@ -34,7 +34,7 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
logger.logWithLevel(
|
||||
'用户点击bindDevice方法API 请求开始',
|
||||
level: 'INFO',
|
||||
data: {'url': 'https://serviceri.satabot.com/iot/device/bindDevice'},
|
||||
data: {'url': 'http://1.95.137.212:8081/iot/device/bindDevice'},
|
||||
);
|
||||
var response = await dio.post(
|
||||
HttpApiConsts.bindDevice,
|
||||
@@ -44,7 +44,7 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
'请求详细参数',
|
||||
level: 'DEBUG',
|
||||
data: {
|
||||
'url': 'https://serviceri.satabot.com/iot/device/bindDevice',
|
||||
'url': 'http://1.95.137.212:8081/iot/device/bindDevice',
|
||||
'deviceId': deviceId,
|
||||
'deviceAlias': deviceAlias,
|
||||
},
|
||||
@@ -122,7 +122,7 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
logger.logWithLevel(
|
||||
'用户点击switchDevice方法API 请求开始',
|
||||
level: 'INFO',
|
||||
data: {'url': 'https://serviceri.satabot.com/iot/device/switchDevice'},
|
||||
data: {'url': 'http://1.95.137.212:8081/iot/device/switchDevice'},
|
||||
);
|
||||
var response = await dio.post(
|
||||
HttpApiConsts.switchDevice,
|
||||
@@ -138,7 +138,7 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
'请求详细参数',
|
||||
level: 'DEBUG',
|
||||
data: {
|
||||
'url': 'https://serviceri.satabot.com/iot/device/switchDevice',
|
||||
'url': 'http://1.95.137.212:8081/iot/device/switchDevice',
|
||||
'platform': platform,
|
||||
'deviceId': deviceId,
|
||||
},
|
||||
@@ -219,12 +219,12 @@ class DeviceHttpDatasourceImpl implements DeviceHttpDatasource {
|
||||
'请求详细参数',
|
||||
level: 'DEBUG',
|
||||
data: {
|
||||
'url': 'https://serviceri.satabot.com/iot/device/userDevice',
|
||||
'url': 'http://1.95.137.212:8081/iot/device/userDevice',
|
||||
'tenantName': deviceame,
|
||||
},
|
||||
);
|
||||
final response = await dio.get(
|
||||
'https://serviceri.satabot.com/iot/device/userDevice',
|
||||
'http://1.95.137.212:8081/iot/device/userDevice',
|
||||
queryParameters: {'tenantName': deviceame},
|
||||
// options: Options(
|
||||
// headers: {
|
||||
|
||||
@@ -21,66 +21,83 @@ class PathHttpDatasourceImpl implements PathHttpDatasource {
|
||||
@override
|
||||
Future<List<DeviceAddPathPointModel>> generatePathRaw({required Map<String, dynamic> body}) async {
|
||||
try {
|
||||
_logger.logWithLevel( '请求体类型:${body.runtimeType}',level: 'INFO');
|
||||
_logger.logWithLevel( '璇锋眰浣撶被鍨嬶細${body.runtimeType}',level: 'INFO');
|
||||
|
||||
// 2. 格式化打印JSON(带缩进,清晰展示嵌套结构)
|
||||
// 2. 鏍煎紡鍖栨墦鍗癑SON锛堝甫缂╄繘锛屾竻鏅板睍绀哄祵濂楃粨鏋勶級
|
||||
final jsonString = const JsonEncoder.withIndent(' ').convert(body);
|
||||
|
||||
final _jsonString = jsonEncode(body);
|
||||
// print('完整JSON请求体:\n$jsonString');
|
||||
_logger.logWithLevel( '请求体:$_jsonString',level: 'INFO');
|
||||
// print('瀹屾暣JSON璇锋眰浣擄細\n$jsonString');
|
||||
_logger.logWithLevel( '璇锋眰浣擄細$_jsonString',level: 'INFO');
|
||||
|
||||
// 3. 可选:单独打印holes的JSON(重点关注)
|
||||
// 3. 鍙€夛細鍗曠嫭鎵撳嵃holes鐨凧SON锛堥噸鐐瑰叧娉級
|
||||
//final holesJson = body['holes'] as List;
|
||||
//print('=== 单独打印holes的JSON ===');
|
||||
//print('=== 鍗曠嫭鎵撳嵃holes鐨凧SON ===');
|
||||
//if (holesJson.isEmpty) {
|
||||
// print('holes 为空数组');
|
||||
// print('holes 涓虹┖鏁扮粍');
|
||||
//} else {
|
||||
// for (int i = 0; i < holesJson.length; i++) {
|
||||
// print('第${i + 1}组hole:\n${const JsonEncoder.withIndent(' ').convert(holesJson[i])}');
|
||||
// print('绗?{i + 1}缁刪ole锛歕n${const JsonEncoder.withIndent(' ').convert(holesJson[i])}');
|
||||
// }
|
||||
//}
|
||||
} catch (e) {
|
||||
// 防止JSON序列化失败导致崩溃
|
||||
//print('打印请求体失败:$e');
|
||||
_logger.logWithLevel( '打印请求体失败:$e',level: 'ERROR');
|
||||
// 降级打印原始body(虽然格式乱,但能看基础数据)
|
||||
// print('原始body数据:$body');
|
||||
_logger.logWithLevel( '原始body数据:$body',level: 'ERROR');
|
||||
// 闃叉JSON搴忓垪鍖栧け璐ュ鑷村穿婧?
|
||||
//print('鎵撳嵃璇锋眰浣撳け璐ワ細$e');
|
||||
_logger.logWithLevel( '鎵撳嵃璇锋眰浣撳け璐ワ細$e',level: 'ERROR');
|
||||
// 闄嶇骇鎵撳嵃鍘熷body锛堣櫧鐒舵牸寮忎贡锛屼絾鑳界湅鍩虹鏁版嵁锛?
|
||||
// print('鍘熷body鏁版嵁锛?body');
|
||||
_logger.logWithLevel( '鍘熷body鏁版嵁锛?body',level: 'ERROR');
|
||||
}
|
||||
|
||||
final token = (await _userStorage.getUser())?.token;
|
||||
if (token == null) throw Exception('Token missing');
|
||||
|
||||
final requestBody = jsonEncode(body);
|
||||
// print('🔍 [路径规划请求] ====== 完整请求体 ======');
|
||||
// print(requestBody);
|
||||
//print('🔍 [路径规划请求] ====== 请求体结束 ======');
|
||||
final response = await http.post(
|
||||
Uri.parse('https://servicepathplan.satabot.com/api/path'),
|
||||
headers: {'Content-Type': 'application/json', 'Authorization': token},
|
||||
body: jsonEncode(body),
|
||||
headers: {'Content'
|
||||
'-Type': 'application/json', 'Authorization': token},
|
||||
body: requestBody,
|
||||
);
|
||||
|
||||
// 🔥 完整打印响应体
|
||||
//printFullString(formatJson(jsonEncode(response.body)));
|
||||
//print('🔄 [路径规划API] 响应状态码: ${response.statusCode}');
|
||||
// print('🔄 [路径规划API] 响应体长度: ${response.body.length}');
|
||||
//print('🔄 [路径规划API] 响应体前500字符: ${response.body.length > 500 ? response.body.substring(0, 500) : response.body}');
|
||||
try {
|
||||
final decoded = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
if (decoded['success'] == true) {
|
||||
final pathJson = decoded['data']?['path'];
|
||||
// print('路径数据: $pathJson'); // 调试输出路径数据
|
||||
_logger.logWithLevel( '路径数据: $pathJson',level: 'INFO');
|
||||
if (pathJson == null) {
|
||||
throw Exception('API returned null for "path"');
|
||||
}
|
||||
// print('🔄 [路径规划API] 解析后 keys: ${decoded.keys.toList()}');
|
||||
//print('🔄 [路径规划API] code=${decoded['code']}, success=${decoded['success']}, msg=${decoded['msg']}, hasData=${decoded.containsKey('data')}');
|
||||
// 🔥 对齐 Web 端:优先判断 data.path 是否存在,兼容 code==200 和 success==true 两种格式
|
||||
final dataObj = decoded['data'];
|
||||
final pathJson = dataObj is Map ? dataObj['path'] : null;
|
||||
if (pathJson != null) {
|
||||
/ print('✅ [路径规划API] 路径数据点数: ${pathJson is List ? pathJson.length : '非列表'}');
|
||||
if (pathJson is! List) {
|
||||
throw Exception('Expected "path" to be a List, but got ${pathJson.runtimeType}');
|
||||
}
|
||||
final List<dynamic> jsonData = pathJson;
|
||||
return jsonData.map((item) => DeviceAddPathPointModel.fromJson(item)).toList();
|
||||
} else {
|
||||
final apiCode = decoded['code'] ?? -1;
|
||||
final msg = decoded['msg'] ?? '未知错误';
|
||||
throw Exception('API 错误 [$apiCode]: $msg');
|
||||
}
|
||||
// 检查 success 字段(旧格式兼容)
|
||||
if (decoded['success'] == true) {
|
||||
if (pathJson == null) {
|
||||
throw Exception('API returned null for "path"');
|
||||
}
|
||||
}
|
||||
// 兼容 code==200 格式
|
||||
final apiCode = decoded['code'];
|
||||
if (apiCode != null && apiCode.toString() == '200') {
|
||||
throw Exception('API code=200 但 data.path 为空');
|
||||
}
|
||||
final msg = decoded['message'] ?? decoded['msg'] ?? '未知错误';
|
||||
throw Exception('API 错误 [${decoded['code'] ?? -1}]: $msg');
|
||||
} catch (e) {
|
||||
throw Exception('Network error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ class ReferencePoint {
|
||||
|
||||
ReferencePoint({required this.lat, required this.lon});
|
||||
|
||||
Map<String, dynamic> toJson() => {'lat': lat, 'lng': lon};
|
||||
Map<String, dynamic> toJson() => {'lat': lat, 'lon': lon};
|
||||
}
|
||||
|
||||
class OuterBoundary {
|
||||
@@ -86,5 +86,5 @@ class Position {
|
||||
return Position(lat: lat, lon: lon);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {'lat': lat, 'lng': lon};
|
||||
Map<String, dynamic> toJson() => {'lat': lat, 'lon': lon};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class WorkRecordEntity {
|
||||
final int userId;
|
||||
final int id;
|
||||
final String workName;
|
||||
final WorkRecordJsonData? jsonData;
|
||||
final dynamic jsonData; // 支持 WorkRecordJsonData(JSON接口)或 String(XML接口原始JSON,避免二次序列化丢失数据)
|
||||
final String? imgUrl;
|
||||
|
||||
WorkRecordEntity({
|
||||
@@ -43,11 +43,8 @@ class WorkRecordEntity {
|
||||
userId: int.tryParse(xmlData['userId']?.toString() ?? '0') ?? 0,
|
||||
id: int.tryParse(xmlData['id']?.toString() ?? '0') ?? 0,
|
||||
workName: xmlData['workName'] as String? ?? '',
|
||||
jsonData: xmlData['jsonData'] != null
|
||||
? WorkRecordJsonData.fromXml(
|
||||
xmlData['jsonData'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
// 🔥 核心修复:_parseJsonDataNode 现在返回 JSON 字符串,直接存储,避免 Map→jsonEncode 丢失路径数据
|
||||
jsonData: xmlData['jsonData'],
|
||||
imgUrl: xmlData['imgUrl'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class DeviceHostrityWorkRepositoryImpl implements DeviceHostrityWorkRepositoryRe
|
||||
_logger.logWithLevel('用户信息获取成功,Token: $token', level: 'info');
|
||||
|
||||
final response = await client.get(
|
||||
'https://serviceri.satabot.com/iot/device/getDeviceRunStatistics?deviceId=$deviceId',
|
||||
'http://1.95.137.212:8081/iot/device/getDeviceRunStatistics?deviceId=$deviceId',
|
||||
options: Options(headers: {'Content-Type': 'application/json', 'Authorization': 'Bearer $token'}),
|
||||
);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:xml/xml.dart';
|
||||
|
||||
import '../../../../core/di/injection.dart';
|
||||
@@ -16,7 +17,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
final PathHttpDatasource _datasource;
|
||||
PathRepositoryImpl({required PathHttpDatasource datasource})
|
||||
: _datasource = datasource;
|
||||
// 生成路径
|
||||
// 鐢熸垚璺<EFBFBD>緞
|
||||
@override
|
||||
Future<List<DeviceAddPathPointModel>> generatePath({
|
||||
required ReferencePoint reference,
|
||||
@@ -31,22 +32,22 @@ class PathRepositoryImpl implements PathRepository {
|
||||
'outer': outer.toJson(),
|
||||
'holes': holes != null && holes.isNotEmpty
|
||||
? holes.values.first
|
||||
.toJson() // 取第一个值并序列化,去掉外层 Map
|
||||
: {},
|
||||
.toJson() // 鍙栫<EFBFBD>涓€涓<EFBFBD>€煎苟搴忓垪鍖栵紝鍘绘帀澶栧眰 Map
|
||||
: [],
|
||||
'workType': workType,
|
||||
};
|
||||
|
||||
return await _datasource.generatePathRaw(body: body);
|
||||
}
|
||||
|
||||
// 保存工作记录
|
||||
// 淇濆瓨宸ヤ綔璁板綍
|
||||
@override
|
||||
Future<Map<String, dynamic>> saveWorkRecord({
|
||||
required String workName,
|
||||
required String userId,
|
||||
required String jsonData,
|
||||
}) async {
|
||||
final url = Uri.parse('https://serviceri.satabot.com/iot/workRecord/add');
|
||||
final url = Uri.parse('http://1.95.137.212:8081/iot/workRecord/add');
|
||||
final headers = {'Content-Type': 'application/json'};
|
||||
final body = jsonEncode({
|
||||
'workName': workName,
|
||||
@@ -73,7 +74,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
}) async {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final url = Uri.parse(
|
||||
'https://serviceri.satabot.com/iot/workRecord/selectByUserId',
|
||||
'http://1.95.137.212:8081/iot/workRecord/selectByUserId',
|
||||
).replace(queryParameters: {'userId': userId, '_t': timestamp.toString()});
|
||||
|
||||
try {
|
||||
@@ -102,7 +103,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final url =
|
||||
Uri.parse(
|
||||
'https://serviceri.satabot.com/iot/workRecord/deleteByWorkName',
|
||||
'http://1.95.137.212:8081/iot/workRecord/deleteByWorkName',
|
||||
).replace(
|
||||
queryParameters: {'workName': workName, '_t': timestamp.toString()},
|
||||
);
|
||||
@@ -112,7 +113,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode == 200 && data['code'] == 200) {
|
||||
return data; // 返回 {"code": 200, "msg": "删除成功"}
|
||||
return data; // 杈斿洖 {"code": 200, "msg": "鍒犻櫎鎴愬姛"}
|
||||
} else {
|
||||
throw Exception(
|
||||
'Delete failed: ${data['msg'] ?? response.reasonPhrase}',
|
||||
@@ -123,7 +124,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 选择工作记录
|
||||
/// 选择工作记录(根据workName)
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> selectWorkRecordByName({
|
||||
required String workName,
|
||||
@@ -131,41 +132,54 @@ class PathRepositoryImpl implements PathRepository {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final url =
|
||||
Uri.parse(
|
||||
'https://serviceri.satabot.com/iot/workRecord/selectByWorkName',
|
||||
'http://1.95.137.212:8081/iot/workRecord/selectByWorkName',
|
||||
).replace(
|
||||
queryParameters: {'workName': workName, '_t': timestamp.toString()},
|
||||
);
|
||||
|
||||
try {
|
||||
final response = await http.get(url);
|
||||
final response = await http.get(url).timeout(const Duration(seconds: 15));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (data['code'] == 200 && data.containsKey('data')) {
|
||||
final dynamic rawData = data['data'];
|
||||
|
||||
// 🔧 关键修复:安全转换为 List
|
||||
|
||||
List<Map<String, dynamic>> records;
|
||||
if (rawData is List) {
|
||||
records = rawData.map((e) {
|
||||
if (e is Map) {
|
||||
return Map<String, dynamic>.from(e);
|
||||
}
|
||||
throw Exception('List item is not a Map: ${e.runtimeType}');
|
||||
}).toList();
|
||||
} else if (rawData is Map) {
|
||||
records = [Map<String, dynamic>.from(rawData)];
|
||||
} else {
|
||||
throw Exception(
|
||||
'Unexpected data type for "data": ${rawData.runtimeType}',
|
||||
);
|
||||
// 判断响应格式:XML 还是 JSON
|
||||
final trimmed = response.body.trim();
|
||||
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||||
// JSON 格式(原有逻辑)
|
||||
Map<String, dynamic> data;
|
||||
try {
|
||||
data = jsonDecode(response.body) as Map<String, dynamic>;
|
||||
} catch (formatError) {
|
||||
print('[selectWorkRecordByName] JSON解析失败: $formatError');
|
||||
throw Exception('JSON解析失败: $formatError');
|
||||
}
|
||||
|
||||
return records;
|
||||
if (data['code'] == 200 && data.containsKey('data')) {
|
||||
final dynamic rawData = data['data'];
|
||||
|
||||
List<Map<String, dynamic>> records;
|
||||
if (rawData is List) {
|
||||
records = rawData.map((e) {
|
||||
if (e is Map) {
|
||||
return Map<String, dynamic>.from(e);
|
||||
}
|
||||
throw Exception('List item is not a Map: ${e.runtimeType}');
|
||||
}).toList();
|
||||
} else if (rawData is Map) {
|
||||
records = [Map<String, dynamic>.from(rawData)];
|
||||
} else {
|
||||
throw Exception(
|
||||
'Unexpected data type for "data": ${rawData.runtimeType}',
|
||||
);
|
||||
}
|
||||
|
||||
return records;
|
||||
} else {
|
||||
throw Exception('API error: ${data['msg'] ?? 'Unknown'}');
|
||||
}
|
||||
} else {
|
||||
throw Exception('API error: ${data['msg'] ?? 'Unknown'}');
|
||||
// XML 格式:使用 XML 解析逻辑
|
||||
print('[selectByWorkName] 检测到 XML 格式响应');
|
||||
return _parseXmlSelectByNameResponse(response.body);
|
||||
}
|
||||
} else {
|
||||
throw Exception(
|
||||
@@ -177,7 +191,75 @@ class PathRepositoryImpl implements PathRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// 根据场站ID查询工作记录列表(XML格式)
|
||||
/// 解析 selectByWorkName 的 XML 响应
|
||||
/// 返回 List<Map<String, dynamic>>,将 jsonData 中的 path/outer 提取到顶层
|
||||
Future<List<Map<String, dynamic>>> _parseXmlSelectByNameResponse(String body) async {
|
||||
print('[selectByWorkName XML] 原始响应前500字符: ${body.length > 500 ? body.substring(0, 500) : body}');
|
||||
final codeMatch = RegExp(r'<\w*:?code[^>]*>(\d+)</\w*:?code>').firstMatch(body);
|
||||
final code = codeMatch?.group(1);
|
||||
print('[selectByWorkName XML] code: $code');
|
||||
|
||||
if (code != '200') {
|
||||
final msgMatch = RegExp(r'<\w*:?msg[^>]*>(.*?)</\w*:?msg>').firstMatch(body);
|
||||
throw Exception('API error: ${msgMatch?.group(1) ?? 'Unknown'}');
|
||||
}
|
||||
|
||||
final dataRegex = RegExp(r'<\w*:?data[^>]*>([\s\S]*?)</\w*:?data>');
|
||||
final dataMatches = dataRegex.allMatches(body);
|
||||
print('[selectByWorkName XML] 找到 data 节点数量: ${dataMatches.length}');
|
||||
|
||||
final List<Map<String, dynamic>> records = [];
|
||||
|
||||
for (final match in dataMatches) {
|
||||
final dataContent = match.group(1)!;
|
||||
print('[selectByWorkName XML] data内容前300字符: ${dataContent.length > 300 ? dataContent.substring(0, 300) : dataContent}');
|
||||
try {
|
||||
final wrappedXml = '<root>$dataContent</root>';
|
||||
final document = XmlDocument.parse(wrappedXml);
|
||||
final recordElement = document.rootElement;
|
||||
final recordData = _parseXmlRecord(recordElement);
|
||||
print('[selectByWorkName XML] recordData keys: ${recordData.keys.toList()}');
|
||||
print('[selectByWorkName XML] jsonData 类型: ${recordData['jsonData']?.runtimeType}');
|
||||
|
||||
// 将 jsonData 中的 path/outer 提取到顶层,供 UI 直接使用
|
||||
if (recordData['jsonData'] is String) {
|
||||
try {
|
||||
final jsonDataMap = jsonDecode(recordData['jsonData'] as String) as Map<String, dynamic>;
|
||||
print('[selectByWorkName XML] jsonData 解析后 keys: ${jsonDataMap.keys.toList()}');
|
||||
print('[selectByWorkName XML] path 类型: ${jsonDataMap['path']?.runtimeType}, outer 类型: ${jsonDataMap['outer']?.runtimeType}');
|
||||
if (jsonDataMap.containsKey('path')) {
|
||||
recordData['path'] = jsonDataMap['path'];
|
||||
}
|
||||
if (jsonDataMap.containsKey('outer')) {
|
||||
recordData['outer'] = jsonDataMap['outer'];
|
||||
}
|
||||
if (jsonDataMap.containsKey('planModel')) {
|
||||
recordData['planModel'] = jsonDataMap['planModel'];
|
||||
}
|
||||
print('[selectByWorkName XML] 提取后 recordData path类型: ${recordData['path']?.runtimeType}, outer类型: ${recordData['outer']?.runtimeType}');
|
||||
} catch (e) {
|
||||
print('[selectByWorkName XML] jsonData 解析失败: $e');
|
||||
}
|
||||
} else {
|
||||
print('[selectByWorkName XML] jsonData 不是 String 类型! 值: ${recordData['jsonData']}');
|
||||
}
|
||||
|
||||
records.add(recordData);
|
||||
} catch (e) {
|
||||
print('[selectByWorkName XML] 解析单个 data 节点失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
print('[selectByWorkName XML] 最终解析记录数: ${records.length}');
|
||||
if (records.isNotEmpty) {
|
||||
final first = records.first;
|
||||
print('[selectByWorkName XML] 第一条记录 path长度: ${(first['path'] as List?)?.length ?? "null"}');
|
||||
print('[selectByWorkName XML] 第一条记录 outer长度: ${(first['outer'] as List?)?.length ?? "null"}');
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
/// 鏍规嵁鍦虹珯ID鏌ヨ<E98F8C>宸ヤ綔璁板綍鍒楄〃锛圶ML鏍煎紡锛?
|
||||
@override
|
||||
Future<List<WorkRecordEntity>> getWorkRecordsBySiteId({
|
||||
required int siteId,
|
||||
@@ -185,7 +267,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
final url =
|
||||
Uri.parse(
|
||||
'http://1.95.137.212:59015/iot/workRecord/selectBySiteId',
|
||||
'http://1.95.137.212:8081/iot/workRecord/selectBySiteId',
|
||||
).replace(
|
||||
queryParameters: {
|
||||
'siteId': siteId.toString(),
|
||||
@@ -198,24 +280,27 @@ class PathRepositoryImpl implements PathRepository {
|
||||
url,
|
||||
headers: {'Accept': 'application/xml, text/xml, */*'},
|
||||
);
|
||||
/// print('[XML接口] 响应状态码: ${response.statusCode}');
|
||||
////print('[XML接口] 响应内容长度: ${response.body.length}');
|
||||
///print('[XML接口] Content-Type: ${response.headers['content-type']}');
|
||||
/// print('[XML鎺ュ彛] 鍝嶅簲鐘舵€佺爜: ${response.statusCode}');
|
||||
////print('[XML鎺ュ彛] 鍝嶅簲鍐呭<E98D90>闀垮害: ${response.body.length}');
|
||||
///print('[XML鎺ュ彛] Content-Type: ${response.headers['content-type']}');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// 打印前200字符确认格式
|
||||
// 鎵撳嵃鍓?00瀛楃<E7809B>纭<EFBFBD><E7BAAD>鏍煎紡
|
||||
final preview = response.body.length > 200
|
||||
? response.body.substring(0, 200)
|
||||
: response.body;
|
||||
/// print('[XML接口] 响应开头: $preview');
|
||||
// 🔥 调试:打印原始 API 响应
|
||||
// print('[selectBySiteId 原始响应] 长度=${response.body.length}');
|
||||
// print('[selectBySiteId 原始响应] 前1000字符: ${response.body.length > 1000 ? response.body.substring(0, 1000) : response.body}');
|
||||
/// print('[XML鎺ュ彛] 鍝嶅簲寮€澶? $preview');
|
||||
|
||||
// 判断是JSON还是XML格式
|
||||
// 鍒ゆ柇鏄疛SON杩樻槸XML鏍煎紡
|
||||
final trimmed = response.body.trim();
|
||||
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||||
///print('[XML接口] 检测到JSON格式,使用JSON解析');
|
||||
///print('[XML鎺ュ彛] 妫€娴嬪埌JSON鏍煎紡锛屼娇鐢↗SON瑙f瀽');
|
||||
return _parseJsonResponse(response.body);
|
||||
} else {
|
||||
///print('[XML接口] 检测到XML格式,使用XML解析');
|
||||
///print('[XML鎺ュ彛] 妫€娴嬪埌XML鏍煎紡锛屼娇鐢╔ML瑙f瀽');
|
||||
return _parseXmlResponse(response.body);
|
||||
}
|
||||
} else {
|
||||
@@ -224,12 +309,12 @@ class PathRepositoryImpl implements PathRepository {
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
/// print('[XML接口] 错误: $e');
|
||||
/// print('[XML鎺ュ彛] 閿欒<E996BF>: $e');
|
||||
throw Exception('Network error in getWorkRecordsBySiteId: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析JSON格式响应
|
||||
/// 瑙f瀽JSON鏍煎紡鍝嶅簲
|
||||
Future<List<WorkRecordEntity>> _parseJsonResponse(String body) async {
|
||||
final data = jsonDecode(body) as Map<String, dynamic>;
|
||||
|
||||
@@ -252,18 +337,18 @@ class PathRepositoryImpl implements PathRepository {
|
||||
records.add(WorkRecordEntity.fromJson(recordsData));
|
||||
}
|
||||
|
||||
/// print('[XML接口] 最终解析记录数: ${records.length}');
|
||||
/// print('[XML鎺ュ彛] 鏈€缁堣В鏋愯<E98F8B>褰曟暟: ${records.length}');
|
||||
return records;
|
||||
}
|
||||
|
||||
/// 解析XML格式响应
|
||||
/// 瑙f瀽XML鏍煎紡鍝嶅簲
|
||||
Future<List<WorkRecordEntity>> _parseXmlResponse(String body) async {
|
||||
// 用更宽松的正则检查响应码(支持命名空间前缀)
|
||||
// 鐢ㄦ洿瀹芥澗鐨勬<EFBFBD>鍒欐<EFBFBD>鏌ュ搷搴旂爜锛堟敮鎸佸懡鍚嶇┖闂村墠缂€锛?
|
||||
final codeMatch = RegExp(
|
||||
r'<\w*:?code[^>]*>(\d+)</\w*:?code>',
|
||||
).firstMatch(body);
|
||||
final code = codeMatch?.group(1);
|
||||
print('[XML接口] code: $code');
|
||||
print('[XML鎺ュ彛] code: $code');
|
||||
|
||||
if (code != '200') {
|
||||
final msgMatch = RegExp(
|
||||
@@ -272,84 +357,104 @@ class PathRepositoryImpl implements PathRepository {
|
||||
throw Exception('API error: ${msgMatch?.group(1) ?? 'Unknown'}');
|
||||
}
|
||||
|
||||
// 使用正则表达式提取所有<data>...</data>节点
|
||||
// 使用非贪婪匹配,确保每个data节点独立提取
|
||||
final dataRegex = RegExp(r'<data>([\s\S]*?)</data>');
|
||||
// 浣跨敤姝e垯琛ㄨ揪寮忔彁鍙栨墍鏈?data>...</data>鑺傜偣
|
||||
// 浣跨敤闈炶椽濠<EFBFBD>尮閰嶏紝纭<EFBFBD>繚姣忎釜data鑺傜偣鐙<EFBFBD>鎻愬彇
|
||||
final dataRegex = RegExp(r'<\w*:?data[^>]*>([\s\S]*?)</\w*:?data>');
|
||||
final dataMatches = dataRegex.allMatches(body);
|
||||
///print('[XML接口] 找到data节点数量: ${dataMatches.length}');
|
||||
///print('[XML鎺ュ彛] 鎵惧埌data鑺傜偣鏁伴噺: ${dataMatches.length}');
|
||||
|
||||
// 解析所有工作记录
|
||||
// 瑙f瀽鎵€鏈夊伐浣滆<EFBFBD>褰?
|
||||
final List<WorkRecordEntity> records = [];
|
||||
|
||||
for (int i = 0; i < dataMatches.length; i++) {
|
||||
final match = dataMatches.elementAt(i);
|
||||
final dataContent = match.group(1)!; // 获取<data>和</data>之间的内容
|
||||
final dataContent = match.group(1)!; // 鑾峰彇<data>鍜?/data>涔嬮棿鐨勫唴瀹?
|
||||
|
||||
try {
|
||||
// 将提取的内容包装成完整XML进行解析
|
||||
// 灏嗘彁鍙栫殑鍐呭<EFBFBD>鍖呰<EFBFBD>鎴愬畬鏁碭ML杩涜<EFBFBD>瑙f瀽
|
||||
final wrappedXml = '<root>$dataContent</root>';
|
||||
final document = XmlDocument.parse(wrappedXml);
|
||||
final recordElement = document.rootElement;
|
||||
|
||||
final recordData = _parseXmlRecord(recordElement);
|
||||
final _jdVal = recordData['jsonData'];
|
||||
// print('[selectBySiteId XML] data[$i] workName=${recordData['workName']}, jsonData类型=${_jdVal?.runtimeType}, jsonData长度=${_jdVal is String ? _jdVal.length : "N/A"}');
|
||||
// if (_jdVal is String && _jdVal.isNotEmpty) {
|
||||
// try {
|
||||
// final _jdDecoded = jsonDecode(_jdVal);
|
||||
// if (_jdDecoded is Map) {
|
||||
// print('[selectBySiteId XML] data[$i] jsonData.path类型=${_jdDecoded['path']?.runtimeType}, outer类型=${_jdDecoded['outer']?.runtimeType}');
|
||||
// if (_jdDecoded['path'] is List) print('[selectBySiteId XML] data[$i] path长度=${(_jdDecoded['path'] as List).length}');
|
||||
// }
|
||||
// } catch (_) {}
|
||||
// }
|
||||
/// print(
|
||||
/// '[XML接口] data[$i] 解析结果: workName=${recordData['workName']}, id=${recordData['id']}, imgUrl=${recordData['imgUrl']}',
|
||||
/// '[XML鎺ュ彛] data[$i] 瑙f瀽缁撴灉: workName=${recordData['workName']}, id=${recordData['id']}, imgUrl=${recordData['imgUrl']}',
|
||||
/// );
|
||||
|
||||
records.add(WorkRecordEntity.fromXml(recordData));
|
||||
} catch (e) {
|
||||
print('[XML接口] 解析单个data节点失败: $e');
|
||||
print('[XML鎺ュ彛] 瑙f瀽鍗曚釜data鑺傜偣澶辫触: $e');
|
||||
}
|
||||
}
|
||||
|
||||
///print('[XML接口] 最终解析记录数: ${records.length}');
|
||||
///print('[XML鎺ュ彛] 鏈€缁堣В鏋愯<E98F8B>褰曟暟: ${records.length}');
|
||||
return records;
|
||||
}
|
||||
|
||||
/// 解析XML工作记录节点
|
||||
/// 瑙f瀽XML宸ヤ綔璁板綍鑺傜偣
|
||||
Map<String, dynamic> _parseXmlRecord(XmlElement recordElement) {
|
||||
final Map<String, dynamic> result = {};
|
||||
|
||||
//// print('[XML解析] 开始解析节点,子元素数量: ${recordElement.childElements.length}');
|
||||
//// print('[XML瑙f瀽] 寮€濮嬭В鏋愯妭鐐癸紝瀛愬厓绱犳暟閲? ${recordElement.childElements.length}');
|
||||
|
||||
for (final child in recordElement.childElements) {
|
||||
final tagName = child.name.local;
|
||||
final innerText = child.innerText.trim();
|
||||
|
||||
// print(
|
||||
// '[XML解析] 标签: $tagName, 值: ${innerText.length > 50 ? innerText.substring(0, 50) + '...' : innerText}',
|
||||
// '[XML瑙f瀽] 鏍囩<E98F8D>: $tagName, 鍊? ${innerText.length > 50 ? innerText.substring(0, 50) + '...' : innerText}',
|
||||
// );
|
||||
|
||||
// 特殊处理jsonData节点(包含嵌套结构)
|
||||
// 鐗规畩澶勭悊jsonData鑺傜偣锛堝寘鍚<EFBFBD>祵濂楃粨鏋勶級
|
||||
if (tagName == 'jsonData') {
|
||||
result['jsonData'] = _parseJsonDataNode(child);
|
||||
} else {
|
||||
// 普通节点直接取值
|
||||
// 鏅<EFBFBD>€氳妭鐐圭洿鎺ュ彇鍊?
|
||||
result[tagName] = innerText;
|
||||
}
|
||||
}
|
||||
|
||||
//print('[XML解析] 解析完成,结果keys: ${result.keys.toList()}');
|
||||
//print('[XML瑙f瀽] 瑙f瀽瀹屾垚锛岀粨鏋渒eys: ${result.keys.toList()}');
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 解析jsonData节点
|
||||
Map<String, dynamic> _parseJsonDataNode(XmlElement jsonDataElement) {
|
||||
/// 返回 JSON 字符串,避免 Map → jsonEncode 的二次序列化导致数据丢失
|
||||
String _parseJsonDataNode(XmlElement jsonDataElement) {
|
||||
final Map<String, dynamic> result = {};
|
||||
|
||||
// // 🔥 调试:打印 jsonData 节点的原始 XML 内容
|
||||
// final rawXml = jsonDataElement.outerXml;
|
||||
// print('[jsonData 原始XML] 长度=${rawXml.length}, 前500字符: ${rawXml.length > 500 ? rawXml.substring(0, 500) : rawXml}');
|
||||
// print('[jsonData 子元素数量] ${jsonDataElement.childElements.length}');
|
||||
// for (final child in jsonDataElement.childElements) {
|
||||
// print('[jsonData 子元素] tag=${child.name.local}, innerText长度=${child.innerText.trim().length}');
|
||||
// }
|
||||
|
||||
for (final child in jsonDataElement.childElements) {
|
||||
final tagName = child.name.local;
|
||||
|
||||
if (tagName == 'path' || tagName == 'outer') {
|
||||
// path和outer可能包含嵌套的path/outer节点
|
||||
result[tagName] = _parseCoordinateList(child, tagName);
|
||||
} else {
|
||||
// 普通字段(name, img, planModel等)
|
||||
result[tagName] = child.innerText.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
final encoded = jsonEncode(result);
|
||||
// print('[jsonData 解析结果] keys=${result.keys.toList()}, 长度=${encoded.length}');
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/// 解析坐标列表(path或outer)
|
||||
@@ -365,7 +470,7 @@ class PathRepositoryImpl implements PathRepository {
|
||||
.toList();
|
||||
|
||||
if (nestedElements.isNotEmpty) {
|
||||
// 有嵌套结构:outer > outer > {lat, lng}
|
||||
// 嵌套结构:outer > outer > {lat, lng}
|
||||
for (final nestedElement in nestedElements) {
|
||||
final latElement = nestedElement.getElement('lat');
|
||||
final lngElement = nestedElement.getElement('lng');
|
||||
@@ -378,55 +483,106 @@ class PathRepositoryImpl implements PathRepository {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 直接包含lat/lng节点
|
||||
final latElement = element.getElement('lat');
|
||||
final lngElement = element.getElement('lng');
|
||||
// 🔥 核心修复:提取所有同级 lat/lng 对,而不是只取第一个
|
||||
// 支持格式:<path><lat>30.1</lat><lng>120.1</lng><lat>30.2</lat><lng>120.2</lng></path>
|
||||
final allLatElements = element.childElements
|
||||
.where((e) => e.name.local == 'lat')
|
||||
.toList();
|
||||
final allLngElements = element.childElements
|
||||
.where((e) => e.name.local == 'lng')
|
||||
.toList();
|
||||
|
||||
if (latElement != null && lngElement != null) {
|
||||
coordinates.add({
|
||||
'lat': double.tryParse(latElement.innerText.trim()) ?? 0.0,
|
||||
'lng': double.tryParse(lngElement.innerText.trim()) ?? 0.0,
|
||||
});
|
||||
final count = allLatElements.length < allLngElements.length
|
||||
? allLatElements.length
|
||||
: allLngElements.length;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
final lat = double.tryParse(allLatElements[i].innerText.trim()) ?? 0.0;
|
||||
final lng = double.tryParse(allLngElements[i].innerText.trim()) ?? 0.0;
|
||||
coordinates.add({'lat': lat, 'lng': lng});
|
||||
}
|
||||
}
|
||||
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
/// 创建设备任务(通过接口执行作业)
|
||||
/// 接口地址: http://1.95.137.212:59015/iot/deviceTask/createDeviceTask
|
||||
/// 入参: {"deviceId":"...","routeId":76,"siteId":22}
|
||||
/// 鍒涘缓璁惧<EFBFBD>浠诲姟锛堥€氳繃鎺ュ彛鎵ц<EFBFBD>浣滀笟锛?
|
||||
/// 鎺ュ彛鍦板潃: http://1.95.137.212:8081/iot/deviceTask/createDeviceTask
|
||||
/// 鍏ュ弬: {"deviceId":"...","routeId":76,"siteId":22,"orgId":5}
|
||||
/// 杩斿洖: 鍒涘缓鎴愬姛鐨勪换鍔<E68DA2>D
|
||||
@override
|
||||
Future<void> createDeviceTask({
|
||||
Future<int> createDeviceTask({
|
||||
required String deviceId,
|
||||
required int routeId,
|
||||
required int siteId,
|
||||
required int orgId,
|
||||
}) async {
|
||||
final url = Uri.parse('http://1.95.137.212:59015/iot/deviceTask/createDeviceTask');
|
||||
|
||||
final body = jsonEncode({
|
||||
final dio = sl<Dio>();
|
||||
|
||||
final body = {
|
||||
'deviceId': deviceId,
|
||||
'routeId': routeId,
|
||||
'siteId': siteId,
|
||||
});
|
||||
'orgId': orgId,
|
||||
};
|
||||
|
||||
print('📤 [创建设备任务] 请求参数: $body');
|
||||
print('[创建设备任务] 请求参数: ${jsonEncode(body)}');
|
||||
|
||||
try {
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: body,
|
||||
final response = await dio.post(
|
||||
'http://1.95.137.212:8081/iot/deviceTask/createDeviceTask',
|
||||
data: body,
|
||||
);
|
||||
|
||||
print('📥 [创建设备任务] 响应状态码: ${response.statusCode}');
|
||||
print('📥 [创建设备任务] 响应内容: ${response.body}');
|
||||
print('[创建设备任务] 响应状态码: ${response.statusCode}');
|
||||
print('[创建设备任务] 响应原始数据: ${response.data}');
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('创建设备任务失败: HTTP ${response.statusCode}');
|
||||
}
|
||||
|
||||
// 解析响应,提取 taskId
|
||||
final data = response.data as Map<String, dynamic>;
|
||||
final code = data['code'];
|
||||
print('[创建设备任务] code字段值: $code, 类型: ${code.runtimeType}');
|
||||
|
||||
// 兼容 int 和 String 类型的 code
|
||||
final codeValue = code is int ? code : int.tryParse(code?.toString() ?? '');
|
||||
if (codeValue == 200) {
|
||||
final taskData = data['data'];
|
||||
print('[创建设备任务] data字段值: $taskData, 类型: ${taskData?.runtimeType}');
|
||||
|
||||
if (taskData is int) {
|
||||
print('[创建设备任务] 任务ID(int): $taskData');
|
||||
return taskData;
|
||||
} else if (taskData is String) {
|
||||
final taskId = int.tryParse(taskData);
|
||||
if (taskId != null) {
|
||||
print('[创建设备任务] 任务ID(String->int): $taskId');
|
||||
return taskId;
|
||||
}
|
||||
} else if (taskData is Map<String, dynamic>) {
|
||||
final taskId = taskData['id'] ?? taskData['taskId'];
|
||||
print('[创建设备任务] data是Map, id=$taskId, 类型=${taskId?.runtimeType}');
|
||||
if (taskId is int) {
|
||||
print('[创建设备任务] 任务ID(Map.id): $taskId');
|
||||
return taskId;
|
||||
} else if (taskId is String) {
|
||||
final parsedId = int.tryParse(taskId);
|
||||
if (parsedId != null) {
|
||||
print('[创建设备任务] 任务ID(Map.id->int): $parsedId');
|
||||
return parsedId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// code==200 但无法解析taskId,可能是新格式,打印完整响应方便调试
|
||||
print('[创建设备任务] WARNING: code=200但无法解析taskId, 完整响应: ${response.data}');
|
||||
throw Exception('任务已创建但无法解析taskId, 完整响应: ${response.data}');
|
||||
} else {
|
||||
throw Exception('创建设备任务失败: ${data['msg'] ?? response.data}');
|
||||
}
|
||||
} catch (e) {
|
||||
print('❌ [创建设备任务] 错误: $e');
|
||||
print('[创建设备任务] 错误: $e');
|
||||
throw Exception('创建设备任务失败: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,14 +84,14 @@ class PathPlanner {
|
||||
|
||||
PathPlanner(this.tcpClient);
|
||||
|
||||
// 🔥 已弃用:路径点逐个下发已改为服务端通过 createDeviceTask HTTP 接口处理,
|
||||
// APP 不再通过 TCP 逐个发送路径点,避免与服务器重复发送导致下位机收到双份数据。
|
||||
void sendNextLocation() {
|
||||
_logger.log("[sendNextLocation] 已弃用 - 路径点下发由服务端 HTTP 接口负责");
|
||||
return;
|
||||
|
||||
/* ========== 以下为原有 TCP 逐个发点逻辑,已注释保留 ==========
|
||||
_logger.log("[发送sendNextLocation方法指令]");
|
||||
// if (isStart && locationQueue.isEmpty) {
|
||||
// isStart = false;
|
||||
// print("[track]");
|
||||
// tcpClient.disconnect();
|
||||
// return;
|
||||
// }
|
||||
// 关键检查:暂停或停止时不再发送
|
||||
if (_isPaused) {
|
||||
_logger.log("[暂停状态],停止发送指令");
|
||||
@@ -101,11 +101,9 @@ class PathPlanner {
|
||||
if (_isStopped || (isStart && locationQueue.isEmpty)) {
|
||||
isStart = false;
|
||||
if (_isStopped) {
|
||||
//print("⏹️ 已停止作业,清空队列");
|
||||
_logger.log("[路径点发送完毕] 停止");
|
||||
locationQueue.clear(); // 清空剩余队列
|
||||
} else {
|
||||
//print("[路径点发送完毕]");
|
||||
_logger.log("[路径点发送完毕]");
|
||||
}
|
||||
_isStopped = false; // 重置停止标志
|
||||
@@ -121,10 +119,10 @@ class PathPlanner {
|
||||
speed: 1000,
|
||||
);
|
||||
|
||||
//print("====Lat:${entity.latitude} ====Lng:${entity.longitude}");
|
||||
_logger.log("[发送指令]");
|
||||
tcpClient.sendPathPoint(routePlanSendEntity);
|
||||
isStart = true;
|
||||
========== 原有 TCP 逐个发点逻辑结束 ========== */
|
||||
}
|
||||
|
||||
/// 开始
|
||||
@@ -186,36 +184,30 @@ class PathPlanner {
|
||||
}
|
||||
}
|
||||
|
||||
/// 暂停
|
||||
/// 暂停(已切换为 HTTP 接口 pauseTask,TCP 方式已弃用)
|
||||
void pauseRPWork() {
|
||||
//print("⏸️ 暂停作业,设置_isPaused = true");
|
||||
_logger.log("[暂停作业,设置_isPaused = true]");
|
||||
_logger.log("[pauseRPWork] 已弃用 - 暂停由 HTTP pauseTask 接口负责");
|
||||
_isPaused = true;
|
||||
|
||||
/* ========== 以下为原有 TCP 发送暂停指令,已注释保留 ==========
|
||||
var entity = new RoutePlanSendEntity(commandType: 0x01, pointCounts: 2, targetLatitude: 0, targetLongitude: 0, speed: 0);
|
||||
// print("📡 发送暂停指令到设备...");
|
||||
_logger.log("[发送暂停指令到设备...]");
|
||||
tcpClient.sendDeviceStateChange(entity);
|
||||
========== 原有 TCP 暂停指令结束 ========== */
|
||||
}
|
||||
|
||||
/// 恢复
|
||||
/// 恢复(已切换为 HTTP 接口 recoveryTask,TCP 方式已弃用)
|
||||
void resumeRPWork() {
|
||||
//print("▶️ 恢复作业,设置_isPaused = false");
|
||||
_logger.log("[恢复作业,设置_isPaused = false]");
|
||||
_logger.log("[resumeRPWork] 已弃用 - 恢复由 HTTP recoveryTask 接口负责");
|
||||
_isPaused = false;
|
||||
|
||||
/* ========== 以下为原有 TCP 发送恢复指令,已注释保留 ==========
|
||||
var entity = new RoutePlanSendEntity(commandType: 0x01, pointCounts: 3, targetLatitude: 0, targetLongitude: 0, speed: 0);
|
||||
// print("📡 发送恢复指令到设备...");
|
||||
_logger.log("[发送恢复指令到设备...]");
|
||||
tcpClient.sendDeviceStateChange(entity);
|
||||
|
||||
// 恢复后继续发送下一个点
|
||||
// if (locationQueue.isNotEmpty) {
|
||||
// print("📍 恢复发送下一个路径点");
|
||||
// sendNextLocation();
|
||||
// }
|
||||
if (locationQueue.isNotEmpty) {
|
||||
//print("📍 恢复发送下一个路径点");
|
||||
_logger.log("[恢复发送下一个路径点]");
|
||||
if (PathPlanningConfig.useAckHandshake) {
|
||||
sendNextLocationWithAck(sl<NetMessageDispatcher>());
|
||||
@@ -223,52 +215,53 @@ class PathPlanner {
|
||||
sendNextLocation();
|
||||
}
|
||||
}
|
||||
========== 原有 TCP 恢复指令结束 ========== */
|
||||
}
|
||||
|
||||
/// 停止
|
||||
/// 停止(已切换为 HTTP 接口 cancelTask,TCP 方式已弃用)
|
||||
void stopRoutePlanning() {
|
||||
// print("⏹️ 停止作业,设置_isStopped = true");
|
||||
_logger.log("[停止作业,设置_isStopped = true]");
|
||||
_logger.log("[stopRoutePlanning] 已弃用 - 停止由 HTTP cancelTask 接口负责");
|
||||
_isStopped = true;
|
||||
_isPaused = false; // 清除暂停状态
|
||||
|
||||
// 🔥 核心修复:清空队列
|
||||
// print("🗑️ 清空待发送队列,剩余 ${locationQueue.length} 个点");
|
||||
_logger.log("[清空待发送队列,剩余 ${locationQueue.length} 个点]");
|
||||
_isPaused = false;
|
||||
locationQueue.clear();
|
||||
final devicesCubit = sl<DevicesCubit>();
|
||||
devicesCubit.setArrivedLocation(0.0, 0.0);
|
||||
|
||||
/* ========== 以下为原有 TCP 发送停止指令,已注释保留 ==========
|
||||
var entity = new RoutePlanSendEntity(commandType: 0x01, pointCounts: 0, targetLatitude: 0, targetLongitude: 0, speed: 0);
|
||||
// print("📡 发送停止指令到设备...");
|
||||
_logger.log("[发送停止指令到设备...] 按下停止按钮");
|
||||
tcpClient.sendDeviceStateChange(entity);
|
||||
// 发送完成工作播报指令
|
||||
//sendWorkCompleteBroadcast();
|
||||
========== 原有 TCP 停止指令结束 ========== */
|
||||
}
|
||||
|
||||
//发送完成工作播报指令
|
||||
// 发送完成工作播报指令(已切换为 HTTP 接口,TCP 方式已弃用)
|
||||
void sendWorkCompleteBroadcast() {
|
||||
_logger.log("[sendWorkCompleteBroadcast] 已弃用 - 由服务端 HTTP 接口负责");
|
||||
|
||||
/* ========== 以下为原有 TCP 发送完成播报指令,已注释保留 ==========
|
||||
var entity = new RoutePlanSendEntity(commandType: 0x01, pointCounts: 0xFF, targetLatitude: 0, targetLongitude: 0, speed: 0);
|
||||
// print("发送完成工作播报指令...");
|
||||
_logger.log("[发送完成工作播报指令...]");
|
||||
tcpClient.sendDeviceStateChange(entity);
|
||||
========== 原有 TCP 完成播报指令结束 ========== */
|
||||
}
|
||||
|
||||
// 🔥 新增:ACK 握手机制的发送方法 (新模式)
|
||||
// 🔥 已弃用:路径点逐个下发已改为服务端通过 createDeviceTask HTTP 接口处理,
|
||||
// APP 不再通过 TCP 逐个发送路径点,避免与服务器重复发送导致下位机收到双份数据。
|
||||
void sendNextLocationWithAck(NetMessageDispatcher dispatcher) {
|
||||
// print("[在发送指令 sendNextLocationWithAck 方法中 - ACK 模式]");
|
||||
_logger.log("[sendNextLocationWithAck] 已弃用 - 路径点下发由服务端 HTTP 接口负责");
|
||||
return;
|
||||
|
||||
/* ========== 以下为原有 TCP 逐个发点逻辑(ACK 模式),已注释保留 ==========
|
||||
_logger.log("[在发送指令 sendNextLocationWithAck 方法中 - ACK 模式]");
|
||||
// 🔥 关键检查:如果正在等待 ACK,不要重复发送
|
||||
// 关键检查:如果正在等待 ACK,不要重复发送
|
||||
if (_waitingForAck) {
|
||||
//print("⏳ 正在等待 ACK 确认,跳过发送");
|
||||
_logger.log("[正在等待 ACK 确认,跳过发送]");
|
||||
return;
|
||||
}
|
||||
|
||||
// 🔥 关键检查:暂停或停止时不再发送
|
||||
// 关键检查:暂停或停止时不再发送
|
||||
if (_isPaused) {
|
||||
//print("⏸️ 当前处于暂停状态,停止发送指令");
|
||||
_logger.log("[暂停状态]");
|
||||
return;
|
||||
}
|
||||
@@ -276,7 +269,6 @@ class PathPlanner {
|
||||
if (_isStopped || (isStart && locationQueue.isEmpty)) {
|
||||
isStart = false;
|
||||
if (_isStopped) {
|
||||
// print("️ 已停止作业,清空队列");
|
||||
_logger.log("[已停止作业,清空队列]");
|
||||
locationQueue.clear(); // 清空剩余队列
|
||||
} else {
|
||||
@@ -289,26 +281,25 @@ class PathPlanner {
|
||||
|
||||
final entity = locationQueue.removeFirst(); // 类型:DeviceAddPathPointModel
|
||||
|
||||
// 🔥 核心改进:使用递增的点编号,确保精准匹配
|
||||
// 核心改进:使用递增的点编号,确保精准匹配
|
||||
final pointIndex = _currentPointIndex++;
|
||||
|
||||
final routePlanSendEntity = RoutePlanSendEntity(
|
||||
commandType: 0x01,
|
||||
pointCounts: 1, // 🔥 使用点编号,而不是固定的 1
|
||||
pointCounts: 1,
|
||||
targetLatitude: entity.latitude,
|
||||
targetLongitude: entity.longitude,
|
||||
speed: 1000,
|
||||
);
|
||||
|
||||
//print("====Lat:${entity.latitude} ====Lng:${entity.longitude}");
|
||||
_logger.log("[发送路径点] Lat:${entity.latitude} Lng:${entity.longitude}");
|
||||
tcpClient.sendPathPoint(routePlanSendEntity);
|
||||
isStart = true;
|
||||
|
||||
// 🔥 关键:标记为正在等待 ACK
|
||||
// 标记为正在等待 ACK
|
||||
_waitingForAck = true;
|
||||
dispatcher.setExpectedPointIndex(pointIndex); // 🔥 通知 Dispatcher
|
||||
//sprint(" 已锁定发送,等待 ACK 确认 (0x02)...");
|
||||
dispatcher.setExpectedPointIndex(pointIndex); // 通知 Dispatcher
|
||||
_logger.log("[已锁定发送,等待 ACK 确认 (0x02)...]");
|
||||
========== 原有 TCP 逐个发点逻辑结束 ========== */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user