RK3568 拍报机需求分析

语法

Posted by LXG on November 26, 2025

拍报机硬件构成

模块 选型/建议 说明
核心处理板 RK3568 四核 A55 CPU + Mali-G52 GPU,负责摄像头采集、图像处理、UI 和打印控制
内存 2~4GB LPDDR4 确保预览、图像缓存和打印队列顺畅
存储 16~32GB eMMC 或 SD 卡 系统、照片缓存和日志存储
摄像头 USB 3.0 相机:Razer 清姬 V2,1080p 支持高帧率 1080p 拍摄,USB 3.0 带宽保证图像流畅,自动曝光/白平衡
镜头 / 光学 固定焦距,宽视角 景区拍照距离适中,保证人脸和半身清晰
LED 补光 白光 / 柔光 LED 提升低光环境下拍摄质量,避免阴影
显示屏 21 英寸触摸屏(可选非触控) 大屏预览照片和界面,便于多人观看
用户交互 按键或触摸屏操作 拍照、确认打印、选择照片
打印机 彩色打印机:HP 5225 USB / 网络接口,支持彩色打印,适合景区纪念照片
打印缓存 JPEG 图片缓存 保证打印速度稳定,减少延迟
电源 12~19V DC 稳压 稳定供电,支持长期运行
散热 散热片 + 风扇(可选) 保证 SOC 长时间运行不降频
网络接口 Ethernet / Wi-Fi(可选) 上传照片或统计数据
外设接口 USB、串口、GPIO 摄像头、打印机、按钮、指示灯扩展

APP 软件工时计算

模块 子功能 功能描述 估算工时(人天)
MQTT 通信模块 订阅主题/重连机制 MQTT 连接、鉴权、断线重连、自动恢复 1.5 天
  文件下发/应答 解析 server URL → 下载视频文件 → 覆盖保存 → 成功/失败上报 1 天
  设备登录/应答 上报主屏电流/电压 → 状态保持 → 应答机制 0.8 天
  OTA 升级/应答 下载 apk → 校验 → 静默安装/提示安装 → 上报应答 1.5 天
  远程重启设备/应答 接收重启命令 → 执行 reboot → 应答 0.3 天
  运营时间设置 解析配置 → 系统定时开关机调度(精确到分钟) 1 天
视频播放模块 全屏循环播放 本地视频循环播放 + 卡顿恢复 + 断点切换 1 天
  视频覆盖信息浮层 左下角:设备编号+APP版本;右下角:网络模式+信号强度 0.6 天
  视频文件管理 新视频覆盖旧视频、本地校验、播放文件校验 0.5 天
业务入口控制 空闲广告播放逻辑 无人 → 播放广告;有人 → 切业务界面提示 1 天
调试页面 打印测试 简单按钮功能触发 0.3 天
  MQTT 地址切换 允许修改/保存/重连 MQTT 地址 0.5 天
  网络连通测试 Ping/HTTP 测试,显示结果 0.5 天
网络自检 开机自检 开机检测网络 → 无网自动重启(限制 3 次) 1 天
心跳机制 每分钟心跳上报 定时任务 + 异常恢复机制 0.5 天
基础框架 App 架构/日志系统 MVVM/Service 前台服务/日志上传接口预留 1.5 天
稳定性、边缘情况处理 Crash 监控、异常恢复 视频卡死、MQTT 卡死、网络抖动处理 1 天
UI/交互开发 所有界面 UI 主界面、调试界面、提示界面 1 天
测试与优化 自测与修复 全功能联调,本地循环测试,Bug 修复 2 天

USB 摄像头

雷蛇(Razer)清姬V2 分辨率1080p

打印机

HP CP5225 是一款 A3 彩色激光打印机 / 打印机设备(Color LaserJet 系列)

方式 接口 开发难度 优缺点 推荐程度
HP 官方 Android SDK USB / 网络 中等 ✅ 支持 CP5225
✅ 封装打印逻辑
✅ 开发简单
❌ SDK可能有兼容性限制
★★★★★
网络打印 IPP / JetDirect Ethernet / Wi-Fi 中等 ✅ 不依赖 USB
✅ 稳定可靠
❌ 需生成 PCL/PS 数据
★★★★☆
USB 直连 + libusb/PCL USB Host ✅ 可直连打印
❌ 需自己实现 PCL/PS协议
❌ 开发复杂
★★★☆☆
Android PrintManager USB / 网络 ✅ 简单调用
❌ USB直连支持有限
❌ 更适合 PDF/图片渲染
★★☆☆☆

可以用 Android 11 的以太网共享功能来打印,但打印机必须支持网络打印(IPP、JetDirect 9100、LPR 等)。USB-only 的打印机(无网卡)无法通过以太网共享直接变成网络打印机。

