Handleliste? Sånt har vi systemer for

Med en strekkodeleser, en raspberry pi, noen linjer kode og Microsoft Azure, oppdateres nå husholdningens handleliste på kolonial.no i takt med at vi går tomme for varer.

For noen måneder siden gikk jeg til innkjøp av en raspberry pi, siden jeg noe naivt trodde jeg ville ha tid og lyst til å bygge et Magic Mirror. Den illusjonen brast umiddelbart da jeg innså at det ville innebære én del koding/programvare og 99 deler finsnekring, så rpien ble liggende i en skuff.

Det endret seg dog veldig raskt da jeg kom over en bloggpost fra 2013 om Oscar, et system som automatisk oppdaterer en handleliste på trello ved hjelp av en strekkodescanner og litt koding.

Vår husholdning ble tidlig kunder av de nettbaserte dagligvarebutikkene, så ideen min var å kombinere prinsippet bak Oscar med en eksisterende netthandel. Som tenkt så utført: En strekkodescanner montert på kjøkkenet og noen linjer kode senere, og vi har et system som automatisk oppdaterer vår handlekurv på kolonial.no når vi scanner varer, enten når vi ser vi trenger mer eller når den tomm eemballasjen går i søpla.

Dette ble samtidig en gylden mulighet til å utforske Azure Functions før jeg tar de i bruk i faktiske kundeprosjekter – håpet er å publisere en liten føljetong med bloggposter som belyser de forskjellige kode- og arkitektur-valgene løsningen består av.

Strekkodeleseren

barcode-scanner

Selve strekkodeleseren kjøpte fra en mer eller mindre tilfeldig valgt forhandler på ebay. Det er en USB-variant som kobles til rpien på helt vanlig måte og registreres som en input-enhet:

python-list-devices

Som man ser av skjermbildet over, valgte jeg python som utviklingsspråk på raspberrien. Jeg befinner meg som oftest på microsoft-stacken i mitt daglige virke, så valget var mest for variasjonens skyld og for å bruke python til noe annet enn å si hallo til verden.

Litt rask googling avslørte at evdev var et naturlig valg når det kommer til å lese input fra en enhet, og det tok ikke mange minutter å snekre sammen en liten snutt som leste alle tegn frem til linjeskift og samlet disse til en barcode.

Den første versjonen (altså nåværende, og eneste, versjon), dytter strekkodene til en Azure Storage Queue, også tar en azure function over derifra. Kodenunder faller forøvrig inn i kategorien “kode som virker”, men neppe “dogmatisk og strukturelt korrekt python” – ta det for det det er

import evdev
from evdev import *
from azure.storage.queue import QueueService, QueueMessageFormat
import threading
import time
from queue import *
import datetime
# responsible for uploading the barcodes to the azure storage queue.
class BarcodeUploader:
def __init__(self):
# Instiantiate the azure queue service (from the azure.storage.queue package)
self.queue_service = QueueService(account_name='wereoutof', account_key='your-key-here')
# azure functions is _very_ confused is the text isn't base64 encoded
self.queue_service.encode_function = QueueMessageFormat.text_base64encode
# use a simple queue to avoid blocking operations
self.queue = LifoQueue()
t = threading.Thread(target=self.worker, args=())
t.daemon = True
t.start()
# processes all messages (barcodes) in queue - uploading them to azure one by one
def worker(self):
while True:
while not self.queue.empty():
try:
barcode = self.queue.get()
self.queue_service.put_message('barcodes', u'account-key:' + barcode)
except Exception as exc:
print("Exception occured when uploading barcode:" + repr(exc))
# re-submit task into queue
self.queue.task_done()
self.queue.put(barcode)
else:
print("Barcode " + barcode + " registered")
self.queue.task_done()
time.sleep(1)
def register(self, barcode):
print "Registering barcode " + barcode + "..."
self.queue.put(barcode)
current_barcode = ""
# Reads barcode from "device"
def readBarcodes():
global current_barcode
print ("Reading barcodes from device")
for event in device.read_loop():
if event.type == evdev.ecodes.EV_KEY and event.value == 1:
keycode = categorize(event).keycode
if keycode == 'KEY_ENTER':
uploader.register(current_barcode)
current_barcode = ""
else:
current_barcode += keycode[4:]
# Finds the input device with the name "Barcode Reader ".
# Could and should be parameterized, of course. Device name as cmd line parameter, perhaps?
def find_device():
device_name = 'Barcode Reader '
devices = [evdev.InputDevice(fn) for fn in evdev.list_devices()]
for d in devices:
if d.name == device_name:
print("Found device " + d.name)
device = d
return device
# Find device...
device = find_device()
if device is None:
print("Unable to find " + device_name)
else:
#... instantiate the uploader...
uploader = BarcodeUploader()
# ... and read the bar codes.
readBarcodes()
view raw barcodereader.py hosted with ❤ by GitHub

Som man ser, gjør kodesnutten ikke mye: Den leser tall frem til “enter”, og sender disse (altså strekkoden) til Azure via azure.storage-pakken. I denne første, rudimentære versjonen brukes en kø som retry-mekanisme.

Scriptet starter ved maskinstart som en cronjobb:

# m h dom mon dow command
@reboot sh /home/pi/launch_barcode_reader.sh > /home/pi/logs/cronlog 2>&1

launch_barcode_reader.sh setter opp pythonmiljøet og starter scriptet.

#!/usr/bin/env bash
cd /home/pi/Devel/barcode_reader/
/home/pi/.virtualenvs/barcode_reader/bin/python reader.py

Neste gang viser jeg hvordan dette håndteres på mottakssiden, men en azure function storage queue trigger (altså en funksjon som eksekveres hver gang noe legges til en spesifikk kø).

One thought on “Handleliste? Sånt har vi systemer for

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s