Lightning Snacks

A Vending Machine using the Bitcoin Lightning Network on a Full Node Raspberry Pi

Problem Definition

Most vending snack machines are coin-operated. This leads to many drawbacks such as incorrect/no change, some coins are not recognized, management of coins by owner and no coins in your wallet.

Sometimes my colleagues ask me for some coins, and in return they give banknotes or pay me via Revolut. Why should we all go through this hassle if when can do online transactions? We can use Visa/Mastercard scanners attached with vending machines and people can buy snacks easily. Well, the problem is that for every authorized transaction, the bank takes a commission. Is it worth for the merchant to pay a commission for a cheap snack item? I don’t think so. Apart from that, this technique is not recommended because there might be hackers that can attach card sniffers to the vending machine intending to hijack your card. So, what’s the solution?

The Solution

Nowadays everyone owns a smartphone. With Bitcoin, you can have your own non-custodial wallet on your phone. You can do global transactions on decentralized platforms without making use of third-party APIs (banks) to verify transactions. Can we apply this technology to our vending machine? We can but it won’t be ideal. The problem lies in the transaction confirmation time and the transaction fees.

One confirmation (1 block) takes about 10 minutes and we usually assume that a transaction is fully confirmed when it is 6 blocks deep. This means that a full confirmation takes about 1 hour (10 minutes * 6 blocks). This is very fast when compared to traditional systems, but it won’t be ideal for our vending machine system. Imagine our client waiting 1 hour to buy some candy. Since snacks are not expensive, we can assume that one confirmation (1 block deep) is enough, but our client must still wait 10 minutes for the transaction to be processed.

The other problem relies in the transaction fees. Transaction fees are included with every Bitcoin transaction in order to have transactions processed by a miner and confirmed by the Bitcoin network. The space available for transactions in a block is currently limited to 1 MB. This means that to get your transaction processed quickly you must outbid other users. Therefore, it can get expensive to buy some snacks.

Bitcoin Cash (another cryptocurrency – fork of Bitcoin) simply solves these problems by increasing the block size and disabling the ‘replace by higher fee’ feature. Therefore, a transaction can be created with a lower transaction fee and once created, we can safely assume that the transaction is going to be placed in the next block. This might lead to other problems, but I don’t want to debate Bitcoin Cash vs Bitcoin in this article.

The Bitcoin Lightning Network (Layer 2 Protocol on Bitcoin) is the ideal solution for our vending snack machine. It is a decentralized network powered by smart contracts that enables instant payments with very low-cost transaction fees across participants. Lightning enables one to send very small funds in Bitcoin without custodial risk. Everyone can be part of the network by hosting and opening a channel with a node. When opening a channel, participants must commit an amount which is stored on the blockchain. Transactions then can be passed through multiple channels in a mesh network of payment channels. These transactions are not stored on the public blockchain and therefore the base layer fees are avoided. I must admit that it is a bit complicated to work with but once you understand what is happening, you will appreciate the principles behind this system.

1024px-17_node_mesh_network.svg_

Building the Prototype

For this project one used the Raspberry Pi 3 B+ with an external hard disk storing the entire Bitcoin testnet blockchain. The Bitcoin Lightning Network was installed using the Raspibolt tutorial and Python was used to develop the whole system. That is creating the GUI using a game engine called PyGame, interfacing with the LND network, connecting with the Coinbase API for price conversion, making invoices into QRCodes, recursively checking for payments and eventually turning the motors using GPIO commands for successful payments. The Eclair Mobile Testnet app was installed on my phone to test for the client perspective.

LightningSnacks.JPG

Description Link
3D printed some of the objects found in this project. https://www.thingiverse.com/thing:3116905

 

