Browse Source

v1.1.4 | 添加打印功能

master
zhaohe 3 months ago
parent
commit
af8b3032d1
  1. 2
      src/main/java/a8k/app/constant/AppConstant.java
  2. 8
      src/main/java/a8k/app/controler/api/v1/app/data/ReactionResultControler.java
  3. 7
      src/main/java/a8k/app/hardware/channel/A8kCanBusConnection.java
  4. 8
      src/main/java/a8k/app/hardware/channel/A8kCanBusService.java
  5. 83
      src/main/java/a8k/app/hardware/channel/LisUartChannelConnection.java
  6. 164
      src/main/java/a8k/app/hardware/channel/PrinterUartChannel.java
  7. 16
      src/main/java/a8k/app/service/appsetup/A8kSubModuleRegInitService.java
  8. 7
      src/main/java/a8k/app/service/data/ReactionRecordMgrService.java
  9. 151
      src/main/java/a8k/app/service/peripheral/PrinterService.java
  10. 4
      src/main/java/a8k/app/service/statemgr/GStateMgrService.java
  11. 1
      src/main/java/a8k/app/service/utils/ReactionPlate2DCodeHelper.java
  12. 1
      src/main/java/a8k/app/utils/ZStringUtils.java
  13. 3
      src/main/java/a8k/extui/mgr/ExtApiPageGroupCfgMgr.java
  14. 58
      src/main/java/a8k/extui/page/driver/PrinterDebugPage.java
  15. 6
      src/main/java/a8k/extui/page/extsetting/db/ReactionRecordMgrDebugPage.java
  16. 1
      src/main/resources/application.yml

2
src/main/java/a8k/app/constant/AppConstant.java

