关于此文其实已有前面两篇文章做铺垫:在Windows系统对接良田高拍仪驱动SDK与高效阉割良田高拍仪JavaSDK所依赖的Eclipse SWT OLE Win32自动化组件,不过呢这三篇也并没有太强的上下文关联。前面是解决平台运行问题和依赖问题,本文是说应用问题,其实Java调用这种驱动打印,众所周知是不太可能像官方一样用什么SWT窗口了,大多情况下都是用消息服务器去远程调用的,它大致是这样一个流程:
用户在网页请求操作设备 -> 后端接收请求发送给ActiveMQ消息服务器 -> ActiveMQ消息服务器广播消息 -> EloamView客户端监听到ActiveMQ消息 -> EloamView客户端执行摄像/扫描的操作。
前面的步骤可以说,懂的都懂,本文主要是说后面的关键步骤:让EloamView客户端监听ActiveMQ消息,根据消息操作良田高拍仪,也就相当于是对良田高拍仪进行ActiveMQ网络调用,当然这个EloamView客户端应当设置在Windows服务器上,因为一般情况下也都是将这种驱动装在Windows上,方便一点,这个客户端的指令是要与驱动交互的,一般也只能放在Windows上。说完平台的问题,下一步就是具体的操作了:
Gradle配置:
先引入spring-boot-starter-activemq
,有spring-boot
就是方便。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-activemq:2.7.13'
}
众所周知用了spring自然会出来一大堆包,将来要打包的时候,是不能直接gradle jar
打的,除了设置Main-Class
还要排除很多重复文件,入口类可以起名为CameraApplication
,然后一般要排除下面这些文件,关于jar
任务综合的配置可以参考这样写:
jar {
manifest {
attributes "Main-Class": "CameraApplication"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} {
exclude "META-INF/*.SF"
exclude "META-INF/DEPENDENCIES"
exclude "META-INF/LICENSE"
exclude "META-INF/LICENSE.txt"
exclude "META-INF/NOTICE"
exclude "META-INF/NOTICE.txt"
exclude "META-INF/*.RSA"
exclude "META-INF/*.RSA"
}
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
建立ActiveMQ连接
首先就是走spring boot
的起步流程,写好前面提到的CameraApplication
并初始化:
public class CameraApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(CameraApplication.class, args);
}
@Override
public void run(String... args) {
...
}
}
那么接下来就是准备好关于ActiveMQ的连接参数remoteURI
,user
,password
(建议用命令行args
或是环境变量传入这3个值),这个自然要由服务器来定,这只是个消息客户端。然后就能建立起连接了:
JmsConnectionFactory factory = new JmsConnectionFactory(remoteURI);
connection = factory.createConnection(user, password);
connection.start();
下一步就是建立起主题会话,会话名我暂定为Camera
,这名字随意了,只要后端服务器对应上就行:
Session session = newSession(connection);
Destination destination = session.createTopic("Camera");
MessageConsumer mc = session.createConsumer(destination);
然后就是不断的循环接收消息,操作高拍仪并不复杂,一般情况也没什么参数可传递的,所以定义为TextMessage
就行(这里要注意如果想改成用MapMessage
,需要注意服务器端那边的版本也要对应上,不然与此处客户端版本不对有可能会接不到消息,而TextMessage
版本的兼容性更广一些):
String body = "";
do {
try {
body = ((TextMessage) mc.receive()).getText();
...
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
} while (!body.equalsIgnoreCase("SHUTDOWN"));
如果收到SHUTDOWN
的文本消息,那么就停止接收消息,这相当于一个惯例了,其实一般不会用到,只是考虑应对一些特殊情况。那么正常发送的话,发什么消息呢,可能是是否自动剪裁,摄像框尺寸调整等参数,不过为了方便理解我这里直接就当做发来的就是最终保存的图像文件名,最终摄像处理好的图片,就放入到一个以收到的body
内容为文件名的图像文件中。
然后就是具体调用阶段了,这也是关键的部分。
接收消息调用驱动
这方面既可以结合前面的文章高效阉割良田高拍仪JavaSDK所依赖的Eclipse SWT OLE Win32自动化组件做,也可以直接依赖Eclipse SWT OLE Win32
,不过有一样是都要做的,那就是在静态块里就早早的加载好swt-win32-4430.dll
:
static {
System.loadLibrary("swt-win32-4430");
}
但接下来两种是有所不同的,因为如果读过前面文章的就知道如果是阉割版的话API都改动了,用起来自然是不一样的,所以下面分这两种方式说:
通过为EloamView专属OLE阉割版调用
首先当然也是用SimpleOleAutomation
了,但是这个初始化过程除非有特殊需求,否则不用每次接到消息都初始化了,所以可以放在上面那个循环之外,我copy了一些前文的代码过来:
SimpleOleAutomation auto = new SimpleOleAutomation("{CA2184F0-5A78-4D81-80F2-B0A7BFC74FBF}");
auto.invoke("InitDev", null).printResult();
auto.invoke("OpenVideoEx", new Variant[]{Variant.ZERO, new Variant(1), Variant.ZERO});
auto.invoke("SetVideoProcAmp",
new Variant[]{Variant.ZERO, new Variant(1), new Variant(180), new Variant(true)});
像这些,就是可以放在前面的do-while
消息循环之外的,然后将拍摄图片,文件名就是body
传过来的名字,保存图片的命令放到循环内,记得这里面不要注销驱动了,不然没办法拍第二张了:
//自动剪裁
auto.invoke("Deskew", new Variant[]{Variant.ZERO, new Variant(true)});
Thread.sleep(1000);
//摄像并将文件保存到temp.jpg
auto.invoke("Scan", new Variant[]{Variant.ZERO, new Variant(body), new Variant(0x0100)});
注意之前的那个temp.jpg
这里是改成body
了,这里可以根据情况来,如果不需要看历史图片文件,其实直接保存成temp.jpg
也没问题的。
还有一点就是我去掉了每个步骤末尾的.printResult()
,如果需要打印消息可以加上。
通过Eclipse SWT OLE Win32原生调用
如果手上没有良田高拍仪的EloamViewJavaDemo
,那可能就只有官方的例子可以参考,但官方的例子其实也就一个Snippet81,内容挺有限的,所以还是看下面的内容吧:
首先一样是先初始化OLE组件驱动,也是在消息循环外完成:
Shell shell = new Shell();
OleFrame frame = new OleFrame(shell, SWT.NONE);
OleControlSite site = new OleControlSite(frame, SWT.NONE, "{CA2184F0-5A78-4D81-80F2-B0A7BFC74FBF}");
OleAutomation auto = new OleAutomation(site);
site.doVerb(OLE.OLEIVERB_SHOW);
auto.invoke(auto.getIDsOfNames(new String[] { "InitDev" })[0], null);
auto.invoke(auto.getIDsOfNames(new String[] { "OpenVideoEx" })[0],
new Variant[]{Variant.ZERO, new Variant(1), Variant.ZERO});
auto.invoke(auto.getIDsOfNames(new String[] { "SetVideoProcAmp" })[0],
new Variant[]{Variant.ZERO, new Variant(1), new Variant(180), new Variant(true)});
对比上面那是啰嗦的多了,然后是在循环内执行接收到消息,摄像保存为消息内容的文件:
//自动剪裁
auto.invoke(auto.getIDsOfNames(new String[] { "Deskew" })[0],
new Variant[]{Variant.ZERO, new Variant(true)});
Thread.sleep(1000);
//摄像并将文件保存到temp.jpg
auto.invoke(auto.getIDsOfNames(new String[] { "Scan" })[0],
new Variant[]{Variant.ZERO, new Variant(body), new Variant(0x0100)});
原生调用就是如此了,想符合那些规范就麻烦的很。
后记
其实真的用起来不会真的如本文所说的这么简单就结束了,比喻说保存图片之后想让用户马上预览,可能还要再把图片传回ActiveMQ
,然后服务器接收到图片再响应给网页端的用户,这就是一种闭环了。但也可能没必要那么复杂,毕竟这个摄像客户端因为要调用驱动,自然是部署在用户的机器上,直接调动看图程序弹出这张图片也是有可能的,这个具体需求就看个人了,做起来也都很简单,就不必多言了。
总的来说这个高拍仪本身是挺好用的,而且愿意公开提供那么丰富的接口和功能,可以说是国内不多见的。不过呢确实存在许多问题,而且不知道官方是否真的以后就不提供对java
这方面的维护了,其实也没必要做什么特别的维护,只要一直是用OLE
这套方式即可,关于良田高拍仪的三部曲也就结束了,如果我发现了什么遗漏也会持续编辑这三篇进行补全。
本文依照作者在2022年的一些开发经验,于2023年7月15日同时写作并发布在lyrieek的稀土掘金社区与阿里云开发者社区。