1、增加电池参数配置

2、增加版本管理页面
This commit is contained in:
weidong 2025-06-10 10:38:02 +08:00
parent 0864ca38c9
commit 5deeb3c70b
26 changed files with 1917 additions and 75 deletions

View File

@ -18,6 +18,7 @@
<module>sec-ntrip-proxy</module> <module>sec-ntrip-proxy</module>
<module>sec-exapi</module> <module>sec-exapi</module>
<module>sec-beidou-ehm</module> <module>sec-beidou-ehm</module>
<module>sec-vermgr</module>
</modules> </modules>
<properties> <properties>

View File

@ -28,6 +28,7 @@ beidou-fwd 9906 解算结果推送
ntrip-proxy 9910 11001(外) ntrip服务器代理 ntrip-proxy 9910 11001(外) ntrip服务器代理
beidou-exapi 9908(外) API beidou-exapi 9908(外) API
beidou-ehm 9912 健康检查、SIM卡检查 beidou-ehm 9912 健康检查、SIM卡检查
vermgr 9914 9916(外) 版本管理服务
2024-9 2024-9
算法: 算法:
@ -55,6 +56,17 @@ beidou-ehm 9912 健康检查、SIM卡检查
2健康检查增加连续无有效解的时长之和统计 2健康检查增加连续无有效解的时长之和统计
3批量升级 3批量升级
a在设备页面增加勾选框和“准备升级”、“完成升级”按钮 a在系统管理里增加一页固件升级页面包括固件列表和设备列表
b点击“准备升级”把模式改为“待升级”同时发连接版本服务器指令 1固件列表固件名、固件大小、创建时间。每次升级只能单选一个固件
c点击“完成升级”把模式改为“正常” 2设备列表所属组织、设备号、项目、桩号、版本号、使用状态正常、维护、停用、当前状态工作、休眠、离线、最近一次版本升级时间
3设备可多选筛选条件所属组织、设备号、项目、桩号、版本号
b升级过程
1WEB服务选择固件->筛选设备->勾选设备
2WEB服务点击“升级”系统检查所勾选的设备批次与所选固件是否一致如果有不一致弹出“固件不匹配”提示窗口结束升级
3WEB服务向版本服务发升级命令UpgradeCmd(deviceList)版本服务如果应答UpgradeAck则把待升级的设备改为“维护”状态发连接服务器指令升级按钮变灰否则提示“版本服务未启动”的提示结束升级
4版本服务当收到WEB服务发来的升级命令UpgradeCmd(deviceList)回应答UpgradeAck为每个待升级设备启动30s升级定时器
5版本服务当收到TCP连接响应则向对端发版本查询指令开始版本传输流程升级过程向WEB服务发送进度指示UpgradeInd(deviceid,progress)每次收到ACK则刷新定时器。
6版本服务当设备升级完毕或升级定时器超时结束升级流程向WEB服务发升级完成指示UpgradeCompleteInd(deviceid,result)。当所有设备升级结束再给WEB发一个全部升级完成的指示UpgradeCompleteInd(all)
7WEB服务当收到全部升级结束指示按钮变正常升级设备状态改为正常
8WEB服务保存升级记录
c增加一页固件升级记录表包括所属组织、设备号、项目、桩号、升级时间、升级固件、是否成功

View File

@ -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);
}

View File