Official Raspberry Pi 7″ Touchscreen Display https://thepihut.com/products/official-raspberry-pi-7-touchscreen-display
Bitcoin Lighting Network on Raspberry Pi (RaspiBolt) https://stadicus.github.io/RaspiBolt/
Python gRPC client for the Lightning Network Daemon https://github.com/lightningnetwork/lnd/blob/master/docs/grpc/python.md
LND gRPC API Reference https://api.lightning.community/#simple-rpc
Conversion from  Euro to Bitcoin https://developers.coinbase.com/api/v2
Python Game Engine for User Interface – PyGame https://www.pygame.org/news
Raspberry pi motor library https://github.com/gavinlyonsrepo/RpiMotorLib
NEMA 17 Motor https://reprap.org/wiki/NEMA_17_Stepper_motor
Shaft Holder Extension Rod https://www.ebay.co.uk/itm/7Pcs-ER11-1-7mm-Spring-Collets-Set-ER11A-5mm-Motor-Shaft-Holder-Extension-Rod/163608271173?ssPageName=STRK%3AMEBIDX%3AIT&_trksid=p2057872.m2749.l2649
QR Code Generator https://pypi.org/project/qrcode/
Android Eclair Mobile Testnet https://play.google.com/store/apps/details?id=fr.acinq.eclair.wallet&hl=en_US

Demonstration

Conclusion

This prototype clearly demonstrates that the Lightning Network is a potential solution for the problems mentioned before. One managed to buy snacks instantly with very low transaction fees. On average, every transaction fee was about 4 satoshis which is equivalent to 0.0002557 EUR (at the time of writing 1 BTC is equivalent to 6385 Euros). One problem that I constantly encountered was the inbound capacity limit which is described clearly here: https://blog.muun.com/the-inbound-capacity-problem-in-the-lightning-network/. Developers are working on this using a solution called Lightning Loops: https://blog.lightning.engineering/posts/2019/03/20/loop.html. In the future I want to test this prototype on the Bitcoin Mainnet using a 4GB Raspberry Pi 4 and experiment with the looping solution.

Thanks for reading 🙂

Photo booth – Raspberry Pi

Photo Booth using the Raspberry Pi and Pi Camera. Designed for 7″ touch screen. Just run  python code in Raspbian.

from __future__ import print_function
import pygame
import time
from picamera import PiCamera
import sys
from PIL import Image

import os

pygame.init()

win = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
win.fill((255,255,255))
camera = PiCamera()
camera.resolution = (800,800)
#camera.color_effects = (128,128) #turn camera to black and white

class button():
    def __init__(self, color, x,y,width,height, text=''):
        self.color = color
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.text = text

    def draw(self,win,outline=None):
        #Call this method to draw the button on the screen
        if outline:
            pygame.draw.rect(win, outline,(self.x-2,self.y-2,self.width+4,self.height+4),0)


        pygame.draw.rect(win, self.color,(self.x,self.y,self.width,self.height),0)


        if self.text != '':
            font = pygame.font.SysFont('comicsans', 60)
            text = font.render(self.text, 1, (0,0,0))
            win.blit(text, (self.x + (self.width/2 - text.get_width()/2), self.y + (self.height/2 - text.get_height()/2)))


    def isOver(self, pos):
        #Pos is the mouse position or a tuple of (x,y) coordinates
        if pos[0] > self.x and pos[0]  self.y and pos[1] < self.y + self.height:
                return True

        return False


def redrawWindow():
    win.fill((255,255,255))
    greenButton.draw(win)

def counterWindow():
    win.fill((255,255,255))
    if(counter!=0):
        counterLabel.text = str(counter)
    else:
        counterLabel.text = "Smile"
    counterLabel.draw(win)

def loadimage():
    global imagename
    win.fill((0,0,0))
    img = pygame.image.load(imagename)
    img = pygame.transform.scale(img,(480,480))
    win.blit(img,(170,0))