@ -5,7 +5,7 @@ public class AppConstant {
public static final int CONSUMABLE_COL_NUM = 5;
public static final int CONSUMABLE_ROW_NUM = 5;
public static final int TIP_NUM = 120;
public static final String APP_VERSION = "1.1.2";
public static final String APP_VERSION = "1.1.4";
public static final int CONSUMABLE_CHANNEL_NUM = 6;

8
src/main/java/a8k/app/controler/api/v1/app/data/ReactionResultControler.java

@ -1,6 +1,7 @@
package a8k.app.controler.api.v1.app.data;
import a8k.app.service.data.ReactionRecordMgrService;
import a8k.app.service.peripheral.PrinterService;
import a8k.app.service.statemgr.GStateMgrService;
import a8k.app.dao.type.db.ReactionReport;
import a8k.app.type.ui.ApiRet;
@ -29,6 +30,9 @@ public class ReactionResultControler {
@Resource
ReactionRecordMgrService reactionRecordMgrService;
@Resource
PrinterService printerService;
@PostMapping("/getAllRecords")
public ApiRet<List<ReactionReport>> getAllRecords() {
@ -43,7 +47,7 @@ public class ReactionResultControler {
@PostMapping("/printfRecord")
public ApiRet<Void> printfRecord(Integer id) {
reactionRecordMgrService.printfRecord(id);
printerService.print(reactionRecordMgrService.getRecordById(id));
return ApiRet.success();
}
@ -54,7 +58,7 @@ public class ReactionResultControler {
@PostMapping("/printfRecords")
public ApiRet<Void> printfRecord(@RequestBody IDList ids) {
for (Integer i : ids.ids) {
reactionRecordMgrService.printfRecord(i);
printerService.print(reactionRecordMgrService.getRecordById(i));
}
return ApiRet.success();
}

7
src/main/java/a8k/app/hardware/channel/A8kCanBusConnection.java

@ -15,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import java.net.URI;
@ -38,6 +39,10 @@ public class A8kCanBusConnection extends WebSocketClient {
String cmdchurl = null;
Boolean firstCall = true;
@Value("${device.enableCanBus}")
Boolean enableCanBus;
static class ProcessContext {
BlockingQueue<A8kPacket> receiptQueue = new LinkedBlockingQueue<>(); //
@ -160,6 +165,8 @@ public class A8kCanBusConnection extends WebSocketClient {
//
@Scheduled(fixedRate = 1000)
private void autoConnect() {
if(!enableCanBus) return;
if (!isOpen()) {
if (getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) {
try {

8
src/main/java/a8k/app/hardware/channel/A8kCanBusService.java

@ -8,6 +8,7 @@ import a8k.app.utils.ByteArray;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.URISyntaxException;
@ -18,9 +19,16 @@ public class A8kCanBusService {
@Resource
A8kCanBusConnection connection;
@Value("${device.enableCanBus}")
Boolean enableCanBus;
@PostConstruct
public void init() throws URISyntaxException {
if (enableCanBus) {
connection.connect();
}else{
log.warn("canBus is disabled");
}
}
/**

83
src/main/java/a8k/app/hardware/channel/LisUartChannelConnection.java

@ -1,83 +0,0 @@
package a8k.app.hardware.channel;
import a8k.app.config.IflytophaldConnectionConfig;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.net.URI;
//
//@Slf4j
//@Component
//@RequiredArgsConstructor
//public class LisUartChannelConnection {
// WebSocketClient conn;
// IflytophaldConnectionConfig iflytophaldConnectionConfig;
//
// @PostConstruct
// void init() {
// conn = new WebSocketClient(URI.create(iflytophaldConnectionConfig.getDatachUrl("lis"))) {
// @Override
// public void onOpen(ServerHandshake serverHandshake) {
// LisUartChannelConnection.this.onOpen(serverHandshake);
// }
//
// @Override
// public void onMessage(String s) {
// LisUartChannelConnection.this.onMessage(s);
// }
//
// @Override
// public void onClose(int i, String s, boolean b) {
// LisUartChannelConnection.this.onClose(i, s, b);
// }
//
// @Override
// public void onError(Exception e) {
// LisUartChannelConnection.this.onError(e);
// }
// };
// }
//
//
// public void onOpen(ServerHandshake serverHandshake) {
// log.info("a8k canbus connect sucess");
// }
//
// public void onMessage(String s) {
// }
//
// public void onClose(int i, String s, boolean b) {
// log.warn("a8k canbus lost connection...");
// }
//
// public void onError(Exception e) {
// log.info("a8k can-websocket-channel on error");
// }
//
// //
// // PRIVATE
// //
// @Scheduled(fixedRate = 1000)
// private void autoConnect() {
// if (!conn.isOpen()) {
// if (conn.getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) {
// try {
// conn.connect();
// } catch (IllegalStateException ignored) {
// }
// } else if (conn.getReadyState().equals(ReadyState.CLOSED)) {
// conn.reconnect();
// }
// }
// }
//}
//

164
src/main/java/a8k/app/hardware/channel/PrinterUartChannel.java

@ -0,0 +1,164 @@
package a8k.app.hardware.channel;
import a8k.OS;
import a8k.app.config.IflytophaldConnectionConfig;
import a8k.app.utils.ByteArray;
import a8k.app.utils.ZStringUtils;
import cn.hutool.core.util.ByteUtil;
import cn.hutool.core.util.CharsetUtil;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
@Slf4j
@Component
@RequiredArgsConstructor
public class PrinterUartChannel {
WebSocketClient conn;
private final IflytophaldConnectionConfig config;
private Queue<byte[]> sendQueue = new LinkedList<>();
@PostConstruct
void init() {
String url = config.getDatachUrl("printer");
log.info("PrinterUartChannel url: {}", url);
conn = new WebSocketClient(URI.create(url)) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
PrinterUartChannel.this.onOpen(serverHandshake);
}
@Override
public void onMessage(String s) {
PrinterUartChannel.this.onMessage(s);
}
@Override
public void onClose(int i, String s, boolean b) {
PrinterUartChannel.this.onClose(i, s, b);
}
@Override
public void onError(Exception e) {
PrinterUartChannel.this.onError(e);
}
};
conn.connect();
}
/**
* 打印字符串
* @param fmt 格式化字符串
* @param args 参数
* 1. 打印%时候需要使用%%否则会报错
*
*/
public void printf(String fmt, Object... args) {
if (conn.isOpen()) {
String s = String.format(fmt, args);
try {
byte[] toSend = s.getBytes("GBK");
pushToSend(toSend);
} catch (UnsupportedEncodingException e) {
log.error("charset error", e);
}
} else {
log.warn("PrinterUartChannel is not open");
}
}
public void printfEnd() {
if (conn.isOpen()) {
printf("\r\n");
printf("\r\n");
printf("\r\n");
}
}
//
// PRIVATE
//
private void onOpen(ServerHandshake serverHandshake) {
log.info("a8k canbus connect sucess");
}
private void onMessage(String s) {
}
private void onClose(int i, String s, boolean b) {
log.warn("a8k canbus lost connection...");
}
private void onError(Exception e) {
log.info("a8k can-websocket-channel on error");
}
synchronized private void pushToSend(byte[] toSend) {
sendQueue.add(toSend);
}
synchronized private byte[] popToSend() {
if (!sendQueue.isEmpty()) {
return sendQueue.poll();
}
return null;
}
// conn.send(toSend);
// if (toSend.length > 10) {
// OS.forceSleep(150);
// }
@Scheduled(fixedRate = 400)
private void autoSend() {
while (true) {
var tosend = popToSend();
if (tosend != null) {
if (conn.isOpen()) {
conn.send(tosend);
}
if (tosend.length > 2) {
break;
}
} else {
break;
}
}
}
@Scheduled(fixedRate = 1000)
private void autoConnect() {
if (!conn.isOpen()) {
if (conn.getReadyState().equals(ReadyState.NOT_YET_CONNECTED)) {
try {
conn.connect();
} catch (IllegalStateException ignored) {
}
} else if (conn.getReadyState().equals(ReadyState.CLOSED)) {
conn.reconnect();
}
}
}
}

16
src/main/java/a8k/app/service/appsetup/A8kSubModuleRegInitService.java

@ -86,6 +86,9 @@ public class A8kSubModuleRegInitService {
log.info("=======================================================");
log.info("= dump all sub board version...... =");
log.info("=");
boolean hasDiffVersion = false;
Integer maxVersion = null;
for (MId mid : MId.values()) {
if (mid == MId.NotSet) {
continue;
@ -93,12 +96,25 @@ public class A8kSubModuleRegInitService {
try {
Integer ver = canBus.moduleReadVersion(mid);
String moduleType = canBus.moduleReadType(mid).toString();
if (maxVersion != null && !maxVersion.equals(ver)) {
hasDiffVersion = true;
}
if (maxVersion == null || ver > maxVersion) {
maxVersion = ver;
}
log.info(String.format("+ %-40s(%d): type:%-20s version:%s", mid, mid.index, moduleType, ver));
} catch (AppException e) {
log.error(String.format("+ %20s(%d): offline", mid, mid.index));
}
}
if (!hasDiffVersion) {
gStateMgrService.setMcuVersion(String.format("V%04d", maxVersion));
} else {
//版本不一致标记为(*)表示需要升级
gStateMgrService.setMcuVersion(String.format("V%04d(*)", maxVersion));
}
}

7
src/main/java/a8k/app/service/data/ReactionRecordMgrService.java

@ -96,11 +96,11 @@ public class ReactionRecordMgrService {
}
}
public void printfRecord(Integer id) {
ReactionReport record = reactionReportDao.findById(id);
logger.info("printfRecord: {}", ZJsonHelper.objToPrettyJson(record));
public ReactionReport getRecordById(Integer id) {
return reactionReportDao.findById(id);
}
public void exportRecordByLIS(Integer id) {
ReactionReport record = reactionReportDao.findById(id);
logger.info("exportRecord: {}", ZJsonHelper.objToPrettyJson(record));
@ -129,7 +129,6 @@ public class ReactionRecordMgrService {
}
public ReactionReport findById(Integer id) {
return reactionReportDao.findById(id);
}

151
src/main/java/a8k/app/service/peripheral/PrinterService.java

@ -0,0 +1,151 @@
package a8k.app.service.peripheral;
import a8k.app.dao.type.db.ProjExtInfoCard;
import a8k.app.dao.type.db.ReactionReport;
import a8k.app.hardware.channel.PrinterUartChannel;
import a8k.app.optalgo.type.ReactionResultStatus;
import a8k.app.service.data.ProjInfoMgrService;
import a8k.app.service.statemgr.GStateMgrService;
import a8k.app.type.a8k.BloodType;
import a8k.app.type.a8k.opt.A8kOptType;
import a8k.app.type.a8k.opt.OptScanResult;
import a8k.app.type.a8k.proj.A8kResultUnit;
import a8k.app.type.a8k.proj.ProjInfo;
import a8k.app.type.a8k.state.SampleInfo;
import a8k.app.type.exception.AppException;
import a8k.extui.factory.FakeOptScanResultFactory;
import a8k.extui.factory.ProjExtInfoCardFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class PrinterService {
private final PrinterUartChannel printer;
private final ProjInfoMgrService projInfoMgrService;
private final GStateMgrService gStateMgrService;
String bloodTypeToString(BloodType bloodType) {
return switch (bloodType) {
case WHOLE_BLOOD -> "全血";
case SERUM_OR_PLASMA -> "血清/血浆";
};
}
public void printDemoReactionReport() {
ProjExtInfoCard.A8kResultUintFn resultBuilder0 = //
new ProjExtInfoCard.A8kResultUintFn(A8kResultUnit.coi, A8kResultUnit.iuPml, A8kResultUnit.uPl, 2.0, 2.0, 3.0, 3.0);
ProjExtInfoCard.A8kResultUintFn resultBuilder1 =//
new ProjExtInfoCard.A8kResultUintFn(A8kResultUnit.coi, A8kResultUnit.iuPml, A8kResultUnit.uPl, 2.0, 2.0, 3.0, 3.0);
ProjExtInfoCard.A8kResultUintFn resultBuilder2 =//
new ProjExtInfoCard.A8kResultUintFn(A8kResultUnit.coi, A8kResultUnit.iuPml, A8kResultUnit.uPl, 2.0, 2.0, 3.0, 3.0);
List<ReactionReport.ReactionResult> reactionResults = new ArrayList<>();
reactionResults.add(new ReactionReport.ReactionResult("Tn-I", "Tn-I", 1.0, resultBuilder0.toResultUnitConverters()));
reactionResults.add(new ReactionReport.ReactionResult("CK-MB", "CK-MB", 2.0, resultBuilder1.toResultUnitConverters()));
reactionResults.add(new ReactionReport.ReactionResult("Myoglobin", "MG", 3.0, resultBuilder2.toResultUnitConverters()));
ReactionReport record = new ReactionReport();
record.sampleBloodType = BloodType.WHOLE_BLOOD;
record.sampleBarcode = "ASDF123456789";
record.sampleUserid = "U1023";
record.sampleId = "1122334456";
record.projName = "HSCrp";
record.lotId = "CA2312345";
record.projId = 1;
record.setExpiryDate(new Date());
record.operator = "张三";
record.projShortName = "CA";
record.appVersion = gStateMgrService.getAppVersion();
record.mcuVersion = gStateMgrService.getMcuVersion();
record.sn = gStateMgrService.getSn();
record.subProjNum = reactionResults.size();
record.projInfoIdCardVersion = 1;
record.projInfo = null;
record.results = reactionResults;
record.detailOptData = null;
print(record);
}
public void print(ReactionReport report) {
/*
* 日期: 2023-10-01 12:00:00
* 操作人: 张三
* 软件版本: 1.0.0
* MCU版本: 1.0.0
* 仪器序列号: 123456789
*
* UID:sampleUserid
* 条码ID: sampleBarcode
*
* 样本类型:全血/血清/血浆
* 项目名称:
* 批次号:
*
* <子项目名称>
* 结果:
* 结果(单位1):
* 结果(单位2):
*
* <子项目名称>
* 结果:
* 结果(单位1):
* 结果(单位2):
*
*/
// 定义日期格式
if(report == null) {
return;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
printer.printf("*****************************\r\n");
printer.printf(" 日期: %s\r\n", dateFormat.format(report.creatDate));
printer.printf(" 操作人: %s\r\n", report.operator);
printer.printf(" 软件版本: %s\r\n", report.appVersion);
printer.printf(" MCU版本: %s\r\n", report.mcuVersion);
printer.printf(" 仪器序列号: %s\r\n", report.sn);
printer.printf(" \r\n");
printer.printf(" \r\n");
printer.printf(" UID: %s\r\n", report.sampleUserid);
printer.printf(" BARCODE: %s\r\n", report.sampleBarcode);
printer.printf(" \r\n");
printer.printf(" 样本类型: %s\r\n", bloodTypeToString(report.sampleBloodType));
printer.printf(" 项目名称: %s\r\n", report.projName);
printer.printf(" 批次号: %s\r\n", report.lotId);
printer.printf(" \r\n");
printer.printf(" \r\n");
for (var result : report.results) {
if (result == null)
continue;
printer.printf(" 子项目名称: %s\r\n", result.subProjName);
if (result.status.equals(ReactionResultStatus.SUCCESS)) {
printer.printf(" 结果: %s\r\n", result.result);
for (int j = 0; j < result.resultConverters.size(); j++) {
var converter = result.resultConverters.get(j);
if (converter == null)
continue;
printer.printf(" 结果(单位%d): %s\r\n", j + 1, converter.convert(result.result));
}
printer.printf(" \r\n");
} else {
printer.printf(" ERROR\r\n");
printer.printf(" %s\r\n", result.errorInfo);
printer.printf(" \r\n");
}
}
printer.printf("*****************************\r\n");
printer.printfEnd();
}
}

4
src/main/java/a8k/app/service/statemgr/GStateMgrService.java

@ -28,12 +28,10 @@ public class GStateMgrService {
//MCU版本
private String mcuVersion = "NOTSET";
//设备SN
private String sn = "NOTSET";
private String sn = "SN25010001";//25年第一个月第一台设备后续改成通过10板的单片机获取
SensorState sensorState = new SensorState();
public Boolean isDeviceInited() {
return deviceInited;
}

1
src/main/java/a8k/app/service/utils/ReactionPlate2DCodeHelper.java

@ -10,6 +10,7 @@ import java.text.SimpleDateFormat;
public class ReactionPlate2DCodeHelper {
// 1||CAGGB66U||2024.03.26||1083||06
// 1||CAHAC66U|2025.4.30||514||04
static public ReactionPlate2DCode parse(String code) {
String[] parts = code.split("\\|\\|");
ReactionPlate2DCode ret = new ReactionPlate2DCode();

1
src/main/java/a8k/app/utils/ZStringUtils.java

@ -47,4 +47,5 @@ public class ZStringUtils {
public static Boolean isNullOrEmpty(String str) {
return str == null || str.isEmpty();
}
}

3
src/main/java/a8k/extui/mgr/ExtApiPageGroupCfgMgr.java

@ -126,7 +126,8 @@ public class ExtApiPageGroupCfgMgr {
new Menu(MotorCtrlPage.class, "电机驱动"),
new Menu(MiniServoCtrlPage.class, "伺服电机驱动"),
new Menu(InputIOStateScannerPage.class, "输入IO状态查看"),
new Menu(CodeScanerDriverCtrlPage.class, "条码扫描仪")
new Menu(CodeScanerDriverCtrlPage.class, "条码扫描仪"),
new Menu(PrinterDebugPage.class, "打印机")
)));

58
src/main/java/a8k/extui/page/driver/PrinterDebugPage.java

@ -0,0 +1,58 @@
package a8k.extui.page.driver;
import a8k.OS;
import a8k.app.hardware.channel.PrinterUartChannel;
import a8k.app.hardware.driver.PipetteCtrlDriver;
import a8k.app.service.peripheral.PrinterService;
import a8k.extui.mgr.ExtApiPageMgr;
import a8k.extui.type.ExtUIPageCfg;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class PrinterDebugPage {
private final PrinterUartChannel printerUartChannel;
private final ExtApiPageMgr extApiPageMgr;
private final PrinterService printerService;
public void printf(String str) {
printerUartChannel.printf(str + "\r\n");
}
public void printerTest() {
printerUartChannel.printf("ABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n");
OS.forceSleep(100);
printerUartChannel.printf("abcdefghijklmnopqrstuvwxyz\r\n");
OS.forceSleep(100);
printerUartChannel.printf("0123456789\r\n");
OS.forceSleep(100);
printerUartChannel.printf("!@#$%%*()^_+\r\n");
OS.forceSleep(100);
printerUartChannel.printf("~`!@#$%%^&*()_+|\\{}[]:\";'<>?,./\r\n");
OS.forceSleep(100);
printerUartChannel.printf("中文测试\r\n");
OS.forceSleep(100);
printerUartChannel.printfEnd();
}
public void printDemoReactionReport() {
printerService.printDemoReactionReport();
}
@PostConstruct
void init() {
ExtUIPageCfg page = new ExtUIPageCfg(this);
page.addFunction("printf", this::printf);
page.addFunction("printerTest", this::printerTest);
page.addFunction("打印报告(示例)", this::printDemoReactionReport);
extApiPageMgr.addPage(page);
}
}

6
src/main/java/a8k/extui/page/extsetting/db/ReactionRecordMgrDebugPage.java

@ -1,5 +1,6 @@
package a8k.extui.page.extsetting.db;
import a8k.app.service.peripheral.PrinterService;
import a8k.app.type.exception.AppException;
import a8k.app.optalgo.type.ReactionResultStatus;
import a8k.app.controler.filemgr.StorageControler;
@ -39,6 +40,9 @@ public class ReactionRecordMgrDebugPage {
ReactionRecordMgrService reactionRecordMgrService;
@Resource
PrinterService printerService;
@Resource
FileMgrService fileMgrService;
public ExtUiTable getReactionRecordList() {
@ -96,7 +100,7 @@ public class ReactionRecordMgrDebugPage {
}
public void printfRecord(Integer id) {
reactionRecordMgrService.printfRecord(id);
printerService.print(reactionRecordMgrService.findById(id));
}

1
src/main/resources/application.yml

@ -4,6 +4,7 @@ server:
#device.runmode: "RealMode"
a8k.enableTemperatureCtrl: false
device.runmode: "RealMode"
device.enableCanBus: true
iflytophald:
# ip: 192.168.8.10

Loading…
Cancel
Save