Go-监听串口-对接扫码枪

1.前言

笔者最近接到了这样一个需求,需要对接扫码枪并获取扫码枪扫出来的值;

2.实现思路

2.1.构思

使用生产者消费者模式作为整体架构设计,生产者持续监听输入直到生成一个链接,然后将该链接放入一个阻塞队列中,消费者会不断从该阻塞队列中消费产品,若队列为空,则阻塞直至消费到产品;

2.1.持续读取键盘缓冲区
2.1.1.原理

扫码枪默认模式是会将扫出来的值输出到键盘缓冲区中,这样就可以直接在文本框上打出来;
通过观察得知,

  1. 计算机对于每一个字符并不是按照ASCII规则,而是按照其内部自定义的编码输出,因此需要对每一个字符进行转换,准换成标准ASCII;
  2. 计算机使用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的系统竟然安装不上扫码枪的驱动,这就意味着要么可能需要手写驱动,获取扫码枪的输入…

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值