diff --git a/pom.xml b/pom.xml index 25480f6c..f0b60bfa 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ sec-ntrip-proxy sec-exapi sec-beidou-ehm + sec-vermgr diff --git a/readme.txt b/readme.txt index 5d54a31d..178c3dee 100644 --- a/readme.txt +++ b/readme.txt @@ -28,6 +28,7 @@ beidou-fwd 9906 解算结果推送 ntrip-proxy 9910 11001(外) ntrip服务器代理 beidou-exapi 9908(外) API beidou-ehm 9912 健康检查、SIM卡检查 +vermgr 9914 9916(外) 版本管理服务 2024-9 算法: @@ -55,6 +56,17 @@ beidou-ehm 9912 健康检查、SIM卡检查 2)健康检查增加连续无有效解的时长之和统计 3)批量升级: -a)在设备页面增加勾选框和“准备升级”、“完成升级”按钮 -b)点击“准备升级”,把模式改为“待升级”,同时发连接版本服务器指令 -c)点击“完成升级”,把模式改为“正常” \ No newline at end of file +a)在系统管理里增加一页固件升级页面,包括固件列表和设备列表 + 1)固件列表:固件名、固件大小、创建时间。每次升级只能单选一个固件 + 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)增加一页固件升级记录表,包括所属组织、设备号、项目、桩号、升级时间、升级固件、是否成功 diff --git a/sec-api/src/main/java/com/imdroid/secapi/client/VersionClient.java b/sec-api/src/main/java/com/imdroid/secapi/client/VersionClient.java new file mode 100644 index 00000000..133d191a --- /dev/null +++ b/sec-api/src/main/java/com/imdroid/secapi/client/VersionClient.java @@ -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); +} diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java index 756f2ea3..044aa7b8 100644 --- a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssDevice.java @@ -3,9 +3,13 @@ package com.imdroid.secapi.dto; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.imdroid.common.util.ByteUtil; +import com.imdroid.common.util.HexUtil; import lombok.Data; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; /** * GNSS设备配置数据 @@ -21,6 +25,7 @@ public class GnssDevice { public static final short OP_MODE_USE = 0; public static final short OP_MODE_CHECK = 1; 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_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_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) private Long id; private Integer tenantid; @@ -48,6 +56,8 @@ public class GnssDevice { private Integer calc_group_id = 1; private String fwd_group_id; private String fwd_group_id2; + private Boolean has_battery; + private Byte voltage_factor; //电压测量分压比 private Boolean syn; //组参数是否同步 private String pictures; private Double ipose; //初始位置 @@ -68,4 +78,44 @@ public class GnssDevice { private String remark; 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 getConfigCmd(){ + List 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; + } + } diff --git a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssGroup.java b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssGroup.java index a053c539..9ba221b1 100644 --- a/sec-api/src/main/java/com/imdroid/secapi/dto/GnssGroup.java +++ b/sec-api/src/main/java/com/imdroid/secapi/dto/GnssGroup.java @@ -28,7 +28,7 @@ public class GnssGroup implements Serializable { Short gnss_sample_s; public String getConfigCmd(GnssDevice device){ - String cmd = "D3110009"; + String cmd = "D311000a"; cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) +HexUtil.Byte2HexString((byte) work_cycle.intValue()) +HexUtil.Byte2HexString((byte) active_time.intValue()) diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java index 7f6f88ac..b27b9ebd 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/APIController.java @@ -12,10 +12,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import java.text.SimpleDateFormat; import java.time.LocalDateTime; +import java.util.List; @Controller -public class APIController extends BasicController{ +public class APIController extends BasicController { 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 RtcmClient rtcmClient; @Autowired @@ -36,52 +38,55 @@ public class APIController extends BasicController{ @ResponseBody public String onConfigAck(String deviceId, Integer tenantId, String configAck) { GnssDevice device = deviceMapper.queryByDeviceId(deviceId); - if(device == null) return null; + if (device == null) return null; - int msgType = Integer.parseInt(configAck.substring(0,4),16); + int msgType = Integer.parseInt(configAck.substring(0, 4), 16); // 配置是否成功 - if(msgType == 0xd311){ + if (msgType == 0xd311) { //最后一个字节为1表示配置成功,0表示配置失败 - if(configAck.endsWith("01")){ + if (configAck.endsWith("01")) { device.setSyn(true); deviceMapper.updateById(device); } - } - else if(msgType == 0xd312){ + } else if (msgType == 0xd312) { // 工作周期一致性检查 checkWorkCycle(device, configAck); + } else if (msgType == 0xd313) { + if(device.getChange_flag()!=0) { + if(device.clearChangeFlag(configAck)){ + deviceMapper.updateById(device); + } + } } // 保存, debug 01 02上来的原始数据不保存 - if(msgType != 0xd313 || configAck.length()<100) { + if (msgType != 0xd313 || configAck.length() < 100) { saveMsg(deviceId, tenantId, msgType, configAck, false); } // 命令行显示 - String rxInfo = "RX "+ dateFormat.format(System.currentTimeMillis())+ - " "+deviceId+" "; - if(msgType == 0xd31a || msgType == 0xd31b){ + String rxInfo = "RX " + dateFormat.format(System.currentTimeMillis()) + + " " + deviceId + " "; + if (msgType == 0xd31a || msgType == 0xd31b) { //转成字符串 - String dtuAck = configAck.substring(9*2); - rxInfo += configAck+"("+HexUtil.HexString2String(dtuAck)+")"; + String dtuAck = configAck.substring(9 * 2); + rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")"; // 检查是否需要更新 ICCID - updateICCID(device,dtuAck); - } - else if(msgType == 0xd313&&configAck.length()>=90){ + updateICCID(device, dtuAck); + } else if (msgType == 0xd313 && configAck.length() >= 90) { //转成字符串 - String dtuAck = configAck.substring(26*2); - rxInfo += configAck+"("+HexUtil.HexString2String(dtuAck)+")"; - } - else{ + String dtuAck = configAck.substring(26 * 2); + rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")"; + } else { rxInfo += configAck; } - WebSocketServer.sendMessageToAll(rxInfo); + CmdLineWebSocketServer.sendMessageToAll(rxInfo); return null; } - void checkWorkCycle(GnssDevice device, String cfgData){ + void checkWorkCycle(GnssDevice device, String cfgData) { // d3 12 00 12 00 81 e7 6f 01 0a09000001c2dd7ae1419ee148 int pos = 9; byte[] hexValues = ByteUtil.hexStringTobyte(cfgData); @@ -90,10 +95,10 @@ public class APIController extends BasicController{ byte workOffset = hexValues[pos++]; GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id()); - if(gnssGroup != null){ - if(gnssGroup.getWork_cycle()!=workCycle || - gnssGroup.getActive_time()!=workDur || - gnssGroup.getActive_offset()!=workOffset){ + if (gnssGroup != null) { + if (gnssGroup.getWork_cycle() != workCycle || + gnssGroup.getActive_time() != workDur || + gnssGroup.getActive_offset() != workOffset) { //产生告警 WarningMsg warningMsg = new WarningMsg(); warningMsg.setDeviceid(device.getDeviceid()); @@ -108,7 +113,7 @@ public class APIController extends BasicController{ String sendCmd = gnssGroup.getConfigCmd(device); rtcmClient.config(device.getDeviceid(), sendCmd); // 保存 - saveMsg(device.getDeviceid(), device.getTenantid(),0xD311, sendCmd, true); + saveMsg(device.getDeviceid(), device.getTenantid(), 0xD311, sendCmd, true); } } } @@ -117,19 +122,19 @@ public class APIController extends BasicController{ @PostMapping(value = "/api/device_online") @ResponseBody public String onLine(String deviceId, Integer tenantId, LocalDateTime lastOnlineTime) { - onDeviceActive(deviceId,tenantId); + onDeviceActive(deviceId, tenantId); // 检查参数一致性 - String getWorkCycleCmd = "d3120005"+ - HexUtil.Int2HexString(Integer.parseInt(deviceId))+"01"; + String getWorkCycleCmd = "d3120005" + + HexUtil.Int2HexString(Integer.parseInt(deviceId)) + "01"; rtcmClient.config(deviceId, getWorkCycleCmd); // 保存 - saveMsg(deviceId, tenantId,0xD312, getWorkCycleCmd, true); + saveMsg(deviceId, tenantId, 0xD312, getWorkCycleCmd, true); // 检查是否自动补传 GnssDevice device = deviceMapper.queryByDeviceId(deviceId); - if(device == null) return null; + if (device == null) return null; GnssGroupCalc groupCalc = groupCalcMapper.selectById(device.getCalc_group_id()); - if(groupCalc==null || !groupCalc.getAuto_upload()) return null; + if (groupCalc == null || !groupCalc.getAuto_upload()) return null; // 检查上次是否离线,如果是则启动补传 LocalDateTime now = LocalDateTime.now(); @@ -149,7 +154,7 @@ public class APIController extends BasicController{ + HexUtil.Byte2HexString((byte) (now.getMinute())); rtcmClient.config(deviceId, uploadCmd); // 保存 - saveMsg(deviceId, tenantId,0xD312, uploadCmd, true); + saveMsg(deviceId, tenantId, 0xD312, uploadCmd, true); return null; } @@ -161,33 +166,40 @@ public class APIController extends BasicController{ // 检查有没有待配置的参数 GnssDevice device = deviceMapper.queryByDeviceId(deviceId); - if(device == null) return null; + if (device == null) return null; - if(!device.getSyn()){ + if (!device.getSyn()) { GnssGroup gnssGroup = groupMapper.selectById(device.getGroup_id()); - if(gnssGroup != null){ + if (gnssGroup != null) { String config = gnssGroup.getConfigCmd(device); - if(config != null){ + if (config != null) { rtcmClient.config(deviceId, config); // 保存 - saveMsg(deviceId, tenantId,0xd311, config, true); + saveMsg(deviceId, tenantId, 0xd311, config, true); } } } + List deviceCmds = device.getConfigCmd(); + for(String deviceCmd:deviceCmds){ + rtcmClient.config(deviceId, deviceCmd); + // 保存 + saveMsg(deviceId, tenantId, 0xd312, deviceCmd, true); + + } // 检查有没有待发送的指令 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("deviceid",deviceId); - queryWrapper.eq("syn",false); + queryWrapper.eq("deviceid", deviceId); + queryWrapper.eq("syn", false); queryWrapper.last("limit 1"); DeviceCacheCmd cacheCmd = cacheCmdMapper.selectOne(queryWrapper); - if(cacheCmd != null){ + if (cacheCmd != null) { rtcmClient.config(deviceId, cacheCmd.getCmd()); cacheCmd.setSyn(true); cacheCmd.setUpdatetime(LocalDateTime.now()); cacheCmdMapper.updateById(cacheCmd); // 保存 - saveMsg(deviceId, tenantId,cacheCmd.getMsgtype(), cacheCmd.getCmd(), true); + saveMsg(deviceId, tenantId, cacheCmd.getMsgtype(), cacheCmd.getCmd(), true); } // 检查iccid @@ -207,45 +219,45 @@ public class APIController extends BasicController{ /****** gnss upload *******/ @PostMapping(value = "/api/gnss_upload") @ResponseBody - public String onGnssUpload(String deviceId, Integer tenantId,LocalDateTime uploadTime,String info) { - saveMsg(deviceId, tenantId,0xd342, - "gnss data upload from "+uploadTime+", "+info,false); + public String onGnssUpload(String deviceId, Integer tenantId, LocalDateTime uploadTime, String info) { + saveMsg(deviceId, tenantId, 0xd342, + "gnss data upload from " + uploadTime + ", " + info, false); return null; } @PostMapping(value = "/api/gnss_upload_pause") @ResponseBody public String onGnssUploadPause(String deviceId, Integer tenantId) { - saveMsg(deviceId, tenantId,0xd342, "gnss data upload pause",false); + saveMsg(deviceId, tenantId, 0xd342, "gnss data upload pause", false); return null; } @PostMapping(value = "/api/gnss_upload_complete") @ResponseBody public String onGnssUploadComplete(String deviceId, Integer tenantId, LocalDateTime uploadTime) { - saveMsg(deviceId, tenantId,0xd342, "gnss data upload completely at "+uploadTime,false); + saveMsg(deviceId, tenantId, 0xd342, "gnss data upload completely at " + uploadTime, false); return null; } - void saveMsg(String deviceId, int tenantId, int msgType, String content,boolean isTx){ + void saveMsg(String deviceId, int tenantId, int msgType, String content, boolean isTx) { GnssMsg gnssMsg = new GnssMsg(); gnssMsg.setCreatetime(LocalDateTime.now()); gnssMsg.setTenantid(tenantId); gnssMsg.setDeviceid(deviceId); gnssMsg.setMsgtype(msgType); gnssMsg.setTx(isTx); - if(content==null) content=""; - gnssMsg.setMsglen(content.length()/2); + if (content == null) content = ""; + gnssMsg.setMsglen(content.length() / 2); int saveContentLen = content.length(); - if(saveContentLen<=128) + if (saveContentLen <= 128) gnssMsg.setContent(content); else - gnssMsg.setContent(content.substring(0,128)); + gnssMsg.setContent(content.substring(0, 128)); msgMapper.insert(gnssMsg); } - void checkAndAskICCID(GnssDevice device){ - if(device.getIccid() == null || device.getIccid().trim().isEmpty()) { + void checkAndAskICCID(GnssDevice device) { + if (device.getIccid() == null || device.getIccid().trim().isEmpty()) { String sendCmd = "AT+ICCID"; int msgType = 0xD310 + 10; // DTU short len = (short) (sendCmd.length() + 5); @@ -255,13 +267,14 @@ public class APIController extends BasicController{ rtcmClient.config(device.getDeviceid(), sendCmd); } } + void updateICCID(GnssDevice device, String dtuAck) { // 只检查 "ICCID:" 的十六进制部分 - if(!dtuAck.contains("49434349443a")){ + if (!dtuAck.contains("49434349443a")) { return; } String content = HexUtil.HexString2String(dtuAck); - if(content.contains("+ICCID:")){ + if (content.contains("+ICCID:")) { //System.out.println(content); String iccid = content.substring(content.indexOf("+ICCID:") + 8).trim(); iccid = iccid.split("\r\n")[0].trim(); @@ -269,4 +282,39 @@ public class APIController extends BasicController{ deviceMapper.updateById(device); } } -} + + /****** upgrade operations *******/ + @PostMapping(value = "/api/upgrade_ack") + @ResponseBody + public String onUpgradeAck() { + //发连接服务器指令 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("opmode", GnssDevice.OP_MODE_UPGRADING); + List 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; + } +} \ No newline at end of file diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/WebSocketServer.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/CmdLineWebSocketServer.java similarity index 90% rename from sec-beidou/src/main/java/com/imdroid/beidou/controller/WebSocketServer.java rename to sec-beidou/src/main/java/com/imdroid/beidou/controller/CmdLineWebSocketServer.java index 81bd171f..2ee07fba 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/WebSocketServer.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/CmdLineWebSocketServer.java @@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap; //单例 -@ServerEndpoint(value = "/websocket",configurator = WebSocketConfig.class) +@ServerEndpoint(value = "/websocket/cmdline",configurator = WebSocketConfig.class) @Component @Slf4j -public class WebSocketServer { +public class CmdLineWebSocketServer { // thread safety counter // thread safety set to hold websocket objects - private static Map webSocketSet = new ConcurrentHashMap<>(); + private static final Map webSocketSet = new ConcurrentHashMap<>(); @OnOpen public void onOpen(Session session){ diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssDeviceController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssDeviceController.java index 28986a21..ac710dfa 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssDeviceController.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/GnssDeviceController.java @@ -175,6 +175,17 @@ public class GnssDeviceController extends BasicController{ if (opmode != null && opmode != QUERY_ALL) { 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"); if (fwd != null && fwd != QUERY_ALL) { @@ -240,6 +251,12 @@ public class GnssDeviceController extends BasicController{ if(!old_device.getGroup_id().equals(device.getGroup_id())){ 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); } } diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/UpgradeWebSocketServer.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/UpgradeWebSocketServer.java new file mode 100644 index 00000000..4c834019 --- /dev/null +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/UpgradeWebSocketServer.java @@ -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 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); + } + } + } +} diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/controller/VersionController.java b/sec-beidou/src/main/java/com/imdroid/beidou/controller/VersionController.java new file mode 100644 index 00000000..9a022298 --- /dev/null +++ b/sec-beidou/src/main/java/com/imdroid/beidou/controller/VersionController.java @@ -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 upgradingDeviceList = new ConcurrentHashMap<>(); + + /**** 推送页面 *****/ + @RequestMapping("/sys/ver_mgr") + public String gnssDevCfg(Model m, HttpSession session) { + initModel(m, session); + List 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 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 pageable = new Page<>(page, limit); + QueryWrapper 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 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> 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() { + 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; + } + +} diff --git a/sec-beidou/src/main/java/com/imdroid/beidou/service/CommonExcelService.java b/sec-beidou/src/main/java/com/imdroid/beidou/service/CommonExcelService.java index e4ea3b89..0fd2299f 100644 --- a/sec-beidou/src/main/java/com/imdroid/beidou/service/CommonExcelService.java +++ b/sec-beidou/src/main/java/com/imdroid/beidou/service/CommonExcelService.java @@ -199,8 +199,12 @@ public interface CommonExcelService { * @param paraValue 查询参数值 */ default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) { + // null + if (paraName.startsWith("NULL") && StringUtils.hasText((String)paraValue)) { + addNullQueryWrapper(queryWrapper, paraName, paraValue); + } // String - if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) { + else if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) { addStringQueryWrapper(queryWrapper, paraName, paraValue); } // Number,-1表示全部 @@ -221,6 +225,17 @@ public interface CommonExcelService { 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) { String column = getColumn(paraName); String value = paraValue.toString(); diff --git a/sec-beidou/src/main/resources/db/schema.sql b/sec-beidou/src/main/resources/db/schema.sql index 50381969..a6e782d7 100644 --- a/sec-beidou/src/main/resources/db/schema.sql +++ b/sec-beidou/src/main/resources/db/schema.sql @@ -69,6 +69,9 @@ CREATE TABLE IF NOT EXISTS `gnssdevices` ( `model` smallint DEFAULT 0, `loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式', `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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/sec-beidou/src/main/resources/static/api/init_super_admin.json b/sec-beidou/src/main/resources/static/api/init_super_admin.json index c50fc81f..7b27bd98 100644 --- a/sec-beidou/src/main/resources/static/api/init_super_admin.json +++ b/sec-beidou/src/main/resources/static/api/init_super_admin.json @@ -24,7 +24,7 @@ { "title": "健康检查报告", "href": "page/gnss_ehm", - "icon": "fa fa-tachometer", + "icon": "fa fa-heartbeat", "target": "_self" }, { @@ -200,6 +200,12 @@ "href": "sys/apikey", "icon": "fa fa-key", "target": "_self" + }, + { + "title": "版本管理", + "href": "sys/ver_mgr", + "icon": "fa fa-file-code-o", + "target": "_self" } ] } diff --git a/sec-beidou/src/main/resources/templates/page/cmd_line.html b/sec-beidou/src/main/resources/templates/page/cmd_line.html index c99b5785..1bb68cef 100644 --- a/sec-beidou/src/main/resources/templates/page/cmd_line.html +++ b/sec-beidou/src/main/resources/templates/page/cmd_line.html @@ -219,7 +219,7 @@ //建立webSocket连接 var webSocktPath; - webSocktPath = (basePath+"/websocket").replace("http","ws"); + webSocktPath = (basePath+"/websocket/cmdline").replace("http","ws"); websocket = new WebSocket(webSocktPath); //打开webSokcet连接时,回调该函数 diff --git a/sec-beidou/src/main/resources/templates/page/gnss_dev_cfg.html b/sec-beidou/src/main/resources/templates/page/gnss_dev_cfg.html index eec193d9..3a79103f 100644 --- a/sec-beidou/src/main/resources/templates/page/gnss_dev_cfg.html +++ b/sec-beidou/src/main/resources/templates/page/gnss_dev_cfg.html @@ -90,6 +90,16 @@ + 内置电池 + + + 全部 + 无 + 是 + + + + 搜 索 @@ -135,6 +145,7 @@ {field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'}, {field: 'model', title: '型号', width: 80,templet: "{{d.model==0?'G505':'G510'}}"}, {field: 'appver', title: '固件版本', width: 80}, + {field: 'voltage_factor', title: '分压系数', width: 60}, {field: 'imei', title: 'IMEI', width: 100}, {title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120} ]; diff --git a/sec-beidou/src/main/resources/templates/page/gnss_msg_status.html b/sec-beidou/src/main/resources/templates/page/gnss_msg_status.html index df076dd8..204cf32e 100644 --- a/sec-beidou/src/main/resources/templates/page/gnss_msg_status.html +++ b/sec-beidou/src/main/resources/templates/page/gnss_msg_status.html @@ -24,6 +24,16 @@ + + 内置电池 + + + 全部 + 无 + 是 + + + 范围 diff --git a/sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html b/sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html new file mode 100644 index 00000000..7d81a1ea --- /dev/null +++ b/sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html @@ -0,0 +1,237 @@ + + + + + 设备参数 + + + + + + + + + + + + 请选择要升级的固件 + + + + + + + + + + 终端固件信息 + + + + 搜索信息 + + + + + 所属部门 + + + 全部 + 非SAAS服务商 + + + + + + 设备号 + + + + + + 项目号 + + + + + + 桩号 + + + + + + 固件版本 + + + + + + 使用状态 + + + 全部 + 正常 + 维护 + 停用 + + + + + + 搜 索 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html b/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html index 5e311686..b6f6a82e 100644 --- a/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html +++ b/sec-beidou/src/main/resources/templates/page/table/gnss_add_dev.html @@ -22,7 +22,7 @@ 设备编号 - + @@ -57,14 +57,21 @@ - 星座 - - - - - + 内置电池 + + + 否 + 是 + + + 电压系数 + + + + *0.1 + @@ -288,6 +295,7 @@ }); + function setEcefEditor(){ var $ = layui.$; 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(); + } +