From 496a38bc811b0561fe998883f27a08ee39ce33d8 Mon Sep 17 00:00:00 2001 From: maehw Date: Fri, 13 Dec 2024 23:13:27 +0100 Subject: [PATCH 01/11] Let printImage() return job ID, add PrinterManager::getJobDetails(), add metricsThread() plus dependencies --- src/logic/BoothLogic.cpp | 80 ++++++++++++++++++++++++++++++++++-- src/logic/BoothLogic.h | 30 ++++++++++++-- src/logic/PrinterManager.cpp | 61 ++++++++++++++++++++++++--- src/logic/PrinterManager.h | 15 ++++++- 4 files changed, 172 insertions(+), 14 deletions(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index e1c9931..1468a25 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -43,10 +43,11 @@ bool BoothLogic::start() { return false; // Start the threads - isLogicThreadRunning = isCameraThreadRunning = isPrinterThreadRunning = true; + isLogicThreadRunning = isCameraThreadRunning = isPrinterThread = isMetricsThreadRunning = true; logicThreadHandle = boost::thread(boost::bind(&BoothLogic::logicThread, this)); cameraThreadHandle = boost::thread(boost::bind(&BoothLogic::cameraThread, this)); printThreadHandle = boost::thread(boost::bind(&BoothLogic::printerThread, this)); + metricsThreadHandle = boost::thread(boost::bind(&BoothLogic::metricsThread, this)); return true; } @@ -94,6 +95,11 @@ void BoothLogic::stop(bool update_mode) { LOG_D(TAG, "waiting for print"); printThreadHandle.join(); } + isMetricsThreadRunning = false; + if (metricsThreadHandle.joinable()) { + LOG_D(TAG, "waiting for metrics"); + metricsThreadHandle.join(); + } if (gui != nullptr) { gui->stop(); @@ -368,6 +374,10 @@ void BoothLogic::printerThread() { LOG_D(TAG, "[Printer Thread] Processing image. Printing enabled: ", std::to_string(do_print));; if (do_print) { + ImagePrintMetrics metrics = {}; + metrics.jobState = STATE_UNKNOWN; + clock_gettime(CLOCK_MONOTONIC, &metrics.processingTs); + // We need the final jpeg image. So lock the mutex { boost::unique_lock lk(jpegImageMutex); @@ -396,6 +406,8 @@ void BoothLogic::printerThread() { LOG_D(TAG, "[Printer Thread] Prepared"); } + clock_gettime(CLOCK_MONOTONIC, &metrics.awaitUserDecisionTs); + { LOG_D(TAG, "[Printer Thread] Waiting for user to decide if they want to print"); boost::unique_lock lk(printerStateMutex); @@ -404,6 +416,7 @@ void BoothLogic::printerThread() { } } + clock_gettime(CLOCK_MONOTONIC, &metrics.gotUserDecisionTs); // We need the info if the user wants to print or not if (printConfirmationEnabled) { @@ -411,10 +424,11 @@ void BoothLogic::printerThread() { boost::unique_lock lk(cancelOrConfirmPrintMutex); if (printConfirmed) { LOG_D(TAG, "[Printer Thread] Printing (user explicitely confirmed)"); - printerManager.printImage(); + metrics.cupsJobId = printerManager.printImage(); } else { LOG_D(TAG, "[Printer Thread] Print not confirmed (auto-canceling)!"); printerManager.cancelPrint(); + metrics.cupsJobId = -1; } } else { @@ -422,12 +436,24 @@ void BoothLogic::printerThread() { boost::unique_lock lk(cancelOrConfirmPrintMutex); if (!printCanceled) { LOG_D(TAG, "[Printer Thread] Printing (user did not cancel)"); - printerManager.printImage(); + metrics.cupsJobId = printerManager.printImage(); } else { LOG_D(TAG, "[Printer Thread] Print canceled by user!"); printerManager.cancelPrint(); + metrics.cupsJobId = -1; } } + + // if job ID > 0, start checking this job from the metrics thread + // (append to list and activate thread if not already active); otherwise just log + if(metrics.cupsJobId > 0) { + boost::unique_lock lk(printMetricsMutex); + printMetricsMutex.push_back(metrics); + // TODO: activate thread; how to signal metricsThread? + } else { + // TODO: log only timings known until here + LOG_D(TAG, "[Printer Thread] No print job available"); + } } else { // We need the final jpeg image. So lock the mutex { @@ -469,6 +495,54 @@ void BoothLogic::printerThread() { } } +void BoothLogic::metricsThread() { + LOG_D(TAG, "Starting Metrics Thread"); + + while (isMetricsThreadRunning) { + // TODO: put this thread to sleep; awake it only when a print job has been added to the list; + // stay active only as long as list of monitored print jobs is not empty + + size_t listLength = 0; + { + boost::unique_lock lk(printMetricsMutex); + + std::list::iterator it = printMetrics.begin(); + while (it != printMetrics.end()) { + int jobId = *it.cupsJobId; + + PrinterJobState jobState; + + time_t cupsCreationTs, cupsProcessingTs, cupsCompletedTs; + + bool gotJobDetails = printerManager.getJobDetails(jobId, jobState, cupsCreationTs, + cupsProcessingTs, cupsCompletedTs); + + if(gotJobDetails) { + *it.jobState = jobState; + *it.cupsCreationTs = cupsCreationTs; + *it.cupsProcessingTs = cupsProcessingTs; + *it.cupsCompletedTs = cupsCompletedTs; + + if (STATE_UNKNOWN == jobState || STATE_CANCELED == jobState || + STATE_ABORTED == jobState || STATE_COMPLETED == jobState) { + LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: ", std::to_string(jobId)); + printMetrics.erase(it++); + } else { + ++it; + } + } else { + ++it; + } + } + listLength = printMetrics.size(); + LOG_D(TAG, "[Metrics Thread] Length of metrics list: ", std::to_string(listLength)); + } + + // TODO: if listLength > 0, wake up periodically, otherwise wait for external activation from printerThread + boost::this_thread::sleep(boost::posix_time::milliseconds(5000)); + } +} + int BoothLogic::getFreeStorageSpaceMB() { if (imageDir.empty()) { LOG_E(TAG, "No image dir specified!"); diff --git a/src/logic/BoothLogic.h b/src/logic/BoothLogic.h index 603df5e..1dd1062 100644 --- a/src/logic/BoothLogic.h +++ b/src/logic/BoothLogic.h @@ -57,6 +57,25 @@ namespace selfomat { PRINTER_STATE_WORKING = 3 }; + struct ImagePrintMetrics { + // Timestamp when processing in printer thread has started + timespec processingTs; + // Timestamp when processing in printer thread has finished and waiting for user decision started + timespec awaitUserDecisionTs; + // Timestamp when user decision (confirmation or cancellation) has arrived in printer thread + timespec gotUserDecisionTs; + // Job ID returned from CUPS (or 0 on CUPS error or when print has been canceled) + int cupsJobId; + // Timestamp when the CUPS job was created + time_t cupsCreationTs; + // Timestamp when the CUPS job was processed + time_t cupsProcessingTs; + // Timestamp when the CUPS job was completed + time_t cupsCompletedTs; + // Job state returned by CUPS and converted to PrinterManager-specific type + PrinterJobState jobState; + }; + class BoothLogic : public ILogicController { public: explicit BoothLogic(ICamera *camera, IGui *gui, bool has_button, const string &button_port, bool has_flash, @@ -115,7 +134,7 @@ namespace selfomat { PrinterManager printerManager; ImageProcessor imageProcessor; - bool isLogicThreadRunning, isCameraThreadRunning, isPrinterThreadRunning; + bool isLogicThreadRunning, isCameraThreadRunning, isPrinterThreadRunning, isMetricsThreadRunning; boost::mutex triggerMutex; bool triggered; @@ -153,6 +172,7 @@ namespace selfomat { boost::thread logicThreadHandle; boost::thread cameraThreadHandle; boost::thread printThreadHandle; + boost::thread metricsThreadHandle; int filterChoice = 0; double filterGain = 1.0; @@ -165,10 +185,9 @@ namespace selfomat { void logicThread(); - - - void printerThread(); + + void metricsThread(); void triggerFlash(); @@ -182,6 +201,9 @@ namespace selfomat { FILTER getFilter(); timespec triggerStart; + + std::list printMetrics; + boost::mutex printMetricsMutex; public: bool isStopped(); void trigger(); diff --git a/src/logic/PrinterManager.cpp b/src/logic/PrinterManager.cpp index 749b211..1d23efd 100644 --- a/src/logic/PrinterManager.cpp +++ b/src/logic/PrinterManager.cpp @@ -200,25 +200,24 @@ bool PrinterManager::resumePrinter() { return true; } -bool PrinterManager::printImage() { +int PrinterManager::printImage() { if(!hasImagePrepared || printer_name.empty()) - return false; + return 0; resumePrinter(); + // Job ID or 0 on error int job_id = cupsCreateJob(CUPS_HTTP_DEFAULT, printer_name.c_str(), "self-o-mat", 0, nullptr); if (job_id > 0) { LOG_D(TAG, "successfully created the print job"); cupsStartDocument(CUPS_HTTP_DEFAULT, printer_name.c_str(), job_id, "my_image", "image/png", 1); - cupsWriteRequestData(CUPS_HTTP_DEFAULT, (const char *) imageTmpBuffer, sizeOfPreparedImage); cupsFinishDocument(CUPS_HTTP_DEFAULT, printer_name.c_str()); - return true; } - return false; + return job_id; } PrinterManager::PrinterManager(ILogger *logger) : logger(logger) { @@ -249,3 +248,55 @@ bool PrinterManager::cancelPrint() { return true; } +PrinterJobState PrinterManager::getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs) { + + if(jobId <= 0) + return false; + + cups_job_t *cupsJobs; + int cupsJobCount = cupsGetJobs2(CUPS_HTTP_DEFAULT, &cupsJobs, NULL, 0, CUPS_WHICHJOBS_ALL); + LOG_D(TAG, "Number of print jobs:", std::to_string(cupsJobCount)); + + for(int i = 0; i < cupsJobCount; i++) { + if (cupsJobs[i].id == jobId) { + LOG_D(TAG, "Found print job with ID:", std::to_string(jobId)); + + creationTs = cupsJobs[i].creation_time; + processingTs = cupsJobs[i].processing_time; + completedTs = cupsJobs[i].completed_time; + + switch(cupsJobs[i].state) + { + // map known values + case IPP_JOB_PENDING: + state = STATE_PENDING; + break; + case IPP_JOB_HELD: + state = STATE_HELD; + break; + case IPP_JOB_PROCESSING: + state = STATE_PROCESSING; + break; + case IPP_JOB_STOPPED: + state = STATE_STOPPED; + break; + case IPP_JOB_CANCELLED: + state = STATE_CANCELED; + break; + case IPP_JOB_ABORTED: + state = STATE_ABORTED; + break; + case IPP_JOB_COMPLETED: + state = STATE_COMPLETED; + break; + default: + state = STATE_UNKNOWN; + break; + } + return true; + } + } + + return false; +} + diff --git a/src/logic/PrinterManager.h b/src/logic/PrinterManager.h index a0979c9..17f4c1d 100644 --- a/src/logic/PrinterManager.h +++ b/src/logic/PrinterManager.h @@ -30,6 +30,17 @@ namespace selfomat { STATE_STOPPED }; + enum PrinterJobState { + STATE_UNKNOWN, + STATE_PENDING, + STATE_HELD, + STATE_PROCESSING, + STATE_STOPPED, + STATE_CANCELED, + STATE_ABORTED, + STATE_COMPLETED + }; + class PrinterManager { private: static std::string TAG; @@ -74,11 +85,11 @@ namespace selfomat { bool resumePrinter(); - bool printImage(); + int printImage(); bool cancelPrint(); - + bool getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs); }; } } From 44862c44c4c182178e51ca676dc5a2bfded135b5 Mon Sep 17 00:00:00 2001 From: maehw Date: Mon, 16 Dec 2024 11:34:57 +0100 Subject: [PATCH 02/11] Fix build errors and improve logging: fix enum naming, some other minor fixes --- src/logic/BoothLogic.cpp | 50 ++++++++++++++++++++++++------------ src/logic/PrinterManager.cpp | 48 ++++++++++++++++++++++++---------- src/logic/PrinterManager.h | 18 +++++++------ 3 files changed, 78 insertions(+), 38 deletions(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 1468a25..01dd1a2 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -43,7 +43,7 @@ bool BoothLogic::start() { return false; // Start the threads - isLogicThreadRunning = isCameraThreadRunning = isPrinterThread = isMetricsThreadRunning = true; + isLogicThreadRunning = isCameraThreadRunning = isPrinterThreadRunning = isMetricsThreadRunning = true; logicThreadHandle = boost::thread(boost::bind(&BoothLogic::logicThread, this)); cameraThreadHandle = boost::thread(boost::bind(&BoothLogic::cameraThread, this)); printThreadHandle = boost::thread(boost::bind(&BoothLogic::printerThread, this)); @@ -375,8 +375,8 @@ void BoothLogic::printerThread() { if (do_print) { ImagePrintMetrics metrics = {}; - metrics.jobState = STATE_UNKNOWN; - clock_gettime(CLOCK_MONOTONIC, &metrics.processingTs); + metrics.jobState = JOB_STATE_UNKNOWN; + clock_gettime(CLOCK_REALTIME, &metrics.processingTs); // We need the final jpeg image. So lock the mutex { @@ -406,7 +406,7 @@ void BoothLogic::printerThread() { LOG_D(TAG, "[Printer Thread] Prepared"); } - clock_gettime(CLOCK_MONOTONIC, &metrics.awaitUserDecisionTs); + clock_gettime(CLOCK_REALTIME, &metrics.awaitUserDecisionTs); { LOG_D(TAG, "[Printer Thread] Waiting for user to decide if they want to print"); @@ -416,7 +416,7 @@ void BoothLogic::printerThread() { } } - clock_gettime(CLOCK_MONOTONIC, &metrics.gotUserDecisionTs); + clock_gettime(CLOCK_REALTIME, &metrics.gotUserDecisionTs); // We need the info if the user wants to print or not if (printConfirmationEnabled) { @@ -448,7 +448,7 @@ void BoothLogic::printerThread() { // (append to list and activate thread if not already active); otherwise just log if(metrics.cupsJobId > 0) { boost::unique_lock lk(printMetricsMutex); - printMetricsMutex.push_back(metrics); + printMetrics.push_back(metrics); // TODO: activate thread; how to signal metricsThread? } else { // TODO: log only timings known until here @@ -495,6 +495,8 @@ void BoothLogic::printerThread() { } } +#define CTIME_NO_NL(x) strtok(ctime(x), "\n") + void BoothLogic::metricsThread() { LOG_D(TAG, "Starting Metrics Thread"); @@ -505,29 +507,43 @@ void BoothLogic::metricsThread() { size_t listLength = 0; { boost::unique_lock lk(printMetricsMutex); - + std::list::iterator it = printMetrics.begin(); while (it != printMetrics.end()) { - int jobId = *it.cupsJobId; + int jobId = it->cupsJobId; PrinterJobState jobState; - + time_t cupsCreationTs, cupsProcessingTs, cupsCompletedTs; bool gotJobDetails = printerManager.getJobDetails(jobId, jobState, cupsCreationTs, cupsProcessingTs, cupsCompletedTs); if(gotJobDetails) { - *it.jobState = jobState; - *it.cupsCreationTs = cupsCreationTs; - *it.cupsProcessingTs = cupsProcessingTs; - *it.cupsCompletedTs = cupsCompletedTs; - - if (STATE_UNKNOWN == jobState || STATE_CANCELED == jobState || - STATE_ABORTED == jobState || STATE_COMPLETED == jobState) { - LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: ", std::to_string(jobId)); + it->jobState = jobState; + it->cupsCreationTs = cupsCreationTs; + it->cupsProcessingTs = cupsProcessingTs; + it->cupsCompletedTs = cupsCompletedTs; + + if (JOB_STATE_UNKNOWN == jobState || JOB_STATE_CANCELED == jobState || + JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { + LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: #", std::to_string(jobId)); + LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); + LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); + LOG_D(TAG, "[Metrics Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); + LOG_D(TAG, "[Metrics Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); + LOG_D(TAG, "[Metrics Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); + LOG_D(TAG, "[Metrics Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); + LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + if(JOB_STATE_COMPLETED == jobState) { + double durationUntilProc = difftime(cupsProcessingTs, cupsCreationTs); + double durationProcCompl = difftime(cupsCompletedTs, cupsProcessingTs); + LOG_D(TAG, "[Metrics Thread] Creation -> processing: ", std::to_string(durationUntilProc)); + LOG_D(TAG, "[Metrics Thread] Processing -> completion: ", std::to_string(durationProcCompl)); + } printMetrics.erase(it++); } else { + LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); ++it; } } else { diff --git a/src/logic/PrinterManager.cpp b/src/logic/PrinterManager.cpp index 1d23efd..00eb5b7 100644 --- a/src/logic/PrinterManager.cpp +++ b/src/logic/PrinterManager.cpp @@ -248,19 +248,18 @@ bool PrinterManager::cancelPrint() { return true; } -PrinterJobState PrinterManager::getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs) { - +bool PrinterManager::getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs) { if(jobId <= 0) return false; cups_job_t *cupsJobs; int cupsJobCount = cupsGetJobs2(CUPS_HTTP_DEFAULT, &cupsJobs, NULL, 0, CUPS_WHICHJOBS_ALL); - LOG_D(TAG, "Number of print jobs:", std::to_string(cupsJobCount)); + LOG_D(TAG, "Number of print jobs: ", std::to_string(cupsJobCount)); for(int i = 0; i < cupsJobCount; i++) { if (cupsJobs[i].id == jobId) { - LOG_D(TAG, "Found print job with ID:", std::to_string(jobId)); - + LOG_D(TAG, "Found print job with ID #", std::to_string(jobId)); + creationTs = cupsJobs[i].creation_time; processingTs = cupsJobs[i].processing_time; completedTs = cupsJobs[i].completed_time; @@ -269,28 +268,28 @@ PrinterJobState PrinterManager::getJobDetails(int jobId, PrinterJobState &state, { // map known values case IPP_JOB_PENDING: - state = STATE_PENDING; + state = JOB_STATE_PENDING; break; case IPP_JOB_HELD: - state = STATE_HELD; + state = JOB_STATE_HELD; break; case IPP_JOB_PROCESSING: - state = STATE_PROCESSING; + state = JOB_STATE_PROCESSING; break; case IPP_JOB_STOPPED: - state = STATE_STOPPED; + state = JOB_STATE_STOPPED; break; case IPP_JOB_CANCELLED: - state = STATE_CANCELED; + state = JOB_STATE_CANCELED; break; case IPP_JOB_ABORTED: - state = STATE_ABORTED; + state = JOB_STATE_ABORTED; break; case IPP_JOB_COMPLETED: - state = STATE_COMPLETED; + state = JOB_STATE_COMPLETED; break; default: - state = STATE_UNKNOWN; + state = JOB_STATE_UNKNOWN; break; } return true; @@ -300,3 +299,26 @@ PrinterJobState PrinterManager::getJobDetails(int jobId, PrinterJobState &state, return false; } +const char* PrinterManager::printerJobStateToString(PrinterJobState &state) { + switch(state) + { + // map known values + case JOB_STATE_PENDING: + return "pending"; + case JOB_STATE_HELD: + return "held"; + case JOB_STATE_PROCESSING: + return "processing"; + case JOB_STATE_STOPPED: + return "stopped"; + case JOB_STATE_CANCELED: + return "canceled"; + case JOB_STATE_ABORTED: + return "aborted"; + case JOB_STATE_COMPLETED: + return "completed"; + case JOB_STATE_UNKNOWN: + default: + return "unknown"; + } +} diff --git a/src/logic/PrinterManager.h b/src/logic/PrinterManager.h index 17f4c1d..118a537 100644 --- a/src/logic/PrinterManager.h +++ b/src/logic/PrinterManager.h @@ -31,14 +31,14 @@ namespace selfomat { }; enum PrinterJobState { - STATE_UNKNOWN, - STATE_PENDING, - STATE_HELD, - STATE_PROCESSING, - STATE_STOPPED, - STATE_CANCELED, - STATE_ABORTED, - STATE_COMPLETED + JOB_STATE_UNKNOWN, + JOB_STATE_PENDING, + JOB_STATE_HELD, + JOB_STATE_PROCESSING, + JOB_STATE_STOPPED, + JOB_STATE_CANCELED, + JOB_STATE_ABORTED, + JOB_STATE_COMPLETED }; class PrinterManager { @@ -90,6 +90,8 @@ namespace selfomat { bool cancelPrint(); bool getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs); + + static const char* printerJobStateToString(PrinterJobState &state); }; } } From 9fec5f48c71589f9eb450d655e924f083746cc7a Mon Sep 17 00:00:00 2001 From: maehw Date: Mon, 16 Dec 2024 22:17:31 +0100 Subject: [PATCH 03/11] Add functionality to query job details via CUPS/IPP; detect printer problems --- src/logic/BoothLogic.cpp | 12 +++++- src/logic/PrinterManager.cpp | 79 ++++++++++++++++++++++++++++++++++++ src/logic/PrinterManager.h | 2 + 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 01dd1a2..271b00f 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -519,7 +519,7 @@ void BoothLogic::metricsThread() { bool gotJobDetails = printerManager.getJobDetails(jobId, jobState, cupsCreationTs, cupsProcessingTs, cupsCompletedTs); - if(gotJobDetails) { + if (gotJobDetails) { it->jobState = jobState; it->cupsCreationTs = cupsCreationTs; it->cupsProcessingTs = cupsProcessingTs; @@ -527,6 +527,7 @@ void BoothLogic::metricsThread() { if (JOB_STATE_UNKNOWN == jobState || JOB_STATE_CANCELED == jobState || JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { + // job is in a state where it is no longer monitored LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: #", std::to_string(jobId)); LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); @@ -543,10 +544,19 @@ void BoothLogic::metricsThread() { } printMetrics.erase(it++); } else { + // job is in a state where it will be still monitored LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + if (JOB_STATE_PROCESSING == jobState) { + timespec tnow; + clock_gettime(CLOCK_REALTIME, &tnow); + double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); + LOG_D(TAG, "[Metrics Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); + printerManager.getJobAttributes(jobId); + } ++it; } } else { + // didn't get job details but want to increment interator anyway ++it; } } diff --git a/src/logic/PrinterManager.cpp b/src/logic/PrinterManager.cpp index 00eb5b7..f6dfb75 100644 --- a/src/logic/PrinterManager.cpp +++ b/src/logic/PrinterManager.cpp @@ -1,4 +1,5 @@ #include +#include // // Created by clemens on 12.02.19. @@ -252,6 +253,8 @@ bool PrinterManager::getJobDetails(int jobId, PrinterJobState &state, time_t &cr if(jobId <= 0) return false; + boost::unique_lock lk(printerStateMutex); + cups_job_t *cupsJobs; int cupsJobCount = cupsGetJobs2(CUPS_HTTP_DEFAULT, &cupsJobs, NULL, 0, CUPS_WHICHJOBS_ALL); LOG_D(TAG, "Number of print jobs: ", std::to_string(cupsJobCount)); @@ -322,3 +325,79 @@ const char* PrinterManager::printerJobStateToString(PrinterJobState &state) { return "unknown"; } } + +bool PrinterManager::getJobAttributes(int jobId) { + if(jobId <= 0) + return false; + + boost::unique_lock lk(printerStateMutex); + + LOG_D(TAG, "Querying attributes for print job with ID #", std::to_string(jobId)); + + http_t *http; + ipp_t *request; + ipp_t *response; + char uri[HTTP_MAX_URI]; + char job_printer_state_reasons[2048]; + ipp_attribute_t *attr; + + http = httpConnect2(cupsServer(), + ippPort(), + NULL, + AF_UNSPEC, + cupsEncryption(), + 1, + 30000, + NULL); + + + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", ippPort(), "/printers/%s", printer_name.c_str()); + + request = ippNewRequest(IPP_OP_GET_JOB_ATTRIBUTES); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri); + ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", jobId); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser()); + ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", NULL, "job-printer-state-reasons"); + + response = cupsDoRequest(http, request, "/admin"); + + job_printer_state_reasons[0] = '\0'; + if ((attr = ippFindAttribute(response, "job-printer-state-reasons", IPP_TAG_KEYWORD)) != NULL) { + ippAttributeString(attr, job_printer_state_reasons, sizeof(job_printer_state_reasons)); + } + + ippDelete(response); + + httpClose(http); + + std::vector needsPaperReasons = {"media-empty-error", "media-needed"}; + std::vector needsInkReasons = {"marker-supply-empty-error"}; + std::string inputTrayMissingReason = "input-tray-missing"; + std::vector reasons; + + boost::split(reasons, + job_printer_state_reasons, + boost::is_any_of(", "), + boost::token_compress_on); + + bool needsPaper = false; + bool needsInk = false; + bool needsTray = false; + for (std::vector::iterator r = reasons.begin(); r != reasons.end(); ++r) { + LOG_D(TAG, "Job printer state reason: ", *r); + if (std::find(needsPaperReasons.begin(), needsPaperReasons.end(), *r) != needsPaperReasons.end()) { + LOG_D(TAG, "Printer needs paper!"); + needsPaper = true; + } + if (std::find(needsInkReasons.begin(), needsInkReasons.end(), *r) != needsInkReasons.end()) { + LOG_D(TAG, "Printer needs ink!"); + needsInk = true; + } + if (inputTrayMissingReason.compare(*r) == 0) { + LOG_D(TAG, "Printer needs input tray!"); + needsTray = true; + } + } + + return true; +} diff --git a/src/logic/PrinterManager.h b/src/logic/PrinterManager.h index 118a537..5ac7276 100644 --- a/src/logic/PrinterManager.h +++ b/src/logic/PrinterManager.h @@ -92,6 +92,8 @@ namespace selfomat { bool getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs); static const char* printerJobStateToString(PrinterJobState &state); + + bool getJobAttributes(int jobId); }; } } From 48f1250396d99ee462fa69680fb5cbe6d4f7d3e8 Mon Sep 17 00:00:00 2001 From: maehw Date: Tue, 17 Dec 2024 09:15:58 +0100 Subject: [PATCH 04/11] Add alarms for printer attention (ink, paper, tray); reduce logging --- i18n/de_frontend.json | 3 +++ src/logic/BoothLogic.cpp | 38 ++++++++++++++++++++++++------------ src/logic/PrinterManager.cpp | 21 ++++++++------------ src/logic/PrinterManager.h | 10 ++++++++-- 4 files changed, 45 insertions(+), 27 deletions(-) diff --git a/i18n/de_frontend.json b/i18n/de_frontend.json index 3e8bb5b..5b6055c 100644 --- a/i18n/de_frontend.json +++ b/i18n/de_frontend.json @@ -4,6 +4,9 @@ "template_saved": "Template wurde gespeichert", "print_in_progress": "Foto wird gedruckt...", "printer_error": "Drucker wurde gestoppt", + "printer_no_paper": "Druckerpapier fehlt", + "printer_no_ink": "Druckertinte leer", + "printer_no_tray": "Drucker-Papierkassette fehlt", "check_camera": "Prüfe deine Kamera", "no_storage_found": "Kein Speichermedium gefunden", "storage_space_low": "Geringe Speicherkapazität: ", diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 271b00f..09d0bfe 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -445,14 +445,12 @@ void BoothLogic::printerThread() { } // if job ID > 0, start checking this job from the metrics thread - // (append to list and activate thread if not already active); otherwise just log + // (append to list and activate thread if not already active); + // otherwise the already known timing values could be logged if(metrics.cupsJobId > 0) { boost::unique_lock lk(printMetricsMutex); printMetrics.push_back(metrics); - // TODO: activate thread; how to signal metricsThread? - } else { - // TODO: log only timings known until here - LOG_D(TAG, "[Printer Thread] No print job available"); + // TODO: explicitely activate thread; how to signal metricsThread? } } else { // We need the final jpeg image. So lock the mutex @@ -508,6 +506,8 @@ void BoothLogic::metricsThread() { { boost::unique_lock lk(printMetricsMutex); + unsigned int printerAttentionFlags = 0; + std::list::iterator it = printMetrics.begin(); while (it != printMetrics.end()) { int jobId = it->cupsJobId; @@ -529,9 +529,9 @@ void BoothLogic::metricsThread() { JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { // job is in a state where it is no longer monitored LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: #", std::to_string(jobId)); - LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); - LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); - LOG_D(TAG, "[Metrics Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); + //LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); + //LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); + //LOG_D(TAG, "[Metrics Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); LOG_D(TAG, "[Metrics Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); LOG_D(TAG, "[Metrics Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); LOG_D(TAG, "[Metrics Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); @@ -539,8 +539,8 @@ void BoothLogic::metricsThread() { if(JOB_STATE_COMPLETED == jobState) { double durationUntilProc = difftime(cupsProcessingTs, cupsCreationTs); double durationProcCompl = difftime(cupsCompletedTs, cupsProcessingTs); - LOG_D(TAG, "[Metrics Thread] Creation -> processing: ", std::to_string(durationUntilProc)); - LOG_D(TAG, "[Metrics Thread] Processing -> completion: ", std::to_string(durationProcCompl)); + LOG_D(TAG, "[Metrics Thread] Duration CUPS creation -> processing: ", std::to_string(durationUntilProc)); + LOG_D(TAG, "[Metrics Thread] Duration CUPS processing -> completion: ", std::to_string(durationProcCompl)); } printMetrics.erase(it++); } else { @@ -551,7 +551,7 @@ void BoothLogic::metricsThread() { clock_gettime(CLOCK_REALTIME, &tnow); double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); LOG_D(TAG, "[Metrics Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); - printerManager.getJobAttributes(jobId); + printerManager.checkPrinterAttentionFromJob(jobId, printerAttentionFlags); } ++it; } @@ -561,7 +561,21 @@ void BoothLogic::metricsThread() { } } listLength = printMetrics.size(); - LOG_D(TAG, "[Metrics Thread] Length of metrics list: ", std::to_string(listLength)); + //LOG_D(TAG, "[Metrics Thread] Length of metrics list: ", std::to_string(listLength)); + //LOG_D(TAG, "[Metrics Thread] Printer attention flags: ", std::to_string(printerAttentionFlags)); + + if ((printerAttentionFlags > 0) && printerEnabled) { + // as only one flag is checked at a time, they are ordered by attention relevance + if (printerAttentionFlags & PRINTER_ATTN_NO_INK) { + gui->addAlert(ALERT_PRINTER_JOB_HINT, getTranslation("frontend.printer_no_ink")); + } else if(printerAttentionFlags & PRINTER_ATTN_NO_TRAY) { + gui->addAlert(ALERT_PRINTER_JOB_HINT, getTranslation("frontend.printer_no_tray")); + } else if(printerAttentionFlags & PRINTER_ATTN_NO_PAPER) { + gui->addAlert(ALERT_PRINTER_JOB_HINT, getTranslation("frontend.printer_no_paper")); + } + } else { + gui->removeAlert(ALERT_PRINTER_JOB_HINT); + } } // TODO: if listLength > 0, wake up periodically, otherwise wait for external activation from printerThread diff --git a/src/logic/PrinterManager.cpp b/src/logic/PrinterManager.cpp index f6dfb75..1b6fe32 100644 --- a/src/logic/PrinterManager.cpp +++ b/src/logic/PrinterManager.cpp @@ -257,11 +257,11 @@ bool PrinterManager::getJobDetails(int jobId, PrinterJobState &state, time_t &cr cups_job_t *cupsJobs; int cupsJobCount = cupsGetJobs2(CUPS_HTTP_DEFAULT, &cupsJobs, NULL, 0, CUPS_WHICHJOBS_ALL); - LOG_D(TAG, "Number of print jobs: ", std::to_string(cupsJobCount)); + //LOG_D(TAG, "Number of print jobs: ", std::to_string(cupsJobCount)); for(int i = 0; i < cupsJobCount; i++) { if (cupsJobs[i].id == jobId) { - LOG_D(TAG, "Found print job with ID #", std::to_string(jobId)); + //LOG_D(TAG, "Found print job with ID #", std::to_string(jobId)); creationTs = cupsJobs[i].creation_time; processingTs = cupsJobs[i].processing_time; @@ -326,13 +326,13 @@ const char* PrinterManager::printerJobStateToString(PrinterJobState &state) { } } -bool PrinterManager::getJobAttributes(int jobId) { +void PrinterManager::checkPrinterAttentionFromJob(int jobId, unsigned int &flags) { if(jobId <= 0) - return false; + return; boost::unique_lock lk(printerStateMutex); - LOG_D(TAG, "Querying attributes for print job with ID #", std::to_string(jobId)); + //LOG_D(TAG, "Querying attributes for print job with ID #", std::to_string(jobId)); http_t *http; ipp_t *request; @@ -380,24 +380,19 @@ bool PrinterManager::getJobAttributes(int jobId) { boost::is_any_of(", "), boost::token_compress_on); - bool needsPaper = false; - bool needsInk = false; - bool needsTray = false; for (std::vector::iterator r = reasons.begin(); r != reasons.end(); ++r) { LOG_D(TAG, "Job printer state reason: ", *r); if (std::find(needsPaperReasons.begin(), needsPaperReasons.end(), *r) != needsPaperReasons.end()) { LOG_D(TAG, "Printer needs paper!"); - needsPaper = true; + flags |= PRINTER_ATTN_NO_PAPER; } if (std::find(needsInkReasons.begin(), needsInkReasons.end(), *r) != needsInkReasons.end()) { LOG_D(TAG, "Printer needs ink!"); - needsInk = true; + flags |= PRINTER_ATTN_NO_INK; } if (inputTrayMissingReason.compare(*r) == 0) { LOG_D(TAG, "Printer needs input tray!"); - needsTray = true; + flags |= PRINTER_ATTN_NO_TRAY; } } - - return true; } diff --git a/src/logic/PrinterManager.h b/src/logic/PrinterManager.h index 5ac7276..671fb4b 100644 --- a/src/logic/PrinterManager.h +++ b/src/logic/PrinterManager.h @@ -41,6 +41,12 @@ namespace selfomat { JOB_STATE_COMPLETED }; + enum PrinterAttentionFlag { + PRINTER_ATTN_NO_PAPER = (1 << 0), + PRINTER_ATTN_NO_INK = (1 << 1), + PRINTER_ATTN_NO_TRAY = (1 << 2) + }; + class PrinterManager { private: static std::string TAG; @@ -92,8 +98,8 @@ namespace selfomat { bool getJobDetails(int jobId, PrinterJobState &state, time_t &creationTs, time_t &processingTs, time_t &completedTs); static const char* printerJobStateToString(PrinterJobState &state); - - bool getJobAttributes(int jobId); + + void checkPrinterAttentionFromJob(int jobId, unsigned int &flags); }; } } From af080773dfb632fdcf5cae713becb9147f661bff Mon Sep 17 00:00:00 2001 From: maehw Date: Tue, 17 Dec 2024 21:17:33 +0100 Subject: [PATCH 05/11] Rname 'metrics' thread to 'print monitoring' thread; add i18n en/fr; add missing thread join --- i18n/en_frontend.json | 3 +++ i18n/fr_frontend.json | 3 +++ src/logic/BoothLogic.cpp | 31 ++++++++++++++++++------------- src/logic/BoothLogic.h | 13 ++++++++----- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/i18n/en_frontend.json b/i18n/en_frontend.json index 8c14944..6b95780 100644 --- a/i18n/en_frontend.json +++ b/i18n/en_frontend.json @@ -4,6 +4,9 @@ "template_saved": "Template saved successfully", "print_in_progress": "Printing in Progress...", "printer_error": "Printer Error!", + "printer_no_paper": "Printer out of paper", + "printer_no_ink": "Printer out of ink", + "printer_no_tray": "Printer's input tray is missing", "check_camera": "Please check your camera", "no_storage_found": "No USB Storage found", "storage_space_low": "Low Storage: ", diff --git a/i18n/fr_frontend.json b/i18n/fr_frontend.json index 245eecf..4d92c8b 100644 --- a/i18n/fr_frontend.json +++ b/i18n/fr_frontend.json @@ -4,6 +4,9 @@ "template_saved": "Gabarit chargé avec succès.", "print_in_progress": "Impression en cours...", "printer_error": "Erreur d'impression !", + "printer_no_paper": "Papier d'imprimante manquant", + "printer_no_ink": "Encre d'imprimante manquante", + "printer_no_tray": "Bac à papier de l'imprimante manquant", "check_camera": "Veuillez vérifier votre caméra.", "no_storage_found": "Clé USB introuvable.", "storage_space_low": "Clé USB pleine : ", diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 09d0bfe..d10005c 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -43,11 +43,11 @@ bool BoothLogic::start() { return false; // Start the threads - isLogicThreadRunning = isCameraThreadRunning = isPrinterThreadRunning = isMetricsThreadRunning = true; + isLogicThreadRunning = isCameraThreadRunning = isPrinterThreadRunning = isPrintMonitoringThreadRunning = true; logicThreadHandle = boost::thread(boost::bind(&BoothLogic::logicThread, this)); cameraThreadHandle = boost::thread(boost::bind(&BoothLogic::cameraThread, this)); printThreadHandle = boost::thread(boost::bind(&BoothLogic::printerThread, this)); - metricsThreadHandle = boost::thread(boost::bind(&BoothLogic::metricsThread, this)); + printMonitoringThreadHandle = boost::thread(boost::bind(&BoothLogic::printMonitoringThread, this)); return true; } @@ -95,10 +95,10 @@ void BoothLogic::stop(bool update_mode) { LOG_D(TAG, "waiting for print"); printThreadHandle.join(); } - isMetricsThreadRunning = false; - if (metricsThreadHandle.joinable()) { - LOG_D(TAG, "waiting for metrics"); - metricsThreadHandle.join(); + isPrintMonitoringThreadRunning = false; + if (printMonitoringThreadHandle.joinable()) { + LOG_D(TAG, "waiting for print monitoring"); + printMonitoringThreadHandle.join(); } if (gui != nullptr) { @@ -450,7 +450,7 @@ void BoothLogic::printerThread() { if(metrics.cupsJobId > 0) { boost::unique_lock lk(printMetricsMutex); printMetrics.push_back(metrics); - // TODO: explicitely activate thread; how to signal metricsThread? + // TODO: explicitely activate thread; how to signal print monitoring thread? } } else { // We need the final jpeg image. So lock the mutex @@ -495,10 +495,10 @@ void BoothLogic::printerThread() { #define CTIME_NO_NL(x) strtok(ctime(x), "\n") -void BoothLogic::metricsThread() { - LOG_D(TAG, "Starting Metrics Thread"); +void BoothLogic::printMonitoringThread() { + LOG_D(TAG, "Starting Print Monitoring Thread"); - while (isMetricsThreadRunning) { + while (isPrintMonitoringThreadRunning) { // TODO: put this thread to sleep; awake it only when a print job has been added to the list; // stay active only as long as list of monitored print jobs is not empty @@ -506,7 +506,7 @@ void BoothLogic::metricsThread() { { boost::unique_lock lk(printMetricsMutex); - unsigned int printerAttentionFlags = 0; + unsigned int printerAttentionFlags = 0; // reset flags std::list::iterator it = printMetrics.begin(); while (it != printMetrics.end()) { @@ -520,6 +520,7 @@ void BoothLogic::metricsThread() { cupsProcessingTs, cupsCompletedTs); if (gotJobDetails) { + // copy job details from printer manager it->jobState = jobState; it->cupsCreationTs = cupsCreationTs; it->cupsProcessingTs = cupsProcessingTs; @@ -527,7 +528,7 @@ void BoothLogic::metricsThread() { if (JOB_STATE_UNKNOWN == jobState || JOB_STATE_CANCELED == jobState || JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { - // job is in a state where it is no longer monitored + // job is in a state where it is no longer monitored ("stopped" is excluded on purpose) LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: #", std::to_string(jobId)); //LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); //LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); @@ -547,6 +548,9 @@ void BoothLogic::metricsThread() { // job is in a state where it will be still monitored LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); if (JOB_STATE_PROCESSING == jobState) { + // when the job is being processed, we can check if the printer needs attention + // e.g. has run out of paper or ink or the input tray is missing; + // we could also let the user know that the processing duration has exceeded a certain threshold timespec tnow; clock_gettime(CLOCK_REALTIME, &tnow); double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); @@ -556,7 +560,7 @@ void BoothLogic::metricsThread() { ++it; } } else { - // didn't get job details but want to increment interator anyway + // didn't get job details but need to increment the iterator anyway ++it; } } @@ -566,6 +570,7 @@ void BoothLogic::metricsThread() { if ((printerAttentionFlags > 0) && printerEnabled) { // as only one flag is checked at a time, they are ordered by attention relevance + // or "level of attention/action/expertise", i.e. changing the ink cartridge may be "harder" to do if (printerAttentionFlags & PRINTER_ATTN_NO_INK) { gui->addAlert(ALERT_PRINTER_JOB_HINT, getTranslation("frontend.printer_no_ink")); } else if(printerAttentionFlags & PRINTER_ATTN_NO_TRAY) { diff --git a/src/logic/BoothLogic.h b/src/logic/BoothLogic.h index 1dd1062..022d02f 100644 --- a/src/logic/BoothLogic.h +++ b/src/logic/BoothLogic.h @@ -134,7 +134,7 @@ namespace selfomat { PrinterManager printerManager; ImageProcessor imageProcessor; - bool isLogicThreadRunning, isCameraThreadRunning, isPrinterThreadRunning, isMetricsThreadRunning; + bool isLogicThreadRunning, isCameraThreadRunning, isPrinterThreadRunning, isPrintMonitoringThreadRunning; boost::mutex triggerMutex; bool triggered; @@ -172,7 +172,7 @@ namespace selfomat { boost::thread logicThreadHandle; boost::thread cameraThreadHandle; boost::thread printThreadHandle; - boost::thread metricsThreadHandle; + boost::thread printMonitoringThreadHandle; int filterChoice = 0; double filterGain = 1.0; @@ -186,8 +186,8 @@ namespace selfomat { void logicThread(); void printerThread(); - - void metricsThread(); + + void printMonitoringThread(); void triggerFlash(); @@ -201,7 +201,7 @@ namespace selfomat { FILTER getFilter(); timespec triggerStart; - + std::list printMetrics; boost::mutex printMetricsMutex; public: @@ -231,6 +231,9 @@ namespace selfomat { if (printThreadHandle.joinable()) { printThreadHandle.join(); } + if (printMonitoringThreadHandle.joinable()) { + printMonitoringThreadHandle.join(); + } if (returnCode == -1) { reboot(LINUX_REBOOT_CMD_POWER_OFF); From 5a2585a1517b98d787e238e4b0cfa13c39dde16a Mon Sep 17 00:00:00 2001 From: maehw Date: Wed, 18 Dec 2024 11:54:41 +0100 Subject: [PATCH 06/11] Add changes from IGui.h: new enum value for printer alarms --- src/ui/IGui.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/IGui.h b/src/ui/IGui.h index 0c49cd1..63deedf 100644 --- a/src/ui/IGui.h +++ b/src/ui/IGui.h @@ -36,6 +36,7 @@ namespace selfomat { ALERT_CAMERA, ALERT_PRINTER, ALERT_PRINTER_HINT, + ALERT_PRINTER_JOB_HINT, ALERT_STORAGE, ALERT_STORAGE_ERROR, ALERT_CAMERA_HINT, @@ -86,6 +87,7 @@ namespace selfomat { (ALERT_CAMERA, "C") (ALERT_PRINTER, "P") (ALERT_PRINTER_HINT, "P") + (ALERT_PRINTER_JOB_HINT, "P") (ALERT_STORAGE, "U") (ALERT_STORAGE_ERROR, "U") (ALERT_CAMERA_HINT, "C") From f1a4dd3fc0f5a7b2d318eb023e01bd9fd1fa107a Mon Sep 17 00:00:00 2001 From: maehw Date: Wed, 18 Dec 2024 15:59:53 +0100 Subject: [PATCH 07/11] Close HTTP connection when leaving PrinterManager::resumePrinter() --- src/logic/PrinterManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/logic/PrinterManager.cpp b/src/logic/PrinterManager.cpp index 1b6fe32..7493a0a 100644 --- a/src/logic/PrinterManager.cpp +++ b/src/logic/PrinterManager.cpp @@ -179,6 +179,8 @@ bool PrinterManager::resumePrinter() { LOG_E(TAG, "Cannot enable printer:", cupsLastErrorString()); + httpClose(http); + return false; } @@ -189,6 +191,8 @@ bool PrinterManager::resumePrinter() { ippDelete(cupsDoRequest(http, request, "/admin/")); + httpClose(http); + if (cupsLastError() > IPP_STATUS_OK_CONFLICTING) { LOG_E(TAG, "Cannot resume printer:", cupsLastErrorString()); From f2d3d8ef6dff240f06db53b0a514be82c1a17911 Mon Sep 17 00:00:00 2001 From: maehw Date: Wed, 18 Dec 2024 16:05:04 +0100 Subject: [PATCH 08/11] Try to improve frontend i18n (French) --- i18n/fr_frontend.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/fr_frontend.json b/i18n/fr_frontend.json index 4d92c8b..2cecf38 100644 --- a/i18n/fr_frontend.json +++ b/i18n/fr_frontend.json @@ -4,9 +4,9 @@ "template_saved": "Gabarit chargé avec succès.", "print_in_progress": "Impression en cours...", "printer_error": "Erreur d'impression !", - "printer_no_paper": "Papier d'imprimante manquant", - "printer_no_ink": "Encre d'imprimante manquante", - "printer_no_tray": "Bac à papier de l'imprimante manquant", + "printer_no_paper": "Papier d'imprimante épuisé", + "printer_no_ink": "Encre d'imprimante épuisé", + "printer_no_tray": "Bac à papier de l'imprimante absent", "check_camera": "Veuillez vérifier votre caméra.", "no_storage_found": "Clé USB introuvable.", "storage_space_low": "Clé USB pleine : ", From a9c56cd794ae859402c3416e852effe7665ab17f Mon Sep 17 00:00:00 2001 From: maehw Date: Wed, 18 Dec 2024 18:57:49 +0100 Subject: [PATCH 09/11] Use semaphore for activation of print monitoring thread; also fix its name in logging --- src/logic/BoothLogic.cpp | 61 +++++++++++++++++++++++++++------------- src/logic/BoothLogic.h | 5 +++- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index d10005c..0a36065 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -1,3 +1,4 @@ + // // Created by clemens on 21.01.19. // @@ -450,7 +451,8 @@ void BoothLogic::printerThread() { if(metrics.cupsJobId > 0) { boost::unique_lock lk(printMetricsMutex); printMetrics.push_back(metrics); - // TODO: explicitely activate thread; how to signal print monitoring thread? + // explicitely activate print monitoring thread + printMetricsSem.post(); } } else { // We need the final jpeg image. So lock the mutex @@ -498,9 +500,26 @@ void BoothLogic::printerThread() { void BoothLogic::printMonitoringThread() { LOG_D(TAG, "Starting Print Monitoring Thread"); + // boolean flag which indicates whether to wait for external activation of this thread + // from the printer thread + bool waitForActivation = true; + while (isPrintMonitoringThreadRunning) { - // TODO: put this thread to sleep; awake it only when a print job has been added to the list; - // stay active only as long as list of monitored print jobs is not empty + if (waitForActivation) { + // blocking wait: only accept activation from printer thread (no periodic execution of this thread) + LOG_D(TAG, "[Print Mon Thread] Waiting for activation from printer thread (going to sleep...)"); + printMetricsSem.wait(); + LOG_D(TAG, "[Print Mon Thread] Activated from printer thread (slept before)"); + } else { + // blocking wait for period time; but also activate earlier if the semaphore has been posted by the printer thread + boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(5000); + bool semPosted = printMetricsSem.timed_wait(timeout); + if (semPosted) { + LOG_D(TAG, "[Print Mon Thread] Activated from print thread (already active before)"); + } else { + LOG_D(TAG, "[Print Mon Thread] Periodic execution (wait for semaphore timeout)"); + } + } size_t listLength = 0; { @@ -529,24 +548,25 @@ void BoothLogic::printMonitoringThread() { if (JOB_STATE_UNKNOWN == jobState || JOB_STATE_CANCELED == jobState || JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { // job is in a state where it is no longer monitored ("stopped" is excluded on purpose) - LOG_D(TAG, "[Metrics Thread] Erasing print job from metrics list: #", std::to_string(jobId)); - //LOG_D(TAG, "[Metrics Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); - //LOG_D(TAG, "[Metrics Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); - //LOG_D(TAG, "[Metrics Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); - LOG_D(TAG, "[Metrics Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); - LOG_D(TAG, "[Metrics Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); - LOG_D(TAG, "[Metrics Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); - LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + LOG_D(TAG, "[Print Mon Thread] Erasing print job from metrics list: #", std::to_string(jobId)); + //LOG_D(TAG, "[Print Mon Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); + //LOG_D(TAG, "[Print Mon Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); + //LOG_D(TAG, "[Print Mon Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); + LOG_D(TAG, "[Print Mon Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); + LOG_D(TAG, "[Print Mon Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); + LOG_D(TAG, "[Print Mon Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); + LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); if(JOB_STATE_COMPLETED == jobState) { + // finally, it's time to emit (log) the collected metrics double durationUntilProc = difftime(cupsProcessingTs, cupsCreationTs); double durationProcCompl = difftime(cupsCompletedTs, cupsProcessingTs); - LOG_D(TAG, "[Metrics Thread] Duration CUPS creation -> processing: ", std::to_string(durationUntilProc)); - LOG_D(TAG, "[Metrics Thread] Duration CUPS processing -> completion: ", std::to_string(durationProcCompl)); + LOG_D(TAG, "[Print Mon Thread] Duration CUPS creation -> processing: ", std::to_string(durationUntilProc)); + LOG_D(TAG, "[Print Mon Thread] Duration CUPS processing -> completion: ", std::to_string(durationProcCompl)); } printMetrics.erase(it++); } else { // job is in a state where it will be still monitored - LOG_D(TAG, "[Metrics Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); if (JOB_STATE_PROCESSING == jobState) { // when the job is being processed, we can check if the printer needs attention // e.g. has run out of paper or ink or the input tray is missing; @@ -554,7 +574,7 @@ void BoothLogic::printMonitoringThread() { timespec tnow; clock_gettime(CLOCK_REALTIME, &tnow); double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); - LOG_D(TAG, "[Metrics Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); + LOG_D(TAG, "[Print Mon Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); printerManager.checkPrinterAttentionFromJob(jobId, printerAttentionFlags); } ++it; @@ -565,8 +585,8 @@ void BoothLogic::printMonitoringThread() { } } listLength = printMetrics.size(); - //LOG_D(TAG, "[Metrics Thread] Length of metrics list: ", std::to_string(listLength)); - //LOG_D(TAG, "[Metrics Thread] Printer attention flags: ", std::to_string(printerAttentionFlags)); + //LOG_D(TAG, "[Print Mon Thread] Length of metrics list: ", std::to_string(listLength)); + //LOG_D(TAG, "[Print Mon Thread] Printer attention flags: ", std::to_string(printerAttentionFlags)); if ((printerAttentionFlags > 0) && printerEnabled) { // as only one flag is checked at a time, they are ordered by attention relevance @@ -581,10 +601,11 @@ void BoothLogic::printMonitoringThread() { } else { gui->removeAlert(ALERT_PRINTER_JOB_HINT); } - } - // TODO: if listLength > 0, wake up periodically, otherwise wait for external activation from printerThread - boost::this_thread::sleep(boost::posix_time::milliseconds(5000)); + // execute periodically if there's still some job to be monitored or a print job alarm active (any flag set); + // otherwise wait for activation from print thread + waitForActivation = (listLength == 0) && (printerAttentionFlags == 0); + } } } diff --git a/src/logic/BoothLogic.h b/src/logic/BoothLogic.h index 022d02f..63ca2fe 100644 --- a/src/logic/BoothLogic.h +++ b/src/logic/BoothLogic.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -89,7 +90,8 @@ namespace selfomat { selfomatController(), force_image_dir_mountpoint(force_image_dir_mountpoint), show_led_setup(show_led_setup), - autofocus_before_trigger(autofocus_before_trigger) { + autofocus_before_trigger(autofocus_before_trigger), + printMetricsSem(0) { selfomatController.setLogic(this); this->triggered = false; this->disable_watchdog = disable_watchdog; @@ -204,6 +206,7 @@ namespace selfomat { std::list printMetrics; boost::mutex printMetricsMutex; + boost::interprocess::interprocess_semaphore printMetricsSem; public: bool isStopped(); void trigger(); From bd2166d573caceaacf9a272d3ba57a04435118b7 Mon Sep 17 00:00:00 2001 From: maehw Date: Thu, 19 Dec 2024 22:02:26 +0100 Subject: [PATCH 10/11] Get rid of semaphore, make print monitoring thread periodic again; add attention flag to metrics --- src/logic/BoothLogic.cpp | 49 ++++++++++++++-------------------------- src/logic/BoothLogic.h | 7 +++--- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 0a36065..2113ba9 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -1,4 +1,3 @@ - // // Created by clemens on 21.01.19. // @@ -445,14 +444,11 @@ void BoothLogic::printerThread() { } } - // if job ID > 0, start checking this job from the metrics thread - // (append to list and activate thread if not already active); - // otherwise the already known timing values could be logged + // if job ID > 0, add this job to the list of jobs monitored by the print monitoring thread; + // otherwise the already known timing values could be logged here (don't seem relevant at the moment) if(metrics.cupsJobId > 0) { boost::unique_lock lk(printMetricsMutex); printMetrics.push_back(metrics); - // explicitely activate print monitoring thread - printMetricsSem.post(); } } else { // We need the final jpeg image. So lock the mutex @@ -500,27 +496,7 @@ void BoothLogic::printerThread() { void BoothLogic::printMonitoringThread() { LOG_D(TAG, "Starting Print Monitoring Thread"); - // boolean flag which indicates whether to wait for external activation of this thread - // from the printer thread - bool waitForActivation = true; - while (isPrintMonitoringThreadRunning) { - if (waitForActivation) { - // blocking wait: only accept activation from printer thread (no periodic execution of this thread) - LOG_D(TAG, "[Print Mon Thread] Waiting for activation from printer thread (going to sleep...)"); - printMetricsSem.wait(); - LOG_D(TAG, "[Print Mon Thread] Activated from printer thread (slept before)"); - } else { - // blocking wait for period time; but also activate earlier if the semaphore has been posted by the printer thread - boost::system_time const timeout = boost::get_system_time() + boost::posix_time::milliseconds(5000); - bool semPosted = printMetricsSem.timed_wait(timeout); - if (semPosted) { - LOG_D(TAG, "[Print Mon Thread] Activated from print thread (already active before)"); - } else { - LOG_D(TAG, "[Print Mon Thread] Periodic execution (wait for semaphore timeout)"); - } - } - size_t listLength = 0; { boost::unique_lock lk(printMetricsMutex); @@ -556,6 +532,7 @@ void BoothLogic::printMonitoringThread() { LOG_D(TAG, "[Print Mon Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); LOG_D(TAG, "[Print Mon Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + if(JOB_STATE_COMPLETED == jobState) { // finally, it's time to emit (log) the collected metrics double durationUntilProc = difftime(cupsProcessingTs, cupsCreationTs); @@ -563,10 +540,12 @@ void BoothLogic::printMonitoringThread() { LOG_D(TAG, "[Print Mon Thread] Duration CUPS creation -> processing: ", std::to_string(durationUntilProc)); LOG_D(TAG, "[Print Mon Thread] Duration CUPS processing -> completion: ", std::to_string(durationProcCompl)); } + printMetrics.erase(it++); } else { // job is in a state where it will be still monitored LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); + if (JOB_STATE_PROCESSING == jobState) { // when the job is being processed, we can check if the printer needs attention // e.g. has run out of paper or ink or the input tray is missing; @@ -575,8 +554,15 @@ void BoothLogic::printMonitoringThread() { clock_gettime(CLOCK_REALTIME, &tnow); double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); LOG_D(TAG, "[Print Mon Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); - printerManager.checkPrinterAttentionFromJob(jobId, printerAttentionFlags); + unsigned int currentPrinterAttentionFlags = 0; + printerManager.checkPrinterAttentionFromJob(jobId, currentPrinterAttentionFlags); + printerAttentionFlags |= currentPrinterAttentionFlags; + + if (currentPrinterAttentionFlags != 0) { + it->printerNeededAttention = true; // set sticky flag + } } + ++it; } } else { @@ -588,7 +574,7 @@ void BoothLogic::printMonitoringThread() { //LOG_D(TAG, "[Print Mon Thread] Length of metrics list: ", std::to_string(listLength)); //LOG_D(TAG, "[Print Mon Thread] Printer attention flags: ", std::to_string(printerAttentionFlags)); - if ((printerAttentionFlags > 0) && printerEnabled) { + if ((printerAttentionFlags != 0) && printerEnabled) { // as only one flag is checked at a time, they are ordered by attention relevance // or "level of attention/action/expertise", i.e. changing the ink cartridge may be "harder" to do if (printerAttentionFlags & PRINTER_ATTN_NO_INK) { @@ -601,11 +587,10 @@ void BoothLogic::printMonitoringThread() { } else { gui->removeAlert(ALERT_PRINTER_JOB_HINT); } - - // execute periodically if there's still some job to be monitored or a print job alarm active (any flag set); - // otherwise wait for activation from print thread - waitForActivation = (listLength == 0) && (printerAttentionFlags == 0); } + + // this thread keeps polling periodically + boost::this_thread::sleep(boost::posix_time::milliseconds(5000)); } } diff --git a/src/logic/BoothLogic.h b/src/logic/BoothLogic.h index 63ca2fe..cfa25bb 100644 --- a/src/logic/BoothLogic.h +++ b/src/logic/BoothLogic.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include @@ -75,6 +74,8 @@ namespace selfomat { time_t cupsCompletedTs; // Job state returned by CUPS and converted to PrinterManager-specific type PrinterJobState jobState; + // Boolean flag indicating that the printer needed attention during this job + bool printerNeededAttention; }; class BoothLogic : public ILogicController { @@ -90,8 +91,7 @@ namespace selfomat { selfomatController(), force_image_dir_mountpoint(force_image_dir_mountpoint), show_led_setup(show_led_setup), - autofocus_before_trigger(autofocus_before_trigger), - printMetricsSem(0) { + autofocus_before_trigger(autofocus_before_trigger) { selfomatController.setLogic(this); this->triggered = false; this->disable_watchdog = disable_watchdog; @@ -206,7 +206,6 @@ namespace selfomat { std::list printMetrics; boost::mutex printMetricsMutex; - boost::interprocess::interprocess_semaphore printMetricsSem; public: bool isStopped(); void trigger(); From ddb30b4bb4aee9e895ee47857ee3a9b6d4ae26ab Mon Sep 17 00:00:00 2001 From: maehw Date: Fri, 20 Dec 2024 12:32:14 +0100 Subject: [PATCH 11/11] Add helper method for difftime; log less; prepare CSV output --- src/logic/BoothLogic.cpp | 59 +++++++++++++++++++++++++++------------- src/logic/BoothLogic.h | 2 ++ 2 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/logic/BoothLogic.cpp b/src/logic/BoothLogic.cpp index 2113ba9..ebd377d 100644 --- a/src/logic/BoothLogic.cpp +++ b/src/logic/BoothLogic.cpp @@ -376,6 +376,7 @@ void BoothLogic::printerThread() { if (do_print) { ImagePrintMetrics metrics = {}; metrics.jobState = JOB_STATE_UNKNOWN; + metrics.printerNeededAttention = false; clock_gettime(CLOCK_REALTIME, &metrics.processingTs); // We need the final jpeg image. So lock the mutex @@ -493,6 +494,22 @@ void BoothLogic::printerThread() { #define CTIME_NO_NL(x) strtok(ctime(x), "\n") +int BoothLogic::difftimeSeconds(time_t later, time_t earlier) { + // starting epoch timestamp is zero (i.e. has not been set) indicate invalid timediff + if (earlier == 0) + return -1; + + // replace missing timestamp with "now" + // please note that this may not be accurate and depends on the period time of the thread + if (later == 0) { + timespec tnow; + clock_gettime(CLOCK_REALTIME, &tnow); + later = tnow.tv_sec; + } + + return difftime(later, earlier); +} + void BoothLogic::printMonitoringThread() { LOG_D(TAG, "Starting Print Monitoring Thread"); @@ -521,38 +538,42 @@ void BoothLogic::printMonitoringThread() { it->cupsProcessingTs = cupsProcessingTs; it->cupsCompletedTs = cupsCompletedTs; + // "now" is a good reference timestamp + timespec tnow; + clock_gettime(CLOCK_REALTIME, &tnow); + + LOG_D(TAG, "[Print Mon Thread] CUPS job #", std::to_string(jobId) + + " in state: " + std::string(printerManager.printerJobStateToString(it->jobState))); + if (JOB_STATE_UNKNOWN == jobState || JOB_STATE_CANCELED == jobState || JOB_STATE_ABORTED == jobState || JOB_STATE_COMPLETED == jobState) { // job is in a state where it is no longer monitored ("stopped" is excluded on purpose) LOG_D(TAG, "[Print Mon Thread] Erasing print job from metrics list: #", std::to_string(jobId)); - //LOG_D(TAG, "[Print Mon Thread] Processing timestamp: ", std::string(CTIME_NO_NL(&it->processingTs.tv_sec))); - //LOG_D(TAG, "[Print Mon Thread] Await user decision timestamp: ", std::string(CTIME_NO_NL(&it->awaitUserDecisionTs.tv_sec))); - //LOG_D(TAG, "[Print Mon Thread] Got user decision timestamp: ", std::string(CTIME_NO_NL(&it->gotUserDecisionTs.tv_sec))); - LOG_D(TAG, "[Print Mon Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); - LOG_D(TAG, "[Print Mon Thread] CUPS processing timestamp: ", std::string(CTIME_NO_NL(&cupsProcessingTs))); - LOG_D(TAG, "[Print Mon Thread] CUPS completed timestamp: ", std::string(CTIME_NO_NL(&cupsCompletedTs))); - LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); - - if(JOB_STATE_COMPLETED == jobState) { - // finally, it's time to emit (log) the collected metrics - double durationUntilProc = difftime(cupsProcessingTs, cupsCreationTs); - double durationProcCompl = difftime(cupsCompletedTs, cupsProcessingTs); - LOG_D(TAG, "[Print Mon Thread] Duration CUPS creation -> processing: ", std::to_string(durationUntilProc)); - LOG_D(TAG, "[Print Mon Thread] Duration CUPS processing -> completion: ", std::to_string(durationProcCompl)); - } + LOG_D(TAG, "[Print Mon Thread] CUPS creation timestamp: ", std::string(CTIME_NO_NL(&cupsCreationTs))); + + // finally, it's time to emit (log) the collected metrics; + // let's use full seconds only (values are floor'ed not rounded for sake of simplicity! + // CUPS timestamps have second granularity anyway) + int durationCupsCreation2CupsProc = difftimeSeconds(cupsProcessingTs, cupsCreationTs); + int durationCupsProc2CupsCompl = difftimeSeconds(cupsCompletedTs, cupsProcessingTs); + + std::string csvLine = std::to_string(jobId) + + ";\"" + std::string(CTIME_NO_NL(&cupsCreationTs)) + + "\";\"" + std::string(printerManager.printerJobStateToString(it->jobState)) + + "\";" + std::to_string(durationCupsCreation2CupsProc) + ";" + + std::to_string(durationCupsProc2CupsCompl) + ";" + + std::to_string(it->printerNeededAttention); + LOG_D(TAG, "[Print Mon Thread] CSV>", csvLine); printMetrics.erase(it++); } else { // job is in a state where it will be still monitored - LOG_D(TAG, "[Print Mon Thread] CUPS state ", std::string(printerManager.printerJobStateToString(it->jobState))); if (JOB_STATE_PROCESSING == jobState) { // when the job is being processed, we can check if the printer needs attention // e.g. has run out of paper or ink or the input tray is missing; // we could also let the user know that the processing duration has exceeded a certain threshold - timespec tnow; - clock_gettime(CLOCK_REALTIME, &tnow); - double durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); + int durationProcessing = difftime(tnow.tv_sec, cupsProcessingTs); LOG_D(TAG, "[Print Mon Thread] CUPS job has been processing for [seconds]: ", std::to_string(durationProcessing)); unsigned int currentPrinterAttentionFlags = 0; printerManager.checkPrinterAttentionFromJob(jobId, currentPrinterAttentionFlags); diff --git a/src/logic/BoothLogic.h b/src/logic/BoothLogic.h index cfa25bb..6f07fb3 100644 --- a/src/logic/BoothLogic.h +++ b/src/logic/BoothLogic.h @@ -202,6 +202,8 @@ namespace selfomat { FILTER getFilter(); + int difftimeSeconds(time_t later, time_t earlier); + timespec triggerStart; std::list printMetrics;