diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt index f9e3636..6a7381d 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt @@ -6,6 +6,7 @@ import com.ffii.fpsms.modules.jobOrder.entity.JobOrder import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendRequest import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendResponse +import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2MarkInfoResponse import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SettingsResponse import com.ffii.fpsms.modules.jobOrder.web.model.LaserLastReceiveSuccessDto import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest @@ -351,6 +352,91 @@ class PlasticBagPrinterService( } } + /** + * TCP text queries on the same [LASER_PRINT] host/port as Bag2 send (UTF-8, line-terminated commands). + * Uses one connection per command so the plugin can answer independently. + */ + fun getLaserBag2MarkInfo(): LaserBag2MarkInfoResponse { + val host = resolveLaserBag2Host() + val port = resolveLaserBag2Port() + val (dataVal, dataErr) = sendLaserBag2QueryCommand(host, port, "GetMarkData") + val (statusVal, statusErr) = sendLaserBag2QueryCommand(host, port, "GetMarkStatus") + val (countVal, countErr) = sendLaserBag2QueryCommand(host, port, "GetMarkedCount") + val statusInt = statusVal?.let { parseFirstIntToken(it) } + val countInt = countVal?.let { parseFirstIntToken(it) } + val errs = listOfNotNull( + dataErr?.let { "GetMarkData:$it" }, + statusErr?.let { "GetMarkStatus:$it" }, + countErr?.let { "GetMarkedCount:$it" }, + ) + return LaserBag2MarkInfoResponse( + host = host, + port = port, + markData = dataVal, + markStatus = statusInt, + markStatusLabel = markStatusToLabel(statusInt), + markedCount = countInt, + rawMarkStatus = statusVal, + rawMarkedCount = countVal, + error = errs.joinToString(";").ifBlank { null }, + ) + } + + /** + * Sends [command] as UTF-8 with LF, half-closes output, reads one response chunk (same pattern as job send ack read). + * @return Pair(trimmed response or null, error message or null) + */ + private fun sendLaserBag2QueryCommand(ip: String, port: Int, command: String): Pair { + var socket: Socket? = null + try { + socket = Socket() + socket.soTimeout = 3000 + socket.connect(InetSocketAddress(ip, port), 3000) + val line = command.trim() + "\n" + val out = socket.getOutputStream() + out.write(line.toByteArray(StandardCharsets.UTF_8)) + out.flush() + try { + socket.shutdownOutput() + } catch (_: Exception) { + } + socket.soTimeout = 2500 + val buf = ByteArray(8192) + val n = socket.getInputStream().read(buf) + val raw = if (n > 0) { + String(buf, 0, n, StandardCharsets.UTF_8).trim() + } else { + "" + } + return raw to null + } catch (e: ConnectException) { + return null to "無法連線" + } catch (e: SocketTimeoutException) { + return null to "讀取逾時" + } catch (e: Exception) { + return null to (e.message ?: e.javaClass.simpleName) + } finally { + try { + socket?.close() + } catch (_: Exception) { + } + } + } + + private fun parseFirstIntToken(text: String): Int? { + val t = text.trim() + if (t.isEmpty()) return null + t.toIntOrNull()?.let { return it } + return Regex("""-?\d+""").find(t)?.value?.toIntOrNull() + } + + private fun markStatusToLabel(v: Int?): String? = when (v) { + 0 -> "待機" + 1 -> "打標中" + 2 -> "其他狀態" + else -> null + } + fun generatePrintJobBundle( itemCode: String, lotNo: String, diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt index cf7fec3..a606555 100644 --- a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt @@ -8,6 +8,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.Laser2Request import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2AutoSendReport import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendRequest import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendResponse +import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2MarkInfoResponse import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SettingsResponse import com.ffii.fpsms.modules.jobOrder.web.model.NgpclPushResponse import com.ffii.fpsms.modules.jobOrder.web.model.OnPackQrDownloadRequest @@ -37,6 +38,15 @@ class PlasticBagPrinterController( return plasticBagPrinterService.getLaserBag2Settings() } + /** + * Live laser TCP queries: `GetMarkData`, `GetMarkStatus` (0 idle / 1 marking / 2 other), `GetMarkedCount`. + * Uses [LASER_PRINT.host] / [LASER_PRINT.port] (same as print-laser-bag2). + */ + @GetMapping("/laser-bag2-mark-info") + fun getLaserBag2MarkInfo(): LaserBag2MarkInfoResponse { + return plasticBagPrinterService.getLaserBag2MarkInfo() + } + /** * Job orders for /laserPrint: same as GET /py/job-orders but filtered by [LASER_PRINT.itemCodes] (comma-separated). * Blank itemCodes setting = no filter (all 包裝 job orders for the day). diff --git a/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2MarkInfoResponse.kt b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2MarkInfoResponse.kt new file mode 100644 index 0000000..fd7dea7 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/LaserBag2MarkInfoResponse.kt @@ -0,0 +1,22 @@ +package com.ffii.fpsms.modules.jobOrder.web.model + +/** + * Live laser TCP query results (EZCAD-style commands on same host/port as Bag2 laser send). + * + * [markStatus]: 0 = idle, 1 = marking, 2 = other (when the device returns a parseable digit). + */ +data class LaserBag2MarkInfoResponse( + val host: String, + val port: Int, + /** Response body from `GetMarkData` (current mark string / job data). */ + val markData: String? = null, + /** Parsed from `GetMarkStatus` when possible. */ + val markStatus: Int? = null, + /** Short label for [markStatus] (Traditional Chinese). */ + val markStatusLabel: String? = null, + val markedCount: Int? = null, + val rawMarkStatus: String? = null, + val rawMarkedCount: String? = null, + /** Non-fatal per-command issues (e.g. read timeout); fields may still be partially filled. */ + val error: String? = null, +)