打印方案选择

设备作为景区拍报机时,使用 Wi-Fi 热点还是 以太网共享(Ethernet Tethering) 来连接 HP CP5225 打印机,哪个更适合。我们可以从 稳定性、部署成本、延迟、操作复杂度几个角度分析

维度 Wi-Fi 热点 以太网共享
稳定性 中等
带宽 54~600 Mbps(视标准) 100~1000 Mbps(有线)
延迟 较高,偶尔掉线 低,基本无丢包
干扰 多,景区人多或有其他 Wi-Fi 可能冲突 无,物理线路保证
部署成本 不需要网线,灵活 需要网线,布线麻烦
适合距离 5~10 米 可长距离延伸,网线长度受限(100 m)
移动性 高,可随拍报机移动 低,打印机位置固定
配置复杂度 简单,手机热点+打印机接入 简单,Ethernet Tethering 打开即可
打印可靠性 偶尔失败,需要重连 极高,不易掉线

APP 打印接口

接口 / 方法 适合场景 优缺点
PrintManager + PrintDocumentAdapter PDF / 图片打印 ✅ 系统自带,API简单
❌ 需要先生成 PDF/Bitmap
TCP Socket 9100(JetDirect) 票据 / 自定义打印 ✅ 直接发送 PCL / PS 命令
❌ 需生成 PCL/PS
IPP PDF / PostScript ✅ 标准协议,跨平台
❌ 需要实现 IPP 协议
HP / Mopria 插件 普通文档打印 ✅ 自动发现网络打印机
❌ 依赖插件

景区拍报机 + CP5225 网络打印,推荐:

  • 票据/票单打印 → JetDirect TCP 9100 + PCL/PS
  • 图片 / PDF 打印 → PrintManager + PDF/Bitmap

PrintManager + PDF/Bitmap


import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.pdf.PdfDocument
import android.print.PrintAttributes
import android.print.PrintManager
import android.print.pdf.PrintedPdfDocument
import android.print.PrintDocumentAdapter
import android.print.PrintDocumentInfo

fun printBitmap(context: Context, bitmap: Bitmap, jobName: String = "PrintJob") {
    val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager

    val adapter = object : PrintDocumentAdapter() {

        override fun onLayout(
            oldAttributes: PrintAttributes?,
            newAttributes: PrintAttributes,
            cancellationSignal: android.os.CancellationSignal,
            callback: LayoutResultCallback,
            metadata: android.os.Bundle?
        ) {
            if (cancellationSignal.isCanceled) {
                callback.onLayoutCancelled()
                return
            }
            val info = PrintDocumentInfo.Builder("$jobName.pdf")
                .setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
                .setPageCount(1)
                .build()
            callback.onLayoutFinished(info, true)
        }

        override fun onWrite(
            pages: Array<android.print.PageRange>,
            destination: android.os.ParcelFileDescriptor,
            cancellationSignal: android.os.CancellationSignal,
            callback: WriteResultCallback
        ) {
            if (cancellationSignal.isCanceled) {
                callback.onWriteCancelled()
                return
            }

            try {
                val pdfDocument = PrintedPdfDocument(context, PrintAttributes.Builder().build())
                val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, 1).create()
                val page = pdfDocument.startPage(pageInfo)

                // 将 Bitmap 绘制到 PDF 页面
                val canvas: Canvas = page.canvas
                canvas.drawBitmap(bitmap, 0f, 0f, null)
                pdfDocument.finishPage(page)

                // 写入文件
                pdfDocument.writeTo(destination.fileDescriptor)
                pdfDocument.close()

                callback.onWriteFinished(arrayOf(android.print.PageRange.ALL_PAGES))
            } catch (e: Exception) {
                callback.onWriteFailed(e.message)
            }
        }
    }

    printManager.print(jobName, adapter, null)
}


JetDirect TCP 9100 + PCL/PS

  • 对于 彩色照片或广告图,你可以先把 Bitmap 渲染到 PDF,然后直接用 TCP 9100 发送 PDF 数据,这样保留彩色和高分辨率。
  • 如果只打印票据、二维码、票单,可以直接用上述黑白 PCL 方法,简单高效

方式一


import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.pdf.PdfDocument
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.net.Socket
import kotlin.concurrent.thread

/**
 * 将 Bitmap 渲染为 PDF,然后通过 TCP 9100 发送到打印机
 *
 * @param context Context
 * @param printerIp 打印机固定 IP
 * @param bitmap 要打印的 Bitmap
 * @param jobName 打印任务名称
 */
