feat: YAB-1785: Initial commit

fix/YAB-1785
Terekhin Alexandr 2 years ago
commit ceeedf6d58
Signed by: didinst
GPG Key ID: D2EF94423C23BF12
  1. 14
      Dockerfile
  2. 25
      Makefile
  3. 0
      README.md
  4. 6
      debian/debian/changelog
  5. 1
      debian/debian/compat
  6. 11
      debian/debian/control
  7. 2
      debian/debian/postinst
  8. 23
      debian/debian/rules
  9. 2
      debian/debian/yablochkov-keyboard-reader.install
  10. 18
      debian/etc/systemd/system/keyboard-reader.service
  11. 182
      evdev/evdev.go
  12. 10
      go.mod
  13. 6
      go.sum
  14. 118
      keyboard-reader.go
  15. 146
      rest.go

@ -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

@ -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

@ -0,0 +1 @@
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

@ -0,0 +1,2 @@
chmod +x /opt/ocppc/bin/keyboard-reader
systemctl enable keyboard-reader --now

@ -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,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

@ -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()
}
Loading…
Cancel
Save