@ -3,9 +3,13 @@ package com.imdroid.secapi.dto;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.imdroid.common.util.ByteUtil;
import com.imdroid.common.util.HexUtil;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/** /**
* GNSS设备配置数据 * GNSS设备配置数据
@ -21,6 +25,7 @@ public class GnssDevice {
public static final short OP_MODE_USE = 0; public static final short OP_MODE_USE = 0;
public static final short OP_MODE_CHECK = 1; public static final short OP_MODE_CHECK = 1;
public static final short OP_MODE_UNUSE = 2; public static final short OP_MODE_UNUSE = 2;
public static final short OP_MODE_UPGRADING = 3;
public static final short MODEL_G505 = 0; //F9P public static final short MODEL_G505 = 0; //F9P
public static final short MODEL_G510 = 1; //博通 public static final short MODEL_G510 = 1; //博通
@ -28,6 +33,9 @@ public class GnssDevice {
public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式 仅D3F0和D3F2 public static final short LOGGING_MODE_SIMPLE = 0; // 精简模式 仅D3F0和D3F2
public static final short LOGGING_MODE_FULL = 1; // 完整模式 public static final short LOGGING_MODE_FULL = 1; // 完整模式
public static final int PARA_MASK_HAS_BATTERY = 0x01;
public static final int PARA_MASK_VOLTAGE_FACTOR = 0x02;
@TableId(value = "id", type = IdType.AUTO) @TableId(value = "id", type = IdType.AUTO)
private Long id; private Long id;
private Integer tenantid; private Integer tenantid;
@ -48,6 +56,8 @@ public class GnssDevice {
private Integer calc_group_id = 1; private Integer calc_group_id = 1;
private String fwd_group_id; private String fwd_group_id;
private String fwd_group_id2; private String fwd_group_id2;
private Boolean has_battery;
private Byte voltage_factor; //电压测量分压比
private Boolean syn; //组参数是否同步 private Boolean syn; //组参数是否同步
private String pictures; private String pictures;
private Double ipose; //初始位置 private Double ipose; //初始位置
@ -68,4 +78,44 @@ public class GnssDevice {
private String remark; private String remark;
private String iccid; private String iccid;
// 参数改变
private Integer change_flag = 0;
private String getBatteryConfigCmd(){
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
"55"+(has_battery?"01":"00");
}
private String getVoltageFactorConfigCmd(){
return "d3130006"+ HexUtil.Int2HexString(Integer.parseInt(deviceid))+
"52"+HexUtil.Byte2HexString(voltage_factor);
}
public List<String> getConfigCmd(){
List<String> cmdList = new ArrayList<>();
if((change_flag&PARA_MASK_HAS_BATTERY) !=0 ){
cmdList.add(getBatteryConfigCmd());
}
if((change_flag&PARA_MASK_VOLTAGE_FACTOR) !=0 ){
cmdList.add(getVoltageFactorConfigCmd());
}
return cmdList;
}
public boolean clearChangeFlag(String ack){
boolean result = false;
if(change_flag!=0) {
byte[] ackBytes = ByteUtil.hexStringTobyte(ack);
if(ackBytes[ackBytes.length-1] == 1) {
if (ackBytes[8] == 0x55) {
change_flag &= ~PARA_MASK_HAS_BATTERY;
result = true;
} else if (ackBytes[8] == 0x52) {
change_flag &= ~PARA_MASK_VOLTAGE_FACTOR;
result = true;
}
}
}
return result;
}
} }

View File

@ -28,7 +28,7 @@ public class GnssGroup implements Serializable {
Short gnss_sample_s; Short gnss_sample_s;
public String getConfigCmd(GnssDevice device){ public String getConfigCmd(GnssDevice device){
String cmd = "D3110009"; String cmd = "D311000a";
cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) cmd += HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid()))
+HexUtil.Byte2HexString((byte) work_cycle.intValue()) +HexUtil.Byte2HexString((byte) work_cycle.intValue())
+HexUtil.Byte2HexString((byte) active_time.intValue()) +HexUtil.Byte2HexString((byte) active_time.intValue())

View File

@ -12,10 +12,12 @@ import org.springframework.web.bind.annotation.ResponseBody;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
@Controller @Controller
public class APIController extends BasicController { public class APIController extends BasicController {
SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss"); SimpleDateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
final String verServerConnectCmd = "AT+QIOPEN=1,2,\"TCP\",\"iot.im-droid.com\",7665,0,1";
@Autowired @Autowired
RtcmClient rtcmClient; RtcmClient rtcmClient;
@Autowired @Autowired
@ -46,10 +48,15 @@ public class APIController extends BasicController{
device.setSyn(true); device.setSyn(true);
deviceMapper.updateById(device); deviceMapper.updateById(device);
} }
} } else if (msgType == 0xd312) {
else if(msgType == 0xd312){
// 工作周期一致性检查 // 工作周期一致性检查
checkWorkCycle(device, configAck); checkWorkCycle(device, configAck);
} else if (msgType == 0xd313) {
if(device.getChange_flag()!=0) {
if(device.clearChangeFlag(configAck)){
deviceMapper.updateById(device);
}
}
} }
// 保存, debug 01 02上来的原始数据不保存 // 保存, debug 01 02上来的原始数据不保存
@ -67,17 +74,15 @@ public class APIController extends BasicController{
// 检查是否需要更新 ICCID // 检查是否需要更新 ICCID
updateICCID(device, dtuAck); updateICCID(device, dtuAck);
} } else if (msgType == 0xd313 && configAck.length() >= 90) {
else if(msgType == 0xd313&&configAck.length()>=90){
//转成字符串 //转成字符串
String dtuAck = configAck.substring(26 * 2); String dtuAck = configAck.substring(26 * 2);
rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")"; rxInfo += configAck + "(" + HexUtil.HexString2String(dtuAck) + ")";
} } else {
else{
rxInfo += configAck; rxInfo += configAck;
} }
WebSocketServer.sendMessageToAll(rxInfo); CmdLineWebSocketServer.sendMessageToAll(rxInfo);
return null; return null;
} }
@ -174,6 +179,13 @@ public class APIController extends BasicController{
} }
} }
} }
List<String> deviceCmds = device.getConfigCmd();
for(String deviceCmd:deviceCmds){
rtcmClient.config(deviceId, deviceCmd);
// 保存
saveMsg(deviceId, tenantId, 0xd312, deviceCmd, true);
}
// 检查有没有待发送的指令 // 检查有没有待发送的指令
QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>(); QueryWrapper<DeviceCacheCmd> queryWrapper = new QueryWrapper<>();
@ -255,6 +267,7 @@ public class APIController extends BasicController{
rtcmClient.config(device.getDeviceid(), sendCmd); rtcmClient.config(device.getDeviceid(), sendCmd);
} }
} }
void updateICCID(GnssDevice device, String dtuAck) { void updateICCID(GnssDevice device, String dtuAck) {
// 只检查 "ICCID:" 的十六进制部分 // 只检查 "ICCID:" 的十六进制部分
if (!dtuAck.contains("49434349443a")) { if (!dtuAck.contains("49434349443a")) {
@ -269,4 +282,39 @@ public class APIController extends BasicController{
deviceMapper.updateById(device); deviceMapper.updateById(device);
} }
} }
/****** upgrade operations *******/
@PostMapping(value = "/api/upgrade_ack")
@ResponseBody
public String onUpgradeAck() {
//发连接服务器指令
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("opmode", GnssDevice.OP_MODE_UPGRADING);
List<GnssDevice> gnssDeviceList = deviceMapper.selectList(queryWrapper);
for (GnssDevice device : gnssDeviceList) {
short len = (short) (verServerConnectCmd.length() + 5);
String sendCmd = "d31a" + HexUtil.Short2HexString(len) +
HexUtil.Int2HexString(Integer.parseInt(device.getDeviceid())) +
"01" + HexUtil.String2HexString(verServerConnectCmd);
rtcmClient.config(device.getDeviceid(), sendCmd);
}
//界面的升级按钮变灰
UpgradeWebSocketServer.sendMessageToAll("on upgrade ack");
return null;
}
@PostMapping(value = "/api/upgrading_ind")
@ResponseBody
public String onUpgradingInd(String deviceId, Byte progress) {
UpgradeWebSocketServer.sendMessageToAll("on upgrade ind " + deviceId + "," + progress);
return null;
}
@PostMapping(value = "/api/upgrade_complete_ind")
@ResponseBody
public String onUpgradeCompleteInd(String deviceId, Byte code) {
UpgradeWebSocketServer.sendMessageToAll("on upgrade complete ind " + deviceId + "," + code);
// 保存升级记录
return null;
}
} }

View File