fun printBitmapAsPdfTCP9100(context: Context, printerIp: String, bitmap: Bitmap, jobName: String = "PrintJob") {
    thread {
        try {
            // 1. 创建 PDF 文档
            val pdfDocument = PdfDocument()
            val pageInfo = PdfDocument.PageInfo.Builder(bitmap.width, bitmap.height, 1).create()
            val page = pdfDocument.startPage(pageInfo)
            val canvas: Canvas = page.canvas
            canvas.drawBitmap(bitmap, 0f, 0f, null)
            pdfDocument.finishPage(page)

            // 2. 将 PDF 写入 ByteArray
            val pdfStream = ByteArrayOutputStream()
            pdfDocument.writeTo(pdfStream)
            pdfDocument.close()
            val pdfBytes = pdfStream.toByteArray()

            // 3. 通过 TCP 9100 发送到打印机
            val socket = Socket(printerIp, 9100)
            val output: OutputStream = socket.getOutputStream()
            output.write(pdfBytes)
            output.flush()
            output.close()
            socket.close()

            println("打印成功: $jobName")
        } catch (e: Exception) {
            e.printStackTrace()
            println("打印失败: ${e.message}")
        }
    }
}

方式二


import android.graphics.Bitmap
import android.graphics.Color
import java.io.OutputStream
import java.net.Socket
import kotlin.concurrent.thread

/**
 * 将 Bitmap 转换为 PCL 简单黑白位图命令,然后通过 TCP 9100 打印
 *
 * @param printerIp 打印机固定 IP
 * @param bitmap 要打印的 Bitmap
 */
fun printBitmapTCP9100(printerIp: String, bitmap: Bitmap) {
    thread {
        try {
            val socket = Socket(printerIp, 9100)
            val output: OutputStream = socket.getOutputStream()

            // PCL 打印头
            val header = "\u001B%-12345X@PJL JOB\n@PJL ENTER LANGUAGE=PCL\n"
            output.write(header.toByteArray())

            // 将 Bitmap 转换为黑白位图
            val width = bitmap.width
            val height = bitmap.height
            val bitmapData = ByteArray(width * height / 8)
            var byteIndex = 0
            var bitIndex = 0
            var currentByte = 0

            for (y in 0 until height) {
                for (x in 0 until width) {
                    val pixel = bitmap.getPixel(x, y)
                    // 灰度值
                    val gray = (0.3 * Color.red(pixel) +
                                0.59 * Color.green(pixel) +
                                0.11 * Color.blue(pixel)).toInt()
                    val bit = if (gray < 128) 1 else 0
                    currentByte = currentByte or (bit shl (7 - bitIndex))
                    bitIndex++
                    if (bitIndex == 8) {
                        bitmapData[byteIndex++] = currentByte.toByte()
                        bitIndex = 0
                        currentByte = 0
                    }
                }
            }

            // PCL 位图打印命令(简化版)
            val pclBitmapStart = "\u001B*r1A"
            val pclBitmapEnd = "\u001B*rB\n"
            output.write(pclBitmapStart.toByteArray())
            output.write(bitmapData)
            output.write(pclBitmapEnd.toByteArray())

            // 打印结束
            val footer = "\u001B%-12345X"
            output.write(footer.toByteArray())
            output.flush()
            output.close()
            socket.close()
            println("打印成功")
        } catch (e: Exception) {
            e.printStackTrace()
            println("打印失败: ${e.message}")
        }
    }
}

21寸高亮屏幕选型

使用场景 典型亮度需求(cd/m² / nits)
室内/阴天 200~300 nits
半户外 / 阴天或有遮阳 500~700 nits
户外阳光下(一般晴天) 1000~1500 nits
强阳光直射户外 2000~2500 nits

12V LVDS 主板方案下,1000+ nits 基本无法直接实现

  • 21 寸高亮屏(1500 nits)大约需要 40~60W 背光功率。
  • 12V 电压下,电流 I = P / V ≈ 40~60W / 12V ≈ 3.3~5A
  • 这样对主板电源设计要求很高,普通工业板可能无法提供

LVDS 对比 HDMI

特性 高亮 LVDS 屏 (~1500 nit) 高亮 HDMI / 工业户外屏 (≥1000 nit)
亮度 ~1000–1500 nit ≥1000 nit
功耗 / 发热 高功耗,热量大,散热要求高 工业设计散热好,适合长时间高亮
接口 / 兼容性 LVDS,依赖主板,升级/替换困难 HDMI / 标准接口,兼容性好,易替换
环境适应性 室内/半户外可控环境 户外/半户外/强光/高温可用,防尘防水
优点 面板尺寸灵活,可嵌入机箱 散热好、稳定性高、模块化,适合长期户外使用
缺点 散热难,寿命受高温影响,主板依赖强 成本较高,体积大,需独立电源/散热