“Who’d I Lend That Book To?” Hard Questions Answered with Python

Hi

@lindemda

 

DanLindeman

 

Senior Software Engineer @ Very

 

You

  • Like Libraries
    • Books
    • Python
  • Dusty Pi
    • Unused
    • ...Still in the plastic

Journey

  • Thing-Level
    • RFID
    • RPI3
    • Raspbian
    • SPI
    • MFRC522-python
  • Internet-Level
    • Flask
    • teensy bit of JS

Motivation

  • GRPL Checkout
  • IoT can be scary
  • Python is helpful

Motivation #2

  • Tiddlywinks
  • Pi Up My Life
  • Elixir App
    • Validated with Python

 

Ready?

Thing Level

Raspberry PI

Python is there to help

RFID

  • Radio Frequency Identification
  • Everywhere
    • Library Checkout
    • Amiibo (NFC)
    • Access Keycards
    • Fyre Festival

RFID "Things"

  • Power
  • Data
  • Size

Buy Things

In short

Reality

hard Way

  • Read specs
    • Standards
    • Datasheets
    • Registers

Simple Way

  • MFRC522-python
from mfrc522 import SimpleMFRC522
reader = SimpleMFRC522()


def read():
    try:
        rfid, title = reader.read()
    finally:
        GPIO.cleanup()

def write(input_text):
    try:
        rfid, title = reader.write(input_text)
    finally:
        GPIO.cleanup()

Python is there to help

Adventure awaits

Internet LEvel

"You're saying Flask really weird there, Dan..."

Site Map

/Inventory

@app.route("/inventory")
def inventory():
    books = Book.query.all()
    return render_template('inventory.html', rows=books)

@app.route("/inventory", methods=['POST'])
def update_inventory():
    if 'return' in request.form:
        rfid = request.form['return']
        book = Book.query.filter(Book.rfid == rfid).first()
        book.loaned_to = None
        db.session.commit()
    elif 'delete' in request.form:
        rfid = request.form['delete']
        book = Book.query.filter(Book.rfid == rfid).first()
        db.session.delete(book)
        db.session.commit()
    return redirect(url_for('inventory'))

|

"click"

   --> 

/Inventory

/Register

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

@app.route('/register', methods=['POST'])
def register_post():
    form_title = request.form['title']
    try:
        rfid, title = reader.write(form_title)
    finally:
        GPIO.cleanup()
    book = Book(rfid=rfid, title=title)
    db.session.add(book)
    db.session.commit()
    return redirect(url_for('index'))

"click" ->

"type-type-type"

Oh...

  def write(self, text):
      id, text_in = self.write_no_block(text)
      while not id:
          id, text_in = self.write_no_block(text)
      return id, text_in

  def write_no_block(self, text):
      (status, TagType) = self.READER.MFRC522_Request(self.READER.PICC_REQIDL)
        ... <Cool and crazy Register stuff>
      return id, text[0:(len(self.BLOCK_ADDRS) * 16)]

Streaming?

Yield

  • Flask can render piece-wise
    • From a generator
from flask import Response

@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in iter_all_rows():
            yield ','.join(row) + '\n'
    return Response(generate(), mimetype='text/csv')
@app.route('/stream')
def streamed_response():
    def generate():
        yield 'Hello '
        yield request.args['name']
        yield '!'
    return Response(stream_with_context(generate()))

2MeIRL4MeIRL

/Scanning

@app.route('/scanning')
def scanning():
    start = time.time()
    timeout = 1.0
    while time.time() < start + timeout:
        try:
            id, text = reader.read_no_block()
            if (id and id not in scans):
                scans.add(id)
                break
        finally:
            GPIO.cleanup()
    books = Book.query.filter(Book.rfid.in_(scans)).all()
    return render_template('stream.html', rows=books)

|

|

"click"

    -> 

Async/Streaming

  • Probably correct
  • Isolated Service
    • Start Scanning
    • Give me scans
    • Stop Scanning

Javascript

  • Definitely a hack
  • Works, really well
<h1>Scan your books</h1>
<a href="/checkout">Done</a>
<ul>
    {% for book in rows %}
      <li>{{ book.title }}</li>
    {% endfor %}
</ul>
<script>
    setTimeout(function() {
      location.reload();
    }, 250);
</script>
<a href="/" >Home Screen</a>

After all... "Simple is better than complex."

Javascript is there to help

/Checkout

@app.route("/checkout")
def checkout():
    global scans, cart
    cart = scans.copy()
    books = Book.query.filter(Book.rfid.in_(cart)).all()
    scans = set()
    return render_template('checkout.html', rows=books)

@app.route("/checkout", methods=['POST'])
def checkout_post():
    global cart
    lendee = request.form['lendee']
    books = Book.query.filter(Book.rfid.in_(cart)).all()
    for book in books:
        book.loaned_to = lendee
        db.session.commit()
    return redirect(url_for('index'))
  • Two Scoops of Django

--------

             |

            "click"

                             -------> 

We did the thing!

HAQS

(Hopefully Asked Questions)

Thank you!

  • For the chance to put this talk together
  • For the chance to spend time with you
  • For being you
  • For any questions you have (or don't)
  • Special thanks to Jace

 

What are your favorite books?

  • Everything is Illuminated

    • Because I like Distributed Systems
  • Designing Data Intensive Applications

    • Because I like a good adventure

You gonna Try that Async stuff at Sprints tonight?

  • Probably
    • But I also need to get a better "coding on a Pi" setup
    • ..and I have the setup working there...

What would you do differently Next time?

  • Less Coding on a Pi
  • Figure out Poetry on Pi
  • Extract RFID to a service
  • Tests > Guesses

Sources

Who'd I lend that book to?

By dlindema

Who'd I lend that book to?

  • 703