Skip to content

Commit 72fb830

Browse files
committed
Restructured barlink to have a setup script.
Also adds the executable barlink.
1 parent 1694a41 commit 72fb830

File tree

5 files changed

+100
-48
lines changed

5 files changed

+100
-48
lines changed

barlink/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Barlink functions as a local WebSocket server for barsystem (https://github.com/TkkrLab/barsystem).
2+
3+
It processes serial input from authenticator devices and forwards it to the barsystem server.

barlink/setup.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from setuptools import setup, find_packages
2+
3+
setup(
4+
name='barlink',
5+
version='1.0.0',
6+
packages=find_packages('src'),
7+
package_dir={'': 'src'},
8+
9+
entry_points={
10+
'console_scripts': [
11+
'barlink = barlink.websocket:main',
12+
]
13+
},
14+
15+
install_requires=[
16+
'pyserial',
17+
],
18+
19+
license='MIT',
20+
description='Compact WebSocket server for barsystem',
21+
long_description=open('README.md').read(),
22+
url='https://github.com/TkkrLab/barsystem',
23+
author='Jasper Seidel',
24+
author_email='[email protected]',
25+
)

barlink/src/barlink/__init__.py

Whitespace-only changes.

barlink/receipts.py barlink/src/barlink/receipts.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import serial
2-
import time
2+
33

44
class ReceiptPrinter:
55
ALIGN_LEFT = 0
@@ -13,7 +13,6 @@ class ReceiptPrinter:
1313
PRINTMODE_DOUBLE_WIDTH = 0x20
1414
PRINTMODE_UNDERLINE = 0x80
1515

16-
1716
CMD_ESC = b'\x1B'
1817
CMD_GS = b'\x1D'
1918

barlink/websocket.py barlink/src/barlink/websocket.py

+71-46
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,22 @@
22

33
import base64
44
import hashlib
5+
import json
6+
import os
7+
# import pyudev
58
import re
69
import select
10+
import serial
11+
import serial.tools.list_ports
712
import socket
8-
import ssl
913
import struct
14+
import ssl
1015
import sys
1116
import threading
1217
import time
1318
import logging
14-
import json
15-
import os
1619

17-
from receipts import ReceiptPrinter
20+
from barlink.receipts import ReceiptPrinter
1821

1922

2023
def readlines(sock, recv_buffer=4096, delim='\n'):
@@ -29,12 +32,15 @@ def readlines(sock, recv_buffer=4096, delim='\n'):
2932
yield line
3033
return
3134

35+
3236
class SocketClosedException(Exception):
3337
pass
3438

39+
3540
def header_split(line):
3641
return map(lambda x: x.strip(), line.split(':', 1))
3742

43+
3844
class WebSocketConnection:
3945
WEBSOCK_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
4046

@@ -61,17 +67,16 @@ def __init__(self, socket, remote_addr):
6167
@property
6268
def addr(self):
6369
return self.remote_addr, self.remote_port
64-
70+
6571
def upgrade(self):
6672
result = self.handshake()
67-
if result == True:
73+
if result is True:
6874
return True
6975
else:
70-
if result == False:
76+
if result is False:
7177
self.handshakeReject()
7278
return False
7379

74-
7580
def handshake(self):
7681
reply = ''
7782
self.request_path = ''
@@ -88,23 +93,25 @@ def handshake(self):
8893
self.write('HTTP/1.1 405 Method not allowed\r\n\r\n')
8994
return 405
9095
continue
91-
if not ':' in line:
96+
if ':' not in line:
9297
continue
9398
key, value = header_split(line)
9499
self.headers[key] = value
95100
# print(key, value)
96101
# if line.startswith('Sec-WebSocket'):
97102
# print(line)
98103
# if key == 'Sec-WebSocket-Key':
99-
104+
100105
if self.headers.get('Sec-WebSocket-Version', None) != '13':
101106
return False
102107
key = self.headers.get('Sec-WebSocket-Key', None)
103108
if not key:
104109
logging.warn('KEY MISSING')
105110
return False
106111

107-
reply = base64.b64encode(hashlib.sha1(key.encode('ascii') + self.WEBSOCK_GUID).digest()).decode('ascii')
112+
reply = base64.b64encode(
113+
hashlib.sha1(key.encode('ascii') + self.WEBSOCK_GUID).digest()
114+
).decode('ascii')
108115

109116
if len(reply) == 0:
110117
logging.warn('REPLY FAILED')
@@ -127,10 +134,16 @@ def write(self, data, encode=True):
127134

128135
def sendMessage(self, message='', opcode=0x01):
129136
opcode_str = self.opcode_map[opcode] if opcode in self.opcode_map else opcode
130-
logging.info('{}:{} SEND {}; opcode: {}; length: {}; payload: "{}"'.format(self.remote_addr, self.remote_port, self.request_path, opcode_str, len(message), message))
137+
logging.info('{}:{} SEND {}; opcode: {}; length: {}; payload: "{}"'.format(
138+
self.remote_addr,
139+
self.remote_port,
140+
self.request_path,
141+
opcode_str,
142+
len(message),
143+
message))
131144
data = bytearray()
132-
data.append(0x80 | (opcode & 0x0F)) # FIN
133-
145+
data.append(0x80 | (opcode & 0x0F)) # FIN
146+
134147
length = len(message)
135148

136149
if length <= 125:
@@ -181,7 +194,7 @@ def receiveMessage(self):
181194
for i in range(len(payload)):
182195
payload2.append(payload[i] ^ masking_key[i % 4])
183196
payload = payload2
184-
197+
185198
opcode_str = opcode
186199
if opcode in self.opcode_map:
187200
opcode_str = self.opcode_map[opcode]
@@ -196,7 +209,13 @@ def receiveMessage(self):
196209
payload = payload.decode('utf-8')
197210
except UnicodeDecodeError:
198211
pass
199-
logging.info('{}:{} RECV {}; opcode: {}; length: {}; payload: "{}"'.format(self.remote_addr, self.remote_port, self.request_path, opcode_str, length, payload))
212+
logging.info('{}:{} RECV {}; opcode: {}; length: {}; payload: "{}"'.format(
213+
self.remote_addr,
214+
self.remote_port,
215+
self.request_path,
216+
opcode_str,
217+
length,
218+
payload))
200219

201220
if opcode == self.OPCODE_CLOSE:
202221
raise SocketClosedException
@@ -207,13 +226,12 @@ def receiveMessage(self):
207226
self.socket.close()
208227
return False
209228

210-
211229
def close(self):
212230
self.socket.close()
213231

214232

215233
class WebSocketServer(threading.Thread):
216-
def __init__(self, monitors, ssl=False):
234+
def __init__(self, monitors, use_ssl=False):
217235
super().__init__()
218236

219237
self._stop_event = threading.Event()
@@ -224,7 +242,7 @@ def __init__(self, monitors, ssl=False):
224242
monitor.start()
225243

226244
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
227-
if ssl:
245+
if use_ssl:
228246
self.sock = ssl.wrap_socket(self.sock, server_side=True, certfile='bar.pem', keyfile='bar.pem')
229247
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
230248
self.sock.bind(('localhost', 1234))
@@ -261,7 +279,7 @@ def run(self):
261279
for sock in r:
262280
conn = [conn for conn in self.conn if conn.socket == sock][0]
263281
msg = conn.receiveMessage()
264-
if msg != False:
282+
if msg is not False:
265283
opcode, payload = msg
266284
if opcode == WebSocketConnection.OPCODE_TEXT:
267285
self.process_text_message(conn.request_path, payload)
@@ -280,7 +298,7 @@ def stop(self):
280298
for monitor in self.monitors:
281299
monitor.stop()
282300
for conn in self.conn:
283-
conn.sendMessage(opcode=WebSocketConnection.OPCODE_CLOSE) # opcode close
301+
conn.sendMessage(opcode=WebSocketConnection.OPCODE_CLOSE) # opcode close
284302
self._stop_event.set()
285303

286304
def on_message(self, message):
@@ -292,12 +310,13 @@ def sendAll(self, message='', opcode=0x01):
292310
conn.sendMessage(message, opcode)
293311
except Exception as e:
294312
print(e)
295-
c.error = True
313+
self.error = True
296314
self.cleanConnections()
297315

298316
def cleanConnections(self):
299317
self.conn[:] = [c for c in self.conn if not c.error]
300318

319+
301320
class Barlink(WebSocketServer):
302321
def __init__(self, *args, printer=None, **kwargs):
303322
super().__init__(*args, **kwargs)
@@ -309,12 +328,12 @@ def process_text_message(self, conn, payload):
309328
except json.JSONDecodeError:
310329
logging.exception('Failed to parse received payload as JSON.')
311330
return
312-
if not 'action' in data:
331+
if 'action' not in data:
313332
logging.warning('Invalid JSON: action field not set.')
314333
return
315334
action = data['action']
316335
if action == 'receipt':
317-
if not 'products' in data:
336+
if 'products' not in data:
318337
logging.warning('Invalid request: receipt with no products')
319338
return
320339
if 'customer_name' in data:
@@ -331,6 +350,7 @@ def process_text_message(self, conn, payload):
331350
else:
332351
logging.warning('Invalid action: "{}"'.format(action))
333352

353+
334354
def print_receipt(printer, customer_name, products):
335355
printer.init()
336356
printer.set_code_table('cp858')
@@ -367,15 +387,9 @@ def print_receipt(printer, customer_name, products):
367387

368388
printer.cut(0)
369389

370-
import serial
371-
import serial.tools.list_ports
372-
import select
373-
import threading
374-
import time
375-
# import pyudev
376390

377391
class PortDetect:
378-
def __init__(self): #, notify_event, vid='16c0', pid='0483'):
392+
def __init__(self): # , notify_event, vid='16c0', pid='0483'):
379393
# self.devices = [[vid, pid]]
380394
# self.notify_event = notify_event
381395
context = pyudev.Context()
@@ -396,14 +410,17 @@ def __init__(self): #, notify_event, vid='16c0', pid='0483'):
396410
# import sys
397411
# sys.exit(0)
398412

413+
399414
class Monitor(threading.Thread):
400415
def __init__(self):
401416
super().__init__()
402417
self._stop_event = threading.Event()
403418
self.server = None
404419
self.buffer = b''
420+
405421
def stop(self):
406422
self._stop_event.set()
423+
407424
def set_server(self, server):
408425
self.server = server
409426

@@ -430,7 +447,7 @@ def reconnect(self):
430447
pass
431448
try:
432449
# print('Connecting to {}...'.format(self.port))
433-
self.serial = serial.Serial(self.port, 19200, timeout = 0)
450+
self.serial = serial.Serial(self.port, 19200, timeout=0)
434451
return True
435452
except Exception as e:
436453
# print('Error in port {}: {}'.format(self.port, e))
@@ -453,12 +470,12 @@ def reconnect(self):
453470
pass
454471
# print('No ports found!')
455472
return False
456-
473+
457474
def processInput(self, data):
458475
try:
459476
data = data.decode('ascii')
460-
except AsciiDecodeError as e:
461-
print('AsciiDecodeError: {}'.format(e))
477+
except UnicodeDecodeError as e:
478+
print('UnicodeDecodeError: {}'.format(e))
462479
data = data.strip()
463480
if len(data) == 0:
464481
return
@@ -468,10 +485,10 @@ def processInput(self, data):
468485
data = 'ibutton{' + old_ibutton.group(1) + '}'
469486
self.notify(data)
470487

471-
def run( self ):
488+
def run(self):
472489
while not self._stop_event.is_set():
473490
try:
474-
r, w, e = select.select([self.serial],[],[], 0.01)
491+
r, w, e = select.select([self.serial], [], [], 0.01)
475492
if len(r) > 0:
476493
try:
477494
self.buffer += self.serial.read()
@@ -487,40 +504,48 @@ def run( self ):
487504
if self.serial:
488505
self.serial.close()
489506

507+
490508
class ConsoleMonitor(Monitor):
491509
def run(self):
492510
while not self._stop_event.is_set():
493-
while sys.stdin in select.select([sys.stdin],[],[], 0.1)[0]:
511+
while sys.stdin in select.select([sys.stdin], [], [], 0.1)[0]:
494512
line = sys.stdin.readline().strip()
495513
if not line:
496514
self.stop()
497515
break
498516
self.notify(line)
499517
time.sleep(0.1)
500518

501-
if __name__ == '__main__':
519+
520+
def main(argv=None):
521+
if not argv:
522+
argv = sys.argv[1:]
502523
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
503524
serial_monitor = SerialMonitor()
504525
console_monitor = ConsoleMonitor()
505526

506527
try:
507-
import settings
528+
from barlink import settings
508529
except ImportError:
509530
logging.warning('No settings found.')
510531
settings = None
511532

512533
receipt_printer = None
513534

514-
try:
515-
if settings:
535+
if settings:
536+
try:
516537
receipt_printer = ReceiptPrinter(settings.RECEIPT_PRINTER_PORT)
517-
except serial.SerialException:
518-
logging.exception('Cannot connect to receipt printer')
538+
logging.info('Configured receipt printer on {}.'.format(settings.RECEIPT_PRINTER_PORT))
539+
except serial.SerialException:
540+
logging.exception('Cannot connect to receipt printer')
519541

520-
print(settings, receipt_printer)
521542
s = Barlink([serial_monitor, console_monitor], printer=receipt_printer)
522543
try:
523544
s.start()
524545
s.join()
525546
except KeyboardInterrupt:
526-
s.stop()
547+
s.stop()
548+
549+
550+
if __name__ == '__main__':
551+
main()

0 commit comments

Comments
 (0)