feat: YAB-1785: Initial commit
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y ca-certificates make git curl gcc libudev-dev dpkg-dev dh-make
|
||||
|
||||
RUN sh -c 'curl -OL https://go.dev/dl/go1.21.3.linux-amd64.tar.gz && \
|
||||
tar -C /usr/local -xvf go1.21.3.linux-amd64.tar.gz'
|
||||
ENV PATH="${PATH}:/usr/local/go/bin"
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
ADD ./ /data
|
||||
WORKDIR /data
|
||||
|
||||
CMD ["make", "docker-deb"]
|
||||
@@ -0,0 +1,25 @@
|
||||
BINARY_NAME=keyboard-reader
|
||||
LD_FLAGS="-s -w -buildid="$(shell git rev-parse HEAD)
|
||||
|
||||
build:
|
||||
#GOARCH=amd64 GOOS=linux go build -o ${BINARY_NAME} -trimpath -ldflags ${LD_FLAGS} -tags netgo
|
||||
go build -o ${BINARY_NAME} -ldflags ${LD_FLAGS} -tags netgo
|
||||
|
||||
run: build
|
||||
./${BINARY_NAME} --list
|
||||
|
||||
clean:
|
||||
go clean
|
||||
rm ./debian/opt/ocppc/bin/${BINARY_NAME} || true
|
||||
rm -rf ./build || true
|
||||
docker container rm ${BINARY_NAME} || true
|
||||
docker image rm ${BINARY_NAME} || true
|
||||
|
||||
deb: clean build
|
||||
cp ${BINARY_NAME} ./debian/opt/ocppc/bin/
|
||||
cd ./debian && dpkg-buildpackage -rfakeroot --no-sign --target-arch amd64 --host-arch amd64
|
||||
|
||||
docker-deb: clean
|
||||
docker build -t ${BINARY_NAME} .
|
||||
docker container run --name ${BINARY_NAME} ${BINARY_NAME}
|
||||
docker container cp ${BINARY_NAME}:/data ./build
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
yablochkov-keyboard-reader (0.1.0) release; urgency=low
|
||||
|
||||
[ Alexander Terekhin ]
|
||||
* Initial version
|
||||
|
||||
-- Alexander Terekhin <alex@bearns> Sun, 03 Dec 2023 17:01:00 +0600
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
11
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
Source: yablochkov-keyboard-reader
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Alexander Terekhin <alex@bearns.me>
|
||||
|
||||
Package: yablochkov-keyboard-reader
|
||||
Version: 0.1
|
||||
Architecture: amd64
|
||||
Installed-Size: 5008848
|
||||
Description: Yablochkov keyboard reader
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
chmod +x /opt/ocppc/bin/keyboard-reader
|
||||
systemctl enable keyboard-reader --now
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
build:
|
||||
@ echo BUILD: nothing to do here
|
||||
|
||||
binary:
|
||||
@ echo BINARY
|
||||
dh_testroot
|
||||
dh_prep
|
||||
dh_install
|
||||
dh_installdocs
|
||||
dh_installchangelogs
|
||||
dh_installexamples
|
||||
dh_installman
|
||||
dh_link
|
||||
dh_compress
|
||||
dh_fixperms
|
||||
dh_installdeb
|
||||
dh_gencontrol
|
||||
dh_md5sums
|
||||
dh_builddeb
|
||||
|
||||
.PHONY: build clean binary
|
||||
@@ -0,0 +1,2 @@
|
||||
etc /
|
||||
opt /
|
||||
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=Yablochkov keyboard reader
|
||||
|
||||
[Service]
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=keyboard-reader
|
||||
WorkingDirectory=/opt/ocppc
|
||||
EnvironmentFile=/opt/conf/configuration_vars.env
|
||||
EnvironmentFile=/opt/conf/configuration_vars_model.env
|
||||
EnvironmentFile=/opt/conf/configuration_vars_personal.env
|
||||
ExecStart=/opt/ocppc/bin/keyboard-reader --serial=OEM_TWN4_B1.64_NKF4.64_STD2.04
|
||||
User=debian
|
||||
Group=input
|
||||
ExecStop=/bin/kill -TERM $MAINPID
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package evdev
|
||||
|
||||
/*
|
||||
#include <linux/input.h>
|
||||
|
||||
static int _EVIOCGBIT(int ev, int len) {return EVIOCGBIT(ev, len);}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
EVIOCGRAB = C.EVIOCGRAB // grab/release device
|
||||
)
|
||||
|
||||
var keyMap = map[uint16]rune{
|
||||
41: '`',
|
||||
2: '1',
|
||||
3: '2',
|
||||
4: '3',
|
||||
5: '4',
|
||||
6: '5',
|
||||
7: '6',
|
||||
8: '7',
|
||||
9: '8',
|
||||
10: '9',
|
||||
11: '0',
|
||||
12: '-',
|
||||
13: '=',
|
||||
98: '/',
|
||||
55: '*',
|
||||
74: '-',
|
||||
16: 'Q',
|
||||
17: 'W',
|
||||
18: 'E',
|
||||
19: 'R',
|
||||
20: 'T',
|
||||
21: 'Y',
|
||||
22: 'U',
|
||||
23: 'I',
|
||||
24: 'O',
|
||||
25: 'P',
|
||||
26: '[',
|
||||
27: ']',
|
||||
71: '7',
|
||||
72: '8',
|
||||
73: '9',
|
||||
78: '+',
|
||||
30: 'A',
|
||||
31: 'S',
|
||||
32: 'D',
|
||||
33: 'F',
|
||||
34: 'G',
|
||||
35: 'H',
|
||||
36: 'J',
|
||||
37: 'K',
|
||||
38: 'L',
|
||||
39: ';',
|
||||
40: '\'',
|
||||
75: '4',
|
||||
76: '5',
|
||||
77: '6',
|
||||
44: 'Z',
|
||||
45: 'X',
|
||||
46: 'C',
|
||||
47: 'V',
|
||||
48: 'B',
|
||||
49: 'N',
|
||||
50: 'M',
|
||||
51: ',',
|
||||
52: '.',
|
||||
53: '/',
|
||||
43: '\\',
|
||||
79: '1',
|
||||
80: '2',
|
||||
81: '3',
|
||||
57: ' ',
|
||||
82: '0',
|
||||
83: '.',
|
||||
}
|
||||
|
||||
var eventSize = int(unsafe.Sizeof(InputEvent{}))
|
||||
|
||||
type InputEvent struct {
|
||||
Time syscall.Timeval // time in seconds since epoch at which event occurred
|
||||
Type uint16 // event type - one of ecodes.EV_*
|
||||
Code uint16 // event code related to the event type
|
||||
Value int32 // event value related to the event type
|
||||
}
|
||||
|
||||
// InputDevice A Linux input device from which events can be read.
|
||||
type InputDevice struct {
|
||||
Fn string // path to input device (devnode)
|
||||
bufLen int // read buffer size
|
||||
|
||||
Name string // device name
|
||||
Phys string // physical topology of device
|
||||
File *os.File // an open file handle to the input device
|
||||
}
|
||||
|
||||
func EVIOCGBIT(ev, l int) int { return int(C._EVIOCGBIT(C.int(ev), C.int(l))) } // get event bits
|
||||
|
||||
// Open an evdev input device.
|
||||
func Open(devNode string, bufLen int) (*InputDevice, error) {
|
||||
f, err := os.Open(devNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dev := InputDevice{}
|
||||
dev.Fn = devNode
|
||||
dev.File = f
|
||||
|
||||
dev.bufLen = bufLen
|
||||
|
||||
return &dev, nil
|
||||
}
|
||||
|
||||
func (dev *InputDevice) Close() {
|
||||
if dev.File != nil {
|
||||
dev.File.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func ioctl(fd uintptr, name uintptr, data unsafe.Pointer) syscall.Errno {
|
||||
_, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, fd, name, uintptr(data))
|
||||
return err
|
||||
}
|
||||
|
||||
// Grab the input device exclusively.
|
||||
func (dev *InputDevice) Grab() error {
|
||||
grab := int(1)
|
||||
if err := ioctl(dev.File.Fd(), uintptr(EVIOCGRAB), unsafe.Pointer(&grab)); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Release a grabbed input device.
|
||||
func (dev *InputDevice) Release() error {
|
||||
if err := ioctl(dev.File.Fd(), uintptr(EVIOCGRAB), unsafe.Pointer(nil)); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read and return a slice of input events from device.
|
||||
func (dev *InputDevice) Read() ([]InputEvent, error) {
|
||||
buffer := make([]byte, eventSize*dev.bufLen)
|
||||
|
||||
count, err := dev.File.Read(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]InputEvent, count/eventSize)
|
||||
|
||||
b := bytes.NewBuffer(buffer)
|
||||
err = binary.Read(b, binary.LittleEndian, &events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events, err
|
||||
}
|
||||
|
||||
// GetKey Return appropriate key rune if present
|
||||
func (event *InputEvent) GetKey() (rune, bool) {
|
||||
if event.Type != 1 || event.Value != 1 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
r, ok := keyMap[event.Code]
|
||||
return r, ok
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module keyboard-reader
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require github.com/farjump/go-libudev v0.0.0-20171109190736-8b0739cd6d0b
|
||||
|
||||
require (
|
||||
github.com/jkeiser/iter v0.0.0-20200628201005-c8aa0ae784d1 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
github.com/farjump/go-libudev v0.0.0-20171109190736-8b0739cd6d0b h1:zMD1x/LqZnujKnuquz9rbl/P6HZomUj0YXD/yxEAXXM=
|
||||
github.com/farjump/go-libudev v0.0.0-20171109190736-8b0739cd6d0b/go.mod h1:yzTdDrJ3rMj/15/ayeyb6HhTtu7VClWfiarB328Iew0=
|
||||
github.com/jkeiser/iter v0.0.0-20200628201005-c8aa0ae784d1 h1:smvLGU3obGU5kny71BtE/ibR0wIXRUiRFDmSn0Nxz1E=
|
||||
github.com/jkeiser/iter v0.0.0-20200628201005-c8aa0ae784d1/go.mod h1:fP/NdyhRVOv09PLRbVXrSqHhrfQypdZwgE2L4h2U5C8=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -0,0 +1,118 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
udev "github.com/farjump/go-libudev"
|
||||
"keyboard-reader/evdev"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const bufferLength = 32
|
||||
|
||||
func main() {
|
||||
device := flag.String("dev", "", "Keyboard device")
|
||||
vendor := flag.String("vendor", "", "Device vendor")
|
||||
serial := flag.String("serial", "", "Device id serial")
|
||||
list := flag.Bool("list", false, "List input devices")
|
||||
connectorId := flag.Uint("connector", 0, "Connector id")
|
||||
timeout := flag.Int("timeout", 5, "Http timeout, sec")
|
||||
url := flag.String("url", "http://localhost:8914/state/idTag", "Endpoint url")
|
||||
flag.Parse()
|
||||
|
||||
if *list {
|
||||
if devs, err := getUdevEnumerate().Devices(); err != nil {
|
||||
log.Printf("Can't list devicies: %e", err)
|
||||
} else {
|
||||
for _, dev := range devs {
|
||||
log.Println(dev.Properties())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(*vendor) == 0 && len(*device) == 0 && len(*serial) == 0 && !*list {
|
||||
flag.Usage()
|
||||
}
|
||||
|
||||
if len(*vendor) > 0 {
|
||||
device = getDevByProperty(*vendor, "ID_VENDOR")
|
||||
}
|
||||
|
||||
if len(*serial) > 0 {
|
||||
device = getDevByProperty(*serial, "ID_SERIAL")
|
||||
}
|
||||
|
||||
if device == nil || len(*device) == 0 {
|
||||
log.Println("Keyboard device not selected")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var dev *evdev.InputDevice
|
||||
var events []evdev.InputEvent
|
||||
var err error
|
||||
|
||||
// Open our argv as a device and a file, so we can get the file descriptor.
|
||||
if dev, err = evdev.Open(*device, bufferLength); err != nil {
|
||||
// If the device can't be opened, spit out an error and exit.
|
||||
log.Printf("Unable to open input device: %s\n", *device)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer dev.Close()
|
||||
|
||||
if err = dev.Grab(); err != nil {
|
||||
log.Printf("Can't grab input from device: %s, error: %e\n", *device, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
defer dev.Release()
|
||||
|
||||
client := NewRest(*connectorId, *url, time.Duration(*timeout)*time.Second)
|
||||
|
||||
// Run an infinite loop.
|
||||
for {
|
||||
// Read the device events.
|
||||
if events, err = dev.Read(); err != nil {
|
||||
log.Printf("Can't read input from device: %s, error: %e\n", *device, err)
|
||||
os.Exit(3)
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
// Iterate through the events.
|
||||
for _, event := range events {
|
||||
if r, ok := event.GetKey(); ok {
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
if sb.Len() > 0 {
|
||||
id := sb.String()
|
||||
log.Printf("Send '%s'", id)
|
||||
client.Send(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getDevByProperty(value string, property string) *string {
|
||||
e := getUdevEnumerate()
|
||||
e.AddMatchProperty(property, value)
|
||||
|
||||
if devices, err := e.Devices(); err == nil {
|
||||
for _, item := range devices {
|
||||
if name, found := item.Properties()["DEVNAME"]; found {
|
||||
log.Printf("Found device by %s: %s\n", property, name)
|
||||
return &name
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUdevEnumerate() *udev.Enumerate {
|
||||
// Create Udev and Enumerate
|
||||
u := udev.Udev{}
|
||||
e := u.NewEnumerate()
|
||||
e.AddMatchSysname("event*")
|
||||
return e
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MonitorIdTag struct {
|
||||
ConnectorId uint `json:"connectorId"`
|
||||
IdTag string `json:"idTag"`
|
||||
}
|
||||
|
||||
type ResponseEntity struct {
|
||||
Timestamp *time.Time `json:"timestamp"`
|
||||
Status uint `json:"status"`
|
||||
Error *string `json:"error"`
|
||||
Path *string `json:"path"`
|
||||
}
|
||||
|
||||
type RestInterface interface {
|
||||
Send(id string)
|
||||
}
|
||||
|
||||
type rest struct {
|
||||
connectorId uint
|
||||
url string
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewRest(connectorId uint, url string, timeout time.Duration) RestInterface {
|
||||
r := rest{connectorId: connectorId, url: url, timeout: timeout}
|
||||
return &r
|
||||
}
|
||||
|
||||
func (r *rest) Send(id string) {
|
||||
go r.send(id)
|
||||
}
|
||||
|
||||
func (r *rest) send(id string) {
|
||||
tag := &MonitorIdTag{IdTag: id, ConnectorId: r.connectorId}
|
||||
|
||||
//if r.connectorId > 0 {
|
||||
// tag.ConnectorId = &r.connectorId
|
||||
//}
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
var request *http.Request
|
||||
var response *http.Response
|
||||
|
||||
if b, err = json.Marshal(tag); err != nil {
|
||||
log.Printf("Can't marschal request: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), r.timeout)
|
||||
byteReader := bytes.NewReader(b)
|
||||
|
||||
if request, err = http.NewRequestWithContext(ctx, "POST", r.url, byteReader); err != nil {
|
||||
log.Printf("Can't build new request: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
|
||||
if response, err = http.DefaultClient.Do(request); err != nil {
|
||||
log.Printf("Error while rerforming request: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch response.StatusCode {
|
||||
case 200:
|
||||
log.Printf("Result OK for id '%s'\n", id)
|
||||
case 401:
|
||||
log.Printf("Result UNAUTHORIZED for id '%s'\n", id)
|
||||
default:
|
||||
log.Printf("Recieve non sucess status code '%d' for id '%s'\n", response.StatusCode, id)
|
||||
}
|
||||
|
||||
if response.ContentLength == 0 {
|
||||
log.Println("Response body empty")
|
||||
return
|
||||
}
|
||||
|
||||
if b, err = io.ReadAll(response.Body); err != nil {
|
||||
log.Printf("Can't read replay body: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = response.Body.Close(); err != nil {
|
||||
log.Printf("Error while closing response body: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
entity := &ResponseEntity{}
|
||||
|
||||
if err = json.Unmarshal(b, entity); err != nil {
|
||||
log.Printf("Can't unmarschal response body: %e\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("ResponseEntity: %v\n", entity)
|
||||
}
|
||||
|
||||
func (r *ResponseEntity) String() string {
|
||||
var sb strings.Builder
|
||||
|
||||
if r.Path != nil {
|
||||
sb.WriteString("path = '")
|
||||
sb.WriteString(*r.Path)
|
||||
sb.WriteString("'")
|
||||
}
|
||||
|
||||
if r.Error != nil {
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString("error = '")
|
||||
sb.WriteString(*r.Error)
|
||||
sb.WriteString("'")
|
||||
}
|
||||
|
||||
if r.Timestamp != nil {
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString("timestamp = '")
|
||||
sb.WriteString(r.Timestamp.String())
|
||||
sb.WriteString("'")
|
||||
}
|
||||
|
||||
if sb.Len() > 0 {
|
||||
sb.WriteString(", ")
|
||||
}
|
||||
sb.WriteString("status = ")
|
||||
sb.WriteString(fmt.Sprint(r.Status))
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
Reference in New Issue
Block a user