@ -12,13 +12,13 @@ import java.util.concurrent.ConcurrentHashMap;
//单例 //单例
@ServerEndpoint(value = "/websocket",configurator = WebSocketConfig.class) @ServerEndpoint(value = "/websocket/cmdline",configurator = WebSocketConfig.class)
@Component @Component
@Slf4j @Slf4j
public class WebSocketServer { public class CmdLineWebSocketServer {
// thread safety counter // thread safety counter
// thread safety set to hold websocket objects // thread safety set to hold websocket objects
private static Map<String,Session> webSocketSet = new ConcurrentHashMap<>(); private static final Map<String,Session> webSocketSet = new ConcurrentHashMap<>();
@OnOpen @OnOpen
public void onOpen(Session session){ public void onOpen(Session session){

View File

@ -175,6 +175,17 @@ public class GnssDeviceController extends BasicController{
if (opmode != null && opmode != QUERY_ALL) { if (opmode != null && opmode != QUERY_ALL) {
queryWrapper.eq("opmode", opmode); queryWrapper.eq("opmode", opmode);
} }
Integer hasBattery = search.getInteger("has_battery");
if(hasBattery != null && hasBattery != QUERY_ALL){
if(hasBattery == 1) {
queryWrapper.and(wrapper -> wrapper.eq("has_battery", 1)
.or().likeRight("deviceid","6")
.or().likeRight("deviceid","7"));
}
else{
queryWrapper.eq("has_battery", 0);
}
}
//推送状 //推送状
Integer fwd = search.getInteger("fwd_group_id"); Integer fwd = search.getInteger("fwd_group_id");
if (fwd != null && fwd != QUERY_ALL) { if (fwd != null && fwd != QUERY_ALL) {
@ -240,6 +251,12 @@ public class GnssDeviceController extends BasicController{
if(!old_device.getGroup_id().equals(device.getGroup_id())){ if(!old_device.getGroup_id().equals(device.getGroup_id())){
device.setSyn(false); device.setSyn(false);
} }
if(diff.contains("has_battery")){
device.setChange_flag(GnssDevice.PARA_MASK_HAS_BATTERY);
}
if(diff.contains("voltage_factor")){
device.setChange_flag(GnssDevice.PARA_MASK_VOLTAGE_FACTOR);
}
num = gnssDeviceMapper.updateById(device); num = gnssDeviceMapper.updateById(device);
} }
} }

View File

@ -0,0 +1,71 @@
package com.imdroid.beidou.controller;
import com.imdroid.beidou.config.WebSocketConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//单例
@ServerEndpoint(value = "/websocket/upgrade",configurator = WebSocketConfig.class)
@Component
@Slf4j
public class UpgradeWebSocketServer{
// thread safety counter
// thread safety set to hold websocket objects
private static final Map<String, Session> webSocketSet = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session){
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
webSocketSet.put(httpSession.getId(), session);
log.info("new websocket opened, total socket num: "+ webSocketSet.size());
}
@OnClose
public void onClose(Session session){
HttpSession httpSession= (HttpSession) session.getUserProperties().get(HttpSession.class.getName());
if(httpSession!=null) {
webSocketSet.remove(httpSession.getId());
}
log.info("websocket closed, total socket num: "+ webSocketSet.size());
}
@OnMessage
public void onMessage(String msg, Session session){
//log.info("websocket: "+msg);
}
@OnError
public void onError(Session session, Throwable error){
error.printStackTrace();
}
public static void sendMessageToAll(String msg){
try {
for (Session item : webSocketSet.values()) {
item.getBasicRemote().sendText(msg);
}
}
catch (Exception e){
log.error("websocket send msg error:", e);
}
}
public static void sendMessage(HttpSession httpSession, String msg) {
Session session = webSocketSet.get(httpSession.getId());
if(session != null){
try {
session.getBasicRemote().sendText(msg);
}
catch (Exception e){
log.error("websocket send msg error:", e);
}
}
}
}

View File

@ -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;
}
}
// 正在升级的设备mapdeviceId <-> UpgradeInfo(高字节升级结果低字节进度)
final ConcurrentHashMap<String, Short> upgradingDeviceList = new ConcurrentHashMap<>();
/**** 推送页面 *****/
@RequestMapping("/sys/ver_mgr")
public String gnssDevCfg(Model m, HttpSession session) {
initModel(m, session);
List<Tenant> tenants = tenantMapper.selectList(null);
m.addAttribute("tenant_list", tenants);
return "/page/gnss_ver_mgr";
}
/**** 推送数据 *****/
@RequestMapping("/sys/ver_mgr/firmware_list")
@ResponseBody
public JSONObject listFirmware(int page, int limit) {
List<FirmwareInfo> firmwareInfoList = new ArrayList();
int total = getFirmwareList(page, limit,firmwareInfoList);
//用PageInfo对结果进行包装,传入连续显示的页数
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 0);
jsonObject.put("msg", "");
jsonObject.put("count", total);
jsonObject.put("data", firmwareInfoList);
return jsonObject;
}
@RequestMapping("/sys/ver_mgr/gnss_device_list")
@ResponseBody
public JSONObject list(HttpSession session, int page, int limit, String searchParams) {
Page<GnssDevice> pageable = new Page<>(page, limit);
QueryWrapper<GnssDevice> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("deviceid");
// 条件查询
if(searchParams != null) {
JSONObject search = (JSONObject) JSONObject.parse(searchParams);
//设备号
String deviceid = search.getString("deviceid");
if (StringUtils.hasText(deviceid)) {
queryWrapper.like("deviceid", deviceid);
}
//项目号
String project_id = search.getString("project_id");
if (StringUtils.hasText(project_id)) {
queryWrapper.like("project_id", project_id);
}
//所属组织
String tenantname = search.getString("tenantname");
if (StringUtils.hasText(tenantname)) {
if(tenantname.startsWith("非SAAS"))
queryWrapper.ne("tenantname",Tenant.SAAS_PROVIDER_NAME);
else queryWrapper.like("tenantname", tenantname);
}
//桩号
String sector = search.getString("sector");
if (StringUtils.hasText(sector)) {
queryWrapper.like("sector", sector);
}
//设备类型
Integer devicetype = search.getInteger("devicetype");
if (devicetype != null && devicetype != QUERY_ALL) {
queryWrapper.eq("devicetype", devicetype);
}
//版本
String appver = search.getString("appver");
if (StringUtils.hasText(appver)) {
queryWrapper.like("appver", appver);
}
//备注
String remark = search.getString("remark");
if (StringUtils.hasText(remark)) {
queryWrapper.like("remark", remark);
}
//使用状态
Integer opmode = search.getInteger("opmode");
if (opmode != null && opmode != QUERY_ALL) {
queryWrapper.eq("opmode", opmode);
}
}
IPage<GnssDevice> cs = gnssDeviceMapper.selectPage(pageable, queryWrapper);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 0);
jsonObject.put("msg", "");
jsonObject.put("count", cs.getTotal());
jsonObject.put("data", cs.getRecords());
return jsonObject;
}
@PostMapping("/sys/ver_mgr/upgrade")
@ResponseBody
public HttpResult upgradeApp(@RequestParam String firmware, @RequestParam String id_list) throws Exception {
if(firmware!=null && !firmware.equals("undefined")) {
//请求版本服务升级
try {
HttpResp<HashMap<String, Object>> rsp = versionClient.upgrade(id_list, firmware);
System.out.println(id_list);
}
catch (Exception e){
return HttpResult.fail("版本服务未启动");
}
}
return HttpResult.success("OK");
}
int getFirmwareList(int page, int limit, List firmwareInfo){
File folder = new File(version_path);
File[] files = folder.listFiles((file) -> file.getName().endsWith(".bin"));
if(files==null) return 0;
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
long diff = f1.lastModified() - f2.lastModified();
if (diff > 0)
return -1;
else if (diff == 0)
return 0;
else
return 1;//如果 if 中修改为 返回-1 同时此处修改为返回 1 排序就会是递减
}
public boolean equals(Object obj) {
return true;
}
});
int count = 0;
for(File file:files){
if((int)count/limit+1 == page) {
firmwareInfo.add(new FirmwareInfo(file.getName(), file.length(), file.lastModified()));
}
count++;
}
return files.length;
}
}

View File

@ -199,8 +199,12 @@ public interface CommonExcelService<T, R> {
* @param paraValue 查询参数值 * @param paraValue 查询参数值
*/ */
default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) { default void setQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
// null
if (paraName.startsWith("NULL") && StringUtils.hasText((String)paraValue)) {
addNullQueryWrapper(queryWrapper, paraName, paraValue);
}
// String // String
if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) { else if (paraName.startsWith("s") && StringUtils.hasText((String)paraValue)) {
addStringQueryWrapper(queryWrapper, paraName, paraValue); addStringQueryWrapper(queryWrapper, paraName, paraValue);
} }
// Number,-1表示全部 // Number,-1表示全部
@ -221,6 +225,17 @@ public interface CommonExcelService<T, R> {
return paraName.substring(paraName.indexOf("_") + 1); return paraName.substring(paraName.indexOf("_") + 1);
} }
default void addNullQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
String column = getColumn(paraName);
String value = paraValue.toString();
if (value.equals("0")) {
queryWrapper.isNull(column);
}
else if(value.equals("1")){
queryWrapper.isNotNull(column);
}
}
default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) { default void addStringQueryWrapper(AbstractWrapper queryWrapper, String paraName, Object paraValue) {
String column = getColumn(paraName); String column = getColumn(paraName);
String value = paraValue.toString(); String value = paraValue.toString();

View File

@ -69,6 +69,9 @@ CREATE TABLE IF NOT EXISTS `gnssdevices` (
`model` smallint DEFAULT 0, `model` smallint DEFAULT 0,
`loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式', `loggingmode` smallint DEFAULT 0 COMMENT '日志模式: 0-精简模式(仅D3F0和D3F2), 1-完整模式',
`iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号唯一', `iccid` VARCHAR(36) DEFAULT NULL COMMENT 'ICCID号唯一',
`has_battery` bit(1) DEFAULT 0 COMMENT '是否内置电池',
`change_flag` int DEFAULT 0 COMMENT '参数改变标识',
`voltage_factor` tinyint DEFAULT NULL COMMENT '分压比',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -24,7 +24,7 @@
{ {
"title": "健康检查报告", "title": "健康检查报告",
"href": "page/gnss_ehm", "href": "page/gnss_ehm",
"icon": "fa fa-tachometer", "icon": "fa fa-heartbeat",
"target": "_self" "target": "_self"
}, },
{ {
@ -200,6 +200,12 @@
"href": "sys/apikey", "href": "sys/apikey",
"icon": "fa fa-key", "icon": "fa fa-key",
"target": "_self" "target": "_self"
},
{
"title": "版本管理",
"href": "sys/ver_mgr",
"icon": "fa fa-file-code-o",
"target": "_self"
} }
] ]
} }