def takePhoto():
    global showImage
    global counterActive
    global counter
    global imagename
    print("Take Photo")
    for x in range(4):
        camera.start_preview()
        time.sleep(1)
        camera.capture('image'+str(x)+'.jpg')
        camera.stop_preview()


    files = [
  'image0.jpg',
  'image1.jpg',
  'image2.jpg',
  'image3.jpg']

    result = Image.new("RGB", (800, 800))

    for index, file in enumerate(files):
      path = os.path.expanduser(file)
      img = Image.open(path)
      img.thumbnail((400, 400), Image.ANTIALIAS)
      x = index // 2 * 400
      y = index % 2 * 400
      w, h = img.size
      print('pos {0},{1} size {2},{3}'.format(x, y, w, h))
      result.paste(img, (x, y, x + w, y + h))

    imagename = str(round(time.time() * 1000)) +'.jpg'
    result.save(os.path.expanduser(imagename))
    showImage = True
    counterActive = False
    counter = 6



def resetStatus():
    global counter
    global startProcess
    global counterActive
    global imageCounter

    counter = 6
    startProcess = True
    counterActive = False
    imageCounter = 0


run = True
counter = 6
startProcess = True
counterActive = False
showImage = False
greenButton = button((0,255,0),280,200,250,100,'Take Photos')
counterLabel = button((100,255,255),300,200,250,100,'0')
imageCounter = 0
imagename =""

while run:
    print(counter)
    if(startProcess == True):
        redrawWindow()
    elif(counterActive == True):
        counter = counter-1
        counterWindow()
        time.sleep(1)
    elif(showImage == True):
        loadimage()
        time.sleep(1)
        imageCounter = imageCounter +1
        if(imageCounter == 15):
             resetStatus()


    pygame.display.update()

    if(counterActive == False):
        for event in pygame.event.get():
            pos = pygame.mouse.get_pos()

            if event.type == pygame.QUIT:
                run = False
                pygame.quit()
                quit()

            if event.type == pygame.MOUSEBUTTONDOWN:
                if greenButton.isOver(pos):
                    print("Clicked the button")
                    counterActive = True
                    startProcess = False

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    run = False
                    pygame.quit()
                    quit()

    if(counter == 0):
        takePhoto()

Mac OS – Stream your screen

python_mac

My Screen

  • Stream your screen with others that are on the same local network. Users can’t control your MAC.
  • Developed using Python
  • Open source
  • Very easy to run
  • Tested with 20 concurrent students in a lab with cable connection

Step 1

  • Download zip and extract on your Desktop (very important)
  • Install python and flask server on Mac (if not already installed)

Step 2

  • Run the following two scripts separately in terminal. (Open two terminals – scripts found in MyScreenMac folder)
  • python screen_capture.py and python server.py

Step 3

  • In the MyScreenMac folder you should find a text file named ‘url.txt’. Open it and distribute the link with your audience. They should write the link in Safari/Chrome or other web browser.

Tips

  • Lower the resolution of your MAC for faster streaming or else edit the ‘screen_capture.py’ script. In my testing environment, I have a projector connected with the iMac. Automatically Mac OS reduces the resolution when it detects the projector.
  • Press ctrl+c in the terminal to stop the scripts

Flask Server with GPIOs

Let’s say that you have a running Flask server and you want your user to control the state (on/off) of an LED via the GPIO. The following technique shows how to connect Flask server with a separate GPIO script using PID.

Download the whole project

Run the following two scripts at the same time using the terminal:

from flask import *
import os
import signal

app = Flask(__name__)

#create a route path for index page
@app.route("/")
def index():
    return render_template("index.html")

#create a route path for about page
@app.route("/about")
def about():

    if 'name' in session: #if session 'name' exists
        print(session['name']) #print what's inside session[name]

        session.pop('name',None) #after printing, delete session name

    return render_template("aboutus.html") #render the html template aboutus

#/students accepts a get and post requests
@app.route("/student",methods=['GET','POST'])
def student():
    if(request.method == 'POST'): #if user fills a form
        studentName = request.form['studentname']
        if(studentName == "jimmy"):
            session["name"] = studentName #create session, name it 'name' and fill with studentName
            return redirect(url_for("about")) #redirect to about page
        else:
            return render_template("student.html") #render student template

    else:
        return render_template("student.html")#render student template

