feat: make gb32960 archive history query production ready
This commit is contained in:
53
modules/apps/command-gateway/pom.xml
Normal file
53
modules/apps/command-gateway/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>lingniu-vehicle-ingest</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>command-gateway</artifactId>
|
||||
<name>command-gateway</name>
|
||||
<description>
|
||||
下行命令网关:HTTP → 设备。复用 session-core 的 CommandDispatcher,
|
||||
覆盖原 JT808Controller / JT1078Controller 的 43 个端点(PoC 阶段先落核心子集)。
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>ingest-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>session-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>protocol-jt808</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>protocol-jt1078</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lingniu.ingest.gateway;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(DispatcherServlet.class)
|
||||
@ConditionalOnProperty(prefix = "lingniu.ingest.command-gateway", name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ComponentScan(basePackageClasses = TerminalCommandController.class)
|
||||
public class CommandGatewayAutoConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.lingniu.ingest.gateway;
|
||||
|
||||
/**
|
||||
* 统一命令响应 envelope。
|
||||
*
|
||||
* @param success 是否成功
|
||||
* @param code 业务状态码(0=成功,其它=失败原因)
|
||||
* @param message 人类可读消息
|
||||
* @param data 可选负载(设备应答对象、JSON 节点等)
|
||||
*/
|
||||
public record CommandResult(boolean success, int code, String message, Object data) {
|
||||
|
||||
public static CommandResult ok(Object data) {
|
||||
return new CommandResult(true, 0, "ok", data);
|
||||
}
|
||||
|
||||
public static CommandResult notImplemented(String hint) {
|
||||
return new CommandResult(false, 501, "not_implemented: " + hint, null);
|
||||
}
|
||||
|
||||
public static CommandResult failure(int code, String message) {
|
||||
return new CommandResult(false, code, message, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.lingniu.ingest.gateway;
|
||||
|
||||
import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands;
|
||||
import com.lingniu.ingest.protocol.jt808.model.Jt808Message;
|
||||
import com.lingniu.ingest.protocol.jt1078.downlink.Jt1078Commands;
|
||||
import com.lingniu.ingest.session.CommandDispatcher;
|
||||
import com.lingniu.ingest.session.DeviceSession;
|
||||
import com.lingniu.ingest.session.SessionStore;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 下行命令 REST 入口。命令通过 {@link CommandDispatcher} 发往协议模块,
|
||||
* 由 protocol-jt808 的 {@code Jt808CommandDispatcher} 实际发送到终端。
|
||||
*
|
||||
* <p>路径参数 {@code clientId} 可以是 sessionId / phone / vin,
|
||||
* 由 {@link SessionStore} 三索引解析。
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/terminal")
|
||||
public class TerminalCommandController {
|
||||
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(20);
|
||||
|
||||
private final SessionStore sessions;
|
||||
private final CommandDispatcher dispatcher;
|
||||
|
||||
public TerminalCommandController(SessionStore sessions, CommandDispatcher dispatcher) {
|
||||
this.sessions = sessions;
|
||||
this.dispatcher = dispatcher;
|
||||
}
|
||||
|
||||
@GetMapping("/session")
|
||||
public CommandResult session(@RequestParam String clientId) {
|
||||
Optional<DeviceSession> s = resolveSession(clientId);
|
||||
return s.<CommandResult>map(CommandResult::ok)
|
||||
.orElseGet(() -> CommandResult.failure(404, "session not found: " + clientId));
|
||||
}
|
||||
|
||||
@GetMapping("/location")
|
||||
public CommandResult queryLocation(@RequestParam String clientId) {
|
||||
return awaitSync(clientId, Jt808Commands.queryLocation(), "query-location");
|
||||
}
|
||||
|
||||
@GetMapping("/parameters")
|
||||
public CommandResult queryParameters(@RequestParam String clientId) {
|
||||
return awaitSync(clientId, Jt808Commands.queryParameters(), "query-parameters");
|
||||
}
|
||||
|
||||
@PutMapping("/parameters")
|
||||
public CommandResult setParameters(@RequestParam String clientId,
|
||||
@RequestBody Map<String, String> body) {
|
||||
List<Jt808Commands.ParamItem> items = body.entrySet().stream()
|
||||
.map(e -> new Jt808Commands.ParamItem(
|
||||
parseId(e.getKey()),
|
||||
e.getValue() == null ? new byte[0] : e.getValue().getBytes(StandardCharsets.UTF_8)))
|
||||
.collect(Collectors.toList());
|
||||
return awaitSync(clientId, Jt808Commands.setParameters(items), "set-parameters");
|
||||
}
|
||||
|
||||
@PostMapping("/control")
|
||||
public CommandResult terminalControl(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
int word = Integer.parseInt(body.getOrDefault("command", "0").toString());
|
||||
String params = body.getOrDefault("params", "").toString();
|
||||
return awaitSync(clientId, Jt808Commands.terminalControl(word, params), "terminal-control");
|
||||
}
|
||||
|
||||
@PostMapping("/ack")
|
||||
public CommandResult ack(@RequestParam String clientId, @RequestBody Map<String, Integer> body) {
|
||||
int ackSerial = body.getOrDefault("ackSerial", 0);
|
||||
int ackMsgId = body.getOrDefault("ackMsgId", 0);
|
||||
int result = body.getOrDefault("result", 0);
|
||||
return fireAndForget(clientId, Jt808Commands.platformAck(ackSerial, ackMsgId, result), "platform-ack");
|
||||
}
|
||||
|
||||
@GetMapping("/attributes")
|
||||
public CommandResult attributes(@RequestParam String clientId) {
|
||||
return awaitSync(clientId, Jt808Commands.queryAttributes(), "query-attributes");
|
||||
}
|
||||
|
||||
@DeleteMapping("/area")
|
||||
public CommandResult deleteArea(@RequestParam String clientId, @RequestParam String ids) {
|
||||
return awaitSync(clientId, Jt808Commands.deleteArea(parseIds(ids)), "delete-area");
|
||||
}
|
||||
|
||||
@PostMapping("/alarm_ack")
|
||||
public CommandResult alarmAck(@RequestParam String clientId, @RequestBody Map<String, Object> body) {
|
||||
int responseSerialNo = Integer.parseInt(body.getOrDefault("responseSerialNo", "0").toString());
|
||||
long type = parseLong(body.getOrDefault("type", "0").toString());
|
||||
return fireAndForget(clientId, Jt808Commands.alarmAck(responseSerialNo, type), "alarm-ack");
|
||||
}
|
||||
|
||||
@GetMapping("/jt1078/attributes")
|
||||
public CommandResult jt1078Attributes(@RequestParam String clientId) {
|
||||
return awaitSync(clientId, Jt1078Commands.queryMediaAttributes(), "jt1078-query-media-attributes");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/realtime")
|
||||
public CommandResult jt1078RealtimePlay(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.realtimePlay(
|
||||
stringValue(body, "ip", ""),
|
||||
intValue(body, "tcpPort", 0),
|
||||
intValue(body, "udpPort", 0),
|
||||
intValue(body, "channelNo", 0),
|
||||
intValue(body, "mediaType", 0),
|
||||
intValue(body, "streamType", 0)), "jt1078-realtime-play");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/realtime_control")
|
||||
public CommandResult jt1078RealtimeControl(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.realtimeControl(
|
||||
intValue(body, "channelNo", 0),
|
||||
intValue(body, "command", 0),
|
||||
intValue(body, "closeType", 0),
|
||||
intValue(body, "streamType", 0)), "jt1078-realtime-control");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/history")
|
||||
public CommandResult jt1078HistoryPlay(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.historyPlay(
|
||||
stringValue(body, "ip", ""),
|
||||
intValue(body, "tcpPort", 0),
|
||||
intValue(body, "udpPort", 0),
|
||||
intValue(body, "channelNo", 0),
|
||||
intValue(body, "mediaType", 0),
|
||||
intValue(body, "streamType", 0),
|
||||
intValue(body, "storageType", 0),
|
||||
intValue(body, "playbackMode", 0),
|
||||
intValue(body, "playbackSpeed", 0),
|
||||
stringValue(body, "startTime", ""),
|
||||
stringValue(body, "endTime", "")), "jt1078-history-play");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/history_control")
|
||||
public CommandResult jt1078HistoryControl(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.historyControl(
|
||||
intValue(body, "channelNo", 0),
|
||||
intValue(body, "playbackMode", 0),
|
||||
intValue(body, "playbackSpeed", 0),
|
||||
stringValue(body, "playbackTime", "")), "jt1078-history-control");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/resource_search")
|
||||
public CommandResult jt1078ResourceSearch(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.resourceSearch(
|
||||
intValue(body, "channelNo", 0),
|
||||
stringValue(body, "startTime", ""),
|
||||
stringValue(body, "endTime", ""),
|
||||
longValue(body, "warnBit1", 0),
|
||||
longValue(body, "warnBit2", 0),
|
||||
intValue(body, "mediaType", 0),
|
||||
intValue(body, "streamType", 0),
|
||||
intValue(body, "storageType", 0)), "jt1078-resource-search");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/file_upload")
|
||||
public CommandResult jt1078FileUpload(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.fileUpload(
|
||||
stringValue(body, "ip", ""),
|
||||
intValue(body, "port", 0),
|
||||
stringValue(body, "username", ""),
|
||||
stringValue(body, "password", ""),
|
||||
stringValue(body, "path", ""),
|
||||
intValue(body, "channelNo", 0),
|
||||
stringValue(body, "startTime", ""),
|
||||
stringValue(body, "endTime", ""),
|
||||
longValue(body, "warnBit1", 0),
|
||||
longValue(body, "warnBit2", 0),
|
||||
intValue(body, "mediaType", 0),
|
||||
intValue(body, "streamType", 0),
|
||||
intValue(body, "storageType", 0),
|
||||
intValue(body, "condition", 0)), "jt1078-file-upload");
|
||||
}
|
||||
|
||||
@PostMapping("/jt1078/file_upload_control")
|
||||
public CommandResult jt1078FileUploadControl(@RequestParam String clientId,
|
||||
@RequestBody Map<String, Object> body) {
|
||||
return awaitSync(clientId, Jt1078Commands.fileUploadControl(
|
||||
intValue(body, "responseSerialNo", 0),
|
||||
intValue(body, "command", 0)), "jt1078-file-upload-control");
|
||||
}
|
||||
|
||||
// ===== helpers =====
|
||||
|
||||
private Optional<DeviceSession> resolveSession(String clientId) {
|
||||
return sessions.findBySessionId(clientId)
|
||||
.or(() -> sessions.findByPhone(clientId))
|
||||
.or(() -> sessions.findByVin(clientId));
|
||||
}
|
||||
|
||||
private CommandResult awaitSync(String clientId, Jt808Commands.DownlinkCommand cmd, String op) {
|
||||
CompletableFuture<Jt808Message> future =
|
||||
dispatcher.request(clientId, cmd, Jt808Message.class, DEFAULT_TIMEOUT);
|
||||
try {
|
||||
Jt808Message resp = future.join();
|
||||
return CommandResult.ok(Map.of(
|
||||
"messageId", "0x" + Integer.toHexString(resp.header().messageId()),
|
||||
"serial", resp.header().serialNo(),
|
||||
"phone", resp.header().phone()));
|
||||
} catch (CompletionException e) {
|
||||
Throwable cause = e.getCause() != null ? e.getCause() : e;
|
||||
return CommandResult.failure(500, op + " failed: " + cause.getMessage());
|
||||
} catch (Exception e) {
|
||||
return CommandResult.failure(500, op + " failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private CommandResult fireAndForget(String clientId, Jt808Commands.DownlinkCommand cmd, String op) {
|
||||
try {
|
||||
dispatcher.notify(clientId, cmd).join();
|
||||
return CommandResult.ok(Map.of("op", op));
|
||||
} catch (Exception e) {
|
||||
return CommandResult.failure(500, op + " failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static long parseId(String key) {
|
||||
if (key == null || key.isBlank()) return 0;
|
||||
String k = key.startsWith("0x") ? key.substring(2) : key;
|
||||
return Long.parseLong(k, 16);
|
||||
}
|
||||
|
||||
private static List<Long> parseIds(String ids) {
|
||||
if (ids == null || ids.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return java.util.Arrays.stream(ids.split(","))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isBlank())
|
||||
.map(TerminalCommandController::parseLong)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static long parseLong(String value) {
|
||||
if (value == null || value.isBlank()) return 0;
|
||||
String v = value.startsWith("0x") || value.startsWith("0X") ? value.substring(2) : value;
|
||||
return value.startsWith("0x") || value.startsWith("0X")
|
||||
? Long.parseLong(v, 16)
|
||||
: Long.parseLong(v);
|
||||
}
|
||||
|
||||
private static String stringValue(Map<String, Object> body, String key, String defaultValue) {
|
||||
Object value = body.get(key);
|
||||
return value == null ? defaultValue : value.toString();
|
||||
}
|
||||
|
||||
private static int intValue(Map<String, Object> body, String key, int defaultValue) {
|
||||
Object value = body.get(key);
|
||||
return value == null ? defaultValue : (int) parseLong(value.toString());
|
||||
}
|
||||
|
||||
private static long longValue(Map<String, Object> body, String key, long defaultValue) {
|
||||
Object value = body.get(key);
|
||||
return value == null ? defaultValue : parseLong(value.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
com.lingniu.ingest.gateway.CommandGatewayAutoConfiguration
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.lingniu.ingest.gateway;
|
||||
|
||||
import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands;
|
||||
import com.lingniu.ingest.protocol.jt808.model.Jt808Header;
|
||||
import com.lingniu.ingest.protocol.jt808.model.Jt808Message;
|
||||
import com.lingniu.ingest.session.CommandDispatcher;
|
||||
import com.lingniu.ingest.session.DeviceSession;
|
||||
import com.lingniu.ingest.session.SessionStore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TerminalCommandControllerTest {
|
||||
|
||||
@Test
|
||||
void queryAttributesDispatchesJt8088107InsteadOfNotImplemented() {
|
||||
RecordingDispatcher dispatcher = new RecordingDispatcher();
|
||||
TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);
|
||||
|
||||
CommandResult result = controller.attributes("13800138000");
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8107);
|
||||
assertThat(dispatcher.lastRequestCommand.body()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void alarmAckDispatchesJt8088203WithResponseSerialAndAlarmType() {
|
||||
RecordingDispatcher dispatcher = new RecordingDispatcher();
|
||||
TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);
|
||||
|
||||
CommandResult result = controller.alarmAck("13800138000", Map.of(
|
||||
"responseSerialNo", 0x1234,
|
||||
"type", 0x0000_0001));
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(dispatcher.lastNotifyCommand.messageId()).isEqualTo(0x8203);
|
||||
assertThat(dispatcher.lastNotifyCommand.body()).containsExactly(
|
||||
0x12, 0x34,
|
||||
0x00, 0x00, 0x00, 0x01);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteAreaDispatchesJt8088601WithCommaSeparatedAreaIds() {
|
||||
RecordingDispatcher dispatcher = new RecordingDispatcher();
|
||||
TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);
|
||||
|
||||
CommandResult result = controller.deleteArea("13800138000", "1,0x01020304");
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8601);
|
||||
assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
|
||||
0x02,
|
||||
0x00, 0x00, 0x00, 0x01,
|
||||
0x01, 0x02, 0x03, 0x04);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jt1078RealtimePlayDispatches9101ThroughSharedDispatcher() {
|
||||
RecordingDispatcher dispatcher = new RecordingDispatcher();
|
||||
TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);
|
||||
|
||||
CommandResult result = controller.jt1078RealtimePlay("13800138000", Map.of(
|
||||
"ip", "10.1.2.3",
|
||||
"tcpPort", 11078,
|
||||
"udpPort", 11079,
|
||||
"channelNo", 2,
|
||||
"mediaType", 1,
|
||||
"streamType", 0));
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9101);
|
||||
assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
|
||||
0x08, '1', '0', '.', '1', '.', '2', '.', '3',
|
||||
0x2B, 0x46,
|
||||
0x2B, 0x47,
|
||||
0x02,
|
||||
0x01,
|
||||
0x00);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jt1078ResourceSearchDispatches9205AndAwaitsFileListResponse() {
|
||||
RecordingDispatcher dispatcher = new RecordingDispatcher();
|
||||
TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher);
|
||||
|
||||
CommandResult result = controller.jt1078ResourceSearch("13800138000", Map.of(
|
||||
"channelNo", 3,
|
||||
"startTime", "2026-06-22 08:09:10",
|
||||
"endTime", "260622180000",
|
||||
"warnBit1", "0x01020304",
|
||||
"warnBit2", "0x05060708",
|
||||
"mediaType", 3,
|
||||
"streamType", 2,
|
||||
"storageType", 1));
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9205);
|
||||
assertThat(dispatcher.lastRequestCommand.body()).containsExactly(
|
||||
0x03,
|
||||
0x26, 0x06, 0x22, 0x08, 0x09, 0x10,
|
||||
0x26, 0x06, 0x22, 0x18, 0x00, 0x00,
|
||||
0x01, 0x02, 0x03, 0x04,
|
||||
0x05, 0x06, 0x07, 0x08,
|
||||
0x03,
|
||||
0x02,
|
||||
0x01);
|
||||
}
|
||||
|
||||
private static final class RecordingDispatcher implements CommandDispatcher {
|
||||
private Jt808Commands.DownlinkCommand lastNotifyCommand;
|
||||
private Jt808Commands.DownlinkCommand lastRequestCommand;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> notify(String sessionId, Object command) {
|
||||
this.lastNotifyCommand = (Jt808Commands.DownlinkCommand) command;
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> CompletableFuture<T> request(String sessionId, Object command, Class<T> responseType, Duration timeout) {
|
||||
this.lastRequestCommand = (Jt808Commands.DownlinkCommand) command;
|
||||
Jt808Message response = new Jt808Message(
|
||||
new Jt808Header(0x0107, 0, 0, false, Jt808Header.ProtocolVersion.V2013,
|
||||
"13800138000", 1, 0, 0),
|
||||
new com.lingniu.ingest.protocol.jt808.model.Jt808Body.Raw(0x0107, new byte[0]));
|
||||
return CompletableFuture.completedFuture((T) response);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EmptySessionStore implements SessionStore {
|
||||
@Override public void put(DeviceSession session) {}
|
||||
@Override public Optional<DeviceSession> findBySessionId(String sessionId) { return Optional.empty(); }
|
||||
@Override public Optional<DeviceSession> findByVin(String vin) { return Optional.empty(); }
|
||||
@Override public Optional<DeviceSession> findByPhone(String phone) { return Optional.empty(); }
|
||||
@Override public Optional<DeviceSession> update(String sessionId, UnaryOperator<DeviceSession> updater) { return Optional.empty(); }
|
||||
@Override public void remove(String sessionId) {}
|
||||
@Override public int size() { return 0; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user