View File

@ -219,7 +219,7 @@
//建立webSocket连接 //建立webSocket连接
var webSocktPath; var webSocktPath;
webSocktPath = (basePath+"/websocket").replace("http","ws"); webSocktPath = (basePath+"/websocket/cmdline").replace("http","ws");
websocket = new WebSocket(webSocktPath); websocket = new WebSocket(webSocktPath);
//打开webSokcet连接时回调该函数 //打开webSokcet连接时回调该函数

View File

@ -90,6 +90,16 @@
</div> </div>
</div> </div>
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">内置电池</label>
<div class="layui-input-inline">
<select name="has_battery" id="has_battery" lay-verify="required" lay-search="">
<option value="-1">全部</option>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button> <button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
</div> </div>
</div> </div>
@ -135,6 +145,7 @@
{field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'}, {field: 'syn', title: '参数同步', width: 80,templet: '#synTrans'},
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"}, {field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
{field: 'appver', title: '固件版本', width: 80}, {field: 'appver', title: '固件版本', width: 80},
{field: 'voltage_factor', title: '分压系数', width: 60},
{field: 'imei', title: 'IMEI', width: 100}, {field: 'imei', title: 'IMEI', width: 100},
{title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120} {title: '操作', toolbar: '#currentTableBar', fixed: "right", width: 120}
]; ];

View File

@ -24,6 +24,16 @@
<input type="text" name="s_deviceid" autocomplete="off" class="layui-input"> <input type="text" name="s_deviceid" autocomplete="off" class="layui-input">
</div> </div>
</div> </div>
<div class="layui-inline">
<label class="layui-form-label">内置电池</label>
<div class="layui-input-inline">
<select name="NULL_solarvoltage" id="NULL_solarvoltage" lay-verify="required" lay-search="">
<option value="-1">全部</option>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
</div>
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label">范围</label> <label class="layui-form-label">范围</label>
<div class="layui-input-inline"> <div class="layui-input-inline">

View File