#routes with paramaters
@app.route("/led")
@app.route("/led/<param>")
def led(param=None):
    if(param == "on"): #if paramater is /led/on
        #read process id from text file generated by ledscript.py
        fh=open("processid.txt","r")
        processId = int(fh.read()) #load the process id
        fh.close()

        fh=open("led.txt","w")
        fh.write("1") #save 1 in text file. 1 means LED ON. 0 means LED Off
        fh.close()

        #send signal to process id
        os.kill(processId, signal.SIGUSR1)
        return "LED ON"
    elif(param =="off"): # if paramater is /led/off
        #read process id from text file
        fh=open("processid.txt","r")
        processId = int(fh.read())
        fh.close()

        fh=open("led.txt","w")
        fh.write("0")
        fh.close()

        #send signal to process id (ledscript)
        os.kill(processId, signal.SIGUSR1)
        return "LED OFF"
    else:
        return "LED MAIN PAGE"

if __name__ == "__main__":
    app.secret_key = 'asdfd!45sdf' #create secret key to secure sessions
    app.run(debug=True, host = "0.0.0.0")

import RPi.GPIO as GPIO
import time
import traceback
import os
import signal

def Main():
    try:
        #when scripts runs create processid.txt
        fh=open("processid.txt","w")
        fh.write(str(os.getpid())) #get current process id and store in file
        fh.close()

        GPIO.setmode(GPIO.BCM)

        GPIO.setwarnings(False)

        GPIO.setup(4, GPIO.OUT, initial = GPIO.LOW) #setup GPIO4 to low

        def handUSR1(signum,frame): #this function is automatically triggered from flask
            fh=open("led.txt","r")
            led = int(fh.read())
            fh.close()

            if(led == 0): #if 0 is found in text file
                print("LED OFF") #turn off led
                GPIO.output(4,GPIO.LOW)
            else:
                print("LED ON") #if not 0 (1) is found in text file
                GPIO.output(4,GPIO.HIGH) #turn on led

        signal.signal(signal.SIGUSR1,handUSR1) #callback function for SIGUSR1 signal (from flask when kill command is given)

        while(True):
            time.sleep(1)

    except Exception as ex:
        traceback.print_exc()
    finally:
        GPIO.cleanup() #this ensures a clean exit

Main()

Control a separate running script from a Web Server (python)- RPi

Let’s say that you have a running Flask server and you want your user to control the state (on/off) of a motion sensor via the GPIO. The most complex way is to create a multi-threading script which handles the server and GPIO code.

Another approach is to separate the server script from the GPIO script. Thus having two layers of scripts. This has the advantage to debug in isolation. For this technique one has to use Unix Signals which can be used to send signals from one process to another.

When you execute a script on your UNIX system, the system creates a process id (pid) which is different every time. A signal is a software interrupt which notifies a process with a significant event or request.

The following table gives a list of the most common signals:

NAME NUMBER DESCRIPTION
SIGHUP 1 Linux sends a process this signal when it becomes disconnected from a terminal.
SIGINT 2 Linux sends a process this signal when the user tries to end it by

pressing CTRL+C.

SIGILL 4 Linux sends a process this signal when it attempts to execute an illegal instruction.
SIGABRT 6 Linux sends a process this signal to the process when the process calls the ‘abort ()’ function
SIGFPE 8 Linux sends a process this signal when it has executed an invalid floating-point math instruction
SIGKILL 9 Linux sends a process this signal to end it immediately
SIGUSR1 10 User programs can send this signal to other process
SIGUSR2 12 User programs can send this signal to other process
SIGSEGV 11 Linux sends a process this signal when the program has attempted an invalid memory access
SIGPIPE 13 Linux sends a process this signal when the program has attempted to access a broken data stream, such as a socket connection that has been already closed
SIGALRM 14 A process can receive this signal from the Linux using the function alarm (), after a time period mentioned in its argument.
SIGTERM 15 Linux sends a process this signal requesting it to terminate
SIGCHLD 17 Linux sends a process this signal when a child process exits
SIGXCPU 24 Linux sends a process this signal when it exceeds the limit of

