1、增加电池参数配置
2、增加版本管理页面
This commit is contained in:
parent
0864ca38c9
commit
5deeb3c70b
1
pom.xml
1
pom.xml
@ -18,6 +18,7 @@
|
|||||||
<module>sec-ntrip-proxy</module>
|
<module>sec-ntrip-proxy</module>
|
||||||
<module>sec-exapi</module>
|
<module>sec-exapi</module>
|
||||||
<module>sec-beidou-ehm</module>
|
<module>sec-beidou-ehm</module>
|
||||||
|
<module>sec-vermgr</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
18
readme.txt
18
readme.txt
@ -28,6 +28,7 @@ beidou-fwd 9906 解算结果推送
|
|||||||
ntrip-proxy 9910 11001(外) ntrip服务器代理
|
ntrip-proxy 9910 11001(外) ntrip服务器代理
|
||||||
beidou-exapi 9908(外) API
|
beidou-exapi 9908(外) API
|
||||||
beidou-ehm 9912 健康检查、SIM卡检查
|
beidou-ehm 9912 健康检查、SIM卡检查
|
||||||
|
vermgr 9914 9916(外) 版本管理服务
|
||||||
|
|
||||||
2024-9
|
2024-9
|
||||||
算法:
|
算法:
|
||||||
@ -55,6 +56,17 @@ beidou-ehm 9912 健康检查、SIM卡检查
|
|||||||
2)健康检查增加连续无有效解的时长之和统计
|
2)健康检查增加连续无有效解的时长之和统计
|
||||||
|
|
||||||
3)批量升级:
|
3)批量升级:
|
||||||
a)在设备页面增加勾选框和“准备升级”、“完成升级”按钮
|
a)在系统管理里增加一页固件升级页面,包括固件列表和设备列表
|
||||||
b)点击“准备升级”,把模式改为“待升级”,同时发连接版本服务器指令
|
1)固件列表:固件名、固件大小、创建时间。每次升级只能单选一个固件
|
||||||
c)点击“完成升级”,把模式改为“正常”
|
2)设备列表:所属组织、设备号、项目、桩号、版本号、使用状态(正常、维护、停用)、当前状态(工作、休眠、离线)、最近一次版本升级时间
|
||||||
|
3)设备可多选,筛选条件:所属组织、设备号、项目、桩号、版本号
|
||||||
|
b)升级过程:
|
||||||
|
1)WEB服务:选择固件->筛选设备->勾选设备
|
||||||
|
2)WEB服务:点击“升级”,系统检查所勾选的设备批次与所选固件是否一致,如果有不一致,弹出“固件不匹配”提示窗口,结束升级
|
||||||
|
3)WEB服务:向版本服务发升级命令UpgradeCmd(deviceList),版本服务如果应答UpgradeAck,则把待升级的设备改为“维护”状态,发连接服务器指令,升级按钮变灰;否则提示“版本服务未启动”的提示,结束升级
|
||||||
|
4)版本服务:当收到WEB服务发来的升级命令UpgradeCmd(deviceList),回应答UpgradeAck,为每个待升级设备启动30s升级定时器
|
||||||
|
5)版本服务:当收到TCP连接响应,则向对端发版本查询指令,开始版本传输流程,升级过程向WEB服务发送进度指示UpgradeInd(deviceid,progress),每次收到ACK则刷新定时器。
|
||||||
|
6)版本服务:当设备升级完毕,或升级定时器超时,结束升级流程,向WEB服务发升级完成指示UpgradeCompleteInd(deviceid,result)。当所有设备升级结束,再给WEB发一个全部升级完成的指示UpgradeCompleteInd(all)
|
||||||
|
7)WEB服务:当收到全部升级结束指示,按钮变正常,升级设备状态改为正常
|
||||||
|
8)WEB服务:保存升级记录
|
||||||
|
c)增加一页固件升级记录表,包括所属组织、设备号、项目、桩号、升级时间、升级固件、是否成功
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.imdroid.secapi.client;
|
||||||
|
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
@FeignClient(name="VersionClient",url = "http://localhost:9916/ver_mgr")
|
||||||
|
public interface VersionClient {
|
||||||
|
@PostMapping("/upgrade_cmd")
|
||||||
|
HttpResp upgrade(@RequestParam(name = "deviceList") String deviceList, @RequestParam(name = "verFile") String verFile);
|
||||||
|
}
|
||||||
@ -3,9 +3,13 @@ package com.imdroid.secapi.dto;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.imdroid.common.util.ByteUtil;
|
||||||
|
import com.imdroid.common.util.HexUtil;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GNSS设备配置数据
|
* GNSS设备配置数据
|
||||||
@ -21,6 +25,7 @@ public class GnssDevice {
|
|||||||
public static final short OP_MODE_USE = 0;
|
public static final short OP_MODE_USE = 0;
|
||||||
public static final short OP_MODE_CHECK = 1;
|
public static final short OP_MODE_CHECK = 1;
|
||||||
public static final short OP_MODE_UNUSE = 2;
|
public static final short OP_MODE_UNUSE = 2;
|
||||||
|
public static final short OP_MODE_UPGRADING = 3;
|
||||||
|
|
||||||
public static final short MODEL_G505 = 0; //F9P
|
public static final short MODEL_G505 = 0; //F9P
|
||||||
public static final short MODEL_G510 = 1; //博通
|
public static final short MODEL_G510 = 1; //博通
|
||||||
@ -28,6 +33,9 @@ public class GnssDevice {
|
|||||||
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式( 仅D3F0和D3F2 )
|
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式( 仅D3F0和D3F2 )
|
||||||
public static final short LOGGING_MODE_FULL = 1; // 完整模式
|
public static final short LOGGING_MODE_FULL = 1; // 完整模式
|
||||||
|
|
||||||
|
public static final int PARA_MASK_HAS_BATTERY = 0x01;
|
||||||
|
public static final int PARA_MASK_VOLTAGE_FACTOR = 0x02;
|
||||||
|
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
private Integer tenantid;
|
private Integer tenantid;
|
||||||
@ -48,6 +56,8 @@ public class GnssDevice {
|
|||||||
private Integer calc_group_id = 1;
|
private Integer calc_group_id = 1;
|
||||||
private String fwd_group_id;
|
private String fwd_group_id;
|
||||||
private String fwd_group_id2;
|
private String fwd_group_id2;
|
||||||
|
private Boolean has_battery;
|
||||||
|
private Byte voltage_factor; //电压测量分压比
|
||||||
private Boolean syn; //组参数是否同步
|
private Boolean syn; //组参数是否同步
|
||||||
private String pictures;
|
private String pictures;
|
||||||
private Double ipose; //初始位置
|
private Double ipose; //初始位置
|
||||||
@ -68,4 +78,44 @@ public class GnssDevice {
|
|||||||
private String remark;
|
private String remark;
|
||||||
private String iccid;
|
private String iccid;
|
||||||
|
|
||||||
|
// 参数改变
|
||||||
|
private Integer change_flag = 0;
|
||||||
|
|
||||||
|
private String getBatteryConfigCmd(){
|
||||||
|
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
|
||||||
|
"55"+(has_battery?"01":"00");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getVoltageFactorConfigCmd(){
|
||||||
|
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
|
||||||
|
"52"+HexUtil.Byte2HexString(voltage_factor);
|
||||||
|
}
|
||||||
|
public List<String> getConfigCmd(){
|
||||||
|
List<String> cmdList = new ArrayList<>();
|
||||||
|
if((change_flag&PARA_MASK_HAS_BATTERY) !=0 ){
|
||||||
|
cmdList.add(getBatteryConfigCmd());
|
||||||
|
}
|
||||||
|
if((change_flag&PARA_MASK_VOLTAGE_FACTOR) !=0 ){
|
||||||
|
cmdList.add(getVoltageFactorConfigCmd());
|
||||||
|
}
|
||||||
|
return cmdList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean clearChangeFlag(String ack){
|
||||||
|
boolean result = false;
|
||||||
|
if(change_flag!=0) {
|
||||||
|
byte[] ackBytes = ByteUtil.hexStringTobyte(ack);
|
||||||
|
if(ackBytes[ackBytes.length-1] == 1) {
|
||||||
|
if (ackBytes[8] == 0x55) {
|
||||||
|
change_flag &= ~PARA_MASK_HAS_BATTERY;
|
||||||
|
result = true;
|
||||||
|
} else if (ackBytes[8] == 0x52) {
|
||||||
|
change_flag &= ~PARA_MASK_VOLTAGE_FACTOR;
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ public class GnssGroup implements Serializable {
|
|||||||
Short gnss_sample_s;
|
Short gnss_sample_s;
|
||||||
|
|
||||||
public String getConfigCmd(GnssDevice device){
|
public String getConfigCmd(GnssDevice device){
|
||||||
String cmd = "D3110009";
|
String cmd = "D311000a";
|
||||||
cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid()))
|
cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid()))
|
||||||
+HexUtil.Byte2HexString((byte) work_cycle.intValue())
|
+HexUtil.Byte2HexString((byte) work_cycle.intValue())
|
||||||
+HexUtil.Byte2HexString((byte) active_time.intValue())
|
+HexUtil.Byte2HexString((byte) active_time.intValue())
|
||||||
|
|||||||
@ -12,10 +12,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class APIController extends BasicController {
|
public class APIController extends BasicController {
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
|
||||||
|
final String verServerConnectCmd = "AT+QIOPEN=1,2,\"TCP\",\"iot.im-droid.com\",7665,0,1";
|
||||||
@Autowired
|
@Autowired
|
||||||
RtcmClient rtcmClient;
|
RtcmClient rtcmClient;
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -46,10 +48,15 @@ public class APIController extends BasicController{
|
|||||||
device.setSyn(true);
|
device.setSyn(true);
|
||||||
deviceMapper.updateById(device);
|
deviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
} else if (msgType == 0xd312) {
|
||||||
else if(msgType == 0xd312){
|
|
||||||
// 工作周期一致性检查
|
// 工作周期一致性检查
|
||||||
checkWorkCycle(device, configAck);
|
checkWorkCycle(device, configAck);
|
||||||
|
} else if (msgType == 0xd313) {
|
||||||
|
if(device.getChange_flag()!=0) {
|
||||||
|
if(device.clearChangeFlag(configAck)){
|
||||||
|
deviceMapper.updateById(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存, debug 01 02上来的原始数据不保存
|
// 保存, debug 01 02上来的原始数据不保存
|
||||||
@ -67,17 +74,15 @@ public class APIController extends BasicController{
|
|||||||
|
|
||||||
// 检查是否需要更新 ICCID
|
// 检查是否需要更新 ICCID
|
||||||
updateICCID(device, dtuAck);
|
updateICCID(device, dtuAck);
|
||||||
}
|
} else if (msgType == 0xd313 && configAck.length() >= 90) {
|
||||||
else if(msgType == 0xd313&&configAck.length()>=90){
|
|
||||||
//转成字符串
|
//转成字符串
|
||||||
String dtuAck = configAck.substring(26 * 2);
|
String dtuAck = configAck.substring(26 * 2);
|
||||||
rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")";
|
rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")";
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
rxInfo += configAck;
|
rxInfo += configAck;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebSocketServer.sendMessageToAll(rxInfo);
|
CmdLineWebSocketServer.sendMessageToAll(rxInfo);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +179,13 @@ public class APIController extends BasicController{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
List<String> deviceCmds = device.getConfigCmd();
|
||||||
|
for(String deviceCmd:deviceCmds){
|
||||||
|
rtcmClient.config(deviceId, deviceCmd);
|
||||||
|
// 保存
|
||||||
|
saveMsg(deviceId, tenantId, 0xd312, deviceCmd, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// 检查有没有待发送的指令
|
// 检查有没有待发送的指令
|
||||||
QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>();
|
||||||
@ -255,6 +267,7 @@ public class APIController extends BasicController{
|
|||||||
rtcmClient.config(device.getDeviceid(), sendCmd);
|
rtcmClient.config(device.getDeviceid(), sendCmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateICCID(GnssDevice device, String dtuAck) {
|
void updateICCID(GnssDevice device, String dtuAck) {
|
||||||
// 只检查 "ICCID:" 的十六进制部分
|
// 只检查 "ICCID:" 的十六进制部分
|
||||||
if (!dtuAck.contains("49434349443a")) {
|
if (!dtuAck.contains("49434349443a")) {
|
||||||
@ -269,4 +282,39 @@ public class APIController extends BasicController{
|
|||||||
deviceMapper.updateById(device);
|
deviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/****** upgrade operations *******/
|
||||||
|
@PostMapping(value = "/api/upgrade_ack")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradeAck() {
|
||||||
|
//发连接服务器指令
|
||||||
|
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("opmode", GnssDevice.OP_MODE_UPGRADING);
|
||||||
|
List<GnssDevice> gnssDeviceList = deviceMapper.selectList(queryWrapper);
|
||||||
|
for (GnssDevice device : gnssDeviceList) {
|
||||||
|
short len = (short) (verServerConnectCmd.length() + 5);
|
||||||
|
String sendCmd = "d31a" + HexUtil.Short2HexString(len) +
|
||||||
|
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) +
|
||||||
|
"01" + HexUtil.String2HexString(verServerConnectCmd);
|
||||||
|
rtcmClient.config(device.getDeviceid(), sendCmd);
|
||||||
|
}
|
||||||
|
//界面的升级按钮变灰
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade ack");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/upgrading_ind")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradingInd(String deviceId, Byte progress) {
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade ind " + deviceId + "," + progress);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/api/upgrade_complete_ind")
|
||||||
|
@ResponseBody
|
||||||
|
public String onUpgradeCompleteInd(String deviceId, Byte code) {
|
||||||
|
UpgradeWebSocketServer.sendMessageToAll("on upgrade complete ind " + deviceId + "," + code);
|
||||||
|
// 保存升级记录
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
//单例
|
//单例
|
||||||
|
|
||||||
@ServerEndpoint(value = "/websocket",configurator = WebSocketConfig.class)
|
@ServerEndpoint(value = "/websocket/cmdline",configurator = WebSocketConfig.class)
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class WebSocketServer {
|
public class CmdLineWebSocketServer {
|
||||||
// thread safety counter
|
// thread safety counter
|
||||||
// thread safety set to hold websocket objects
|
// thread safety set to hold websocket objects
|
||||||
private static Map<String,Session> webSocketSet = new ConcurrentHashMap<>();
|
private static final Map<String,Session> webSocketSet = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@OnOpen
|
@OnOpen
|
||||||
public void onOpen(Session session){
|
public void onOpen(Session session){
|
||||||
@ -175,6 +175,17 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if (opmode != null && opmode != QUERY_ALL) {
|
if (opmode != null && opmode != QUERY_ALL) {
|
||||||
queryWrapper.eq("opmode", opmode);
|
queryWrapper.eq("opmode", opmode);
|
||||||
}
|
}
|
||||||
|
Integer hasBattery = search.getInteger("has_battery");
|
||||||
|
if(hasBattery != null && hasBattery != QUERY_ALL){
|
||||||
|
if(hasBattery == 1) {
|
||||||
|
queryWrapper.and(wrapper -> wrapper.eq("has_battery", 1)
|
||||||
|
.or().likeRight("deviceid","6")
|
||||||
|
.or().likeRight("deviceid","7"));
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
queryWrapper.eq("has_battery", 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
//推送状
|
//推送状
|
||||||
Integer fwd = search.getInteger("fwd_group_id");
|
Integer fwd = search.getInteger("fwd_group_id");
|
||||||
if (fwd != null && fwd != QUERY_ALL) {
|
if (fwd != null && fwd != QUERY_ALL) {
|
||||||
@ -240,6 +251,12 @@ public class GnssDeviceController extends BasicController{
|
|||||||
if(!old_device.getGroup_id().equals(device.getGroup_id())){
|
if(!old_device.getGroup_id().equals(device.getGroup_id())){
|
||||||
device.setSyn(false);
|
device.setSyn(false);
|
||||||
}
|
}
|
||||||
|
if(diff.contains("has_battery")){
|
||||||
|
device.setChange_flag(GnssDevice.PARA_MASK_HAS_BATTERY);
|
||||||
|
}
|
||||||
|
if(diff.contains("voltage_factor")){
|
||||||
|
device.setChange_flag(GnssDevice.PARA_MASK_VOLTAGE_FACTOR);
|
||||||
|
}
|
||||||
num = gnssDeviceMapper.updateById(device);
|
num = gnssDeviceMapper.updateById(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
|
import com.imdroid.beidou.config.WebSocketConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.websocket.*;
|
||||||
|
import javax.websocket.server.ServerEndpoint;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
//单例
|
||||||
|
|
||||||
|
@ServerEndpoint(value = "/websocket/upgrade",configurator = WebSocketConfig.class)
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class UpgradeWebSocketServer{
|
||||||
|
// thread safety counter
|
||||||
|
// thread safety set to hold websocket objects
|
||||||
|
private static final Map<String, Session> webSocketSet = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@OnOpen
|
||||||
|
public void onOpen(Session session){
|
||||||
|
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
|
||||||
|
webSocketSet.put(httpSession.getId(), session);
|
||||||
|
log.info("new websocket opened, total socket num: "+ webSocketSet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClose
|
||||||
|
public void onClose(Session session){
|
||||||
|
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
|
||||||
|
if(httpSession!=null) {
|
||||||
|
webSocketSet.remove(httpSession.getId());
|
||||||
|
}
|
||||||
|
log.info("websocket closed, total socket num: "+ webSocketSet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnMessage
|
||||||
|
public void onMessage(String msg, Session session){
|
||||||
|
//log.info("websocket: "+msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnError
|
||||||
|
public void onError(Session session, Throwable error){
|
||||||
|
error.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessageToAll(String msg){
|
||||||
|
try {
|
||||||
|
for (Session item : webSocketSet.values()) {
|
||||||
|
item.getBasicRemote().sendText(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
log.error("websocket send msg error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMessage(HttpSession httpSession, String msg) {
|
||||||
|
Session session = webSocketSet.get(httpSession.getId());
|
||||||
|
if(session != null){
|
||||||
|
try {
|
||||||
|
session.getBasicRemote().sendText(msg);
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
log.error("websocket send msg error:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
package com.imdroid.beidou.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.imdroid.beidou.common.HttpResult;
|
||||||
|
import com.imdroid.secapi.client.HttpResp;
|
||||||
|
import com.imdroid.secapi.client.RtcmClient;
|
||||||
|
import com.imdroid.secapi.client.VersionClient;
|
||||||
|
import com.imdroid.secapi.dto.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class VersionController extends BasicController{
|
||||||
|
@Value("${version.path}")
|
||||||
|
private String version_path;
|
||||||
|
@Autowired
|
||||||
|
GnssDeviceMapper gnssDeviceMapper;
|
||||||
|
@Autowired
|
||||||
|
private GnssStatusMapper gnssStatusMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
RtcmClient rtcmClient;
|
||||||
|
@Autowired
|
||||||
|
OpLogManager opLogManager;
|
||||||
|
@Autowired
|
||||||
|
TenantMapper tenantMapper;
|
||||||
|
@Autowired
|
||||||
|
VersionClient versionClient;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class FirmwareInfo{
|
||||||
|
String name;
|
||||||
|
long length;
|
||||||
|
long create_time;
|
||||||
|
|
||||||
|
public FirmwareInfo(String n, long l, long t){
|
||||||
|
name = n;
|
||||||
|
length = l;
|
||||||
|
create_time = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正在升级的设备map,deviceId <-> UpgradeInfo(高字节:升级结果;低字节:进度)
|
||||||
|
final ConcurrentHashMap<String, Short> upgradingDeviceList = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**** 推送页面 *****/
|
||||||
|
@RequestMapping("/sys/ver_mgr")
|
||||||
|
public String gnssDevCfg(Model m, HttpSession session) {
|
||||||
|
initModel(m, session);
|
||||||
|
List<Tenant> tenants = tenantMapper.selectList(null);
|
||||||
|
m.addAttribute("tenant_list", tenants);
|
||||||
|
|
||||||
|
return "/page/gnss_ver_mgr";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** 推送数据 *****/
|
||||||
|
@RequestMapping("/sys/ver_mgr/firmware_list")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject listFirmware(int page, int limit) {
|
||||||
|
List<FirmwareInfo> firmwareInfoList = new ArrayList();
|
||||||
|
int total = getFirmwareList(page, limit,firmwareInfoList);
|
||||||
|
//用PageInfo对结果进行包装,传入连续显示的页数
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("code", 0);
|
||||||
|
jsonObject.put("msg", "");
|
||||||
|
jsonObject.put("count", total);
|
||||||
|
jsonObject.put("data", firmwareInfoList);
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/sys/ver_mgr/gnss_device_list")
|
||||||
|
@ResponseBody
|
||||||
|
public JSONObject list(HttpSession session, int page, int limit, String searchParams) {
|
||||||
|
Page<GnssDevice> pageable = new Page<>(page, limit);
|
||||||
|
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.orderByAsc("deviceid");
|
||||||
|
|
||||||
|
// 条件查询
|
||||||
|
if(searchParams != null) {
|
||||||
|
JSONObject search = (JSONObject) JSONObject.parse(searchParams);
|
||||||
|
//设备号
|
||||||
|
String deviceid = search.getString("deviceid");
|
||||||
|
if (StringUtils.hasText(deviceid)) {
|
||||||
|
queryWrapper.like("deviceid", deviceid);
|
||||||
|
}
|
||||||
|
//项目号
|
||||||
|
String project_id = search.getString("project_id");
|
||||||
|
if (StringUtils.hasText(project_id)) {
|
||||||
|
queryWrapper.like("project_id", project_id);
|
||||||
|
}
|
||||||
|
//所属组织
|
||||||
|
String tenantname = search.getString("tenantname");
|
||||||
|
if (StringUtils.hasText(tenantname)) {
|
||||||
|
if(tenantname.startsWith("非SAAS"))
|
||||||
|
queryWrapper.ne("tenantname",Tenant.SAAS_PROVIDER_NAME);
|
||||||
|
else queryWrapper.like("tenantname", tenantname);
|
||||||
|
}
|
||||||
|
//桩号
|
||||||
|
String sector = search.getString("sector");
|
||||||
|
if (StringUtils.hasText(sector)) {
|
||||||
|
queryWrapper.like("sector", sector);
|
||||||
|
}
|
||||||
|
//设备类型
|
||||||
|
Integer devicetype = search.getInteger("devicetype");
|
||||||
|
if (devicetype != null && devicetype != QUERY_ALL) {
|
||||||
|
queryWrapper.eq("devicetype", devicetype);
|
||||||
|
}
|
||||||
|
//版本
|
||||||
|
String appver = search.getString("appver");
|
||||||
|
if (StringUtils.hasText(appver)) {
|
||||||
|
queryWrapper.like("appver", appver);
|
||||||
|
}
|
||||||
|
//备注
|
||||||
|
String remark = search.getString("remark");
|
||||||
|
if (StringUtils.hasText(remark)) {
|
||||||
|
queryWrapper.like("remark", remark);
|
||||||
|
}
|
||||||
|
//使用状态
|
||||||
|
Integer opmode = search.getInteger("opmode");
|
||||||
|
if (opmode != null && opmode != QUERY_ALL) {
|
||||||
|
queryWrapper.eq("opmode", opmode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPage<GnssDevice> cs = gnssDeviceMapper.selectPage(pageable, queryWrapper);
|
||||||
|
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("code", 0);
|
||||||
|
jsonObject.put("msg", "");
|
||||||
|
jsonObject.put("count", cs.getTotal());
|
||||||
|
jsonObject.put("data", cs.getRecords());
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/sys/ver_mgr/upgrade")
|
||||||
|
@ResponseBody
|
||||||
|
public HttpResult upgradeApp(@RequestParam String firmware, @RequestParam String id_list) throws Exception {
|
||||||
|
|
||||||
|
if(firmware!=null && !firmware.equals("undefined")) {
|
||||||
|
//请求版本服务升级
|
||||||
|
try {
|
||||||
|
HttpResp<HashMap<String, Object>> rsp = versionClient.upgrade(id_list, firmware);
|
||||||
|
System.out.println(id_list);
|
||||||
|
}
|
||||||
|
catch (Exception e){
|
||||||
|
return HttpResult.fail("版本服务未启动");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HttpResult.success("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
int getFirmwareList(int page, int limit, List firmwareInfo){
|
||||||
|
File folder = new File(version_path);
|
||||||
|
File[] files = folder.listFiles((file) -> file.getName().endsWith(".bin"));
|
||||||
|
if(files==null) return 0;
|
||||||
|
|
||||||
|
Arrays.sort(files, new Comparator<File>() {
|
||||||
|
public int compare(File f1, File f2) {
|
||||||
|
long diff = f1.lastModified() - f2.lastModified();
|
||||||
|
if (diff > 0)
|
||||||
|
return -1;
|
||||||
|
else if (diff == 0)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 1;//如果 if 中修改为 返回-1 同时此处修改为返回 1 排序就会是递减
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for(File file:files){
|
||||||
|
if((int)count/limit+1 == page) {
|
||||||
|
firmwareInfo.add(new FirmwareInfo(file.getName(), file.length(), file.lastModified()));
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -199,8 +199,12 @@ public interface CommonExcelService<T, R> {
|
|||||||
* @param paraValue 查询参数值
|
* @param paraValue 查询参数值
|
||||||
*/
|
*/
|
||||||
default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
|
// null
|
||||||
|
if (paraName.startsWith("NULL") && StringUtils.hasText((String)paraValue)) {
|
||||||
|
addNullQueryWrapper(queryWrapper, paraName, paraValue);
|
||||||
|
}
|
||||||
// String
|
// String
|
||||||
if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) {
|
else if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) {
|
||||||
addStringQueryWrapper(queryWrapper, paraName, paraValue);
|
addStringQueryWrapper(queryWrapper, paraName, paraValue);
|
||||||
}
|
}
|
||||||
// Number,-1表示全部
|
// Number,-1表示全部
|
||||||
@ -221,6 +225,17 @@ public interface CommonExcelService<T, R> {
|
|||||||
return paraName.substring(paraName.indexOf("_") + 1);
|
return paraName.substring(paraName.indexOf("_") + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void addNullQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
|
String column = getColumn(paraName);
|
||||||
|
String value = paraValue.toString();
|
||||||
|
if (value.equals("0")) {
|
||||||
|
queryWrapper.isNull(column);
|
||||||
|
}
|
||||||
|
else if(value.equals("1")){
|
||||||
|
queryWrapper.isNotNull(column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
|
||||||
String column = getColumn(paraName);
|
String column = getColumn(paraName);
|
||||||
String value = paraValue.toString();
|
String value = paraValue.toString();
|
||||||
|
|||||||
@ -69,6 +69,9 @@ CREATE TABLE IF NOT EXISTS `gnssdevices` (
|
|||||||
`model` smallint DEFAULT 0,
|
`model` smallint DEFAULT 0,
|
||||||
`loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式',
|
`loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式',
|
||||||
`iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号,唯一',
|
`iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号,唯一',
|
||||||
|
`has_battery` bit(1) DEFAULT 0 COMMENT '是否内置电池',
|
||||||
|
`change_flag` int DEFAULT 0 COMMENT '参数改变标识',
|
||||||
|
`voltage_factor` tinyint DEFAULT NULL COMMENT '分压比',
|
||||||
PRIMARY KEY (`id`)
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
{
|
{
|
||||||
"title": "健康检查报告",
|
"title": "健康检查报告",
|
||||||
"href": "page/gnss_ehm",
|
"href": "page/gnss_ehm",
|
||||||
"icon": "fa fa-tachometer",
|
"icon": "fa fa-heartbeat",
|
||||||
"target": "_self"
|
"target": "_self"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -200,6 +200,12 @@
|
|||||||
"href": "sys/apikey",
|
"href": "sys/apikey",
|
||||||
"icon": "fa fa-key",
|
"icon": "fa fa-key",
|
||||||
"target": "_self"
|
"target": "_self"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "版本管理",
|
||||||
|
"href": "sys/ver_mgr",
|
||||||
|
"icon": "fa fa-file-code-o",
|
||||||
|
"target": "_self"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -219,7 +219,7 @@
|
|||||||
|
|
||||||
//建立webSocket连接
|
//建立webSocket连接
|
||||||
var webSocktPath;
|
var webSocktPath;
|
||||||
webSocktPath = (basePath+"/websocket").replace("http","ws");
|
webSocktPath = (basePath+"/websocket/cmdline").replace("http","ws");
|
||||||
websocket = new WebSocket(webSocktPath);
|
websocket = new WebSocket(webSocktPath);
|
||||||
|
|
||||||
//打开webSokcet连接时,回调该函数
|
//打开webSokcet连接时,回调该函数
|
||||||
|
|||||||
@ -90,6 +90,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">内置电池</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="has_battery" id="has_battery" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">无</option>
|
||||||
|
<option value="1">是</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -135,6 +145,7 @@
|
|||||||
{field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'},
|
{field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'},
|
||||||
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
||||||
{field: 'appver', title: '固件版本', width: 80},
|
{field: 'appver', title: '固件版本', width: 80},
|
||||||
|
{field: 'voltage_factor', title: '分压系数', width: 60},
|
||||||
{field: 'imei', title: 'IMEI', width: 100},
|
{field: 'imei', title: 'IMEI', width: 100},
|
||||||
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
|
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -24,6 +24,16 @@
|
|||||||
<input type="text" name="s_deviceid" autocomplete="off" class="layui-input">
|
<input type="text" name="s_deviceid" autocomplete="off" class="layui-input">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">内置电池</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="NULL_solarvoltage" id="NULL_solarvoltage" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">无</option>
|
||||||
|
<option value="1">是</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label">范围</label>
|
<label class="layui-form-label">范围</label>
|
||||||
<div class="layui-input-inline">
|
<div class="layui-input-inline">
|
||||||
|
|||||||
237
sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html
Normal file
237
sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>设备参数</title>
|
||||||
|
<meta name="renderer" content="webkit">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
|
||||||
|
<link rel="stylesheet" href="../css/public.css" media="all">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="layuimini-container">
|
||||||
|
<div class="layuimini-main">
|
||||||
|
<div class="layui-card layui-panel">
|
||||||
|
<div class="layui-card-header">
|
||||||
|
<h3>请选择要升级的固件</h3>
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<table class="layui-hide" id="firmware-table" lay-filter="firmware-table"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div class="layui-card layui-panel">
|
||||||
|
<div class="layui-card-header" name="app-title">
|
||||||
|
<h3>终端固件信息</h3>
|
||||||
|
</div>
|
||||||
|
<div class="layui-card-body">
|
||||||
|
<fieldset class="table-search-fieldset">
|
||||||
|
<legend>搜索信息</legend>
|
||||||
|
<div style="margin: 10px 10px 10px 10px">
|
||||||
|
<form class="layui-form layui-form-pane" action="">
|
||||||
|
<div class="layui-form-item">
|
||||||
|
<div class="layui-inline" th:if="${tenant_id==0}">
|
||||||
|
<label class="layui-form-label">所属部门</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="tenantname" id="tenantname" lay-search="">
|
||||||
|
<option value="">全部</option>
|
||||||
|
<option value="非SAAS服务商">非SAAS服务商</option>
|
||||||
|
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.name}"></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">设备号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="deviceid" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">项目号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="project_id" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">桩号</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="sector" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">固件版本</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="appver" autocomplete="off" class="layui-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<label class="layui-form-label">使用状态</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<select name="opmode" id="opmode" lay-verify="required" lay-search="">
|
||||||
|
<option value="-1">全部</option>
|
||||||
|
<option value="0">正常</option>
|
||||||
|
<option value="1">维护</option>
|
||||||
|
<option value="2">停用</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<table class="layui-hide" id="app-table" lay-filter="app-table"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<script type="text/html" id="app-toolbar">
|
||||||
|
<div class="layui-btn-container">
|
||||||
|
<button class="layui-btn layui-btn-sm" lay-event="upgrade" id="btn_upgrade">升级</button>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
layui.use(['form', 'table'], function () {
|
||||||
|
var $ = layui.$,
|
||||||
|
form = layui.form,
|
||||||
|
table = layui.table;
|
||||||
|
var cfg_cols = [
|
||||||
|
{type: 'checkbox', fixed: 'left'},
|
||||||
|
{field: 'deviceid', title: '设备号', width: 100, sort: true},
|
||||||
|
{field: 'remark', title: '备注', width: 100},
|
||||||
|
{field: 'project_id', title: '项目号', width: 120, sort: true},
|
||||||
|
{field: 'sector', title: '桩号', width: 120, sort: true},
|
||||||
|
{field: 'name', title: '监测点号', width: 80},
|
||||||
|
{field: 'devicetype', title: '类型', width: 80,templet: "<div>{{d.devicetype==0?'监测站':'基准站'}}</div>"},
|
||||||
|
{field: 'parentid', title: '基站编号', width: 80, sort: true},
|
||||||
|
{field: 'tenantname', title: '所属组织', width: 120},
|
||||||
|
{field: 'opmode', title: '使用状态', width: 80,templet: '#modeTrans'},
|
||||||
|
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
|
||||||
|
{field: 'appver', title: '固件版本', width: 80}
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 初始化表单,要加上,不然刷新部分组件可能会不加载
|
||||||
|
*/
|
||||||
|
form.render();
|
||||||
|
|
||||||
|
table.render({
|
||||||
|
elem: '#app-table',
|
||||||
|
url: '/sys/ver_mgr/gnss_device_list',
|
||||||
|
toolbar: '#app-toolbar',
|
||||||
|
defaultToolbar: ['filter','exports'],
|
||||||
|
cols: [
|
||||||
|
cfg_cols
|
||||||
|
],
|
||||||
|
limits: [10, 20, 50],
|
||||||
|
limit: 10,
|
||||||
|
page: true,
|
||||||
|
skin: 'line'
|
||||||
|
});
|
||||||
|
|
||||||
|
table.render({
|
||||||
|
elem: '#firmware-table'
|
||||||
|
,url:'/sys/ver_mgr/firmware_list'
|
||||||
|
,cols: [
|
||||||
|
[
|
||||||
|
{type: 'radio', fixed: 'left'}
|
||||||
|
,{field:'name', title:'固件', sort: true}
|
||||||
|
,{field:'length', title:'长度(字节)'}
|
||||||
|
,{field:'create_time', title:'时间', sort: true, templet: "<div>{{layui.util.toDateString(d.create_time, 'yyyy-MM-dd HH:mm:ss')}}</div>"}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
,page: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听搜索操作
|
||||||
|
form.on('submit(data-search-btn)', function (data) {
|
||||||
|
var result = JSON.stringify(data.field);
|
||||||
|
|
||||||
|
//执行搜索重载
|
||||||
|
table.reload('app-table', {
|
||||||
|
page: {
|
||||||
|
curr: 1
|
||||||
|
}
|
||||||
|
, where: {
|
||||||
|
searchParams: result
|
||||||
|
}
|
||||||
|
}, 'data');
|
||||||
|
table.render();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toolbar事件监听
|
||||||
|
*/
|
||||||
|
//头工具栏事件
|
||||||
|
table.on('toolbar(app-table)', function(obj){
|
||||||
|
let $ = layui.$;
|
||||||
|
switch(obj.event){
|
||||||
|
case 'upgrade':
|
||||||
|
var optionStatus = table.checkStatus('firmware-table')//获取选中行状态
|
||||||
|
var fdata = optionStatus.data;
|
||||||
|
var checkStatus = table.checkStatus('app-table')//获取选中行状态
|
||||||
|
var adata = checkStatus.data;
|
||||||
|
if(fdata.length>0 && adata.length>0){
|
||||||
|
var firmware = fdata[0]["name"];
|
||||||
|
var device_list = "";
|
||||||
|
for(var i=0;i<adata.length; i++){
|
||||||
|
device_list = device_list+adata[i]["deviceid"]+";";
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
type:"POST",
|
||||||
|
url:"/sys/ver_mgr/upgrade",
|
||||||
|
data: {
|
||||||
|
'firmware': firmware,
|
||||||
|
'id_list': device_list
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
if(result.code == 0) layer.msg('升级进行中...');
|
||||||
|
else layer.alert(result.msg);
|
||||||
|
},
|
||||||
|
error: function () {
|
||||||
|
console.log("ajax error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function onDeviceCfgUpdated(){
|
||||||
|
layui.table.reload('currentTableId');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="modeTrans">
|
||||||
|
{{# if(d.opmode == 0){ }}
|
||||||
|
<span class="layui-badge layui-bg-green">正常</span>
|
||||||
|
{{# } else if(d.opmode == 1){ }}
|
||||||
|
<span class="layui-badge layui-bg-blue">维护</span>
|
||||||
|
{{# } else { }}
|
||||||
|
<span class="layui-badge layui-bg-gray">停用</span>
|
||||||
|
{{# } }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/html" id="synTrans">
|
||||||
|
{{# if(d.syn == 0){ }}
|
||||||
|
<span class="layui-badge layui-bg-orange">否</span>
|
||||||
|
{{# } else { }}
|
||||||
|
<span class="layui-badge layui-bg-green">是</span>
|
||||||
|
{{# } }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
<label class="layui-form-label required">设备编号</label>
|
<label class="layui-form-label required">设备编号</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-block">
|
||||||
<input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input">
|
<input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input" onblur="checkDeviceId()">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -57,14 +57,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-inline" pane="">
|
<div class="layui-inline" pane="">
|
||||||
<label class="layui-form-label">星座</label>
|
<label class="layui-form-label">内置电池</label>
|
||||||
<div class="layui-input-block">
|
<div class="layui-input-inline">
|
||||||
<input type="checkbox" name="like1[write]" lay-skin="primary" title="北斗" checked="" disabled="">
|
<select name="has_battery" id="has_battery" th:field="*{has_battery}" lay-verify="required" lay-search="">
|
||||||
<input type="checkbox" name="like1[read]" lay-skin="primary" title="GPS" disabled="">
|
<option value="0">否</option>
|
||||||
<input type="checkbox" name="like1[game]" lay-skin="primary" title="GLONASS" disabled="">
|
<option value="1">是</option>
|
||||||
<input type="checkbox" name="like1[game]" lay-skin="primary" title="Galileo" disabled="">
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="layui-inline" pane="">
|
||||||
|
<label class="layui-form-label">电压系数</label>
|
||||||
|
<div class="layui-input-inline">
|
||||||
|
<input type="text" name="voltage_factor" id="voltage_factor" th:field="*{voltage_factor}" class="layui-input">
|
||||||
|
</div>
|
||||||
|
<div class="layui-form-mid layui-word-aux">*0.1</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layui-form-item">
|
<div class="layui-form-item">
|
||||||
<div class="layui-inline">
|
<div class="layui-inline">
|
||||||
@ -288,6 +295,7 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function setEcefEditor(){
|
function setEcefEditor(){
|
||||||
var $ = layui.$;
|
var $ = layui.$;
|
||||||
console.log($('#devicetype').val(), $('#model').val());
|
console.log($('#devicetype').val(), $('#model').val());
|
||||||
@ -299,6 +307,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkDeviceId(){
|
||||||
|
var $ = layui.$;
|
||||||
|
var value = $("#deviceid").val();
|
||||||
|
console.log(value);
|
||||||
|
if(value.startsWith("6") || value.startsWith("7")){
|
||||||
|
$("#has_battery").val('1');
|
||||||
|
$("#has_battery").attr('disabled',true);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$("#has_battery").attr('disabled',false);
|
||||||
|
}
|
||||||
|
layui.form.render();
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -56,6 +56,12 @@
|
|||||||
<version>5.5.8</version>
|
<version>5.5.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.78.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
104
sec-vermgr/pom.xml
Normal file
104
sec-vermgr/pom.xml
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>security-monitor</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>sec-vermgr</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.78.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>sec-api</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.imdroid</groupId>
|
||||||
|
<artifactId>sec-common</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<!-- 矩阵工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ejml</groupId>
|
||||||
|
<artifactId>ejml-all</artifactId>
|
||||||
|
<version>0.41</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<name>ali-mirror</name>
|
||||||
|
<url>https://maven.aliyun.com/repository/central</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
package com.imdroid.vermgr.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
|
||||||
|
//member name must be same as the field name in mysql
|
||||||
|
@Data
|
||||||
|
public class DeviceApp {
|
||||||
|
//0:未连接;1:连接;2:升级中;3:传输完成;4:升级失败
|
||||||
|
public static final short STATE_DISCONNECTED = 0;
|
||||||
|
public static final short STATE_CONNECTED = 1;
|
||||||
|
public static final short STATE_UPGRADING = 2;
|
||||||
|
public static final short STATE_TX_COMPLETED = 3;
|
||||||
|
public static final short STATE_TX_FAILED = 4;
|
||||||
|
public static final String[] state_description = {
|
||||||
|
"未连接","连接","升级中","发送完成","发送失败"
|
||||||
|
};
|
||||||
|
public static final String[] result_description = {
|
||||||
|
"成功","其他错误","传输中断","超时","数据错误","超时","保存失败","文件错误"
|
||||||
|
};
|
||||||
|
|
||||||
|
long device_sn = 0;
|
||||||
|
String version;
|
||||||
|
String firmware;
|
||||||
|
short upgrade_state = STATE_DISCONNECTED;
|
||||||
|
short upgrade_result; //失败原因
|
||||||
|
Timestamp upgrade_time;
|
||||||
|
String strState;
|
||||||
|
String strResult;
|
||||||
|
|
||||||
|
public void translateCode() {
|
||||||
|
if(upgrade_state>STATE_TX_FAILED) upgrade_state=STATE_DISCONNECTED;
|
||||||
|
strState = state_description[upgrade_state];
|
||||||
|
if(upgrade_result>7) upgrade_result=7;
|
||||||
|
strResult = result_description[upgrade_result];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,394 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import com.imdroid.vermgr.entity.DeviceApp;
|
||||||
|
import com.imdroid.vermgr.utils.CRC16;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Arrays;
|
||||||
|
@Slf4j
|
||||||
|
public class UpgradeSession {
|
||||||
|
public static final byte COM_OK = 0;
|
||||||
|
public static final byte COM_ERROR = 1;
|
||||||
|
public static final byte COM_ABORT = 2;
|
||||||
|
public static final byte COM_TIMEOUT = 3;
|
||||||
|
public static final byte COM_DATA = 4;
|
||||||
|
public static final byte COM_LIMIT = 5;
|
||||||
|
public static final byte COM_SAVE_ERR = 6;
|
||||||
|
public static final byte COM_FILE_ERR = 7;
|
||||||
|
|
||||||
|
public static final byte SOH = 0x01; /* start of 128-byte data packet */
|
||||||
|
public static final byte STX = 0x02; /* start of 1024-byte data packet */
|
||||||
|
public static final byte EOT = 0x04; /* end of transmission */
|
||||||
|
public static final byte ACK = 0x06; /* acknowledge */
|
||||||
|
public static final byte NAK = 0x15; /* negative acknowledge */
|
||||||
|
public static final byte CA = 0x18; /* two of these in succession aborts transfer */
|
||||||
|
public static final byte CC = 0x43; /* 'C' == 0x43, request 16-bit CRC */
|
||||||
|
|
||||||
|
public static final short PACKET_HEADER_SIZE = 3;
|
||||||
|
public static final short PACKET_TRAILER_SIZE = 2;
|
||||||
|
public static final short PACKET_SIZE = 128;
|
||||||
|
public static final short PACKET_1K_SIZE = 1024;
|
||||||
|
|
||||||
|
static private String version_path="firmware";
|
||||||
|
|
||||||
|
private DeviceApp deviceApp;
|
||||||
|
File firmware;
|
||||||
|
FileInputStream reader;
|
||||||
|
short tx_seq = 0;
|
||||||
|
int tx_bytes = 0;
|
||||||
|
byte nak_num = 0;
|
||||||
|
|
||||||
|
byte[] header_buff;
|
||||||
|
byte[] tailer_buff;
|
||||||
|
byte[] soh_buff;
|
||||||
|
byte[] stx_buff;
|
||||||
|
boolean session_done = false;
|
||||||
|
boolean file_done = false;
|
||||||
|
boolean is_file_accepted = false;
|
||||||
|
byte result = COM_OK;
|
||||||
|
|
||||||
|
public UpgradeSession(DeviceApp app) {
|
||||||
|
deviceApp = app;
|
||||||
|
firmware = null;
|
||||||
|
reader = null;
|
||||||
|
header_buff = new byte[PACKET_HEADER_SIZE];
|
||||||
|
tailer_buff = new byte[PACKET_TRAILER_SIZE];
|
||||||
|
soh_buff = new byte[PACKET_SIZE];
|
||||||
|
stx_buff = new byte[PACKET_1K_SIZE];
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getResult(){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
public DeviceApp getDeviceApp(){return deviceApp;}
|
||||||
|
public long getSN(){return deviceApp.getDevice_sn();}
|
||||||
|
public int getState() {return deviceApp.getUpgrade_state();}
|
||||||
|
public int getTxPercentage() {
|
||||||
|
if(firmware == null) return 0;
|
||||||
|
return (int) (tx_bytes*100/firmware.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(){
|
||||||
|
session_done = true;
|
||||||
|
file_done = true;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
tx_bytes = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
log.error(firmware+" reader.close()",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean start(Channel channel) {
|
||||||
|
session_done = false;
|
||||||
|
file_done = false;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
tx_bytes = 0;
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
|
||||||
|
String file_name = deviceApp.getFirmware();
|
||||||
|
if(file_name != null){
|
||||||
|
firmware = new File(version_path+"/" + file_name);
|
||||||
|
if (firmware.exists()) {
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
reader = new FileInputStream(firmware);
|
||||||
|
result = COM_OK;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
log.error(firmware+" reader open",e);
|
||||||
|
reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(firmware+" reader open",e);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发升级指示
|
||||||
|
if(result != COM_OK){
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
long sn = deviceApp.getDevice_sn();
|
||||||
|
byte[] ind_msg = new byte[5];
|
||||||
|
ind_msg[0] = VerManager.UPGRADE_IND;
|
||||||
|
ind_msg[1] = (byte) (sn >> 24);
|
||||||
|
ind_msg[2] = (byte) (sn >> 16);
|
||||||
|
ind_msg[3] = (byte) (sn >> 8);
|
||||||
|
ind_msg[4] = (byte) sn;
|
||||||
|
byte tailer_buff[] = {0, 0};
|
||||||
|
int crc = CRC16.calculate(ind_msg);
|
||||||
|
tailer_buff[0] = (byte) (crc >> 8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(ind_msg);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send UPGRADE_IND");
|
||||||
|
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_UPGRADING);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean restart(Channel channel) {
|
||||||
|
session_done = false;
|
||||||
|
file_done = false;
|
||||||
|
is_file_accepted = false;
|
||||||
|
tx_seq = 0;
|
||||||
|
nak_num = 0;
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(reader!=null) reader.close();
|
||||||
|
reader = new FileInputStream(firmware);
|
||||||
|
result = COM_OK;
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException e) {
|
||||||
|
log.error(firmware+" reader open failed: ",e);
|
||||||
|
reset();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(firmware+" reader open failed: ",e);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (result == COM_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reject(){
|
||||||
|
result = COM_ERROR;
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTimeout(){
|
||||||
|
result = COM_TIMEOUT;
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean procYmodem(Channel channel, byte[] rx_data){
|
||||||
|
if(session_done) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (rx_data[0]) {
|
||||||
|
case CC:
|
||||||
|
log.info("C");
|
||||||
|
if(tx_seq!=0){
|
||||||
|
//这种情况说明上次接收端意外中断了,需要重新开始
|
||||||
|
restart(channel);
|
||||||
|
}
|
||||||
|
if (!is_file_accepted) {
|
||||||
|
sendFileInfo(channel);
|
||||||
|
} else {
|
||||||
|
tx_seq++;
|
||||||
|
sendPacket(channel);
|
||||||
|
//sendSmallPacket(channel);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ACK:
|
||||||
|
if(rx_data.length == 4)
|
||||||
|
log.info("ACK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
|
||||||
|
else log.info("ACK");
|
||||||
|
nak_num = 0;
|
||||||
|
if (!is_file_accepted) {
|
||||||
|
is_file_accepted = true;
|
||||||
|
}
|
||||||
|
else if(!file_done){
|
||||||
|
tx_seq++;
|
||||||
|
sendPacket(channel);
|
||||||
|
//sendSmallPacket(channel);
|
||||||
|
}
|
||||||
|
if(file_done) {
|
||||||
|
is_file_accepted=false;
|
||||||
|
tx_seq=0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NAK:
|
||||||
|
if(rx_data.length == 4)
|
||||||
|
log.info("NAK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
|
||||||
|
else log.info("NAK");
|
||||||
|
nak_num++;
|
||||||
|
if(nak_num>=3){
|
||||||
|
log.info("too much NAK, cancel transmit");
|
||||||
|
result = COM_ERROR;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
else resendPacket(channel);
|
||||||
|
break;
|
||||||
|
case CA:
|
||||||
|
log.info("CA");
|
||||||
|
result = rx_data[1];
|
||||||
|
reset();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.info("unknown ind received: "+rx_data[0]+" "+rx_data[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e){
|
||||||
|
reset();
|
||||||
|
log.error(firmware+" reader error: ",e);
|
||||||
|
result = COM_FILE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result == COM_ERROR || result==COM_FILE_ERR){
|
||||||
|
header_buff[0] = CA;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(session_done){
|
||||||
|
if(result == COM_OK) {
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_COMPLETED);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
|
||||||
|
}
|
||||||
|
deviceApp.setUpgrade_result(result);
|
||||||
|
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
return session_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendFileInfo(Channel channel){
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
Arrays.fill(soh_buff, (byte) 0);
|
||||||
|
if(file_done) {
|
||||||
|
reset();//end of session
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
int pos = 0;
|
||||||
|
byte[] file_name = firmware.getName().getBytes();
|
||||||
|
for (int i = 0; i < file_name.length; i++) soh_buff[pos++] = file_name[i];
|
||||||
|
soh_buff[pos++] = 0;
|
||||||
|
String strFileSize = "" + firmware.length();
|
||||||
|
byte[] file_size = strFileSize.getBytes();
|
||||||
|
for (int i = 0; i < file_size.length; i++) soh_buff[pos++] = file_size[i];
|
||||||
|
soh_buff[pos++] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate(soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write(soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send file info");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendPacket(Channel channel) throws IOException {
|
||||||
|
if(tx_seq==0) return;
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_buff[0] = STX;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
int read_bytes = reader.read(stx_buff);
|
||||||
|
if(read_bytes != -1) tx_bytes+=read_bytes;
|
||||||
|
|
||||||
|
if(read_bytes == -1 || read_bytes==0){
|
||||||
|
// send EOT
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
file_done = true;
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(read_bytes>=PACKET_1K_SIZE){
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(read_bytes<PACKET_1K_SIZE && read_bytes>PACKET_SIZE){
|
||||||
|
for(int i=read_bytes; i<PACKET_1K_SIZE; i++) stx_buff[i]=0x1A;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
Arrays.fill(soh_buff, (byte) 0x1A);
|
||||||
|
for(int i=0; i<read_bytes; i++) soh_buff[i] = stx_buff[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate((header_buff[0] == STX)?stx_buff:soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write((header_buff[0] == STX)?stx_buff:soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
|
||||||
|
}
|
||||||
|
void sendSmallPacket(Channel channel) throws IOException {
|
||||||
|
if(tx_seq==0) return;
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
header_buff[0] = SOH;
|
||||||
|
header_buff[1] = (byte) tx_seq;
|
||||||
|
header_buff[2] = (byte) ~tx_seq;
|
||||||
|
|
||||||
|
int read_bytes = reader.read(soh_buff);
|
||||||
|
if(read_bytes != -1) tx_bytes+=read_bytes;
|
||||||
|
|
||||||
|
if(read_bytes == -1 || read_bytes==0){
|
||||||
|
// send EOT
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
file_done = true;
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(read_bytes>=PACKET_SIZE){
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
for(int i=read_bytes; i<PACKET_SIZE; i++) soh_buff[i]=0x1A;
|
||||||
|
}
|
||||||
|
|
||||||
|
int crc = CRC16.calculate(soh_buff);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write(soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resendPacket(Channel channel){
|
||||||
|
if(file_done){
|
||||||
|
header_buff[0] = EOT;
|
||||||
|
channel.writeAndFlush(header_buff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channel.write(header_buff);
|
||||||
|
channel.write((header_buff[0] == STX) ? stx_buff : soh_buff);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
}
|
||||||
|
log.info(deviceApp.getDevice_sn()+": resend packet "+header_buff[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,376 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import com.imdroid.vermgr.entity.DeviceApp;
|
||||||
|
import com.imdroid.vermgr.utils.CRC16;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/******************************************************
|
||||||
|
* 批量升级终端版本
|
||||||
|
* 单例,要求线程安全
|
||||||
|
* 流程:
|
||||||
|
* 1)收到待升级的设备列表UpgradeDeviceList。如果当前处于升级完毕状态,则更新待升级列表。回应答
|
||||||
|
* 2)TCP通道激活 ---> 获取版本信息 ---> 关联TCP通道和待升级的设备
|
||||||
|
* 3)启动升级会话 ---> 实时更新会话
|
||||||
|
* 数据结构:deviceId,cur_ver,upgrade_firmware,TCP_channel,upgrade_session
|
||||||
|
* ****************************************************/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class VerManager {
|
||||||
|
public static final byte GET_APP_INFO = (byte) 0xA0;
|
||||||
|
public static final byte APP_INFO_IND = (byte) 0xA1;
|
||||||
|
public static final byte UPGRADE_IND = (byte) 0xA2;
|
||||||
|
public static final byte NOT_UPGRADE_IND = (byte) 0xA3;
|
||||||
|
public static final byte UPGRADE_ACK = (byte) 0xA4;
|
||||||
|
|
||||||
|
public static final int NOT_APP_INFO = 0;
|
||||||
|
public static final int UPGRADING = 1;
|
||||||
|
public static final int UPGRADE_DONE= 2;
|
||||||
|
|
||||||
|
public static final int STATE_IDLE = 0;
|
||||||
|
public static final int STATE_VER_GETTING = 1;
|
||||||
|
public static final int STATE_UPGRADING = 2;
|
||||||
|
|
||||||
|
int mgr_state;
|
||||||
|
int ack_expected;
|
||||||
|
int ack_num;
|
||||||
|
|
||||||
|
// thread safety set and map
|
||||||
|
Map<Long, DeviceApp> upgradeDeviceList;
|
||||||
|
Map<Channel, UpgradeSession> sessionChannelMap;
|
||||||
|
|
||||||
|
@Data //加这个注解才能用JSON转换
|
||||||
|
class WebSocketMsg {
|
||||||
|
byte state = STATE_IDLE;
|
||||||
|
byte state_changed = 0;
|
||||||
|
long sn = 0;
|
||||||
|
String msg;
|
||||||
|
}
|
||||||
|
TimerTask operationTimerTask = null;
|
||||||
|
TimerTask txProgressTimerTask = null;
|
||||||
|
|
||||||
|
Timer timer; //获取app info操作、升级操作的定时器
|
||||||
|
|
||||||
|
public static final VerManager INSTANCE = new VerManager();
|
||||||
|
|
||||||
|
private VerManager(){
|
||||||
|
mgr_state = STATE_IDLE;
|
||||||
|
upgradeDeviceList = new ConcurrentHashMap<Long, DeviceApp>();
|
||||||
|
sessionChannelMap = new ConcurrentHashMap<Channel, UpgradeSession>();
|
||||||
|
timer = new Timer();
|
||||||
|
operationTimerTask = null;
|
||||||
|
txProgressTimerTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public int getState(){
|
||||||
|
return mgr_state;
|
||||||
|
}
|
||||||
|
synchronized public boolean addAckNum(){
|
||||||
|
if(ack_num<ack_expected) ack_num++;
|
||||||
|
return (ack_num==ack_expected);
|
||||||
|
}
|
||||||
|
synchronized public boolean isAllUpgraded(){
|
||||||
|
for(UpgradeSession session:sessionChannelMap.values()){
|
||||||
|
if(session.getState() == DeviceApp.STATE_UPGRADING) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startOperationTimerTask(int delay){
|
||||||
|
if(operationTimerTask == null) {
|
||||||
|
operationTimerTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onOperationTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
timer.schedule(operationTimerTask,delay);
|
||||||
|
}
|
||||||
|
void stopOperationTimerTask(){
|
||||||
|
if(operationTimerTask != null){
|
||||||
|
operationTimerTask.cancel();
|
||||||
|
operationTimerTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void startTxProgressTimerTask(){
|
||||||
|
if(txProgressTimerTask==null){
|
||||||
|
txProgressTimerTask = new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
onRefreshTxProgress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
timer.schedule(txProgressTimerTask,1000,1000);
|
||||||
|
}
|
||||||
|
void stopTxProgressTimerTask(){
|
||||||
|
if(txProgressTimerTask != null){
|
||||||
|
txProgressTimerTask.cancel();
|
||||||
|
txProgressTimerTask = null;
|
||||||
|
log.info("stopTxProgressTimerTask");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void onOperationTimeout(){
|
||||||
|
log.info("operation timeout!");
|
||||||
|
// 检查session状态
|
||||||
|
if(mgr_state == STATE_UPGRADING){
|
||||||
|
for(UpgradeSession session:sessionChannelMap.values()){
|
||||||
|
if(session.getState() == DeviceApp.STATE_UPGRADING) {
|
||||||
|
session.onTimeout();
|
||||||
|
deviceAppMapper.setUpgrade(session.getDeviceApp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changeState(STATE_IDLE);
|
||||||
|
}
|
||||||
|
void onRefreshTxProgress(){
|
||||||
|
WebSocketMsg webSocketMsg = new WebSocketMsg();
|
||||||
|
webSocketMsg.state = (byte) mgr_state;
|
||||||
|
webSocketMsg.state_changed = 0;
|
||||||
|
for(UpgradeSession session:sessionChannelMap.values()){
|
||||||
|
if(session.getState() == DeviceApp.STATE_UPGRADING) {
|
||||||
|
webSocketMsg.sn = session.getSN();
|
||||||
|
webSocketMsg.msg = session.getTxPercentage() + "%";
|
||||||
|
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void changeState(int new_state){
|
||||||
|
mgr_state = new_state;
|
||||||
|
log.info("transfer to state "+new_state);
|
||||||
|
// 通知UI
|
||||||
|
WebSocketMsg webSocketMsg = new WebSocketMsg();
|
||||||
|
webSocketMsg.state = (byte) mgr_state;
|
||||||
|
webSocketMsg.state_changed = 1;
|
||||||
|
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
|
||||||
|
|
||||||
|
switch(mgr_state){
|
||||||
|
case STATE_IDLE:
|
||||||
|
stopOperationTimerTask();
|
||||||
|
stopTxProgressTimerTask();
|
||||||
|
break;
|
||||||
|
case STATE_VER_GETTING:
|
||||||
|
startOperationTimerTask(5000);
|
||||||
|
break;
|
||||||
|
case STATE_UPGRADING:
|
||||||
|
startOperationTimerTask(60000);
|
||||||
|
startTxProgressTimerTask();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSessionChanged(UpgradeSession session){
|
||||||
|
// 通知UI
|
||||||
|
WebSocketMsg webSocketMsg = new WebSocketMsg();
|
||||||
|
webSocketMsg.state = (byte) mgr_state;
|
||||||
|
webSocketMsg.sn = session.getSN();
|
||||||
|
|
||||||
|
switch (session.getState()){
|
||||||
|
case DeviceApp.STATE_UPGRADING:
|
||||||
|
webSocketMsg.msg = "升级中";
|
||||||
|
break;
|
||||||
|
case DeviceApp.STATE_TX_COMPLETED:
|
||||||
|
webSocketMsg.msg = "传输完成";
|
||||||
|
break;
|
||||||
|
case DeviceApp.STATE_TX_FAILED:
|
||||||
|
webSocketMsg.msg = "传输失败";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChannelActive(Channel channel){
|
||||||
|
// get app info
|
||||||
|
Long deviceId = (Long) channel.attr(AttributeKey.valueOf("deviceId")).get();
|
||||||
|
if(deviceId == null){
|
||||||
|
getVerInfo(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onChannelInactive(Channel channel){
|
||||||
|
// 更新状态为”未连接“
|
||||||
|
setDeviceAppState(channel, DeviceApp.STATE_DISCONNECTED);
|
||||||
|
channelSet.remove(channel);
|
||||||
|
sessionChannelMap.remove(channel);
|
||||||
|
log.info("channel inactive. total channel num: "+channelSet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDeviceAppState(Channel channel, short state){
|
||||||
|
long sn = (long) channel.attr(AttributeKey.valueOf("sn")).get();
|
||||||
|
if(sn > 0){
|
||||||
|
DeviceApp deviceApp = deviceAppMapper.queryById(sn);
|
||||||
|
if (deviceApp != null) {
|
||||||
|
deviceApp.setUpgrade_state(state);
|
||||||
|
deviceAppMapper.setUpgrade(deviceApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** 获取终端版本信息 ****
|
||||||
|
* 这个函数是controller调用,为避免多个用户同时操作,要避免多线程同时调用
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void getVerInfo(Channel channel){
|
||||||
|
// 向所有连接终端发读取版本请求
|
||||||
|
byte get_app_info[] = {GET_APP_INFO,0};
|
||||||
|
byte tailer_buff[] = {0,0};
|
||||||
|
int crc = CRC16.calculate(get_app_info);
|
||||||
|
tailer_buff[0] = (byte) (crc>>8);
|
||||||
|
tailer_buff[1] = (byte) crc;
|
||||||
|
channel.write(get_app_info);
|
||||||
|
channel.writeAndFlush(tailer_buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onVerInfoReceived(Channel channel, byte[] rx_data){
|
||||||
|
log.info(channel.remoteAddress()+": received app info");
|
||||||
|
// 记录获取到的版本信息
|
||||||
|
if(rx_data.length<9) return;
|
||||||
|
int crc = CRC16.calculate(rx_data, 7);
|
||||||
|
int crc0 = (((short)rx_data[7]&0xFF)<<8) | ((short)rx_data[8]&0xFF);
|
||||||
|
if(crc != crc0) {
|
||||||
|
log.info(channel.remoteAddress()+": crc error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int i=1;
|
||||||
|
long sn = 0;
|
||||||
|
for(i=1;i<=4;i++){
|
||||||
|
sn = (sn<<8) | ((short)rx_data[i]&0xFF);
|
||||||
|
}
|
||||||
|
String cur_ver = ((short)rx_data[i++]&0xFF) + "." + ((short)rx_data[i++]&0xFF);
|
||||||
|
|
||||||
|
// 更新APP记录并检查此终端是否需要升级
|
||||||
|
DeviceApp deviceApp = upgradeDeviceList.get(sn);
|
||||||
|
|
||||||
|
if (deviceApp == null) return;
|
||||||
|
|
||||||
|
deviceApp.setVersion(cur_ver);
|
||||||
|
//记录这个channel是有应答的
|
||||||
|
channel.attr(AttributeKey.valueOf("deviceId")).set(sn);
|
||||||
|
// 如果原来是未连接状态,则改为连接;如果是其他状态则保持不变
|
||||||
|
if(deviceApp.getUpgrade_state() == DeviceApp.STATE_DISCONNECTED) {
|
||||||
|
deviceApp.setUpgrade_state(DeviceApp.STATE_CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** 批量升级 ****/
|
||||||
|
Channel getChannelBySn(long sn){
|
||||||
|
for(Channel channel: channelSet){
|
||||||
|
long id = (long) channel.attr(AttributeKey.valueOf("sn")).get();
|
||||||
|
boolean acked = (boolean) channel.attr(AttributeKey.valueOf("acked")).get();
|
||||||
|
if(acked && (id == sn)){
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized public boolean upgradeBatch(String firmware, ArrayList<Long> id_list){
|
||||||
|
if(mgr_state != STATE_IDLE) return false;
|
||||||
|
changeState(STATE_UPGRADING);
|
||||||
|
|
||||||
|
// 清除上次的session
|
||||||
|
for(UpgradeSession session : sessionChannelMap.values()){
|
||||||
|
session.reset();
|
||||||
|
}
|
||||||
|
sessionChannelMap.clear();
|
||||||
|
|
||||||
|
// 创建升级session
|
||||||
|
int upgrade_expected = 0;
|
||||||
|
for(long sn:id_list){
|
||||||
|
DeviceApp deviceApp = deviceAppMapper.queryById(sn);
|
||||||
|
Channel channel = getChannelBySn(sn);
|
||||||
|
if(deviceApp!=null && channel!=null){
|
||||||
|
deviceApp.setFirmware(firmware);
|
||||||
|
UpgradeSession session = new UpgradeSession(deviceApp);
|
||||||
|
sessionChannelMap.put(channel,session);
|
||||||
|
|
||||||
|
// 启动升级会话,这里边会改变deviceApp的状态
|
||||||
|
if(session.start(channel)) {
|
||||||
|
upgrade_expected++;
|
||||||
|
}
|
||||||
|
deviceAppMapper.setUpgrade(deviceApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(upgrade_expected == 0) changeState(STATE_IDLE);
|
||||||
|
ack_expected = upgrade_expected;
|
||||||
|
ack_num = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**** 消息处理 ****
|
||||||
|
* 允许多线程操作
|
||||||
|
*/
|
||||||
|
void outputHex(byte[] rx_data)
|
||||||
|
{
|
||||||
|
byte[] hex = new byte[rx_data.length*3];
|
||||||
|
int pos = 0;
|
||||||
|
for(int i=0; i<rx_data.length; i++){
|
||||||
|
hex[pos] = (byte) (((short)rx_data[i]&0xFF)>>4);
|
||||||
|
if(hex[pos]<=9) hex[pos]+='0';
|
||||||
|
else hex[pos] = (byte) (hex[pos] - 0xA + 'A');
|
||||||
|
pos++;
|
||||||
|
hex[pos] = (byte) (rx_data[i]&0x0F);
|
||||||
|
if(hex[pos]<=9) hex[pos]+='0';
|
||||||
|
else hex[pos] = (byte) (hex[pos] - 0xA + 'A');
|
||||||
|
pos++;
|
||||||
|
hex[pos++] = ' ';
|
||||||
|
}
|
||||||
|
log.info(new String(hex));
|
||||||
|
}
|
||||||
|
public void procMsg(Channel channel, byte[] rx_data) {
|
||||||
|
//outputHex(rx_data);
|
||||||
|
if(mgr_state == STATE_IDLE){
|
||||||
|
// 临时,空闲状态下,如果连续3次收到C,则认为有终端主动请求升级
|
||||||
|
if(rx_data[0]==UpgradeSession.CC && rx_data.length<=4){
|
||||||
|
//onUpgradeRequest(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(mgr_state == STATE_UPGRADING){
|
||||||
|
UpgradeSession session = sessionChannelMap.get(channel);
|
||||||
|
if(session == null) return;
|
||||||
|
|
||||||
|
boolean session_done = false;
|
||||||
|
|
||||||
|
if(rx_data[0]==UPGRADE_ACK){
|
||||||
|
log.info("upgrade ack");
|
||||||
|
//outputHex(rx_data);
|
||||||
|
if(rx_data[5]!=0) {
|
||||||
|
//终端拒绝升级,原因是产品序列号不对
|
||||||
|
session.reject();
|
||||||
|
session_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
session_done = session.procYmodem(channel, rx_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(session_done){
|
||||||
|
deviceAppMapper.setUpgrade(session.getDeviceApp());
|
||||||
|
if(isAllUpgraded()){
|
||||||
|
//全部发完,等待新版本启动回应答
|
||||||
|
stopTxProgressTimerTask();
|
||||||
|
}
|
||||||
|
onSessionChanged(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询状态或升级状态下都会收到这个指示
|
||||||
|
if(rx_data[0]==APP_INFO_IND && rx_data.length>2) {
|
||||||
|
onVerInfoReceived(channel, rx_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.timeout.IdleState;
|
||||||
|
import io.netty.handler.timeout.IdleStateEvent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class VersionHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
super.channelActive(ctx);
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
VerManager.INSTANCE.onChannelActive(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if(evt instanceof IdleStateEvent) {
|
||||||
|
IdleStateEvent event = (IdleStateEvent) evt;
|
||||||
|
|
||||||
|
if (event.state() == IdleState.READER_IDLE) {
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
log.info(channel.remoteAddress() + " idle too long to be closed");
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx,
|
||||||
|
Object msg) throws Exception{
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
VerManager.INSTANCE.procMsg(channel, (byte[]) msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx)
|
||||||
|
throws Exception {
|
||||||
|
Channel channel = ctx.channel();
|
||||||
|
VerManager.INSTANCE.onChannelInactive(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
cause.printStackTrace();
|
||||||
|
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
package com.imdroid.vermgr.service;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.codec.bytes.ByteArrayDecoder;
|
||||||
|
import io.netty.handler.codec.bytes.ByteArrayEncoder;
|
||||||
|
import io.netty.handler.logging.LogLevel;
|
||||||
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class VersionServer {
|
||||||
|
@Value("${version_server_port}")
|
||||||
|
private int data_port;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
|
||||||
|
// 创建socket服务,线程阻塞
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
log.info("dtu config server starting...");
|
||||||
|
// Configure the server.
|
||||||
|
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
|
||||||
|
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServerBootstrap b = new ServerBootstrap();
|
||||||
|
b.group(bossGroup, workerGroup)
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.option(ChannelOption.SO_BACKLOG, 100)
|
||||||
|
//.option(ChannelOption.SO_REUSEADDR, true)
|
||||||
|
.handler(new LoggingHandler(LogLevel.INFO))
|
||||||
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
public void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
ChannelPipeline p = ch.pipeline();
|
||||||
|
//p.addLast(new LoggingHandler(LogLevel.INFO));
|
||||||
|
p.addLast(new ByteArrayDecoder());
|
||||||
|
p.addLast(new ByteArrayEncoder());
|
||||||
|
p.addLast(new IdleStateHandler(300, 300, 300)); //设置心跳超时时间,秒
|
||||||
|
p.addLast(new VersionHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server.
|
||||||
|
ChannelFuture f = b.bind(data_port).sync();
|
||||||
|
|
||||||
|
// Wait until the server socket is closed.
|
||||||
|
f.channel().closeFuture().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("error occurred during version server running: "+e);
|
||||||
|
} finally {
|
||||||
|
// Shut down all event loops to terminate all threads.
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
87
sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java
Normal file
87
sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package com.imdroid.vermgr.utils;
|
||||||
|
|
||||||
|
public class CRC16 {
|
||||||
|
static final char[] TABLE1021 = { /* CRC1021余式表 */
|
||||||
|
|
||||||
|
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
|
||||||
|
|
||||||
|
0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231,
|
||||||
|
|
||||||
|
0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339,
|
||||||
|
|
||||||
|
0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462,
|
||||||
|
|
||||||
|
0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a,
|
||||||
|
|
||||||
|
0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653,
|
||||||
|
|
||||||
|
0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b,
|
||||||
|
|
||||||
|
0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4,
|
||||||
|
|
||||||
|
0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc,
|
||||||
|
|
||||||
|
0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5,
|
||||||
|
|
||||||
|
0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd,
|
||||||
|
|
||||||
|
0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6,
|
||||||
|
|
||||||
|
0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
|
||||||
|
|
||||||
|
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97,
|
||||||
|
|
||||||
|
0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f,
|
||||||
|
|
||||||
|
0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188,
|
||||||
|
|
||||||
|
0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080,
|
||||||
|
|
||||||
|
0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9,
|
||||||
|
|
||||||
|
0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1,
|
||||||
|
|
||||||
|
0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea,
|
||||||
|
|
||||||
|
0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2,
|
||||||
|
|
||||||
|
0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db,
|
||||||
|
|
||||||
|
0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3,
|
||||||
|
|
||||||
|
0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c,
|
||||||
|
|
||||||
|
0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844,
|
||||||
|
|
||||||
|
0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d,
|
||||||
|
|
||||||
|
0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75,
|
||||||
|
|
||||||
|
0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
|
||||||
|
|
||||||
|
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26,
|
||||||
|
|
||||||
|
0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f,
|
||||||
|
|
||||||
|
0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17,
|
||||||
|
|
||||||
|
0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };
|
||||||
|
|
||||||
|
public static int calculate(byte[] buf, int length) {
|
||||||
|
|
||||||
|
int counter;
|
||||||
|
int crc = 0;
|
||||||
|
|
||||||
|
if(length > buf.length) return 0;
|
||||||
|
|
||||||
|
for (counter = 0; counter < length; counter++) {
|
||||||
|
crc = (crc << 8) ^ TABLE1021[((crc >> 8) ^ buf[counter]) & 0x00FF];
|
||||||
|
}
|
||||||
|
return crc & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int calculate(byte[] buf) {
|
||||||
|
return calculate(buf, buf.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user