Compare commits
2 Commits
8bc91b3bac
...
efa2cacd3b
| Author | SHA1 | Date | |
|---|---|---|---|
| efa2cacd3b | |||
| bf9efff854 |
@ -0,0 +1,29 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("rtkrcv_profile")
|
||||||
|
public class RtkrcvProfile {
|
||||||
|
|
||||||
|
@TableId(value = "device_id", type = IdType.INPUT)
|
||||||
|
private String deviceId;
|
||||||
|
@TableField("inpstr1_path")
|
||||||
|
private String inpstr1Path;
|
||||||
|
@TableField("inpstr2_path")
|
||||||
|
private String inpstr2Path;
|
||||||
|
@TableField("inpstr3_path")
|
||||||
|
private String inpstr3Path;
|
||||||
|
@TableField("outstr1_path")
|
||||||
|
private String outstr1Path;
|
||||||
|
@TableField("out_height")
|
||||||
|
private Integer outHeight;
|
||||||
|
@TableField("updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface RtkrcvProfileMapper extends BaseMapper<RtkrcvProfile> {
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("rtkrcv_session")
|
||||||
|
public class RtkrcvSession {
|
||||||
|
|
||||||
|
@TableId(value = "session_id", type = IdType.AUTO)
|
||||||
|
private Long sessionId;
|
||||||
|
|
||||||
|
@TableField("device_id")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@TableField("state")
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
@TableField("rtk_conf_path")
|
||||||
|
private String rtkConfPath;
|
||||||
|
|
||||||
|
@TableField("work_dir")
|
||||||
|
private String workDir;
|
||||||
|
@TableField("work_port")
|
||||||
|
private Integer workPort;
|
||||||
|
|
||||||
|
@TableField("pid")
|
||||||
|
private Integer pid;
|
||||||
|
|
||||||
|
@TableField("start_time")
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
@TableField("end_time")
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
@TableField("heartbeat_at")
|
||||||
|
private LocalDateTime heartbeatAt;
|
||||||
|
|
||||||
|
@TableField("error_message")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
@TableField("created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@TableField("updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface RtkrcvSessionMapper extends BaseMapper<RtkrcvSession> {
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("rtkrcv_solution")
|
||||||
|
public class RtkrcvSolution {
|
||||||
|
|
||||||
|
@TableId(value = "solution_id", type = IdType.AUTO)
|
||||||
|
private Long solutionId;
|
||||||
|
|
||||||
|
@TableField("device_id")
|
||||||
|
private String deviceId;
|
||||||
|
|
||||||
|
@TableField("lat")
|
||||||
|
private Double lat;
|
||||||
|
|
||||||
|
@TableField("lon")
|
||||||
|
private Double lon;
|
||||||
|
|
||||||
|
@TableField("height")
|
||||||
|
private Double height;
|
||||||
|
|
||||||
|
@TableField("ecef_x")
|
||||||
|
private Double ecefX;
|
||||||
|
|
||||||
|
@TableField("ecef_y")
|
||||||
|
private Double ecefY;
|
||||||
|
|
||||||
|
@TableField("ecef_z")
|
||||||
|
private Double ecefZ;
|
||||||
|
|
||||||
|
@TableField("quality")
|
||||||
|
private String quality;
|
||||||
|
|
||||||
|
@TableField("duration_s")
|
||||||
|
private Integer durationSeconds;
|
||||||
|
|
||||||
|
@TableField("obs_count")
|
||||||
|
private Integer obsCount;
|
||||||
|
|
||||||
|
@TableField("created_at")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package com.imdroid.secapi.dto;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface RtkrcvSolutionMapper extends BaseMapper<RtkrcvSolution> {
|
||||||
|
}
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ import com.imdroid.sideslope.server.DeviceChannel;
|
|||||||
import com.imdroid.sideslope.server.OnlineChannels;
|
import com.imdroid.sideslope.server.OnlineChannels;
|
||||||
import com.imdroid.sideslope.service.DataPersistService;
|
import com.imdroid.sideslope.service.DataPersistService;
|
||||||
import com.imdroid.sideslope.bd.RtcmGgaUtil;
|
import com.imdroid.sideslope.bd.RtcmGgaUtil;
|
||||||
|
import com.imdroid.sideslope.rtkcluster.RtkClusterService;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -48,6 +49,8 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
UdpNtripServer ntripServer;
|
UdpNtripServer ntripServer;
|
||||||
@Autowired
|
@Autowired
|
||||||
private GNSSDataCalcService gnssCalcService;
|
private GNSSDataCalcService gnssCalcService;
|
||||||
|
@Autowired
|
||||||
|
private RtkClusterService rtkClusterService;
|
||||||
|
|
||||||
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
|
// 添加一个成员变量用于追踪每个测站最后一次转发D300数据的时间
|
||||||
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
|
private final Map<String, Long> lastD300ForwardTimeMap = new ConcurrentHashMap<>();
|
||||||
@ -124,7 +127,15 @@ public class D331RtcmMessageExecutor implements Executor<D331RtcmMessage, Void>
|
|||||||
byte[] srcdata = message.getSrcData();
|
byte[] srcdata = message.getSrcData();
|
||||||
String rtcm = ByteUtil.bytesToHexString(srcdata);
|
String rtcm = ByteUtil.bytesToHexString(srcdata);
|
||||||
sendToNtrip(id, rtcm);
|
sendToNtrip(id, rtcm);
|
||||||
|
|
||||||
|
try {
|
||||||
|
rtkClusterService.sendRtcm(id, rtcm);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.debug("send to rtkCluster failed for {}: {}", id, e.getMessage());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ThreadManager.getFixedThreadPool().submit(() -> {
|
ThreadManager.getFixedThreadPool().submit(() -> {
|
||||||
// 原始码流输出到日志文件 -- INFO 级别
|
// 原始码流输出到日志文件 -- INFO 级别
|
||||||
|
|||||||
@ -0,0 +1,110 @@
|
|||||||
|
package com.imdroid.sideslope.ntrip;
|
||||||
|
|
||||||
|
import com.imdroid.secapi.dto.RtkrcvProfile;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class RtkrcvConfigService {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RtkrcvConfigService.class);
|
||||||
|
|
||||||
|
@Value("${rtkrcv.workdir:/opt/rtk}")
|
||||||
|
private String defaultWorkDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an RTKLIB rtkrcv config file from template using profile values.
|
||||||
|
* The file will be saved to `${rtkrcv.workdir:/opt/rtk}/rtkrcv_{deviceId}.conf`.
|
||||||
|
*/
|
||||||
|
public Path generateConfig(RtkrcvProfile profile) throws IOException {
|
||||||
|
return generateConfig(profile, java.nio.file.Paths.get(defaultWorkDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an RTKLIB rtkrcv config file from the classpath template `rtkrcv_default.conf`.
|
||||||
|
* Only the following keys are substituted: inpstr1-path, inpstr2-path, inpstr3-path, out-height.
|
||||||
|
* The output filename will be `rtkrcv_{deviceId}.conf` under the provided outputDir.
|
||||||
|
*/
|
||||||
|
public Path generateConfig(RtkrcvProfile profile, Path outputDir) throws IOException {
|
||||||
|
if (profile == null) throw new IllegalArgumentException("profile must not be null");
|
||||||
|
if (profile.getDeviceId() == null || profile.getDeviceId().trim().isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("profile.deviceId must not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> lines = readTemplateLines();
|
||||||
|
|
||||||
|
List<String> rendered = new ArrayList<>(lines.size());
|
||||||
|
for (String line : lines) {
|
||||||
|
String replaced = line;
|
||||||
|
replaced = replaceValueLine(replaced, "inpstr1-path", nz(profile.getInpstr1Path()));
|
||||||
|
replaced = replaceValueLine(replaced, "inpstr2-path", nz(profile.getInpstr2Path()));
|
||||||
|
replaced = replaceValueLine(replaced, "inpstr3-path", nz(profile.getInpstr3Path()));
|
||||||
|
replaced = replaceValueLine(replaced, "outstr1-path", nz(profile.getOutstr1Path()));
|
||||||
|
Integer outHeight = profile.getOutHeight();
|
||||||
|
int heightValue = (outHeight == null) ? 1 : (outHeight == 0 ? 0 : 1);
|
||||||
|
replaced = replaceValueLine(replaced, "out-height", String.valueOf(heightValue));
|
||||||
|
rendered.add(replaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.exists(outputDir)) {
|
||||||
|
Files.createDirectories(outputDir);
|
||||||
|
}
|
||||||
|
Path out = outputDir.resolve("rtkrcv_" + profile.getDeviceId() + ".conf");
|
||||||
|
try (BufferedWriter writer = Files.newBufferedWriter(out, StandardCharsets.UTF_8)) {
|
||||||
|
for (String l : rendered) {
|
||||||
|
writer.write(l);
|
||||||
|
writer.newLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGGER.info("Generated rtkrcv config: {}", out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> readTemplateLines() throws IOException {
|
||||||
|
ClassPathResource resource = new ClassPathResource("rtkrcv_default.conf");
|
||||||
|
if (!resource.exists()) {
|
||||||
|
throw new IOException("Template rtkrcv_default.conf not found on classpath");
|
||||||
|
}
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
lines.add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nz(String s) {
|
||||||
|
return s == null ? "" : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the value of a line like: "key = value # comment" while preserving spacing and comment.
|
||||||
|
*/
|
||||||
|
private String replaceValueLine(String line, String key, String newValue) {
|
||||||
|
String regex = "^(\\s*" + key + "\\s*=\\s*)([^#]*?)(\\s*(#.*)?)$";
|
||||||
|
Pattern p = Pattern.compile(regex);
|
||||||
|
Matcher m = p.matcher(line);
|
||||||
|
if (!m.find()) return line;
|
||||||
|
String prefix = m.group(1);
|
||||||
|
String suffix = m.group(3) == null ? "" : m.group(3);
|
||||||
|
String value = newValue == null ? "" : newValue.trim();
|
||||||
|
return prefix + value + suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,286 @@
|
|||||||
|
package com.imdroid.sideslope.rtkcluster;
|
||||||
|
|
||||||
|
import com.imdroid.common.util.ByteUtil;
|
||||||
|
import com.imdroid.secapi.dto.RtkrcvProfile;
|
||||||
|
import com.imdroid.secapi.dto.RtkrcvProfileMapper;
|
||||||
|
import com.imdroid.secapi.dto.RtkrcvSession;
|
||||||
|
import com.imdroid.secapi.dto.RtkrcvSessionMapper;
|
||||||
|
import com.imdroid.sideslope.ntrip.RtkrcvConfigService;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RtkCluster: per-device TCP server on a single port number for both IN (RTCM to rtkrcv) and OUT (NMEA from rtkrcv).
|
||||||
|
* - For each deviceId, we create a local server bound to 127.0.0.1:workPort.
|
||||||
|
* - rtkrcv connects twice to the same port: one connection will be classified as OUT (it sends data quickly),
|
||||||
|
* the other as IN (no initial data; we will write RTCM to it).
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class RtkClusterService implements ApplicationRunner {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RtkClusterService.class);
|
||||||
|
|
||||||
|
@Value("${rtk.cluster.basePort:24000}")
|
||||||
|
private int basePort;
|
||||||
|
|
||||||
|
@Value("${rtkrcv.workdir:/opt/rtk}")
|
||||||
|
private String rtkWorkDir;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RtkrcvProfileMapper profileMapper;
|
||||||
|
@Autowired
|
||||||
|
private RtkrcvSessionMapper sessionMapper;
|
||||||
|
@Autowired
|
||||||
|
private RtkrcvConfigService configService;
|
||||||
|
|
||||||
|
private final Map<String, DeviceEndpoint> endpoints = new ConcurrentHashMap<>();
|
||||||
|
private final Map<String, Process> processes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final ExecutorService worker = Executors.newCachedThreadPool(r -> {
|
||||||
|
Thread t = new Thread(r, "rtkcluster-worker");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args) {
|
||||||
|
// Load all profiles and bootstrap endpoints and sessions.
|
||||||
|
List<RtkrcvProfile> profiles = profileMapper.selectList(null);
|
||||||
|
if (profiles == null || profiles.isEmpty()) {
|
||||||
|
LOGGER.info("No rtkrcv_profile records found. RtkCluster idle.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int slot = 1;
|
||||||
|
for (RtkrcvProfile profile : profiles) {
|
||||||
|
try {
|
||||||
|
int port = basePort + slot++;
|
||||||
|
bootstrapDevice(profile, port);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Bootstrap device {} failed: {}", profile.getDeviceId(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bootstrapDevice(RtkrcvProfile profile, int workPort) throws IOException {
|
||||||
|
String deviceId = profile.getDeviceId();
|
||||||
|
// 1) Start endpoint server (if not exists)
|
||||||
|
endpoints.computeIfAbsent(deviceId, k -> new DeviceEndpoint(workPort));
|
||||||
|
DeviceEndpoint ep = endpoints.get(deviceId);
|
||||||
|
ep.ensureStarted();
|
||||||
|
|
||||||
|
// 2) Update profile inp/out to local cluster port
|
||||||
|
String localPath = "127.0.0.1:" + workPort;
|
||||||
|
profile.setInpstr1Path(localPath);
|
||||||
|
profile.setOutstr1Path(localPath);
|
||||||
|
profileMapper.updateById(profile);
|
||||||
|
|
||||||
|
// 3) Generate config and start rtkrcv
|
||||||
|
Path conf = configService.generateConfig(profile);
|
||||||
|
// 4) Insert session with rtk_conf_path present
|
||||||
|
RtkrcvSession session = new RtkrcvSession();
|
||||||
|
session.setDeviceId(deviceId);
|
||||||
|
session.setState("starting");
|
||||||
|
session.setWorkDir(rtkWorkDir);
|
||||||
|
session.setWorkPort(workPort);
|
||||||
|
session.setRtkConfPath(conf.toString());
|
||||||
|
session.setCreatedAt(LocalDateTime.now());
|
||||||
|
session.setUpdatedAt(LocalDateTime.now());
|
||||||
|
sessionMapper.insert(session);
|
||||||
|
|
||||||
|
startRtkrcv(deviceId, conf.getParent().toString(), conf.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRtkrcv(String deviceId, String workDir, String confPath) {
|
||||||
|
if (processes.containsKey(deviceId)) {
|
||||||
|
Process p = processes.get(deviceId);
|
||||||
|
if (p != null && p.isAlive()) {
|
||||||
|
LOGGER.warn("rtkrcv for {} already running", deviceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
worker.submit(() -> {
|
||||||
|
try {
|
||||||
|
ProcessBuilder builder = new ProcessBuilder("rtkrcv", "-nc", "-o", confPath);
|
||||||
|
builder.directory(new File(workDir));
|
||||||
|
builder.redirectErrorStream(true);
|
||||||
|
LOGGER.info("Starting rtkrcv for {} with {}", deviceId, confPath);
|
||||||
|
Process p = builder.start();
|
||||||
|
processes.put(deviceId, p);
|
||||||
|
// async log stdout
|
||||||
|
logOutput(deviceId, p);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Failed to start rtkrcv for {}: {}", deviceId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logOutput(String deviceId, Process p) {
|
||||||
|
worker.submit(() -> {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
LOGGER.info("[rtkrcv:{}] {}", deviceId, line);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("Error reading rtkrcv output for {}: {}", deviceId, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
int code = p.waitFor();
|
||||||
|
LOGGER.info("rtkrcv for {} exited with {}", deviceId, code);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendRtcm(String deviceId, String hexRtcm) {
|
||||||
|
DeviceEndpoint ep = endpoints.get(deviceId);
|
||||||
|
if (ep == null) return;
|
||||||
|
byte[] data = ByteUtil.hexStringTobyte(hexRtcm);
|
||||||
|
ep.enqueueRtcm(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DeviceEndpoint {
|
||||||
|
private final int port;
|
||||||
|
private final ExecutorService exec;
|
||||||
|
private volatile boolean started = false;
|
||||||
|
private volatile ServerSocket server;
|
||||||
|
private volatile Socket inConn; // server writes RTCM to this
|
||||||
|
private volatile Socket outConn; // server reads NMEA from this
|
||||||
|
private final BlockingQueue<byte[]> rtcmQueue = new LinkedBlockingQueue<>(1024);
|
||||||
|
|
||||||
|
DeviceEndpoint(int port) {
|
||||||
|
this.port = port;
|
||||||
|
this.exec = Executors.newCachedThreadPool(r -> {
|
||||||
|
Thread t = new Thread(r, "rtkcluster-ep-" + this.port);
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void ensureStarted() {
|
||||||
|
if (started) return;
|
||||||
|
exec.submit(this::acceptLoop);
|
||||||
|
exec.submit(this::dequeueLoop);
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enqueueRtcm(byte[] data) {
|
||||||
|
if (data == null || data.length == 0) return;
|
||||||
|
rtcmQueue.offer(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void acceptLoop() {
|
||||||
|
try (ServerSocket ss = new ServerSocket()) {
|
||||||
|
ss.setReuseAddress(true);
|
||||||
|
ss.bind(new InetSocketAddress("127.0.0.1", port));
|
||||||
|
this.server = ss;
|
||||||
|
LOGGER.info("RtkCluster device endpoint listening on 127.0.0.1:{}", port);
|
||||||
|
while (true) {
|
||||||
|
Socket s = ss.accept();
|
||||||
|
classifyConnection(s);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Device endpoint on {} stopped: {}", port, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void classifyConnection(Socket s) {
|
||||||
|
exec.submit(() -> {
|
||||||
|
try {
|
||||||
|
s.setSoTimeout(300);
|
||||||
|
InputStream in = s.getInputStream();
|
||||||
|
byte[] probe = new byte[64];
|
||||||
|
int n = 0;
|
||||||
|
try { n = in.read(probe); } catch (IOException ignore) {}
|
||||||
|
if (n > 0 && isLikelyText(probe, n)) {
|
||||||
|
// OUT connection (NMEA etc.)
|
||||||
|
closeQuietly(outConn);
|
||||||
|
outConn = s;
|
||||||
|
LOGGER.info("Endpoint {} OUT connected", port);
|
||||||
|
pumpOut(outConn);
|
||||||
|
} else {
|
||||||
|
// IN connection (RTCM sink)
|
||||||
|
closeQuietly(inConn);
|
||||||
|
inConn = s;
|
||||||
|
LOGGER.info("Endpoint {} IN connected", port);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("classifyConnection error: {}", e.getMessage());
|
||||||
|
closeQuietly(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLikelyText(byte[] buf, int n) {
|
||||||
|
int printable = 0;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int b = buf[i] & 0xFF;
|
||||||
|
if (b >= 32 && b <= 126) printable++;
|
||||||
|
}
|
||||||
|
return printable >= Math.max(1, n - 4); // tolerate some non-printables
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pumpOut(Socket s) {
|
||||||
|
exec.submit(() -> {
|
||||||
|
try (InputStream in = s.getInputStream()) {
|
||||||
|
byte[] buf = new byte[2048];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buf)) != -1) {
|
||||||
|
// For now, just log a short preview
|
||||||
|
String preview = new String(buf, 0, Math.min(read, 64), StandardCharsets.US_ASCII).replaceAll("\n", "\\n");
|
||||||
|
LOGGER.info("[OUT:{}] {} bytes: {}...", port, read, preview);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.info("OUT connection closed on {}: {}", port, e.getMessage());
|
||||||
|
} finally {
|
||||||
|
if (outConn == s) outConn = null;
|
||||||
|
closeQuietly(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dequeueLoop() {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
byte[] data = rtcmQueue.take();
|
||||||
|
Socket sink = inConn;
|
||||||
|
if (sink == null || sink.isClosed()) continue;
|
||||||
|
try {
|
||||||
|
OutputStream os = sink.getOutputStream();
|
||||||
|
os.write(data);
|
||||||
|
os.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("Write RTCM failed on {}: {}", port, e.getMessage());
|
||||||
|
closeQuietly(sink);
|
||||||
|
inConn = null;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeQuietly(Socket s) {
|
||||||
|
if (s == null) return;
|
||||||
|
try { s.close(); } catch (IOException ignore) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -100,6 +100,9 @@ public class Device {
|
|||||||
byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP
|
byte dataChannelType = CHANNEL_TYPE_UDP; // 0:TCP;1:DUP
|
||||||
int lasQuality = 0;
|
int lasQuality = 0;
|
||||||
|
|
||||||
|
// rtkrcv cluster work port (127.0.0.1:port)
|
||||||
|
Integer rtkrcvWorkPort;
|
||||||
|
|
||||||
public void updateRx(int head, int bytes,int count){
|
public void updateRx(int head, int bytes,int count){
|
||||||
lastRxHead = head;
|
lastRxHead = head;
|
||||||
|
|
||||||
|
|||||||
@ -39,6 +39,12 @@ warning.log.directory=./log
|
|||||||
|
|
||||||
ntrip_server.port = 11100
|
ntrip_server.port = 11100
|
||||||
|
|
||||||
|
rtkrcv.enable=true
|
||||||
|
rtkrcv.binary=rtkrcv
|
||||||
|
rtkrcv.config=/opt/rtk/rtk.conf
|
||||||
|
rtkrcv.workdir=/opt/rtk
|
||||||
|
rtk.tcp.port=20001
|
||||||
|
|
||||||
sim.url = http://120.78.169.220:8089
|
sim.url = http://120.78.169.220:8089
|
||||||
sim.username = gzyzdz
|
sim.username = gzyzdz
|
||||||
sim.key = 632629d1269a202c9d49a574623e4e4c
|
sim.key = 632629d1269a202c9d49a574623e4e4c
|
||||||
|
|||||||
162
sec-beidou-rtcm/src/main/resources/rtkrcv_default.conf
Normal file
162
sec-beidou-rtcm/src/main/resources/rtkrcv_default.conf
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
pos1-posmode =single # (0:single,1:dgps,2:kinematic,3:static,4:static-start,5:movingbase,6:fixed,7:ppp-kine,8:ppp-static,9:ppp-fixed)
|
||||||
|
pos1-frequency =l1+l2+l5 # (1:l1,2:l1+l2,3:l1+l2+l5,4:l1+l2+l5+l6)
|
||||||
|
pos1-soltype =forward # (0:forward,1:backward,2:combined,3:combined-nophasereset)
|
||||||
|
pos1-elmask =10 # (deg)
|
||||||
|
pos1-snrmask_r =on # (0:off,1:on)
|
||||||
|
pos1-snrmask_b =on # (0:off,1:on)
|
||||||
|
pos1-snrmask_L1 =0,0,0,0,0,0,0,0,0
|
||||||
|
pos1-snrmask_L2 =0,0,0,0,0,0,0,0,0
|
||||||
|
pos1-snrmask_L5 =0,0,0,0,0,0,0,0,0
|
||||||
|
pos1-dynamics =off # (0:off,1:on)
|
||||||
|
pos1-tidecorr =off # (0:off,1:on,2:otl)
|
||||||
|
pos1-ionoopt =brdc # (0:off,1:brdc,2:sbas,3:dual-freq,4:est-stec,5:ionex-tec,6:qzs-brdc)
|
||||||
|
pos1-tropopt =saas # (0:off,1:saas,2:sbas,3:est-ztd,4:est-ztdgrad)
|
||||||
|
pos1-sateph =brdc+sbas # (0:brdc,1:precise,2:brdc+sbas,3:brdc+ssrapc,4:brdc+ssrcom)
|
||||||
|
pos1-posopt1 =off # (0:off,1:on)
|
||||||
|
pos1-posopt2 =off # (0:off,1:on)
|
||||||
|
pos1-posopt3 =off # (0:off,1:on,2:precise)
|
||||||
|
pos1-posopt4 =off # (0:off,1:on)
|
||||||
|
pos1-posopt5 =off # (0:off,1:on)
|
||||||
|
pos1-posopt6 =off # (0:off,1:on)
|
||||||
|
pos1-exclsats = # (prn ...)
|
||||||
|
pos1-navsys =45 # (1:gps+2:sbas+4:glo+8:gal+16:qzs+32:bds+64:navic)
|
||||||
|
pos2-armode =continuous # (0:off,1:continuous,2:instantaneous,3:fix-and-hold)
|
||||||
|
pos2-gloarmode =fix-and-hold # (0:off,1:on,2:autocal,3:fix-and-hold)
|
||||||
|
pos2-bdsarmode =on # (0:off,1:on)
|
||||||
|
pos2-arfilter =on # (0:off,1:on)
|
||||||
|
pos2-arthres =3
|
||||||
|
pos2-arthresmin =2
|
||||||
|
pos2-arthresmax =2
|
||||||
|
pos2-arthres1 =0.1
|
||||||
|
pos2-arthres2 =0
|
||||||
|
pos2-arthres3 =1e-09
|
||||||
|
pos2-arthres4 =1e-05
|
||||||
|
pos2-varholdamb =0.1 # (cyc^2)
|
||||||
|
pos2-gainholdamb =0.01
|
||||||
|
pos2-arlockcnt =5
|
||||||
|
pos2-minfixsats =4
|
||||||
|
pos2-minholdsats =5
|
||||||
|
pos2-mindropsats =10
|
||||||
|
pos2-arelmask =10 # (deg)
|
||||||
|
pos2-arminfix =20
|
||||||
|
pos2-armaxiter =1
|
||||||
|
pos2-elmaskhold =10 # (deg)
|
||||||
|
pos2-aroutcnt =20
|
||||||
|
pos2-maxage =30 # (s)
|
||||||
|
pos2-syncsol =off # (0:off,1:on)
|
||||||
|
pos2-slipthres =0.05 # (m)
|
||||||
|
pos2-dopthres =0 # (m)
|
||||||
|
pos2-rejionno =5 # (m)
|
||||||
|
pos2-rejcode =30 # (m)
|
||||||
|
pos2-niter =1
|
||||||
|
pos2-baselen =0 # (m)
|
||||||
|
pos2-basesig =0 # (m)
|
||||||
|
out-solformat =llh # (0:llh,1:xyz,2:enu,3:nmea)
|
||||||
|
out-outhead =on # (0:off,1:on)
|
||||||
|
out-outopt =on # (0:off,1:on)
|
||||||
|
out-outvel =off # (0:off,1:on)
|
||||||
|
out-timesys =gpst # (0:gpst,1:utc,2:jst)
|
||||||
|
out-timeform =hms # (0:tow,1:hms)
|
||||||
|
out-timendec =3
|
||||||
|
out-degform =deg # (0:deg,1:dms)
|
||||||
|
out-fieldsep =
|
||||||
|
out-outsingle =off # (0:off,1:on)
|
||||||
|
out-maxsolstd =0 # (m)
|
||||||
|
out-height =1 # (0:ellipsoidal,1:geodetic)
|
||||||
|
out-geoid =internal # (0:internal,1:egm96,2:egm08_2.5,3:egm08_1,4:gsi2000)
|
||||||
|
out-solstatic =all # (0:all,1:single)
|
||||||
|
out-nmeaintv1 =0 # (s)
|
||||||
|
out-nmeaintv2 =0 # (s)
|
||||||
|
out-outstat =residual # (0:off,1:state,2:residual)
|
||||||
|
stats-eratio1 =300
|
||||||
|
stats-eratio2 =300
|
||||||
|
stats-eratio5 =300
|
||||||
|
stats-errphase =0.003 # (m)
|
||||||
|
stats-errphaseel =0.003 # (m)
|
||||||
|
stats-errphasebl =0 # (m/10km)
|
||||||
|
stats-errdoppler =1 # (Hz)
|
||||||
|
stats-snrmax =52 # (dB.Hz)
|
||||||
|
stats-errsnr =0 # (m)
|
||||||
|
stats-errrcv =0 # ( )
|
||||||
|
stats-stdbias =30 # (m)
|
||||||
|
stats-stdiono =0.03 # (m)
|
||||||
|
stats-stdtrop =0.3 # (m)
|
||||||
|
stats-prnaccelh =3 # (m/s^2)
|
||||||
|
stats-prnaccelv =1 # (m/s^2)
|
||||||
|
stats-prnbias =0.0001 # (m)
|
||||||
|
stats-prniono =0.001 # (m)
|
||||||
|
stats-prntrop =0.0001 # (m)
|
||||||
|
stats-prnpos =0 # (m)
|
||||||
|
stats-clkstab =5e-12 # (s/s)
|
||||||
|
ant1-postype =llh # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
|
||||||
|
ant1-pos1 =90 # (deg|m)
|
||||||
|
ant1-pos2 =0 # (deg|m)
|
||||||
|
ant1-pos3 =-6335367.6285 # (m|m)
|
||||||
|
ant1-anttype =
|
||||||
|
ant1-antdele =0 # (m)
|
||||||
|
ant1-antdeln =0 # (m)
|
||||||
|
ant1-antdelu =0 # (m)
|
||||||
|
ant2-postype =rtcm # (0:llh,1:xyz,2:single,3:posfile,4:rinexhead,5:rtcm,6:raw)
|
||||||
|
ant2-pos1 =0 # (deg|m)
|
||||||
|
ant2-pos2 =0 # (deg|m)
|
||||||
|
ant2-pos3 =0 # (m|m)
|
||||||
|
ant2-anttype =
|
||||||
|
ant2-antdele =0 # (m)
|
||||||
|
ant2-antdeln =0 # (m)
|
||||||
|
ant2-antdelu =0 # (m)
|
||||||
|
ant2-maxaveep =1
|
||||||
|
ant2-initrst =on # (0:off,1:on)
|
||||||
|
misc-timeinterp =off # (0:off,1:on)
|
||||||
|
misc-sbasatsel =0 # (0:all)
|
||||||
|
misc-rnxopt1 =
|
||||||
|
misc-rnxopt2 =
|
||||||
|
misc-pppopt =
|
||||||
|
file-satantfile =
|
||||||
|
file-rcvantfile =
|
||||||
|
file-staposfile =
|
||||||
|
file-geoidfile =
|
||||||
|
file-ionofile =
|
||||||
|
file-dcbfile =
|
||||||
|
file-eopfile =
|
||||||
|
file-blqfile =
|
||||||
|
file-tempdir =./RTKNAVI
|
||||||
|
file-geexefile =
|
||||||
|
file-solstatfile =
|
||||||
|
file-tracefile =
|
||||||
|
#
|
||||||
|
|
||||||
|
inpstr1-type =ntripcli # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,6:ntripcli,7:ftp,8:http)
|
||||||
|
inpstr2-type =ntripcli # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,6:ntripcli,7:ftp,8:http)
|
||||||
|
inpstr3-type =ntripcli # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,6:ntripcli,7:ftp,8:http)
|
||||||
|
inpstr1-path =beidou:29832611@8.134.185.53:8001/6539837
|
||||||
|
#inpstr2-path =cedr25866:fyx25455@120.253.226.97:8001/RTCM33_GRCEJ
|
||||||
|
inpstr2-path =ytcors14847:fyx25943@gnss.ytcors.cn:8003/RTCM33GRCEJpro
|
||||||
|
#inpstr2-path =beidou:29832611@192.168.100.206:2101/8507903
|
||||||
|
inpstr3-path =Ming:@Zhang12345@ntrip.data.gnss.ga.gov.au:2101/BCEP00BKG0
|
||||||
|
inpstr1-format =rtcm3 # (0:rtcm2,1:rtcm3,2:oem4,4:ubx,5:swift,6:hemis,7:skytraq,8:javad,9:nvs,10:binex,11:rt17,12:sbf,15:sp3)
|
||||||
|
inpstr2-format =rtcm3 # (0:rtcm2,1:rtcm3,2:oem4,4:ubx,5:swift,6:hemis,7:skytraq,8:javad,9:nvs,10:binex,11:rt17,12:sbf,15:sp3)
|
||||||
|
inpstr3-format =rtcm3 # (0:rtcm2,1:rtcm3,2:oem4,4:ubx,5:swift,6:hemis,7:skytraq,8:javad,9:nvs,10:binex,11:rt17,12:sbf,15:sp3)
|
||||||
|
inpstr2-nmeareq =single # (0:off,1:latlon,2:single)
|
||||||
|
inpstr2-nmealat = # (deg)
|
||||||
|
inpstr2-nmealon = # (deg)
|
||||||
|
outstr1-type =tcpcli # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,5:ntripsvr,9:ntripcas)
|
||||||
|
outstr2-type =off # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,5:ntripsvr,9:ntripcas)
|
||||||
|
outstr1-path =127.0.0.1:20001
|
||||||
|
outstr2-path =
|
||||||
|
outstr1-format =llh # (0:llh,1:xyz,2:enu,3:nmea:4:stat)
|
||||||
|
outstr2-format = # (0:llh,1:xyz,2:enu,3:nmea:4:stat)
|
||||||
|
logstr1-type =0 # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,5:ntripsvr,9:ntripcas)
|
||||||
|
logstr2-type =off # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,5:ntripsvr,9:ntripcas)
|
||||||
|
logstr3-type =off # (0:off,1:serial,2:file,3:tcpsvr,4:tcpcli,5:ntripsvr,9:ntripcas)
|
||||||
|
logstr1-path =
|
||||||
|
logstr2-path =
|
||||||
|
logstr3-path =
|
||||||
|
misc-svrcycle =10 # (ms)
|
||||||
|
misc-timeout =10000 # (ms)
|
||||||
|
misc-reconnect =10000 # (ms)
|
||||||
|
misc-nmeacycle =5000 # (ms)
|
||||||
|
misc-buffsize =32768 # (bytes)
|
||||||
|
misc-navmsgsel =all # (0:all,1:rover,2:base,3:corr)
|
||||||
|
misc-proxyaddr =
|
||||||
|
misc-fswapmargin =30 # (s)
|
||||||
|
console-passwd =
|
||||||
Loading…
x
Reference in New Issue
Block a user