CPU time that it can consume.

SIGVTALRM 26 A process can receive this signal from the Linux using the function setitimer (), after a time period mentioned in its argument.

We are interested in SIGUSR1 and SIGUSR2 which can be used to send user signals.

Step 1:

First create the server layer (app.py). Documentation about Flask server can be found here.


from flask import *
import os
import signal

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/trigger1')
def process1():
    #read process id from text file
    fh=open("processid.txt","r")
    processId = int(fh.read())
    fh.close()

    #send signal to process id
    os.kill(processId, signal.SIGUSR1)

    return render_template('trigger1.html')

@app.route('/trigger2')
def process2():
    #read process id from text file
    fh=open("processid.txt","r")
    processId = int(fh.read())
    fh.close()

    #send signal to process id
    os.kill(processId, signal.SIGUSR2)

    return render_template('trigger2.html')

if __name__ == '__main__':
    app.run(debug=True,host='0.0.0.0')

Step 2:

Create the SignalReceiver.py script (you can modify this to control the GPIOs)


import os
import signal
import time

fh=open("processid.txt","w")
fh.write(str(os.getpid())) #get current process id and store in file
fh.close()

def handUSR1(signum,frame):
    print('triggered',signum)

def handUSR2(signum,frame):
    print('triggered',signum)

signal.signal(signal.SIGUSR1,handUSR1) #callback function for SIGUSR1 signal
signal.signal(signal.SIGUSR2,handUSR2) #callback function for SIGUSR2 signal

while(True):
    time.sleep(1)
    print("Waiting for signal")

Step 3: Run Server

flaskupload3.JPG

Step 4: Run SignalReceiver.py

flaskupload4.JPG

Step 5: 

In the browser, click Trigger 1 and Trigger 2 hyperlink buttons. Notice that SignalReceiver.py outputs ‘triggered 10′ and ‘triggered 12′. This means that both signals were sent and received correctly.

flaskupload5

Link to project: https://sourceforge.net/projects/pi-send-signals-to-scripts/

PIXEL(Raspbian)-Virtual Box-Persistence Drive

Please note that this is not officially approved by the PI team but the MagPi magazine offers a similar tutorial using a pendrive.

Step 1

Download the Pixel (Jessie Raspbian) image file: http://downloads.raspberrypi.org/pixel_x86/images/pixel_x86-2016-12-13/2016-12-13-pixel-x86-jessie.iso

Step 2:

Open Virtual Box, press new and create the following settings:

step1

Press Create and enter the following settings

step2

Press Start

step3

 

Step 3:

Browse to the downloaded ISO file and press Start

step4

Pixel is loaded

step5.JPG

Step 4:

To test persistence, create a new folder on your desktop and restart your OS.

step6

step7

Once your OS is restarted, you will notice that the folder you created is lost. This is because there is no persistence drive present.

step8.JPG

Step 5 (to create persistence drive):

Open terminal window and enter sudo apt install gparted

step9.JPG

When installation is finished, enter sudo gparted

step10.JPG

Click on Device > Create Partition Table and press Apply

step11.JPG

Right Click on the unallocated partition and press New

step13.JPG

Enter label name persistence with the following default settings and press Add

step14

Press the green very good sign and press Apply to any warning messages

step15

Press Close and close everything

step16

Now we are going to copy  the whole operating system from the virtual optical drive to
the hard drive.

Open the terminal window and enter sudo dd if=/dev/sr0 of=/dev/sda bs=1M

step17

Close the virtual machine (shutdown the OS)

Step 6:

Create a new Virtual Machine with the following settings. It is very important that you use your previously created virtual hard disk file. Do no create a new virtual hard disk.

 step18

Step 7:

Start your new machine and create a new python script to test persistence. I saved my file in the desktop. Restart your OS.

step19

Step 8:

Test file is still there. Persistence worked 🙂

step20

References: