1.前言
笔者最近接到了这样一个需求,需要对接扫码枪并获取扫码枪扫出来的值;
2.实现思路
2.1.构思
使用生产者消费者模式作为整体架构设计,生产者持续监听输入直到生成一个链接,然后将该链接放入一个阻塞队列中,消费者会不断从该阻塞队列中消费产品,若队列为空,则阻塞直至消费到产品;
2.1.持续读取键盘缓冲区
2.1.1.原理
扫码枪默认模式是会将扫出来的值输出到键盘缓冲区中,这样就可以直接在文本框上打出来;
通过观察得知,
- 计算机对于每一个字符并不是按照ASCII规则,而是按照其内部自定义的编码输出,因此需要对每一个字符进行转换,准换成标准ASCII;
- 计算机使用SHIFT区分大小写以及其他字符如 ",:,+,这类字符同大写一样,需要先按住SHIFT,因此可通过这个方式区分字符
是否有经过SHIFT转义,且键盘中输出的字符默认全为大写;
2.1.2.具体实现 (Java)
//字符转换
//若前一个字符是SHIFT,则需要转换
private void initWithShiftCharacter() {
particularKeyToLetterWithShift.put(192, '~');
particularKeyToLetterWithShift.put(49, '!');
particularKeyToLetterWithShift.put(50, '@');
particularKeyToLetterWithShift.put(51, '#');
particularKeyToLetterWithShift.put(52, '$');
particularKeyToLetterWithShift.put(53, '%');
particularKeyToLetterWithShift.put(54, '^');
particularKeyToLetterWithShift.put(55, '&');
particularKeyToLetterWithShift.put(56, '*');
particularKeyToLetterWithShift.put(57, '(');
particularKeyToLetterWithShift.put(48, ')');
particularKeyToLetterWithShift.put(189, '_');
particularKeyToLetterWithShift.put(187, '+');
particularKeyToLetterWithShift.put(219, '{');
particularKeyToLetterWithShift.put(221, '}');
particularKeyToLetterWithShift.put(220, '|');
particularKeyToLetterWithShift.put(186, ':');
particularKeyToLetterWithShift.put(222, '"');
particularKeyToLetterWithShift.put(188, '<');
particularKeyToLetterWithShift.put(190, '>');
particularKeyToLetterWithShift.put(191, '?');
}
//若前一个字符不是SHIFT,则无需转换
private void initWithoutShiftCharacter() {
particularKeyToLetterWithoutShift.put(189, '-');// - ok
particularKeyToLetterWithoutShift.put(187, '=');
particularKeyToLetterWithoutShift.put(219, '[');//{ ok
particularKeyToLetterWithoutShift.put(221, ']');
particularKeyToLetterWithoutShift.put(220, '\\');// \ ok
particularKeyToLetterWithoutShift.put(186, ';');//: ok
particularKeyToLetterWithoutShift.put(222, '\'');
particularKeyToLetterWithoutShift.put(188, ',');
particularKeyToLetterWithoutShift.put(190, '.');
particularKeyToLetterWithoutShift.put(191, '/');
particularKeyToLetterWithoutShift.put(107, '+');
particularKeyToLetterWithoutShift.put(109, '-');
particularKeyToLetterWithoutShift.put(106, '*');
particularKeyToLetterWithoutShift.put(111, '/');
particularKeyToLetterWithoutShift.put(110, '.');
particularKeyToLetterWithoutShift.put((int) '\u008D', '-');
}
//开启服务
private void startService() throws InterruptedException {
logger.info("Barcode producer启动成功");
BarcodeKeyboardListenerProducer listener = new BarcodeKeyboardListenerProducer();
WinUser.LowLevelKeyboardProc lowLevelKeyboardProc = new WinUser.LowLevelKeyboardProc() {
@Override
public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wparam, WinUser.KBDLLHOOKSTRUCT hook) {
if (nCode >= 0 && wparam.intValue() == WinUser.WM_KEYUP) {
int keyCode = hook.vkCode;
listener.onKey(keyCode);
}
return lib.CallNextHookEx(hhook, nCode, wparam, new WinDef.LPARAM(Pointer.nativeValue(hook.getPointer())));
}
};
WinDef.HMODULE hmodule = Kernel32.INSTANCE.GetModuleHandle(null);
hhook = lib.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, lowLevelKeyboardProc, hmodule, 0);
WinUser.MSG msg = new WinUser.MSG();
int result;
while ((result = lib.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
logger.error("quit ,error in get message: " + result);
break;
} else {
lib.TranslateMessage(msg);
lib.DispatchMessage(msg);
}
}
}
//监听键盘缓冲区的每一个字符
// 160 = SHIFT
//此处判断识别的为可显示的字符
public void onKey(int keyCode) {
// logger.info(keyCode+" "+(char)keyCode);
if (keyCode == SHIFT_VALUE) {
IS_SHIFT = true;
return;
}
if (barcode == null) {
IS_SHIFT = false;
barcode = new StringBuilder();
start = System.currentTimeMillis();
}
long cost = System.currentTimeMillis() - start;
if (cost > maxScanTime) {
IS_SHIFT = false;
barcode = new StringBuilder();
start = System.currentTimeMillis();
}
if (isNumber(keyCode) && !IS_SHIFT) {
// logger.info("number:" + keyCode + " " + (char) keyCode);
} else if (isUpperLetter(keyCode)) {
// logger.info("alpha:" + keyCode + " " + (char) keyCode);
//默认字母小写
keyCode = keyCode + 32;
if (IS_SHIFT) {
keyCode = keyCode - 32;
}
} else if (keyCode != CODE_ENTER) {
// logger.info("other:" + keyCode + " " + (char) keyCode);
keyCode = handleParticularCharacter(IS_SHIFT, keyCode);
} else {
//最终结果
if (barcode.length() > barcodeMinLength && cost < maxScanTime) {
BarcodeBuffer.produce(barcode.toString());
}
barcode = new StringBuilder();
return;
}
barcode.append((char) keyCode);
//默认全部字符都是大写
IS_SHIFT = false;
// logger.info(keyCode+" "+(char)keyCode);
}
2.1.3.具体实现 (Go)
//字符转换
func initWithoutShiftCharacter() {
particularKeyToLetterWithoutShift = make(map[uint32]uint32)
particularKeyToLetterWithoutShift[189] = '-' // - ok
particularKeyToLetterWithoutShift[187] = '='
particularKeyToLetterWithoutShift[219] = '[' //{ ok
particularKeyToLetterWithoutShift[221] = ']'
particularKeyToLetterWithoutShift[220] = '\\' // \ ok
particularKeyToLetterWithoutShift[186] = ';' //: ok
particularKeyToLetterWithoutShift[222] = '\''
particularKeyToLetterWithoutShift[188] = ','
particularKeyToLetterWithoutShift[190] = '.'
particularKeyToLetterWithoutShift[191] = '/'
particularKeyToLetterWithoutShift[107] = '+'
particularKeyToLetterWithoutShift[109] = '-'
particularKeyToLetterWithoutShift[106] = '*'
particularKeyToLetterWithoutShift[111] = '/'
particularKeyToLetterWithoutShift[110] = '.'
//particularKeyToLetterWithoutShift[uint32('\u08D')] = '-'
}
func initWithShiftCharacter() {
particularKeyToLetterWithShift = make(map[uint32]uint32)
particularKeyToLetterWithShift[48] = ')'
particularKeyToLetterWithShift[49] = '!'
particularKeyToLetterWithShift[50] = '@'
particularKeyToLetterWithShift[51] = '#'
particularKeyToLetterWithShift[52] = '$'
particularKeyToLetterWithShift[53] = '%'
particularKeyToLetterWithShift[54] = '^'
particularKeyToLetterWithShift[55] = '&'
particularKeyToLetterWithShift[56] = '*'
particularKeyToLetterWithShift[57] = '('
particularKeyToLetterWithShift[189] = '_'
particularKeyToLetterWithShift[187] = '+'
particularKeyToLetterWithShift[219] = '{'
particularKeyToLetterWithShift[221] = '}'
particularKeyToLetterWithShift[220] = '|'
particularKeyToLetterWithShift[186] = ':'
particularKeyToLetterWithShift[222] = '"'
particularKeyToLetterWithShift[188] = '<'
particularKeyToLetterWithShift[190] = '>'
particularKeyToLetterWithShift[191] = '?'
}
//开启监听键盘缓冲区服务
func StartProviderService() error {
log.Println("[INFO]start barcode provider service")
QuitProvider = false
if err := keyboard.Install(nil, KeyBoardChan); err != nil {
return err
}
defer keyboard.Uninstall()
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
for !QuitProvider {
select {
case <-signalChan:
log.Println("[INFO]stop barcode provider service~")
return nil
case k := <-KeyBoardChan:
if k.Message == types.WM_KEYDOWN {
handle(k.VKCode)
}
continue
}
}
log.Println("[INFO] stop barcode provider service=")
return nil
}
//监听每一个字符
func handle(code types.VKCode) {
if code == types.VK_SHIFT || code == types.VK_LSHIFT || code == types.VK_RSHIFT {
IsShift = true
return
}
if barcode.Len() == 0 {
start = time.Now().UnixMilli()
}
cost := time.Now().UnixMilli() - start
if cost > maxScanTime {
IsShift = false
barcode = bytes.Buffer{}
start = time.Now().UnixMilli()
}
c := uint32(code)
if isNumber(c) && !IsShift {
} else if isUpperLetter(c) {
c = c + 32
if IsShift {
c = c - 32
}
} else if code != types.VK_RETURN {
c = handleParticularCharacter(c)
} else {
if barcode.Len() > barcodeMinLength && cost < maxScanTime {
// fmt.Println(barcode.String())
Provide(barcode.String())
}
barcode = bytes.Buffer{}
return
}
e := recover()
if e != nil {
log.Println("Erorr transfer key->", c)
}
// fmt.Println(barcode.String())
barcode.WriteByte(byte(c))
IsShift = false
}
2.1.4.不足之处
通过大量测试,发现使用键盘缓冲区的方式会有卡顿现象,即如果我们将光标聚焦于文本处,那输出的字符会很顺畅,程序正常运行;但当我们不将光标放在文本或输入框中,发现监听的字符会产生混乱,因此效果不尽人意,因此舍弃该种思路;
2.2.读取串口
通过仔细阅读扫码枪说明书,发现其可以变成串口模式,即使用串口的方式,扫码枪会将解析出来的字符串输出到串口中,此时我们读取该串口中的内容即可;
2.2.1.具体实现
//开启服务,将串口读取方式变为阻塞读模式,即未读到值则一致阻塞;
var S *serial.Port
var c = &serial.Config{Name: COM, Baud: 9600 /*毫秒*/}
var tmpS *serial.Port
func StartProviderService() error {
//如果provider已经开启了,就不用再开了
if IsRunningProvider {
return nil
}
//若未开启
if S==nil{
S, _ = serial.OpenPort(c)
}
logs.Info("start Barcode provider service")
QuitProvider = false
IsRunningProvider = true
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt)
//该bonux的意义是因为经过测试发现若扫描二维码,会出现分2次获取值的情况,即若读取的值为 http://www.bilibili.com
//则第一次获得的值为h
//第二次获取的值为 ttp://bilibili.com
//因此需要手动拼接
bonux := ""
for true {
select {
case <-signalChan:
logs.Info("stop Barcode provider service~")
return nil
default:
b := make([]byte, 300)
S.Read(b)
if QuitProvider {
continue
}
Barcode = trimBytes(b)
if len(Barcode) <= 1 {
bonux = Barcode
continue
}
Barcode = strings.TrimSpace(bonux + Barcode)
Provide(Barcode)
Barcode = ""
bonux = ""
}
}
logs.Info("stop Barcode provider service=")
return nil
}
2.2.2.不足之处
仅测试,大部分电脑都可以正常安装扫码枪的驱动,但有那么几台win7的系统竟然安装不上扫码枪的驱动,这就意味着要么可能需要手写驱动,获取扫码枪的输入…