diff --git a/README.md b/README.md index ad237bc..0e6a753 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,47 @@ -# Merch Embedded - +# Merch Embedded [![Join the chat at https://gitter.im/acm-uiuc/merch-development](https://badges.gitter.im/acm-uiuc/merch-development.svg)](https://gitter.im/acm-uiuc/merch-development?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -Possible information sources: + +This repository contains all of the code related to controlling the underlying merch hardware (the vending machine). +Merch runs an embedded webserver that can be accessed at merch.acm.illinois.edu. + +## API + +Requests to the device must contain a valid token in the Authorization Header for a request to be processed. +As of now the only token will be given solely to the groot merch service, so if you wish to make merch requests go through groot. + + +### Vend a location + +To vend a list of items, POST a request to `/vend`. +The request is of the form +```json +{ + "transaction_id": 1, + "items": ["A1", "B2", "C3"] +} +``` + +The machine will respond with +```json +{ + "transaction_id": 1, + "items": [ + {"location": "A1", "error": null}, + {"location": "B2", "error": "some sort of error"}, + {"location": "C3", "error": null}, + + ] +} +``` + +The errors that can take place while vending are currently: +* `"Invalid location"` + + +## Some related datasheets + [http://bajavending.com/Manual-de-Operacion-BevMax.pdf](http://bajavending.com/Manual-de-Operacion-BevMax.pdf) * Has the right picture of the main controller board, no programming information though @@ -18,15 +56,15 @@ has some useful info about what commands are sent ## License -This project is licensed under the University of Illinois/NCSA Open Source License. For a full copy of this license take a look at the LICENSE file. +This project is licensed under the University of Illinois/NCSA Open Source License. For a full copy of this license take a look at the LICENSE file. -When contributing new files to this project, preappend the following header to the file as a comment: +When contributing new files to this project, preappend the following header to the file as a comment: ``` Copyright © 2017, ACM@UIUC -This file is part of the Merch Project. - -The Merch Project is open source software, released under the University of Illinois/NCSA Open Source License. +This file is part of the Merch Project. + +The Merch Project is open source software, released under the University of Illinois/NCSA Open Source License. You should have received a copy of this license in a file with the distribution. ``` diff --git a/machine_controller/app.py b/machine_controller/app.py index 1460f5e..7e8ae89 100644 --- a/machine_controller/app.py +++ b/machine_controller/app.py @@ -42,15 +42,45 @@ merch = Merch() @app.route('/vend', methods=['POST']) -def hello_world(): - if request.headers.get('TOKEN', '') != token_value: +def vend(): + if request.headers.get('Authorization', '') != token_value: abort(401) - if 'item' not in request.args: - abort(400) - item = request.args['item'] - merch.vend(item[0], int(item[1])) - return json.dumps({'success': True}), 200, {'ContentType': 'application/json'} + data = request.json + items = data['items'] + transaction_id = data['transaction_id'] -if __name__ == '__main__': - app.run(debug=True, host='0.0.0.0') + statuses = [] + merch.acquire() + for i, item in enumerate(items): + try: + merch.vend(item[0], int(item[1])) + statuses.append({'error': None, 'location': item}) + + except Exception as e: + # Some error occurred while vending + # I'd prefer to catch Merch.VendError's only, but if something else + # goes wrong, we still need to let the client know instead of + # throwing a 500 + statuses.append({'error': str(e), 'location': item}) + merch.release() + + return jsonify(transaction_id=transaction_id, items=statuses) + +@app.route('/status', methods=['GET']) +def status(): + if request.headers.get('Authorization', '') != token_value: + abort(401) + + notready = merch.inUse() + + if(notready): + return ('', 503) + else: + # 200 to indicate success + return ('', 200) + + + +if __name__ == '__main__': + app.run(debug=True, host='0.0.0.0', threaded=True) diff --git a/machine_controller/vend.py b/machine_controller/vend.py index bf2fb18..5b1f5ce 100644 --- a/machine_controller/vend.py +++ b/machine_controller/vend.py @@ -34,6 +34,7 @@ # THE SOFTWARE. import RPi.GPIO as GPIO import time +from threading import Condition, Lock class Merch: @@ -42,7 +43,12 @@ class Merch: ROW = [21, 20, 16] COL = [19, 13] MAX_LETTER = 'F' - MAX_NUMBER = '0' + MAX_NUMBER = 9 + + class VendError(Exception): + pass + + InvalidLocationError = VendError("Invalid location") def __init__(self, debug=False): self.debug = debug @@ -51,9 +57,22 @@ def __init__(self, debug=False): self.__low() self.__commit() + self.lock = Lock() + def __del__(self): self.__cleanup() + def acquire(self): + self.lock.acquire() + + def release(self): + self.lock.release() + + def inUse(self): + # Trylock + return self.lock.locked() + + def __cleanup(self): ''' Clean up all of the GPIO pins ''' GPIO.cleanup() @@ -93,22 +112,20 @@ def vend(self, letter, number): try: char = ord(letter) except TypeError: - raise TypeError('Letter %s does not represent a character' % - str(letter)) + raise self.InvalidLocationError # Maybe we should use the actual keypad value? - if char < ord('A') or char > ord('Z'): - raise ValueError('Invalid Letter: %s' % str(letter)) + if char < ord('A') or char > ord(self.MAX_LETTER): + raise self.InvalidLocationError num = 0 try: num = int(number) except TypeError: - raise TypeError('Number %s is not convertible to an integer' % - str(num)) + raise self.InvalidLocationError - if num < 0 or num > 10: - raise ValueError('Number %d is not in the range 1-10' % num) + if num < 1 or num > self.MAX_NUMBER: + raise self.InvalidLocationError self.__vend(letter, str(number)) @@ -123,6 +140,9 @@ def __vend(self, letter, number): self.__sendKey(number) self.__commit() + # Wait for vend to complete + time.sleep(10) + def __sendKey(self, key): # TABLE OF OUTPUTS # ROW = {ROW[0],ROW[1],ROW[2]}