@ -0,0 +1,237 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>设备参数</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../lib/layui-v2.6.3/css/layui.css" media="all">
<link rel="stylesheet" href="../css/public.css" media="all">
</head>
<body>
<div class="layuimini-container">
<div class="layuimini-main">
<div class="layui-card layui-panel">
<div class="layui-card-header">
<h3>请选择要升级的固件</h3>
</div>
<div class="layui-card-body">
<table class="layui-hide" id="firmware-table" lay-filter="firmware-table"></table>
</div>
</div>
<br><br>
<div class="layui-card layui-panel">
<div class="layui-card-header" name="app-title">
<h3>终端固件信息</h3>
</div>
<div class="layui-card-body">
<fieldset class="table-search-fieldset">
<legend>搜索信息</legend>
<div style="margin: 10px 10px 10px 10px">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-inline" th:if="${tenant_id==0}">
<label class="layui-form-label">所属部门</label>
<div class="layui-input-inline">
<select name="tenantname" id="tenantname" lay-search="">
<option value="">全部</option>
<option value="非SAAS服务商">非SAAS服务商</option>
<option th:each="item : ${tenant_list}" th:text="${item.name}" th:value="${item.name}"></option>
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">设备号</label>
<div class="layui-input-inline">
<input type="text" name="deviceid" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">项目号</label>
<div class="layui-input-inline">
<input type="text" name="project_id" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">桩号</label>
<div class="layui-input-inline">
<input type="text" name="sector" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">固件版本</label>
<div class="layui-input-inline">
<input type="text" name="appver" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">使用状态</label>
<div class="layui-input-inline">
<select name="opmode" id="opmode" lay-verify="required" lay-search="">
<option value="-1">全部</option>
<option value="0">正常</option>
<option value="1">维护</option>
<option value="2">停用</option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
</div>
</div>
</form>
</div>
</fieldset>
<table class="layui-hide" id="app-table" lay-filter="app-table"></table>
</div>
</div>
<br><br>
<script type="text/html" id="app-toolbar">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="upgrade" id="btn_upgrade">升级</button>
</div>
</script>
</div>
</div>
<script src="../lib/layui-v2.6.3/layui.js" charset="utf-8"></script>
<script th:inline="javascript">
layui.use(['form', 'table'], function () {
var $ = layui.$,
form = layui.form,
table = layui.table;
var cfg_cols = [
{type: 'checkbox', fixed: 'left'},
{field: 'deviceid', title: '设备号', width: 100, sort: true},
{field: 'remark', title: '备注', width: 100},
{field: 'project_id', title: '项目号', width: 120, sort: true},
{field: 'sector', title: '桩号', width: 120, sort: true},
{field: 'name', title: '监测点号', width: 80},
{field: 'devicetype', title: '类型', width: 80,templet: "<div>{{d.devicetype==0?'监测站':'基准站'}}</div>"},
{field: 'parentid', title: '基站编号', width: 80, sort: true},
{field: 'tenantname', title: '所属组织', width: 120},
{field: 'opmode', title: '使用状态', width: 80,templet: '#modeTrans'},
{field: 'model', title: '型号', width: 80,templet: "<div>{{d.model==0?'G505':'G510'}}</div>"},
{field: 'appver', title: '固件版本', width: 80}
];
/**
* 初始化表单,要加上,不然刷新部分组件可能会不加载
*/
form.render();
table.render({
elem: '#app-table',
url: '/sys/ver_mgr/gnss_device_list',
toolbar: '#app-toolbar',
defaultToolbar: ['filter','exports'],
cols: [
cfg_cols
],
limits: [10, 20, 50],
limit: 10,
page: true,
skin: 'line'
});
table.render({
elem: '#firmware-table'
,url:'/sys/ver_mgr/firmware_list'
,cols: [
[
{type: 'radio', fixed: 'left'}
,{field:'name', title:'固件', sort: true}
,{field:'length', title:'长度(字节)'}
,{field:'create_time', title:'时间', sort: true, templet: "<div>{{layui.util.toDateString(d.create_time, 'yyyy-MM-dd HH:mm:ss')}}</div>"}
]
]
,page: true
});
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
var result = JSON.stringify(data.field);
//执行搜索重载
table.reload('app-table', {
page: {
curr: 1
}
, where: {
searchParams: result
}
}, 'data');
table.render();
return false;
});
/**
* toolbar事件监听
*/
//头工具栏事件
table.on('toolbar(app-table)', function(obj){
let $ = layui.$;
switch(obj.event){
case 'upgrade':
var optionStatus = table.checkStatus('firmware-table')//获取选中行状态
var fdata = optionStatus.data;
var checkStatus = table.checkStatus('app-table')//获取选中行状态
var adata = checkStatus.data;
if(fdata.length>0 && adata.length>0){
var firmware = fdata[0]["name"];
var device_list = "";
for(var i=0;i<adata.length; i++){
device_list = device_list+adata[i]["deviceid"]+";";
}
$.ajax({
type:"POST",
url:"/sys/ver_mgr/upgrade",
data: {
'firmware': firmware,
'id_list': device_list
},
success: function (result) {
if(result.code == 0) layer.msg('升级进行中...');
else layer.alert(result.msg);
},
error: function () {
console.log("ajax error");
}
});
}
break;
};
});
});
function onDeviceCfgUpdated(){
layui.table.reload('currentTableId');
}
</script>
<script type="text/html" id="modeTrans">
{{# if(d.opmode == 0){ }}
<span class="layui-badge layui-bg-green">正常</span>
{{# } else if(d.opmode == 1){ }}
<span class="layui-badge layui-bg-blue">维护</span>
{{# } else { }}
<span class="layui-badge layui-bg-gray">停用</span>
{{# } }}
</script>
<script type="text/html" id="synTrans">
{{# if(d.syn == 0){ }}
<span class="layui-badge layui-bg-orange"></span>
{{# } else { }}
<span class="layui-badge layui-bg-green"></span>
{{# } }}
</script>
</body>
</html>

View File

@ -22,7 +22,7 @@
<div class="layui-inline"> <div class="layui-inline">
<label class="layui-form-label required">设备编号</label> <label class="layui-form-label required">设备编号</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input"> <input type="text" name="deviceid" id="deviceid" th:field="*{deviceid}" lay-verify="required" lay-reqtext="不能为空" placeholder="请输入设备编号" value="" class="layui-input" onblur="checkDeviceId()">
</div> </div>
</div> </div>
<div class="layui-inline"> <div class="layui-inline">
@ -57,14 +57,21 @@
</div> </div>
</div> </div>
<div class="layui-inline" pane=""> <div class="layui-inline" pane="">
<label class="layui-form-label">星座</label> <label class="layui-form-label">内置电池</label>
<div class="layui-input-block"> <div class="layui-input-inline">
<input type="checkbox" name="like1[write]" lay-skin="primary" title="北斗" checked="" disabled=""> <select name="has_battery" id="has_battery" th:field="*{has_battery}" lay-verify="required" lay-search="">
<input type="checkbox" name="like1[read]" lay-skin="primary" title="GPS" disabled=""> <option value="0"></option>
<input type="checkbox" name="like1[game]" lay-skin="primary" title="GLONASS" disabled=""> <option value="1"></option>
<input type="checkbox" name="like1[game]" lay-skin="primary" title="Galileo" disabled=""> </select>
</div> </div>
</div> </div>
<div class="layui-inline" pane="">
<label class="layui-form-label">电压系数</label>
<div class="layui-input-inline">
<input type="text" name="voltage_factor" id="voltage_factor" th:field="*{voltage_factor}" class="layui-input">
</div>
<div class="layui-form-mid layui-word-aux">*0.1</div>
</div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<div class="layui-inline"> <div class="layui-inline">
@ -288,6 +295,7 @@
}); });
function setEcefEditor(){ function setEcefEditor(){
var $ = layui.$; var $ = layui.$;
console.log($('#devicetype').val(), $('#model').val()); console.log($('#devicetype').val(), $('#model').val());
@ -299,6 +307,20 @@
} }
} }
function checkDeviceId(){
var $ = layui.$;
var value = $("#deviceid").val();
console.log(value);
if(value.startsWith("6") || value.startsWith("7")){
$("#has_battery").val('1');
$("#has_battery").attr('disabled',true);
}
else{
$("#has_battery").attr('disabled',false);
}
layui.form.render();
}
</script> </script>
</body> </body>

View File

@ -56,6 +56,12 @@
<version>5.5.8</version> <version>5.5.8</version>
</dependency> </dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.78.Final</version>
</dependency>
</dependencies> </dependencies>

104
sec-vermgr/pom.xml Normal file
View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.imdroid</groupId>
<artifactId>security-monitor</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>sec-vermgr</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.78.Final</version>
</dependency>
<dependency>
<groupId>com.imdroid</groupId>
<artifactId>sec-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.imdroid</groupId>
<artifactId>sec-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 矩阵工具 -->
<dependency>
<groupId>org.ejml</groupId>
<artifactId>ejml-all</artifactId>
<version>0.41</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>central</id>
<name>ali-mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@ -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];
}
}

View File

@ -0,0 +1,394 @@
package com.imdroid.vermgr.service;
import com.imdroid.vermgr.entity.DeviceApp;
import com.imdroid.vermgr.utils.CRC16;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Arrays;
@Slf4j
public class UpgradeSession {
public static final byte COM_OK = 0;
public static final byte COM_ERROR = 1;
public static final byte COM_ABORT = 2;
public static final byte COM_TIMEOUT = 3;
public static final byte COM_DATA = 4;
public static final byte COM_LIMIT = 5;
public static final byte COM_SAVE_ERR = 6;
public static final byte COM_FILE_ERR = 7;
public static final byte SOH = 0x01; /* start of 128-byte data packet */
public static final byte STX = 0x02; /* start of 1024-byte data packet */
public static final byte EOT = 0x04; /* end of transmission */
public static final byte ACK = 0x06; /* acknowledge */
public static final byte NAK = 0x15; /* negative acknowledge */
public static final byte CA = 0x18; /* two of these in succession aborts transfer */
public static final byte CC = 0x43; /* 'C' == 0x43, request 16-bit CRC */
public static final short PACKET_HEADER_SIZE = 3;
public static final short PACKET_TRAILER_SIZE = 2;
public static final short PACKET_SIZE = 128;
public static final short PACKET_1K_SIZE = 1024;
static private String version_path="firmware";
private DeviceApp deviceApp;
File firmware;
FileInputStream reader;
short tx_seq = 0;
int tx_bytes = 0;
byte nak_num = 0;
byte[] header_buff;
byte[] tailer_buff;
byte[] soh_buff;
byte[] stx_buff;
boolean session_done = false;
boolean file_done = false;
boolean is_file_accepted = false;
byte result = COM_OK;
public UpgradeSession(DeviceApp app) {
deviceApp = app;
firmware = null;
reader = null;
header_buff = new byte[PACKET_HEADER_SIZE];
tailer_buff = new byte[PACKET_TRAILER_SIZE];
soh_buff = new byte[PACKET_SIZE];
stx_buff = new byte[PACKET_1K_SIZE];
}
public byte getResult(){
return result;
}
public DeviceApp getDeviceApp(){return deviceApp;}
public long getSN(){return deviceApp.getDevice_sn();}
public int getState() {return deviceApp.getUpgrade_state();}
public int getTxPercentage() {
if(firmware == null) return 0;
return (int) (tx_bytes*100/firmware.length());
}
public void reset(){
session_done = true;
file_done = true;
is_file_accepted = false;
tx_seq = 0;
nak_num = 0;
tx_bytes = 0;
try {
if(reader!=null) reader.close();
}
catch (IOException e){
log.error(firmware+" reader.close()",e);
}
}
public boolean start(Channel channel) {
session_done = false;
file_done = false;
is_file_accepted = false;
tx_seq = 0;
nak_num = 0;
tx_bytes = 0;
result = COM_FILE_ERR;
String file_name = deviceApp.getFirmware();
if(file_name != null){
firmware = new File(version_path+"/" + file_name);
if (firmware.exists()) {
try {
if(reader!=null) reader.close();
reader = new FileInputStream(firmware);
result = COM_OK;
}
catch (FileNotFoundException e) {
log.error(firmware+" reader open",e);
reset();
} catch (IOException e) {
log.error(firmware+" reader open",e);
reset();
}
}
}
// 发升级指示
if(result != COM_OK){
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
deviceApp.setUpgrade_result(result);
return false;
}
else {
long sn = deviceApp.getDevice_sn();
byte[] ind_msg = new byte[5];
ind_msg[0] = VerManager.UPGRADE_IND;
ind_msg[1] = (byte) (sn >> 24);
ind_msg[2] = (byte) (sn >> 16);
ind_msg[3] = (byte) (sn >> 8);
ind_msg[4] = (byte) sn;
byte tailer_buff[] = {0, 0};
int crc = CRC16.calculate(ind_msg);
tailer_buff[0] = (byte) (crc >> 8);
tailer_buff[1] = (byte) crc;
channel.write(ind_msg);
channel.writeAndFlush(tailer_buff);
log.info(deviceApp.getDevice_sn()+": send UPGRADE_IND");
deviceApp.setUpgrade_state(DeviceApp.STATE_UPGRADING);
return true;
}
}
public boolean restart(Channel channel) {
session_done = false;
file_done = false;
is_file_accepted = false;
tx_seq = 0;
nak_num = 0;
result = COM_FILE_ERR;
try {
if(reader!=null) reader.close();
reader = new FileInputStream(firmware);
result = COM_OK;
}
catch (FileNotFoundException e) {
log.error(firmware+" reader open failed: ",e);
reset();
} catch (IOException e) {
log.error(firmware+" reader open failed: ",e);
reset();
}
return (result == COM_OK);
}
public void reject(){
result = COM_ERROR;
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
deviceApp.setUpgrade_result(result);
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
}
public void onTimeout(){
result = COM_TIMEOUT;
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
deviceApp.setUpgrade_result(result);
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
}
public boolean procYmodem(Channel channel, byte[] rx_data){
if(session_done) return true;
try {
switch (rx_data[0]) {
case CC:
log.info("C");
if(tx_seq!=0){
//这种情况说明上次接收端意外中断了需要重新开始
restart(channel);
}
if (!is_file_accepted) {
sendFileInfo(channel);
} else {
tx_seq++;
sendPacket(channel);
//sendSmallPacket(channel);
}
break;
case ACK:
if(rx_data.length == 4)
log.info("ACK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
else log.info("ACK");
nak_num = 0;
if (!is_file_accepted) {
is_file_accepted = true;
}
else if(!file_done){
tx_seq++;
sendPacket(channel);
//sendSmallPacket(channel);
}
if(file_done) {
is_file_accepted=false;
tx_seq=0;
}
break;
case NAK:
if(rx_data.length == 4)
log.info("NAK "+rx_data[1]+" "+(rx_data[2]<<8|((short)rx_data[3]&0xFF)));
else log.info("NAK");
nak_num++;
if(nak_num>=3){
log.info("too much NAK, cancel transmit");
result = COM_ERROR;
reset();
}
else resendPacket(channel);
break;
case CA:
log.info("CA");
result = rx_data[1];
reset();
break;
default:
log.info("unknown ind received: "+rx_data[0]+" "+rx_data[1]);
break;
}
}
catch (IOException e){
reset();
log.error(firmware+" reader error: ",e);
result = COM_FILE_ERR;
}
if(result == COM_ERROR || result==COM_FILE_ERR){
header_buff[0] = CA;
channel.writeAndFlush(header_buff);
}
if(session_done){
if(result == COM_OK) {
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_COMPLETED);
}
else{
deviceApp.setUpgrade_state(DeviceApp.STATE_TX_FAILED);
}
deviceApp.setUpgrade_result(result);
deviceApp.setUpgrade_time(new Timestamp(System.currentTimeMillis()));
}
return session_done;
}
void sendFileInfo(Channel channel){
header_buff[0] = SOH;
header_buff[1] = (byte) tx_seq;
header_buff[2] = (byte) ~tx_seq;
Arrays.fill(soh_buff, (byte) 0);
if(file_done) {
reset();//end of session
}
else{
int pos = 0;
byte[] file_name = firmware.getName().getBytes();
for (int i = 0; i < file_name.length; i++) soh_buff[pos++] = file_name[i];
soh_buff[pos++] = 0;
String strFileSize = "" + firmware.length();
byte[] file_size = strFileSize.getBytes();
for (int i = 0; i < file_size.length; i++) soh_buff[pos++] = file_size[i];
soh_buff[pos++] = 0;
}
int crc = CRC16.calculate(soh_buff);
tailer_buff[0] = (byte) (crc>>8);
tailer_buff[1] = (byte) crc;
channel.write(header_buff);
channel.write(soh_buff);
channel.writeAndFlush(tailer_buff);
log.info(deviceApp.getDevice_sn()+": send file info");
}
void sendPacket(Channel channel) throws IOException {
if(tx_seq==0) return;
if(file_done){
header_buff[0] = EOT;
channel.writeAndFlush(header_buff);
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
return;
}
header_buff[0] = STX;
header_buff[1] = (byte) tx_seq;
header_buff[2] = (byte) ~tx_seq;
int read_bytes = reader.read(stx_buff);
if(read_bytes != -1) tx_bytes+=read_bytes;
if(read_bytes == -1 || read_bytes==0){
// send EOT
header_buff[0] = EOT;
channel.writeAndFlush(header_buff);
file_done = true;
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
return;
}
else if(read_bytes>=PACKET_1K_SIZE){
}
else if(read_bytes<PACKET_1K_SIZE && read_bytes>PACKET_SIZE){
for(int i=read_bytes; i<PACKET_1K_SIZE; i++) stx_buff[i]=0x1A;
}
else{
header_buff[0] = SOH;
Arrays.fill(soh_buff, (byte) 0x1A);
for(int i=0; i<read_bytes; i++) soh_buff[i] = stx_buff[i];
}
int crc = CRC16.calculate((header_buff[0] == STX)?stx_buff:soh_buff);
tailer_buff[0] = (byte) (crc>>8);
tailer_buff[1] = (byte) crc;
channel.write(header_buff);
channel.write((header_buff[0] == STX)?stx_buff:soh_buff);
channel.writeAndFlush(tailer_buff);
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
}
void sendSmallPacket(Channel channel) throws IOException {
if(tx_seq==0) return;
if(file_done){
header_buff[0] = EOT;
channel.writeAndFlush(header_buff);
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
return;
}
header_buff[0] = SOH;
header_buff[1] = (byte) tx_seq;
header_buff[2] = (byte) ~tx_seq;
int read_bytes = reader.read(soh_buff);
if(read_bytes != -1) tx_bytes+=read_bytes;
if(read_bytes == -1 || read_bytes==0){
// send EOT
header_buff[0] = EOT;
channel.writeAndFlush(header_buff);
file_done = true;
log.info(deviceApp.getDevice_sn()+": send end of tx, seq="+tx_seq);
return;
}
else if(read_bytes>=PACKET_SIZE){
}
else{
for(int i=read_bytes; i<PACKET_SIZE; i++) soh_buff[i]=0x1A;
}
int crc = CRC16.calculate(soh_buff);
tailer_buff[0] = (byte) (crc>>8);
tailer_buff[1] = (byte) crc;
channel.write(header_buff);
channel.write(soh_buff);
channel.writeAndFlush(tailer_buff);
log.info(deviceApp.getDevice_sn()+": send packet "+tx_seq);
}
void resendPacket(Channel channel){
if(file_done){
header_buff[0] = EOT;
channel.writeAndFlush(header_buff);
}
else {
channel.write(header_buff);
channel.write((header_buff[0] == STX) ? stx_buff : soh_buff);
channel.writeAndFlush(tailer_buff);
}
log.info(deviceApp.getDevice_sn()+": resend packet "+header_buff[1]);
}
}

View File

@ -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如果当前处于升级完毕状态则更新待升级列表回应答
* 2TCP通道激活 ---> 获取版本信息 ---> 关联TCP通道和待升级的设备
* 3启动升级会话 ---> 实时更新会话
* 数据结构deviceId,cur_ver,upgrade_firmware,TCP_channel,upgrade_session
* ****************************************************/
@Slf4j
@Service
public class VerManager {
public static final byte GET_APP_INFO = (byte) 0xA0;
public static final byte APP_INFO_IND = (byte) 0xA1;
public static final byte UPGRADE_IND = (byte) 0xA2;
public static final byte NOT_UPGRADE_IND = (byte) 0xA3;
public static final byte UPGRADE_ACK = (byte) 0xA4;
public static final int NOT_APP_INFO = 0;
public static final int UPGRADING = 1;
public static final int UPGRADE_DONE= 2;
public static final int STATE_IDLE = 0;
public static final int STATE_VER_GETTING = 1;
public static final int STATE_UPGRADING = 2;
int mgr_state;
int ack_expected;
int ack_num;
// thread safety set and map
Map<Long, DeviceApp> upgradeDeviceList;
Map<Channel, UpgradeSession> sessionChannelMap;
@Data //加这个注解才能用JSON转换
class WebSocketMsg {
byte state = STATE_IDLE;
byte state_changed = 0;
long sn = 0;
String msg;
}
TimerTask operationTimerTask = null;
TimerTask txProgressTimerTask = null;
Timer timer; //获取app info操作升级操作的定时器
public static final VerManager INSTANCE = new VerManager();
private VerManager(){
mgr_state = STATE_IDLE;
upgradeDeviceList = new ConcurrentHashMap<Long, DeviceApp>();
sessionChannelMap = new ConcurrentHashMap<Channel, UpgradeSession>();
timer = new Timer();
operationTimerTask = null;
txProgressTimerTask = null;
}
synchronized public int getState(){
return mgr_state;
}
synchronized public boolean addAckNum(){
if(ack_num<ack_expected) ack_num++;
return (ack_num==ack_expected);
}
synchronized public boolean isAllUpgraded(){
for(UpgradeSession session:sessionChannelMap.values()){
if(session.getState() == DeviceApp.STATE_UPGRADING) {
return false;
}
}
return true;
}
void startOperationTimerTask(int delay){
if(operationTimerTask == null) {
operationTimerTask = new TimerTask() {
@Override
public void run() {
onOperationTimeout();
}
};
}
timer.schedule(operationTimerTask,delay);
}
void stopOperationTimerTask(){
if(operationTimerTask != null){
operationTimerTask.cancel();
operationTimerTask = null;
}
}
void startTxProgressTimerTask(){
if(txProgressTimerTask==null){
txProgressTimerTask = new TimerTask() {
@Override
public void run() {
onRefreshTxProgress();
}
};
}
timer.schedule(txProgressTimerTask,1000,1000);
}
void stopTxProgressTimerTask(){
if(txProgressTimerTask != null){
txProgressTimerTask.cancel();
txProgressTimerTask = null;
log.info("stopTxProgressTimerTask");
}
}
void onOperationTimeout(){
log.info("operation timeout!");
// 检查session状态
if(mgr_state == STATE_UPGRADING){
for(UpgradeSession session:sessionChannelMap.values()){
if(session.getState() == DeviceApp.STATE_UPGRADING) {
session.onTimeout();
deviceAppMapper.setUpgrade(session.getDeviceApp());
}
}
}
changeState(STATE_IDLE);
}
void onRefreshTxProgress(){
WebSocketMsg webSocketMsg = new WebSocketMsg();
webSocketMsg.state = (byte) mgr_state;
webSocketMsg.state_changed = 0;
for(UpgradeSession session:sessionChannelMap.values()){
if(session.getState() == DeviceApp.STATE_UPGRADING) {
webSocketMsg.sn = session.getSN();
webSocketMsg.msg = session.getTxPercentage() + "%";
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
}
}
}
synchronized void changeState(int new_state){
mgr_state = new_state;
log.info("transfer to state "+new_state);
// 通知UI
WebSocketMsg webSocketMsg = new WebSocketMsg();
webSocketMsg.state = (byte) mgr_state;
webSocketMsg.state_changed = 1;
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
switch(mgr_state){
case STATE_IDLE:
stopOperationTimerTask();
stopTxProgressTimerTask();
break;
case STATE_VER_GETTING:
startOperationTimerTask(5000);
break;
case STATE_UPGRADING:
startOperationTimerTask(60000);
startTxProgressTimerTask();
break;
}
}
void onSessionChanged(UpgradeSession session){
// 通知UI
WebSocketMsg webSocketMsg = new WebSocketMsg();
webSocketMsg.state = (byte) mgr_state;
webSocketMsg.sn = session.getSN();
switch (session.getState()){
case DeviceApp.STATE_UPGRADING:
webSocketMsg.msg = "升级中";
break;
case DeviceApp.STATE_TX_COMPLETED:
webSocketMsg.msg = "传输完成";
break;
case DeviceApp.STATE_TX_FAILED:
webSocketMsg.msg = "传输失败";
break;
}
WebSocketServer.sendMessageToAll(JSON.toJSONString(webSocketMsg));
}
public void onChannelActive(Channel channel){
// get app info
Long deviceId = (Long) channel.attr(AttributeKey.valueOf("deviceId")).get();
if(deviceId == null){
getVerInfo(channel);
}
}
public void onChannelInactive(Channel channel){
// 更新状态为未连接
setDeviceAppState(channel, DeviceApp.STATE_DISCONNECTED);
channelSet.remove(channel);
sessionChannelMap.remove(channel);
log.info("channel inactive. total channel num: "+channelSet.size());
}
void setDeviceAppState(Channel channel, short state){
long sn = (long) channel.attr(AttributeKey.valueOf("sn")).get();
if(sn > 0){
DeviceApp deviceApp = deviceAppMapper.queryById(sn);
if (deviceApp != null) {
deviceApp.setUpgrade_state(state);
deviceAppMapper.setUpgrade(deviceApp);
}
}
}
/**** 获取终端版本信息 ****
* 这个函数是controller调用为避免多个用户同时操作要避免多线程同时调用
*/
public void getVerInfo(Channel channel){
// 向所有连接终端发读取版本请求
byte get_app_info[] = {GET_APP_INFO,0};
byte tailer_buff[] = {0,0};
int crc = CRC16.calculate(get_app_info);
tailer_buff[0] = (byte) (crc>>8);
tailer_buff[1] = (byte) crc;
channel.write(get_app_info);
channel.writeAndFlush(tailer_buff);
}
void onVerInfoReceived(Channel channel, byte[] rx_data){
log.info(channel.remoteAddress()+": received app info");
// 记录获取到的版本信息
if(rx_data.length<9) return;
int crc = CRC16.calculate(rx_data, 7);
int crc0 = (((short)rx_data[7]&0xFF)<<8) | ((short)rx_data[8]&0xFF);
if(crc != crc0) {
log.info(channel.remoteAddress()+": crc error");
return;
}
int i=1;
long sn = 0;
for(i=1;i<=4;i++){
sn = (sn<<8) | ((short)rx_data[i]&0xFF);
}
String cur_ver = ((short)rx_data[i++]&0xFF) + "." + ((short)rx_data[i++]&0xFF);
// 更新APP记录并检查此终端是否需要升级
DeviceApp deviceApp = upgradeDeviceList.get(sn);
if (deviceApp == null) return;
deviceApp.setVersion(cur_ver);
//记录这个channel是有应答的
channel.attr(AttributeKey.valueOf("deviceId")).set(sn);
// 如果原来是未连接状态则改为连接如果是其他状态则保持不变
if(deviceApp.getUpgrade_state() == DeviceApp.STATE_DISCONNECTED) {
deviceApp.setUpgrade_state(DeviceApp.STATE_CONNECTED);
}
}
/**** 批量升级 ****/
Channel getChannelBySn(long sn){
for(Channel channel: channelSet){
long id = (long) channel.attr(AttributeKey.valueOf("sn")).get();
boolean acked = (boolean) channel.attr(AttributeKey.valueOf("acked")).get();
if(acked && (id == sn)){
return channel;
}
}
return null;
}
synchronized public boolean upgradeBatch(String firmware, ArrayList<Long> id_list){
if(mgr_state != STATE_IDLE) return false;
changeState(STATE_UPGRADING);
// 清除上次的session
for(UpgradeSession session : sessionChannelMap.values()){
session.reset();
}
sessionChannelMap.clear();
// 创建升级session
int upgrade_expected = 0;
for(long sn:id_list){
DeviceApp deviceApp = deviceAppMapper.queryById(sn);
Channel channel = getChannelBySn(sn);
if(deviceApp!=null && channel!=null){
deviceApp.setFirmware(firmware);
UpgradeSession session = new UpgradeSession(deviceApp);
sessionChannelMap.put(channel,session);
// 启动升级会话这里边会改变deviceApp的状态
if(session.start(channel)) {
upgrade_expected++;
}
deviceAppMapper.setUpgrade(deviceApp);
}
}
if(upgrade_expected == 0) changeState(STATE_IDLE);
ack_expected = upgrade_expected;
ack_num = 0;
return true;
}
/**** 消息处理 ****
* 允许多线程操作
*/
void outputHex(byte[] rx_data)
{
byte[] hex = new byte[rx_data.length*3];
int pos = 0;
for(int i=0; i<rx_data.length; i++){
hex[pos] = (byte) (((short)rx_data[i]&0xFF)>>4);
if(hex[pos]<=9) hex[pos]+='0';
else hex[pos] = (byte) (hex[pos] - 0xA + 'A');
pos++;
hex[pos] = (byte) (rx_data[i]&0x0F);
if(hex[pos]<=9) hex[pos]+='0';
else hex[pos] = (byte) (hex[pos] - 0xA + 'A');
pos++;
hex[pos++] = ' ';
}
log.info(new String(hex));
}
public void procMsg(Channel channel, byte[] rx_data) {
//outputHex(rx_data);
if(mgr_state == STATE_IDLE){
// 临时空闲状态下如果连续3次收到C则认为有终端主动请求升级
if(rx_data[0]==UpgradeSession.CC && rx_data.length<=4){
//onUpgradeRequest(channel);
}
}
if(mgr_state == STATE_UPGRADING){
UpgradeSession session = sessionChannelMap.get(channel);
if(session == null) return;
boolean session_done = false;
if(rx_data[0]==UPGRADE_ACK){
log.info("upgrade ack");
//outputHex(rx_data);
if(rx_data[5]!=0) {
//终端拒绝升级原因是产品序列号不对
session.reject();
session_done = true;
}
}
else {
session_done = session.procYmodem(channel, rx_data);
}
if(session_done){
deviceAppMapper.setUpgrade(session.getDeviceApp());
if(isAllUpgraded()){
//全部发完等待新版本启动回应答
stopTxProgressTimerTask();
}
onSessionChanged(session);
}
}
// 查询状态或升级状态下都会收到这个指示
if(rx_data[0]==APP_INFO_IND && rx_data.length>2) {
onVerInfoReceived(channel, rx_data);
}
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,71 @@
package com.imdroid.vermgr.service;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
@Service
@Slf4j
public class VersionServer {
@Value("${version_server_port}")
private int data_port;
@PostConstruct
public void init() {
// 创建socket服务线程阻塞
new Thread(new Runnable() {
@Override
public void run() {
log.info("dtu config server starting...");
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
//.option(ChannelOption.SO_REUSEADDR, true)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(new ByteArrayDecoder());
p.addLast(new ByteArrayEncoder());
p.addLast(new IdleStateHandler(300, 300, 300)); //设置心跳超时时间
p.addLast(new VersionHandler());
}
});
// Start the server.
ChannelFuture f = b.bind(data_port).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("error occurred during version server running: "+e);
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}).start();
}
}

View File

@ -0,0 +1,87 @@
package com.imdroid.vermgr.utils;
public class CRC16 {
static final char[] TABLE1021 = { /* CRC1021余式表 */
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108,
0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231,
0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339,
0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462,
0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a,
0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653,
0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b,
0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4,
0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc,
0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5,
0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd,
0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6,
0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97,
0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f,
0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188,
0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080,
0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9,
0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1,
0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea,
0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2,
0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db,
0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3,
0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c,
0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844,
0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d,
0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75,
0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26,
0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f,
0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17,
0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };
public static int calculate(byte[] buf, int length) {
int counter;
int crc = 0;
if(length > buf.length) return 0;
for (counter = 0; counter < length; counter++) {
crc = (crc << 8) ^ TABLE1021[((crc >> 8) ^ buf[counter]) & 0x00FF];
}
return crc & 0xFFFF;
}
public static int calculate(byte[] buf) {
return calculate(buf, buf.length);
}
}