From 5deeb3c70bb9069e7df94f37e3c8ec3268c19523 Mon Sep 17 00:00:00 2001 From: weidong Date: Tue, 10 Jun 2025 10:38:02 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=A2=9E=E5=8A=A0=E7=94=B5=E6=B1=A0?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE=202=E3=80=81=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E7=89=88=E6=9C=AC=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + readme.txt | 18 +- .../imdroid/secapi/client/VersionClient.java | 11 + .../com/imdroid/secapi/dto/GnssDevice.java | 50 +++ .../com/imdroid/secapi/dto/GnssGroup.java | 2 +- .../beidou/controller/APIController.java | 164 +++++--- ...erver.java => CmdLineWebSocketServer.java} | 6 +- .../controller/GnssDeviceController.java | 17 + .../controller/UpgradeWebSocketServer.java | 71 ++++ .../beidou/controller/VersionController.java | 199 +++++++++ .../beidou/service/CommonExcelService.java | 17 +- sec-beidou/src/main/resources/db/schema.sql | 3 + .../static/api/init_super_admin.json | 8 +- .../resources/templates/page/cmd_line.html | 2 +- .../templates/page/gnss_dev_cfg.html | 11 + .../templates/page/gnss_msg_status.html | 10 + .../templates/page/gnss_ver_mgr.html | 237 +++++++++++ .../templates/page/table/gnss_add_dev.html | 36 +- sec-common/pom.xml | 6 + sec-vermgr/pom.xml | 104 +++++ .../com/imdroid/vermgr/entity/DeviceApp.java | 39 ++ .../vermgr/service/UpgradeSession.java | 394 ++++++++++++++++++ .../imdroid/vermgr/service/VerManager.java | 376 +++++++++++++++++ .../vermgr/service/VersionHandler.java | 52 +++ .../imdroid/vermgr/service/VersionServer.java | 71 ++++ .../java/com/imdroid/vermgr/utils/CRC16.java | 87 ++++ 26 files changed, 1917 insertions(+), 75 deletions(-) create mode 100644 sec-api/src/main/java/com/imdroid/secapi/client/VersionClient.java rename sec-beidou/src/main/java/com/imdroid/beidou/controller/{WebSocketServer.java => CmdLineWebSocketServer.java} (90%) create mode 100644 sec-beidou/src/main/java/com/imdroid/beidou/controller/UpgradeWebSocketServer.java create mode 100644 sec-beidou/src/main/java/com/imdroid/beidou/controller/VersionController.java create mode 100644 sec-beidou/src/main/resources/templates/page/gnss_ver_mgr.html create mode 100644 sec-vermgr/pom.xml create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/entity/DeviceApp.java create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/service/UpgradeSession.java create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/service/VerManager.java create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionHandler.java create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionServer.java create mode 100644 sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java 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 @@ + + + + + 设备参数 + + + + + + + +
+
+
+
+

请选择要升级的固件

+
+
+
+
+
+

+ +
+
+

终端固件信息

+
+
+
+ 搜索信息 +
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+

+ + + +
+
+ + + + + + + + + \ 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(); + } + diff --git a/sec-common/pom.xml b/sec-common/pom.xml index 49281e9a..a9834f92 100644 --- a/sec-common/pom.xml +++ b/sec-common/pom.xml @@ -56,6 +56,12 @@ 5.5.8 + + io.netty + netty-all + 4.1.78.Final + + diff --git a/sec-vermgr/pom.xml b/sec-vermgr/pom.xml new file mode 100644 index 00000000..5f445914 --- /dev/null +++ b/sec-vermgr/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + + com.imdroid + security-monitor + 1.0-SNAPSHOT + + + sec-vermgr + + + 8 + 8 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-devtools + true + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + io.netty + netty-all + 4.1.78.Final + + + + com.imdroid + sec-api + 1.0-SNAPSHOT + + + com.imdroid + sec-common + 1.0-SNAPSHOT + + + + org.projectlombok + lombok + true + + + + org.ejml + ejml-all + 0.41 + + + + + ${project.artifactId} + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + + + + + + + + central + ali-mirror + https://maven.aliyun.com/repository/central + + true + + + true + + + + \ No newline at end of file diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/entity/DeviceApp.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/entity/DeviceApp.java new file mode 100644 index 00000000..a91ab89a --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/entity/DeviceApp.java @@ -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]; + } + +} diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/service/UpgradeSession.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/UpgradeSession.java new file mode 100644 index 00000000..8afcdb21 --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/UpgradeSession.java @@ -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_bytesPACKET_SIZE){ + for(int i=read_bytes; i>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>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]); + } +} diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VerManager.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VerManager.java new file mode 100644 index 00000000..9aa71bd9 --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VerManager.java @@ -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 upgradeDeviceList; + Map 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(); + sessionChannelMap = new ConcurrentHashMap(); + timer = new Timer(); + operationTimerTask = null; + txProgressTimerTask = null; + } + + synchronized public int getState(){ + return mgr_state; + } + synchronized public boolean addAckNum(){ + if(ack_num 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 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>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); + } + } + + +} diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionHandler.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionHandler.java new file mode 100644 index 00000000..9fca637a --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionHandler.java @@ -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(); + } +} diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionServer.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionServer.java new file mode 100644 index 00000000..4ee9174d --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/service/VersionServer.java @@ -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() { + @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(); + } +} diff --git a/sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java b/sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java new file mode 100644 index 00000000..a47d4576 --- /dev/null +++ b/sec-vermgr/src/main/java/com/imdroid/vermgr/utils/CRC16.java @@ -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); + } + +}