├── .gitattributes ├── .github └── workflows │ └── deploy_pages.yml ├── .gitignore ├── Doxyfile ├── LICENSE ├── README.md ├── data ├── alerts.json ├── in_machine_products.json ├── prices.json ├── products.json └── uuids_handles.json └── src ├── Dump.py ├── MFRC522.py ├── Read.py ├── Write.py ├── blue.py ├── blue_tests.py ├── bt_encoder.py ├── database.py ├── i2c_lib.py ├── jura_encoder.py ├── lcddriver.py ├── old_blue.py └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/deploy_pages.yml: -------------------------------------------------------------------------------- 1 | # This workflow generates documentation for a project using Doxygen and sets it up for GitHub pages. It will push the 2 | # documentation onto a special orphan branch to avoid cluttering the source code. This implementation comes from 3 | # https://ntamonsec.blogspot.com/2020/06/github-actions-doxygen-documentation.html 4 | name: Deploy Documentation 5 | 6 | # Controls when the action will run. 7 | on: 8 | push: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build-documentation: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Run Doxygen 18 | uses: mattnotmitt/doxygen-action@v1.2.1 19 | with: 20 | # These are default values, but included for clarity 21 | doxyfile-path: ./Doxyfile 22 | working-directory: . 23 | - name: Pages Deployment 24 | uses: peaceiris/actions-gh-pages@v3 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: ./docs/html/ 28 | enable_jekyll: false 29 | allow_empty_commit: false 30 | force_orphan: true 31 | publish_branch: github-pages 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | *.sh 123 | 124 | # Spyder project settings 125 | .spyderproject 126 | .spyproject 127 | 128 | # Rope project settings 129 | .ropeproject 130 | 131 | # mkdocs documentation 132 | /site 133 | 134 | # mypy 135 | .mypy_cache/ 136 | .dmypy.json 137 | dmypy.json 138 | 139 | # Pyre type checker 140 | .pyre/ 141 | 142 | # pytype static type analyzer 143 | .pytype/ 144 | 145 | # Cython debug symbols 146 | cython_debug/ 147 | 148 | # PyCharm 149 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 150 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 151 | # and can be added to the global gitignore or merged into this file. For a more nuclear 152 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 153 | #.idea/ 154 | src/templog.txt 155 | *.out 156 | docs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Francisco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Documentation - Jura Coffee Machine Bluetooth Conection 2 | 3 | Disclaimer: This repo is in active development with a focus on getting a working project so things may change or not work as intended. If you have any questions or suggestions please open an issue. When the project is finished there will be a more formal bluetooth protocol documentation. 4 | 5 | This document serves as documentation for the bluetooth conection between the Jura Coffee Machine and a Raspberry Pi3, with the aim to explain all the steps to follow to make the connection between the two devices, as well as present possible problems and solutions. 6 | 7 | The documentation for the project can be seen at https://franfrancisco9.github.io/Jura-Python-BT/. 8 | 9 | The encoding and decoding are come from the [Jura Bluetooth Protocol](https://github.com/Jutta-Proto/protocol-cpp) project. A special shoutout to @COM8 for the help. 10 | 11 | The table of contents is as follows: 12 | - [Documentation - Jura Coffee Machine Bluetooth Conection](#documentation---jura-coffee-machine-bluetooth-conection) 13 | - [Project Flow](#project-flow) 14 | - [SSH connection to the Raspberry Pi3](#ssh-connection-to-the-raspberry-pi3) 15 | - [Bluetooth Conections](#bluetooth-conections) 16 | - [Obtaining the MAC Address](#obtaining-the-mac-address) 17 | - [Obatining the Manufacturer Key and Data](#obatining-the-manufacturer-key-and-data) 18 | - [Characteristics and Services](#characteristics-and-services) 19 | - [Main Script](#main-script) 20 | - [Common Errors and Solutions](#common-errors-and-solutions) 21 | - [Usefull Links](#usefull-links) 22 | 23 | ## Project Flow 24 | 25 | The current project shows a possible implementation of the bluetooth connection between the Pi and the Jura coffee machine using a rfid reader, a lcd and a buzzer as well as a database to store the purchases, balances and users. This is later presentend in a web interface using phpmyadmin. 26 | 27 | Add a line to your ```/etc/rc.local``` file to start the script on boot: 28 | 29 | ```bash 30 | sudo python3 /home/pi/Jura-Python-BT/src/blue.py >> /home/pi/Jura-Python-BT/src/templog.txt 2>&1 & 31 | ``` 32 | 33 | If you want to have logs in case of possible errors use the second part of the above code. 34 | 35 | If your jura coffee machine is working and you have the correct setup using a rfid reader, a lcd and a buzzer you should be able to use this project out of the box. 36 | 37 | The flow of the project is as follows: 38 | 39 | 1. The script is started on boot. 40 | 2. A message appears on the LCD letting the user know they must present their tag to the reader and the machine is locked. 41 | 3. The user presents the tag to the reader and the machine is unlocked. 42 | 4. The user can now select a product. 43 | 5. The product is detected and the purchases is registered in the database. 44 | 6. The products ends and the program detects it and locks the machine again while also charging the user. 45 | 7. Repeat from step 2. 46 | 47 | The code can be easily adjusted to not use the database and functions only with a lock unlock function using the rfid reader. 48 | 49 | The statistics part of the code is to control how many products are being paid versus how many were actually made since a user could just unplug the Pi and not pay for the product. 50 | 51 | ## SSH connection to the Raspberry Pi3 52 | To connect to the Raspberry Pi3 via SSH, the following command must be used: 53 | ```bash 54 | ssh pi@IP_ADDRESS 55 | ``` 56 | Where IP_ADDRESS is the IP address of the Raspberry Pi3. 57 | 58 | The first time you connect you have to accept the fingerprint. Then you must enter the password. After that you inside the Raspberry Pi3. 59 | 60 | ## Bluetooth Conections 61 | The Coffee Machine used is a `Jura Giga x8c` which is equipped with a bluetooth dongle that can be found by the name `TT214H BlueFrog` which is usually used to connect with the Jura app. 62 | 63 | Raspberry Pi3 has a built-in bluetooth module, which can be used to connect to the coffee machine. 64 | 65 | Below are the steps to follow to make the connection between the two devices. 66 | 67 | ### Obtaining the MAC Address 68 | 69 | The first step is to obtain the MAC address of the coffee machine. This can be done by using the `hcitool` command, which is a tool that allows to scan for bluetooth devices. 70 | 71 | Make sure that the bluetooth module is turned on by using the command `sudo hciconfig hci0 up`. 72 | 73 | Then, use the command `sudo hcitool lescan` to scan for bluetooth devices. The output should be similar to the following: 74 | 75 | ```bash 76 | pi@raspberrypi:~ $ sudo hcitool lescan 77 | LE Scan ... 78 | 00:1A:7D:DA:71:13 (unknown) 79 | 00:1A:7D:DA:71:14 TT214H BlueFrog 80 | ``` 81 | 82 | The MAC address of the coffee machine is the one that is shown in the last line of the output. In this case, the MAC address is `00:1A:7D:DA:71:14`. 83 | 84 | To pair and connect with the device we can use bluetoothctl command. 85 | 86 | ```bash 87 | pi@raspberrypi:~ $ bluetoothctl 88 | [NEW] Controller 00:1A:7D:DA:71:13 raspberrypi [default] 89 | [NEW] Device 00:1A:7D:DA:71:14 TT214H BlueFrog 90 | [bluetooth]# pair 00:1A:7D:DA:71:14 91 | Attempting to pair with 00:1A:7D:DA:71:14 92 | [CHG] Device 00:1A:7D:DA:71:14 Connected: yes 93 | [CHG] Device 00:1A:7D:DA:71:14 ServicesResolved: yes 94 | [CHG] Device 00:1A:7D:DA:71:14 Paired: yes 95 | Pairing successful 96 | [bluetooth]# connect 00:1A:7D:DA:71:14 97 | Attempting to connect to 00:1A:7D:DA:71:14 98 | [CHG] Device 00:1A:7D:DA:71:14 Connected: yes 99 | Connection successful 100 | [bluetooth]# trust 00:1A:7D:DA:71:14 101 | [CHG] Device 00:1A:7D:DA:71:14 Trusted: yes 102 | Changing 00:1A:7D:DA:71:14 trust succeeded 103 | ``` 104 | 105 | It is important to keep in mind that the coffee machine will disconnect every few seconds and will need to be connected again. So it is normal you see the following message: 106 | 107 | ```bash 108 | [CHG] Device 00:1A:7D:DA:71:14 Connected: no 109 | ``` 110 | 111 | If you want to keep a connection with the coffee machine, you can use the following command: 112 | 113 | ```bash 114 | pi@raspberrypi:~ $ for i in {1..100}; do bluetoothctl connect 00:1A:7D:DA:71:14; sleep 5; done 115 | ``` 116 | 117 | This command will try to connect to the coffee machine every 5 seconds, for 100 times. You can change the number of times you want to try to connect to the coffee machine by changing the number in the first line of the command. This allows to do some tests without having to connect to the coffee machine every time. Further down, we will see how to make a script that will allow us to connect to the coffee machine automatically (a heartbeat script). 118 | 119 | ### Obatining the Manufacturer Key and Data 120 | 121 | The next step is to obtain the Manufacturer Key and Data. This can be done by using the `info` command inside the `bluetoothctl` menu. 122 | 123 | ```bash 124 | [bluetooth]# info 00:1A:7D:DA:71:14 125 | ``` 126 | 127 | The output should be similar to the following: 128 | 129 | ```bash 130 | Name: TT214H BlueFrog 131 | Alias: TT214H BlueFrog 132 | Paired: yes 133 | Trusted: yes 134 | Blocked: no 135 | Connected: yes 136 | LegacyPairing: no 137 | UUID: Vendor specific (00000000-0000-aaaa-0000-000aaaaaa000) 138 | UUID: Generic Access Profile (00000000-0000-aaaa-0000-000aaaaaa000) 139 | UUID: Generic Attribute Profile (00000000-0000-aaaa-0000-000aaaaaa000) 140 | UUID: Vendor specific (00000000-0000-aaaa-0000-000aaaaaa000) 141 | UUID: Vendor specific (00000000-0000-aaaa-0000-000aaaaaa000) 142 | ManufacturerData Key: 0x0000 143 | ManufacturerData Value: 144 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 145 | 00 00 00 00 00 00 00 00 00 00 00 ........... 146 | ``` 147 | 148 | The Manufacturer Key and Data are the ones that are shown in the last two lines of the output. In this case, the Manufacturer Key is `0x0000` and the Manufacturer Data is `00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00` (which is made up in this case). 149 | 150 | 151 | ### Characteristics and Services 152 | Inside the bluetoothctl menu, we can use the `menu gatt` command to access the GATT menu. This menu allows us to see the characteristics and services of the coffee machine. 153 | 154 | ```bash 155 | [bluetooth]# menu gatt 156 | ``` 157 | 158 | Inside this menu we can use the `list-attributes` command to see the characteristics and services of the coffee machine. Furthermore we can use the `select-attribute` command to select a specific characteristic or service. From there we can use the `read` and `write` commands to read and write to the characteristic or service. 159 | 160 | 161 | I used the command read for all the attributes found with the command list-attributes. The results can be found in the file `services_and_characteristics.txt`. The file contains the UUID of the characteristic or service, the name of the characteristic or service, the value of the characteristic or service and the type of the characteristic or service. 162 | 163 | 164 | 165 | In case you do not see a bluetooth module in the bluetoothctl menu, you can install the necessary packages by using the following commands: 166 | 167 | ```bash 168 | sudo apt-get install bluetooth bluez libbluetooth-dev libudev-dev 169 | ``` 170 | 171 | ## Main Script 172 | 173 | To run the main script, go into the `src` folder and run the following command: 174 | 175 | ```bash 176 | sudo python3 blue.py 177 | ``` 178 | 179 | If you open a seperate terminal and run the command `bluetoothctl` you should see the following: 180 | 181 | ```bash 182 | [TT214H BlueFrog]# 183 | ``` 184 | 185 | Meaning that the script is running and the coffee machine is connected. 186 | 187 | The script sends a heartbeat every 15 seconds. 188 | 189 | The script as it stands reads the machine status every second and converts it to the respective alerts that should appear on the terminal. 190 | 191 | Furthermore if you send a message to the `start_product` characteristic, you can make a coffee. For more details check the repository mentioned in the beginning. 192 | 193 | ## Common Errors and Solutions 194 | 195 | ## Usefull Links 196 | 197 | * [Forum Domoticz](https://www.domoticz.com/forum/viewtopic.php?t=25128) 198 | * [Hardware Pi](https://github.com/Jutta-Proto/hardware-pi) 199 | * [ESPHome Jura Component](https://github.com/ryanalden/esphome-jura-component/issues/7) 200 | * [ESP32 Jura](https://github.com/COM8/esp32-jura) 201 | * [ESPHome Jura Component](https://github.com/ryanalden/esphome-jura-component) 202 | * [Protocol BT CPP](https://github.com/Jutta-Proto/protocol-bt-cpp) 203 | 204 | -------------------------------------------------------------------------------- /data/alerts.json: -------------------------------------------------------------------------------- 1 | { 2 | "alerts": 3 | { 4 | "0":"insert tray", 5 | "1":"fill water", 6 | "2":"empty grounds", 7 | "3":"empty tray", 8 | "4":"insert coffee bin", 9 | "5":"outlet missing", 10 | "6":"rear cover missing", 11 | "7":"milk alert", 12 | "8":"fill system", 13 | "9":"system filling", 14 | "10":"no beans", 15 | "11":"welcome", 16 | "12":"heating up", 17 | "13":"coffee ready", 18 | "14":"no milk (milk sensor)", 19 | "15":"error milk (milk sensor)", 20 | "16":"no signal (milk sensor)", 21 | "17":"please wait", 22 | "18":"coffee rinsing", 23 | "19":"ventilation closed", 24 | "20":"close powder cover", 25 | "21":"fill powder", 26 | "22":"system emptying", 27 | "23":"not enough powder", 28 | "24":"remove water tank", 29 | "25":"press rinse", 30 | "26":"goodbye", 31 | "27":"periphery alert", 32 | "28":"powder product", 33 | "29":"program-mode status", 34 | "30":"error status", 35 | "31":"enjoy product", 36 | "32":"filter alert", 37 | "33":"decalc alert", 38 | "34":"cleaning alert", 39 | "35":"cappu rinse alert", 40 | "36":"energy safe", 41 | "37":"active RF filter", 42 | "38":"RemoteScreen", 43 | "39":"LockedKeys", 44 | "40":"close tab", 45 | "41":"cappu clean alert", 46 | "42":"Info - cappu clean alert", 47 | "43":"Info - coffee clean alert", 48 | "44":"Info - decalc alert", 49 | "45":"Info - filter used up alert", 50 | "46":"steam ready", 51 | "47":"SwitchOff Delay active", 52 | "48":"close front cover", 53 | "49":"left bean alert", 54 | "50":"right bean alert", 55 | "51":"cleaning" 56 | } 57 | } -------------------------------------------------------------------------------- /data/in_machine_products.json: -------------------------------------------------------------------------------- 1 | { 2 | "in_machine_products": 3 | { 4 | "0":"Overall", 5 | "1":"Americano", 6 | "2":"Espresso", 7 | "3":"Coffee", 8 | "4":"Cappuccino", 9 | "5":"Milkcoffee", 10 | "6":"Espresso Macchiato", 11 | "7":"Latte Macchiato", 12 | "8":"Milk Foam", 13 | "46":"Flat White" 14 | } 15 | } -------------------------------------------------------------------------------- /data/prices.json: -------------------------------------------------------------------------------- 1 | { 2 | "pricecoffee": 3 | { 4 | "Americano":0.45, 5 | "Espresso":0.45, 6 | "Coffee":0.45, 7 | "Cappuccino":0.6, 8 | "Milkcoffee":0.6, 9 | "Espresso Macchiato":0.55, 10 | "Latte Macchiato":0.75, 11 | "Milk Foam":0.25, 12 | "Flat White":0.7 13 | } 14 | } -------------------------------------------------------------------------------- /data/products.json: -------------------------------------------------------------------------------- 1 | { 2 | "products": 3 | { 4 | "0":"Overall", 5 | "1":"Americano", 6 | "2":"Espresso", 7 | "3":"Coffee", 8 | "4":"Cappuccino", 9 | "5":"Milkcoffee", 10 | "6":"Espresso Macchiato", 11 | "7":"Latte Macchiato", 12 | "8":"Milk Foam", 13 | "46":"Flat White" 14 | } 15 | } -------------------------------------------------------------------------------- /data/uuids_handles.json: -------------------------------------------------------------------------------- 1 | { 2 | "characteristics" : 3 | { 4 | "machine_status": ["5a401524-ab2e-2548-c435-08c300000710", "0x000b"], 5 | "barista_mode": ["5a401530-ab2e-2548-c435-08c300000710", "0x0017"], 6 | "product_progress": ["5a401527-ab2e-2548-c435-08c300000710", "0x001a"], 7 | "heartbeat": ["5a401529-ab2e-2548-c435-08c300000710", "0x0011"], 8 | "heartbeat_read": ["5a401538-ab2e-2548-c435-08c300000710", "0x0032"], 9 | "start_product": ["5a401525-ab2e-2548-c435-08c300000710", "0x000e"], 10 | "statistics_command": ["5A401533-ab2e-2548-c435-08c300000710", "0x0026"], 11 | "statistics_data": ["5A401534-ab2e-2548-c435-08c300000710", "0x0029"], 12 | "uart_tx": ["5a401625-ab2e-2548-c435-08c300000710", "0x0039"], 13 | "uart_rx": ["5a401624-ab2e-2548-c435-08c300000710", "0x0036"] 14 | } 15 | } -------------------------------------------------------------------------------- /src/Dump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | 4 | import RPi.GPIO as GPIO 5 | import MFRC522 6 | import signal 7 | 8 | continue_reading = True 9 | 10 | # Capture SIGINT for cleanup when the script is aborted 11 | def end_read(signal,frame): 12 | global continue_reading 13 | print "Ctrl+C captured, ending read." 14 | continue_reading = False 15 | GPIO.cleanup() 16 | 17 | # Hook the SIGINT 18 | signal.signal(signal.SIGINT, end_read) 19 | 20 | # Create an object of the class MFRC522 21 | MIFAREReader = MFRC522.MFRC522() 22 | 23 | # This loop keeps checking for chips. If one is near it will get the UID and authenticate 24 | while continue_reading: 25 | 26 | # Scan for cards 27 | (status,TagType) = MIFAREReader.MFRC522_Request(MIFAREReader.PICC_REQIDL) 28 | 29 | # If a card is found 30 | if status == MIFAREReader.MI_OK: 31 | print "Card detected" 32 | 33 | # Get the UID of the card 34 | (status,uid) = MIFAREReader.MFRC522_Anticoll() 35 | 36 | # If we have the UID, continue 37 | if status == MIFAREReader.MI_OK: 38 | 39 | # Print UID 40 | print "Card read UID: "+str(uid[0])+","+str(uid[1])+","+str(uid[2])+","+str(uid[3]) 41 | 42 | # This is the default key for authentication 43 | key = [0xFF,0xFF,0xFF,0xFF,0xFF,0xFF] 44 | 45 | # Select the scanned tag 46 | MIFAREReader.MFRC522_SelectTag(uid) 47 | 48 | # Dump the data 49 | MIFAREReader.MFRC522_DumpClassic1K(key, uid) 50 | 51 | # Stop 52 | MIFAREReader.MFRC522_StopCrypto1() 53 | -------------------------------------------------------------------------------- /src/MFRC522.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | # 4 | # Copyright 2014,2018 Mario Gomez 5 | # 6 | # This file is part of MFRC522-Python 7 | # MFRC522-Python is a simple Python implementation for 8 | # the MFRC522 NFC Card Reader for the Raspberry Pi. 9 | # 10 | # MFRC522-Python is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU Lesser General Public License as published by 12 | # the Free Software Foundation, either version 3 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # MFRC522-Python is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU Lesser General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU Lesser General Public License 21 | # along with MFRC522-Python. If not, see . 22 | # 23 | 24 | import RPi.GPIO as GPIO 25 | import spi 26 | import signal 27 | import time 28 | 29 | class MFRC522: 30 | NRSTPD = 11 31 | 32 | MAX_LEN = 16 33 | 34 | PCD_IDLE = 0x00 35 | PCD_AUTHENT = 0x0E 36 | PCD_RECEIVE = 0x08 37 | PCD_TRANSMIT = 0x04 38 | PCD_TRANSCEIVE = 0x0C 39 | PCD_RESETPHASE = 0x0F 40 | PCD_CALCCRC = 0x03 41 | 42 | PICC_REQIDL = 0x26 43 | PICC_REQALL = 0x52 44 | PICC_ANTICOLL = 0x93 45 | PICC_SElECTTAG = 0x93 46 | PICC_AUTHENT1A = 0x60 47 | PICC_AUTHENT1B = 0x61 48 | PICC_READ = 0x30 49 | PICC_WRITE = 0xA0 50 | PICC_DECREMENT = 0xC0 51 | PICC_INCREMENT = 0xC1 52 | PICC_RESTORE = 0xC2 53 | PICC_TRANSFER = 0xB0 54 | PICC_HALT = 0x50 55 | 56 | MI_OK = 0 57 | MI_NOTAGERR = 1 58 | MI_ERR = 2 59 | 60 | Reserved00 = 0x00 61 | CommandReg = 0x01 62 | CommIEnReg = 0x02 63 | DivlEnReg = 0x03 64 | CommIrqReg = 0x04 65 | DivIrqReg = 0x05 66 | ErrorReg = 0x06 67 | Status1Reg = 0x07 68 | Status2Reg = 0x08 69 | FIFODataReg = 0x09 70 | FIFOLevelReg = 0x0A 71 | WaterLevelReg = 0x0B 72 | ControlReg = 0x0C 73 | BitFramingReg = 0x0D 74 | CollReg = 0x0E 75 | Reserved01 = 0x0F 76 | 77 | Reserved10 = 0x10 78 | ModeReg = 0x11 79 | TxModeReg = 0x12 80 | RxModeReg = 0x13 81 | TxControlReg = 0x14 82 | TxAutoReg = 0x15 83 | TxSelReg = 0x16 84 | RxSelReg = 0x17 85 | RxThresholdReg = 0x18 86 | DemodReg = 0x19 87 | Reserved11 = 0x1A 88 | Reserved12 = 0x1B 89 | MifareReg = 0x1C 90 | Reserved13 = 0x1D 91 | Reserved14 = 0x1E 92 | SerialSpeedReg = 0x1F 93 | 94 | Reserved20 = 0x20 95 | CRCResultRegM = 0x21 96 | CRCResultRegL = 0x22 97 | Reserved21 = 0x23 98 | ModWidthReg = 0x24 99 | Reserved22 = 0x25 100 | RFCfgReg = 0x30 101 | GsNReg = 0x27 102 | CWGsPReg = 0x28 103 | ModGsPReg = 0x29 104 | TModeReg = 0x2A 105 | TPrescalerReg = 0x2B 106 | TReloadRegH = 0x2C 107 | TReloadRegL = 0x2D 108 | TCounterValueRegH = 0x2E 109 | TCounterValueRegL = 0x2F 110 | 111 | Reserved30 = 0x30 112 | TestSel1Reg = 0x31 113 | TestSel2Reg = 0x32 114 | TestPinEnReg = 0x33 115 | TestPinValueReg = 0x34 116 | TestBusReg = 0x35 117 | AutoTestReg = 0x36 118 | VersionReg = 0x37 119 | AnalogTestReg = 0x38 120 | TestDAC1Reg = 0x39 121 | TestDAC2Reg = 0x3A 122 | TestADCReg = 0x3B 123 | Reserved31 = 0x3C 124 | Reserved32 = 0x3D 125 | Reserved33 = 0x3E 126 | Reserved34 = 0x3F 127 | spidev = 0 128 | serNum = [] 129 | 130 | def __init__(self, dev='/dev/spidev0.0', spd=1000000): 131 | global spidev 132 | spidev = spi.openSPI(device=dev,speed=spd) 133 | GPIO.setmode(GPIO.BOARD) 134 | GPIO.setup(self.NRSTPD, GPIO.OUT) 135 | GPIO.output(self.NRSTPD, 1) 136 | self.MFRC522_Init() 137 | 138 | def MFRC522_Reset(self): 139 | self.Write_MFRC522(self.CommandReg, self.PCD_RESETPHASE) 140 | 141 | def Write_MFRC522(self, addr, val): 142 | spi.transfer(spidev,((addr<<1)&0x7E,val)) 143 | 144 | def Read_MFRC522(self, addr): 145 | val = spi.transfer(spidev,(((addr<<1)&0x7E) | 0x80,0)) 146 | return val[1] 147 | 148 | def SetBitMask(self, reg, mask): 149 | tmp = self.Read_MFRC522(reg) 150 | self.Write_MFRC522(reg, tmp | mask) 151 | 152 | def ClearBitMask(self, reg, mask): 153 | tmp = self.Read_MFRC522(reg); 154 | self.Write_MFRC522(reg, tmp & (~mask)) 155 | 156 | def AntennaOn(self): 157 | temp = self.Read_MFRC522(self.TxControlReg) 158 | if(~(temp & 0x03)): 159 | self.SetBitMask(self.TxControlReg, 0x03) 160 | 161 | def AntennaOff(self): 162 | self.ClearBitMask(self.TxControlReg, 0x03) 163 | 164 | def MFRC522_ToCard(self,command,sendData): 165 | backData = [] 166 | backLen = 0 167 | status = self.MI_ERR 168 | irqEn = 0x00 169 | waitIRq = 0x00 170 | lastBits = None 171 | n = 0 172 | i = 0 173 | 174 | if command == self.PCD_AUTHENT: 175 | irqEn = 0x12 176 | waitIRq = 0x10 177 | if command == self.PCD_TRANSCEIVE: 178 | irqEn = 0x77 179 | waitIRq = 0x30 180 | 181 | self.Write_MFRC522(self.CommIEnReg, irqEn|0x80) 182 | self.ClearBitMask(self.CommIrqReg, 0x80) 183 | self.SetBitMask(self.FIFOLevelReg, 0x80) 184 | 185 | self.Write_MFRC522(self.CommandReg, self.PCD_IDLE); 186 | 187 | while(i self.MAX_LEN: 223 | n = self.MAX_LEN 224 | 225 | i = 0 226 | while i :) <----- ", 4) 117 | 118 | # update PRICECOFFEE with function get_price 119 | for key in PRICECOFFEE: 120 | PRICECOFFEE[key] = get_price(DB, key) 121 | 122 | def lockUnlockMachine(code, lock_status, unlock_code = "77e1"): 123 | '''! 124 | Lock or unlock the machine 125 | @param code The code to unlock the machine. 126 | @param lock_status The current status of the machine. 127 | @param unlock_code The code to unlock the machine (default 77e1). 128 | 129 | @return lock_status The new status of the machine ("locked" or "unlocked") 130 | ''' 131 | child.sendline("char-write-req " + CHARACTERISTICS["barista_mode"][1] + " " + code) 132 | 133 | if code == unlock_code: 134 | lock_status = "unlocked" 135 | else: 136 | lock_status = "locked" 137 | 138 | return lock_status 139 | 140 | 141 | def scanCard(): 142 | '''! 143 | Scan for RFID tag 144 | @return uid_str The UID of the tag or "0" if no tag is found 145 | ''' 146 | RFIDREADER.MFRC522_Init() 147 | # Scan for cards 148 | (status,TagType) = RFIDREADER.MFRC522_Request(RFIDREADER.PICC_REQIDL) 149 | 150 | # If a card is found 151 | if status == RFIDREADER.MI_OK: 152 | print("Card detected") 153 | 154 | # Get the UID of the card 155 | (status,uid) = RFIDREADER.MFRC522_Anticoll() 156 | 157 | # If we have the UID, continue 158 | if status == RFIDREADER.MI_OK: 159 | # Change UID to string 160 | uid_str = str(uid[0]).zfill(3) + str(uid[1]).zfill(3) + str(uid[2]).zfill(3) + str(uid[3]).zfill(3) 161 | return uid_str 162 | else: 163 | return "0" 164 | 165 | 166 | def getAlerts(status): 167 | '''! 168 | Get alerts from the decoded machine status and convert it to the corresponding alerts (if any) 169 | If the corresponding bit is not set, the alert is not active 170 | @param status: machine status 171 | @return list of alerts according to ..data/alerts.json 172 | ''' 173 | # status comes like this: 2a 00 04 00 00 04 40 00 00 00 00 00 00 00 00 00 00 00 00 06 174 | # remove key from the beggining 175 | status = status[3:] 176 | # bin(int('ff', base=16))[2:] 177 | status = [x for x in status.split()] 178 | # only use the first 13 bytes 179 | status = status[:13] 180 | status = [bin(int(byte,16))[2:]for byte in status] 181 | # divide each item in status into 8 bits 182 | status = [list(byte.zfill(8)) for byte in status] 183 | # combine into one string 184 | status = ''.join([item for sublist in status for item in sublist]) 185 | 186 | # print("status: ", status) 187 | for i in range(len(status)): 188 | # if bit is set, print corresponding alert 189 | if status[i] == "1": 190 | print("Alert in bit " + str(i) + " with the alert " + ALERTS[str(i)]) 191 | 192 | ## Get all necessary variables from setup.py -> check file for more information 193 | child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, CHARACTERISTICS) 194 | 195 | 196 | if int(getUID_stats(DB)) < CURRENT_STATISTICS[0]: 197 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 198 | # this ensures the current number of coffees made by the machine is in the database 199 | print("updating the value in the table") 200 | c = DB.cursor() 201 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 202 | DB.commit() 203 | c.close() 204 | 205 | def end_read(signal,frame): 206 | '''! 207 | End the program 208 | Runs when Ctrl+C is pressed 209 | ''' 210 | global continue_reading 211 | beep(2) 212 | print("Ctrl+C captured, ending read.") 213 | lcd.lcd_display_string(" Program over ", 1) 214 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 215 | lcd.lcd_display_string(" Unlocking ", 3) 216 | lcd.lcd_display_string(" -----> :( <----- ", 4) 217 | time.sleep(3) 218 | lcd.lcd_clear() 219 | lcd.lcd_display_string(" Program not ", 1) 220 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 221 | lcd.lcd_display_string(" Running ", 3) 222 | lcd.lcd_display_string(" -----> :( <----- ", 4) 223 | continue_reading = False 224 | GPIO.cleanup() 225 | os.system("cd /home/pi/Jura-Python-BT/src/ && sudo ./backupMySQLdb.sh") 226 | print("Bakcup done!") 227 | logging.info("Backup done") 228 | logging.info("Program ended") 229 | child.sendline("char-write-req " + CHARACTERISTICS["statistics_command"][1] + " " + all_statistics) 230 | time.sleep(1.5) 231 | child.sendline("char-read-hnd " + CHARACTERISTICS["statistics_data"][1]) 232 | child.expect(": ") 233 | data = child.readline() 234 | #print(b"Statistics data: " + data) 235 | # decode the statistics data 236 | data = [int(x, 16) for x in data.split()] 237 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 238 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 239 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 240 | # for every hex string in decoded list, convert to int 241 | decoded = [int(x, 16) for x in decoded] 242 | CURRENT_STATISTICS = decoded 243 | print("Current Statistics: " + str(decoded)) 244 | if int(getUID_stats(DB)) < CURRENT_STATISTICS[0]: 245 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 246 | print("updating the value in the table from", getUID_stats(DB), "to", CURRENT_STATISTICS[0]) 247 | c = DB.cursor() 248 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 249 | DB.commit() 250 | c.close() 251 | DB.close() 252 | _ = lockUnlockMachine(unlock_code, "locked") 253 | exit(0) 254 | 255 | def read_statistics(): 256 | '''! 257 | Read the statistics from the machine 258 | @return list of statistics 259 | ''' 260 | try: 261 | product_made = False 262 | # write the all statistics command to statistics_command_handle 263 | child.sendline("char-write-cmd " + CHARACTERISTICS["statistics_command"][1] + " " + all_statistics) 264 | #print("All statistics sent!") 265 | time.sleep(1.5) 266 | # read the data from product progress handle 267 | # read the statistics data from statistics_data_handle 268 | child.sendline("char-read-hnd " + CHARACTERISTICS["statistics_data"][1]) 269 | child.expect(": ") 270 | data = child.readline() 271 | #print(b"Statistics data: " + data) 272 | # decode the statistics data 273 | data = [int(x, 16) for x in data.split()] 274 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 275 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 276 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 277 | # for every hex string in decoded list, convert to int 278 | decoded = [int(x, 16) for x in decoded] 279 | #print("Statistics data: ", decoded) 280 | # change the values that are different from the previous ones when comparing with CURRENT_STATISTICS 281 | if decoded[0] != CURRENT_STATISTICS[0]: 282 | #lock_status = lockUnlockMachine(locking_code, lock_status) 283 | print("Overall products increased by 1") 284 | CURRENT_STATISTICS[0] = decoded[0] 285 | 286 | for i in range(1, len(decoded)): 287 | if decoded[i] != CURRENT_STATISTICS[i]: 288 | print("A " + PRODUCTS[str(i)] + " was made!") 289 | product_made = PRODUCTS[str(i)] 290 | print("Value changed to " + str(decoded[i]) + " from " + str(CURRENT_STATISTICS[i])) 291 | CURRENT_STATISTICS[i] = decoded[i] 292 | if int(getUID_stats(DB)) < CURRENT_STATISTICS[0]: 293 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 294 | print("updating the value in the table from", getUID_stats(DB), "to", CURRENT_STATISTICS[0]) 295 | c = DB.cursor() 296 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 297 | DB.commit() 298 | c.close() 299 | return product_made 300 | except Exception as e: 301 | print("Error reading statistics!") 302 | logging.error("Error reading statistics " + str(e)) 303 | return False 304 | 305 | # if no arguments assume that emergency unlock is 0 306 | # if emergency unlock is 0, lock the machine 307 | # if emergency unlock is 1, unlock the machine 308 | if len(sys.argv) == 1: 309 | emergency_unlock = 0 310 | else: 311 | emergency_unlock = int(sys.argv[1]) 312 | if emergency_unlock == 0: 313 | lock_status = "unlocked" 314 | lock_status = lockUnlockMachine(locking_code, lock_status) 315 | print("Machine locked!") 316 | else: 317 | lock_status = "locked" 318 | lock_status = lockUnlockMachine(unlock_code, lock_status) 319 | print("Machine unlocked!") 320 | exit() 321 | 322 | # Hook the SIGINT to end_read function (ctrl + c) 323 | signal.signal(signal.SIGINT, end_read) 324 | 325 | # Init buzzer 326 | setupBuzzer(BUZZER_PIN) 327 | 328 | while True: 329 | try: 330 | # Init Serial 331 | port = serial.Serial("/dev/serial0", baudrate = 9600, timeout = 1.0) 332 | print("Serial connection initialized") 333 | break 334 | except: 335 | print("Serial connection failed, ending program") 336 | logging.error("Serial connection failed, ending program") 337 | end_read(0,0) 338 | 339 | lcd.lcd_display_string(" Machine Locked ", 1) 340 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 341 | lcd.lcd_display_string(" Put tag to unlock ", 3) 342 | lcd.lcd_display_string(" -----> :) <----- ", 4) 343 | time.sleep(1) 344 | beep(0.01) 345 | 346 | # Welcome message 347 | print("Welcome to the BlackBetty 2") 348 | print("Press Ctrl-C to stop.") 349 | 350 | port.flushInput() 351 | 352 | ## The variables used in the main loop 353 | buttonPress = False 354 | continue_reading = True 355 | lastSeen = "" 356 | counter = 0 357 | disp_init = 1 358 | payment_to_date = 1 359 | client_to_pay = "" 360 | admin_locked = 0 361 | admin_prod = 0 362 | total_prod = 0 363 | payed_prod = 0 364 | 365 | time.sleep(1) 366 | while continue_reading: 367 | '''! 368 | Main loop 369 | In this loop the program makes sure the connection is alive and the machine is locked 370 | Scans for tags 371 | If a tag is found, it unlocks the machine and waits for a product to be made 372 | If a product is made, it locks the machine again and charges the user 373 | ''' 374 | #time.sleep(0.2) 375 | current_time = int(time.time() - initial_time) 376 | #print("Current time: " + str(current_time)) 377 | # get hour of the day 378 | hour = int(time.strftime("%H")) 379 | minute = int(time.strftime("%M")) 380 | second = int(time.strftime("%S")) 381 | if (hour == 17 and minute == 0 and second == 0): 382 | os.system("cd /home/pi/Jura-Python-BT/src/ && sudo ./backupMySQLdb.sh") 383 | if (hour == 1 or hour == 5) and minute == 30 : 384 | # reboot pi 385 | child.sendline("char-write-req " + CHARACTERISTICS["statistics_command"][1] + " " + all_statistics) 386 | time.sleep(1.5) 387 | child.sendline("char-read-hnd " + CHARACTERISTICS["statistics_data"][1]) 388 | child.expect(": ") 389 | data = child.readline() 390 | #print(b"Statistics data: " + data) 391 | # decode the statistics data 392 | data = [int(x, 16) for x in data.split()] 393 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 394 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 395 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 396 | # for every hex string in decoded list, convert to int 397 | decoded = [int(x, 16) for x in decoded] 398 | CURRENT_STATISTICS = decoded 399 | print("Current Statistics: " + str(decoded)) 400 | if int(getUID_stats(DB)) < CURRENT_STATISTICS[0]: 401 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 402 | print("updating the value in the table") 403 | c = DB.cursor() 404 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 405 | DB.commit() 406 | c.close() 407 | print("Rebooting pi") 408 | lcd.lcd_clear() 409 | os.system("cd /home/pi/Jura-Python-BT/src/ && sudo ./backupMySQLdb.sh") 410 | os.system("sudo reboot") 411 | logging.info("Rebooting pi") 412 | 413 | if current_time % 5 == 0: 414 | 415 | child.sendline("char-write-req " + CHARACTERISTICS["heartbeat"][1] + " " + keep_alive_code) 416 | data = child.readline() 417 | data += child.readline() 418 | if "Disconnected" in str(data): 419 | # run setup 420 | print("Disconnected here") 421 | logging.debug("Disconnected here") 422 | child.close() 423 | while True: 424 | try: 425 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 426 | # Connect to the device. 427 | print("Connecting to ") 428 | print(DEVICE) 429 | child.sendline("connect") 430 | #print(child.child_fd) 431 | #try: 432 | child.expect("Connection successful", timeout=5) 433 | print("Connected!") 434 | logging.info("Connected") 435 | lock_status = "unlocked" 436 | lock_status = lockUnlockMachine(locking_code, lock_status) 437 | print("Machine locked!") 438 | break 439 | except: 440 | continue 441 | # child.sendline("char-read-hnd " + CHARACTERISTICS["product_progress"][1]) 442 | # child.expect(": ") 443 | # data = child.readline() 444 | # data2 = [int(x, 16) for x in data.split()] 445 | # print("Encoded: ", data) 446 | # decoded = BtEncoder.encDecBytes(data2, KEY_DEC) 447 | # as_hex = ["%02x" % d for d in decoded] 448 | # print(as_hex) 449 | # data = [int(x, 16) for x in data.split()] 450 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 451 | # print("Decoded: ", decoded) 452 | # # read machine status and get alerts: 453 | # child.sendline("char-read-hnd " + CHARACTERISTICS["machine_status"][1]) 454 | # child.expect(": ") 455 | # data = child.readline() 456 | # print(b"Data: " + data) 457 | # data = [int(x, 16) for x in data.split()] 458 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 459 | # print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 460 | # getAlerts(" ".join(["%02x" % d for d in decoded])) 461 | 462 | if disp_init == 1: 463 | lcd.lcd_clear() 464 | lcd.lcd_display_string(" Put Tag and then ", 1) 465 | lcd.lcd_display_string(" Choose Product ", 2) 466 | lcd.lcd_display_string(" In machine ", 3) 467 | lcd.lcd_display_string(" :) ", 4) 468 | disp_init = 0 469 | time.sleep(0.5) 470 | 471 | uid_str = scanCard() 472 | GPIO.cleanup() 473 | 474 | if admin_locked == 1 and current_time % 10 == 0: 475 | prod = read_statistics() 476 | if prod != False: 477 | admin_prod += 1 478 | total_prod += 1 479 | set_buylist(DB, "01", prod) 480 | print("Admin made a product that was not payed for") 481 | 482 | if uid_str != "0": 483 | print("last seen: ", lastSeen) 484 | product_made = False 485 | if admin_locked == 0: 486 | lcd.lcd_clear() 487 | 488 | if (uid_str == MASTERCARD1) or (uid_str == MASTERCARD2): 489 | if lock_status == "locked": 490 | lock_status = lockUnlockMachine(unlock_code, lock_status) 491 | print("Machine unlocked permanently!") 492 | 493 | lcd.lcd_display_string(" Admin Unlock ", 2) 494 | lcd.lcd_display_string(" -----> :) <----- ", 3) 495 | admin_locked = 1 496 | 497 | elif lock_status == "unlocked": 498 | lock_status = lockUnlockMachine(locking_code, lock_status) 499 | print("Machine locked!") 500 | lcd.lcd_display_string(" Admin Lock ", 2) 501 | lcd.lcd_display_string(" -----> :( <----- ", 3) 502 | disp_init = 1 503 | admin_locked = 0 504 | time.sleep(1) 505 | 506 | elif admin_locked == 1: 507 | pass 508 | else: 509 | try: 510 | if lastSeen == "": 511 | lastSeen = uid_str 512 | value = get_value(DB, uid_str) 513 | if value < 0: 514 | # alert user in lcd that they need to charge balance 515 | lcd.lcd_clear() 516 | lcd.lcd_display_string(" Your balance is ", 1) 517 | lcd.lcd_display_string(" < 0! ", 2) 518 | lcd.lcd_display_string(" Please charge it ", 3) 519 | lcd.lcd_display_string(" Locking Machine ", 4) 520 | beep(0.5) 521 | time.sleep(0.5) 522 | beep(0.5) 523 | time.sleep(0.5) 524 | beep(0.5) 525 | time.sleep(2) 526 | lock_status = lockUnlockMachine(locking_code, lock_status) 527 | print("User balance is < 0") 528 | lastSeen = "" 529 | client_to_pay = "" 530 | disp_init = 1 531 | else: 532 | value_str = str("Balance: " + str('%.2f' % value) + " EUR") 533 | lastName = get_name(DB, uid_str) 534 | preName = get_vorname(DB, uid_str) 535 | welStr = str("Hello " + preName) 536 | msgStr3 = str("Hold for 2s please ") 537 | msgStr4 = str("Chip below ") 538 | 539 | lcd.lcd_display_string(welStr, 1) 540 | lcd.lcd_display_string(value_str, 2) 541 | lcd.lcd_display_string(msgStr3, 3) 542 | lcd.lcd_display_string(msgStr4, 4) 543 | time.sleep(1.5) 544 | 545 | elif lastSeen == uid_str and product_made == False: 546 | beep(0.1) 547 | print("Opening coffe machine for choice") 548 | lcd.lcd_clear() 549 | lcd.lcd_display_string(" Select a ", 1) 550 | lcd.lcd_display_string(" product ", 2) 551 | lcd.lcd_display_string(" in the machine ", 3) 552 | lcd.lcd_display_string(" -----> :) <----- ", 4) 553 | lock_status = lockUnlockMachine(unlock_code, lock_status) 554 | intial_time_2 = time.time() 555 | chosen = 0 556 | started = 0 557 | over = 0 558 | while over == 0: 559 | #print("Waiting for product to be made") 560 | time_total = int(time.time() - intial_time_2) 561 | if time_total > 30 and started == 0: 562 | #print("No product made in 25 seconds, locking machine") 563 | lock_status = lockUnlockMachine(locking_code, lock_status) 564 | lcd.lcd_display_string(" No Product ", 1) 565 | lcd.lcd_display_string(" selected ", 2) 566 | lcd.lcd_display_string(" Locking Machine ", 3) 567 | lcd.lcd_display_string(" -----> :( <----- ", 4) 568 | time.sleep(1.5) 569 | #lcd.lcd_clear() 570 | lastSeen = "" 571 | disp_init = 1 572 | chosen = 0 573 | break 574 | if int(time.time() - intial_time_2) % 2 == 0: 575 | child.sendline("char-write-req " + CHARACTERISTICS["heartbeat"][1] + " " + keep_alive_code) 576 | try: 577 | # read the data from product progress handle 578 | child.sendline("char-read-hnd " + CHARACTERISTICS["product_progress"][1]) 579 | try: 580 | child.expect(": ") 581 | except: 582 | pass 583 | data = child.readline() 584 | data2 = [int(x, 16) for x in data.split()] 585 | #print("Encoded: ", data) 586 | data = [x for x in data.split()] 587 | decoded = BtEncoder.encDecBytes(data2, KEY_DEC) 588 | as_hex = ["%02x" % d for d in decoded] 589 | if as_hex[1] not in ["3e", "00"] and time_total > 5 and started == 0: 590 | #lock_status = lockUnlockMachine(locking_code, lock_status) 591 | beep(0.5) 592 | print("PRODUCT MADE") 593 | client_to_pay = uid_str 594 | lcd.lcd_clear() 595 | preName = get_vorname(DB, uid_str) 596 | print("as_hex[2]: ", as_hex[2]) 597 | product_made = PRODUCTS[str(int(as_hex[2], 16))] 598 | price_product = PRICECOFFEE[product_made] 599 | lcd.lcd_display_string(" " + product_made + " detected ", 1) 600 | lcd.lcd_display_string(" Will charge " + str(price_product), 2) 601 | lcd.lcd_display_string(" " + preName + " ", 3) 602 | lcd.lcd_display_string(" -----> :) <----- ", 4) 603 | time.sleep(1.5) 604 | print("Product made was: ", product_made) 605 | started = 1 606 | disp_init = 1 607 | chosen = 1 608 | lock_status = lockUnlockMachine(locking_code, lock_status) 609 | lastSeen = "" 610 | elif started == 1 and as_hex[1] in ["3e", "00"]: 611 | over = 1 612 | print("Checking costumer payment") 613 | value_new = 0 614 | if chosen == 1: 615 | print("Setting value for uid: " + client_to_pay + " Name: " + preName) 616 | value_new = value - PRICECOFFEE[product_made] 617 | if value_new > 0 and chosen == 1: 618 | print("PAYING") 619 | # beep(0.05) 620 | # time.sleep(0.1) 621 | # beep(0.05) 622 | set_value(DB, client_to_pay, value_new) 623 | payed_prod += 1 624 | total_prod += 1 625 | payment_to_date = 1 626 | preName = get_vorname(DB, client_to_pay) 627 | #beep(1) 628 | set_buylist(DB, client_to_pay, product_made) 629 | lcd.lcd_clear() 630 | msgStr1 = str(product_made + " was made!") 631 | msgStr2 = str(" Happy betty :) ") 632 | msgStr3 = str(" " + preName + " ") 633 | value_str = str("Balance: " + str('%.2f' % value_new) + " EUR") 634 | lcd.lcd_display_string(msgStr1, 1) 635 | lcd.lcd_display_string(msgStr2, 2) 636 | lcd.lcd_display_string(msgStr3, 3) 637 | lcd.lcd_display_string(value_str, 4) 638 | time.sleep(2) 639 | client_to_pay = "" 640 | counter = 0 641 | disp_init = 1 642 | product_made = False 643 | elif value_new < 0 and chosen == 1: 644 | print("PAYING") 645 | lcd.lcd_clear() 646 | set_value(DB, client_to_pay, value_new) 647 | payed_prod += 1 648 | total_prod += 1 649 | payment_to_date = 1 650 | #beep(1) 651 | set_buylist(DB, client_to_pay, product_made) 652 | msgStr1 = str(product_made + " was made!") 653 | msgStr2 = str(" Thank you!") 654 | msgStr3 = str("Balance < 0!") 655 | value_str = str("Balance: " + str('%.2f' % value_new) + " EUR") 656 | lcd.lcd_display_string(msgStr1, 1) 657 | lcd.lcd_display_string(msgStr2, 2) 658 | lcd.lcd_display_string(msgStr2, 3) 659 | lcd.lcd_display_string(value_str, 4) 660 | time.sleep(2) 661 | client_to_pay = "" 662 | counter = 0 663 | disp_init = 1 664 | product_made = False 665 | lcd.lcd_clear() 666 | preName = get_vorname(DB, uid_str) 667 | lcd.lcd_display_string(" Product Ended ", 1) 668 | lcd.lcd_display_string(" charged ", 2) 669 | lcd.lcd_display_string(" " + preName + " ", 3) 670 | lcd.lcd_display_string(" -----> :) <----- ", 4) 671 | client_to_pay = "" 672 | lastSeen = "" 673 | time.sleep(2) 674 | else: 675 | continue 676 | 677 | except Exception as e: 678 | print("Error: " + str(e)) 679 | logging.error("Error: " + str(e)) 680 | try: 681 | # run setup 682 | logging.debug("Disconnected") 683 | child.close() 684 | while True: 685 | try: 686 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 687 | # Connect to the device. 688 | print("Connecting to ") 689 | print(DEVICE) 690 | child.sendline("connect") 691 | child.expect("Connection successful", timeout=5) 692 | print("Connected!") 693 | logging.info("Connected") 694 | lock_status = "unlocked" 695 | lock_status = lockUnlockMachine(locking_code, lock_status) 696 | print("Machine locked!") 697 | break 698 | except: 699 | continue 700 | except: 701 | pass 702 | continue 703 | except Exception as e: 704 | print("The error raised is: ", e) 705 | logging.error("The error raised is: " + str(e)) 706 | try: 707 | logging.debug("Disconnected") 708 | child.close() 709 | while True: 710 | try: 711 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 712 | # Connect to the device. 713 | print("Connecting to ") 714 | print(DEVICE) 715 | child.sendline("connect") 716 | #print(child.child_fd) 717 | #try: 718 | child.expect("Connection successful", timeout=5) 719 | print("Connected!") 720 | logging.info("Connected") 721 | lock_status = "unlocked" 722 | lock_status = lockUnlockMachine(locking_code, lock_status) 723 | print("Machine locked!") 724 | break 725 | except: 726 | continue 727 | except: 728 | pass 729 | continue 730 | else: 731 | if lastSeen != "": 732 | counter = counter + 1 733 | #print(counter) 734 | if counter >= 10: 735 | lcd.lcd_clear() 736 | lastSeen = "" 737 | counter = 0 738 | beep(0.2) 739 | time.sleep(0.1) 740 | beep(0.2) 741 | msgStr1 = str("Chip removed?") 742 | msgStr3 = str("Product not chosen") 743 | msgStr4 = str("bye! :(") 744 | lcd.lcd_display_string(msgStr1, 1) 745 | lcd.lcd_display_string(msgStr3, 3) 746 | lcd.lcd_display_string(msgStr4, 4) 747 | time.sleep(4) 748 | disp_init = 1 749 | client_to_pay = "" 750 | lastSeen = "" 751 | lock_status = zockUnlockMachine(locking_code, lock_status) 752 | time.sleep(0.1) z 753 | -------------------------------------------------------------------------------- /src/blue_tests.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | import time 3 | from bitarray import bitarray 4 | from bt_encoder import BtEncoder 5 | from jura_encoder import JuraEncoder 6 | from setup import setup 7 | import os 8 | from dotenv import load_dotenv 9 | from connection import * 10 | import pymysql 11 | pymysql.install_as_MySQLdb() 12 | import MySQLdb as mdb 13 | import RPi.GPIO as GPIO 14 | import lcddriver 15 | import MFRC522 16 | import signal 17 | import time 18 | import serial 19 | import sys 20 | 21 | def setupBuzzer(pin): 22 | global BuzzerPin 23 | BuzzerPin = pin 24 | GPIO.setmode(GPIO.BOARD) # Numbers GPIOs by physical location 25 | GPIO.setup(BuzzerPin, GPIO.OUT) 26 | GPIO.output(BuzzerPin, GPIO.LOW) 27 | 28 | def beep(duration): 29 | GPIO.output(BuzzerPin, GPIO.HIGH) 30 | time.sleep(duration) 31 | GPIO.output(BuzzerPin, GPIO.LOW) 32 | 33 | # Define pin for buzzer 34 | BUZZER = 7 35 | # Init buzzer 36 | setupBuzzer(BUZZER) 37 | beep(0.5) 38 | 39 | load_dotenv() 40 | 41 | BtEncoder = BtEncoder() 42 | JuraEncoder = JuraEncoder() 43 | 44 | # BlackBetty2 mac address read from .env file 45 | DEVICE = os.getenv("DEVICE") 46 | print(DEVICE) 47 | 48 | mastercard1 = os.getenv("MASTER_CARD_1") 49 | mastercard2 = os.getenv("MASTER_CARD_2") 50 | passwd = os.getenv("PASSWD") 51 | 52 | # Open database connection 53 | db = mdb.connect(host = "127.0.0.1", user = "root", passwd = os.getenv("PASSWD"), db = "AnnelieseDB") 54 | 55 | # Initialize LCD 56 | lcd = lcddriver.lcd() 57 | lcd.lcd_clear() 58 | 59 | priceCoffee = { 60 | "Ristretto":0.45, 61 | "Espresso":0.45, 62 | "Coffee":0.45, 63 | "Cappuccino":0.6, 64 | "Milkcoffee":0.6, 65 | "Espresso Macchiato":0.55, 66 | "Latte Macchiato":0.75, 67 | "Milk Foam":0.25, 68 | "Flat White":0.7 69 | } 70 | 71 | 72 | PRODUCTS = { 73 | 0:"Overall", 74 | 1:"Ristretto", 75 | 2:"Espresso", 76 | 4:"Coffee", 77 | 29:"Cappuccino", 78 | 5:"Milkcoffee", 79 | 6:"Espresso Macchiato", 80 | 7:"Latte Macchiato", 81 | 8:"Milk Foam", 82 | 46:"Flat White" 83 | } 84 | 85 | in_machine_products = { 86 | 0:"Overall", 87 | 1:"Ristretto", 88 | 2:"Espresso", 89 | 3:"Coffee", 90 | 8:"Cappuccino", 91 | 5:"Milkcoffee", 92 | 6:"Espresso Macchiato", 93 | 7:"Latte Macchiato", 94 | 8:"Milk Foam", 95 | 9:"Flat White" 96 | } 97 | 98 | ALERTS = { 99 | 0: "insert tray", 1: "fill water", 2: "empty grounds", 3: "empty tray", 4: "insert coffee bin", 100 | 5: "outlet missing", 6: "rear cover missing", 7: "milk alert", 8: "fill system", 9: "system filling", 101 | 10: "no beans", 11: "welcome", 12: "heating up", 13: "coffee ready", 14: "no milk (milk sensor)", 102 | 15: "error milk (milk sensor)", 16: "no signal (milk sensor)", 17: "please wait", 18: "coffee rinsing", 103 | 19: "ventilation closed", 20: "close powder cover", 21: "fill powder", 22: "system emptying", 104 | 23: "not enough powder", 24: "remove water tank", 25: "press rinse", 26: "goodbye", 27: "periphery alert", 105 | 28: "powder product", 29: "program-mode status", 30: "error status", 31: "enjoy product", 32: "filter alert", 106 | 33: "decalc alert", 34: "cleaning alert", 35: "cappu rinse alert", 36: "energy safe", 37: "active RF filter", 107 | 38: "RemoteScreen", 39: "LockedKeys", 40: "close tab", 41: "cappu clean alert", 42: "Info - cappu clean alert", 108 | 43: "Info - coffee clean alert", 44: "Info - decalc alert", 45: "Info - filter used up alert", 46: "steam ready", 109 | 47: "SwitchOff Delay active", 48: "close front cover", 49: "left bean alert", 50: "right bean alert", 51: "cleaning", 110 | 52: "cleaning finished", 53: "cleaning finished", 54: "cleaning finished", 55: "cleaning finished", 111 | 56: "cleaning finished", 57: "cleaning finished", 58: "cleaning finished", 59: "cleaning finished", 112 | } 113 | 114 | def sleepTimer(secs): 115 | startTime = time.time() 116 | while (time.time() - startTime) < secs: 117 | pass 118 | 119 | 120 | # define function that locks or unlocks the machine 121 | def lockUnlockMachine(code, lock_status): 122 | child.sendline("char-write-req " + barista_mode_handle + " " + code) 123 | print(child.readline()) 124 | print(child.readline()) 125 | if lock_status == "locked": 126 | lock_status = "unlocked" 127 | else: 128 | lock_status = "locked" 129 | return lock_status 130 | 131 | 132 | # Function to get full name of given UID 133 | def get_name(UID): 134 | c = db.cursor() 135 | db.commit() 136 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 137 | for row in c.fetchall(): 138 | name = row[2] 139 | c.close 140 | return name 141 | 142 | # Function to get full name of given UID 143 | def get_vorname(UID): 144 | c = db.cursor() 145 | db.commit() 146 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 147 | for row in c.fetchall(): 148 | vorname = row[3] 149 | c.close 150 | return vorname 151 | 152 | # Function to get ID/chip number of given UID 153 | def get_chip(UID): 154 | c = db.cursor() 155 | db.commit() 156 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 157 | for row in c.fetchall(): 158 | chip = row[0] 159 | c.close 160 | return chip 161 | 162 | # Function to get value of given UID 163 | def get_value(UID): 164 | value = float() 165 | c = db.cursor() 166 | db.commit() 167 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 168 | for row in c.fetchall(): 169 | value = row[4] 170 | c.close 171 | return value 172 | 173 | # Function to set new value of given UID 174 | def set_value(UID, value): 175 | c = db.cursor() 176 | c.execute("UPDATE Benutzerverwaltung SET Guthaben = " + str(value) + " WHERE UID = " + UID + " ") 177 | db.commit() 178 | c.close() 179 | 180 | # Function to get price of selected product 181 | def get_price(product): 182 | price = priceCoffee[product] 183 | return price 184 | 185 | # Function to set insert new row into Kaufliste 186 | def set_buylist(UID, product_name, price): 187 | chip = get_chip(UID) 188 | c = db.cursor() 189 | c.execute("INSERT INTO Kaufliste (ID, Chip, Produkt, Preis, Timestamp) VALUES (NULL, '" + str(chip) + "', '" + product_name + "', '" + str(price) + "', CURRENT_TIMESTAMP)") 190 | db.commit() 191 | c.close() 192 | 193 | def readlineCR(port): 194 | rv = "" 195 | while True: 196 | ch = port.read() 197 | rv += ch 198 | if ch == '\r' or ch == '': 199 | return rv 200 | 201 | def scanCard(): 202 | # Scan for cards 203 | (status,TagType) = MIFAREReader.MFRC522_Request(MIFAREReader.PICC_REQIDL) 204 | 205 | # If a card is found 206 | if status == MIFAREReader.MI_OK: 207 | print("Card detected") 208 | 209 | # Get the UID of the card 210 | (status,uid) = MIFAREReader.MFRC522_Anticoll() 211 | 212 | # If we have the UID, continue 213 | if status == MIFAREReader.MI_OK: 214 | # Change UID to string 215 | uid_str = str(uid[0]).zfill(3) + str(uid[1]).zfill(3) + str(uid[2]).zfill(3) + str(uid[3]).zfill(3) 216 | return uid_str 217 | else: 218 | return "0" 219 | # define function that locks or unlocks the machine 220 | 221 | 222 | # define function that receives decoded machine status and converts it to the corresponding alerts (if any) 223 | # if corresponing bit is not set, the alert is not active 224 | # remove key from the beggining 225 | def getAlerts(status): 226 | # status comes like this: 2a 00 04 00 00 04 40 00 00 00 00 00 00 00 00 00 00 00 00 06 227 | # remove key from the beggining 228 | status = status[3:] 229 | # bin(int('ff', base=16))[2:] 230 | status = [x for x in status.split()] 231 | # only use the first 13 bytes 232 | status = status[:13] 233 | status = [bin(int(byte,16))[2:]for byte in status] 234 | # divide each item in status into 8 bits 235 | status = [list(byte.zfill(8)) for byte in status] 236 | # print status 237 | # print(status) 238 | # combine into one string 239 | status = ''.join([item for sublist in status for item in sublist]) 240 | 241 | # print("status: ", status) 242 | for i in range(len(status)): 243 | # if bit is set, print corresponding alert 244 | if status[i] == "1": 245 | print("Alert in bit " + str(i) + " with the alert " + ALERTS[i]) 246 | 247 | # Main Characteristic UUID and handle 248 | machine_status = "5a401524-ab2e-2548-c435-08c300000710" 249 | machine_status_handle = "0x000b" 250 | 251 | barista_mode = "5a401530-ab2e-2548-c435-08c300000710" 252 | barista_mode_handle = "0x0017" 253 | 254 | product_progress = "5a401527-ab2e-2548-c435-08c300000710" 255 | product_progress_handle = "0x001a" 256 | 257 | heartbeat_uuid = "5a401529-ab2e-2548-c435-08c300000710" # or p_mode 258 | heartbeat_handle = "0x0011" 259 | 260 | heartbeat_read_uuid = "5a401538-ab2e-2548-c435-08c300000710" # or p_mode_read 261 | heartbeat_read_handle = "0x0032" 262 | 263 | start_product = "5a401525-ab2e-2548-c435-08c300000710" 264 | start_product_handle = "0x000e" 265 | 266 | statistics_command_uuid = "5A401533-ab2e-2548-c435-08c300000710" 267 | statistics_command_handle = "0x0026" 268 | 269 | statistics_data_uuid = "5A401534-ab2e-2548-c435-08c300000710" 270 | statistics_data_handle = "0x0029" 271 | 272 | uart_rx_uuid = "5a401624-ab2e-2548-c435-08c300000710" 273 | uart_rx_hnd = "0x0036" 274 | 275 | uart_tx_uuid = "5a401625-ab2e-2548-c435-08c300000710" 276 | uart_tx_hnd = "0x0039" 277 | 278 | # make dictionary with name: [uuid, handle] 279 | characteristics = { 280 | "machine_status": [machine_status, machine_status_handle], 281 | "barista_mode": [barista_mode, barista_mode_handle], 282 | "product_progress": [product_progress, product_progress_handle], 283 | "heartbeat": [heartbeat_uuid, heartbeat_handle], 284 | "heartbeat_read": [heartbeat_read_uuid, heartbeat_read_handle], 285 | "start_product": [start_product, start_product_handle], 286 | "statistics_command": [statistics_command_uuid, statistics_command_handle], 287 | "statistics_data": [statistics_data_uuid, statistics_data_handle], 288 | "uart_tx": [uart_tx_uuid, uart_tx_hnd], 289 | "uart_rx": [uart_rx_uuid, uart_rx_hnd] 290 | } 291 | 292 | 293 | child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, characteristics) 294 | # Capture SIGINT for cleanup when the script is aborted 295 | def end_read(signal,frame): 296 | global continue_reading 297 | beep(2) 298 | print("Ctrl+C captured, ending read.") 299 | inkasso = 0 300 | lcd.lcd_display_string(" Program over ", 1) 301 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 302 | lcd.lcd_display_string(" Unlocking ", 3) 303 | lcd.lcd_display_string(" -----> :( <----- ", 4) 304 | time.sleep(1) 305 | lcd.lcd_clear() 306 | db.close() 307 | continue_reading = False 308 | GPIO.cleanup() 309 | _ = lockUnlockMachine(unlock_code, "locked") 310 | 311 | def read_statistics(): 312 | chosen = 0 313 | product_made = False 314 | # write the all statistics command to statistics_command_handle 315 | child.sendline("char-write-req " + statistics_command_handle + " " + all_statistics) 316 | #print("All statistics sent!") 317 | time.sleep(1.2) 318 | # read the data from product progress handle 319 | # read the statistics data from statistics_data_handle 320 | child.sendline("char-read-hnd " + statistics_data_handle) 321 | child.expect(": ") 322 | data = child.readline() 323 | #print(b"Statistics data: " + data) 324 | # decode the statistics data 325 | data = [int(x, 16) for x in data.split()] 326 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 327 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 328 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 329 | # for every hex string in decoded list, convert to int 330 | decoded = [int(x, 16) for x in decoded] 331 | # change the values that are different from the previous ones when comparing with CURRENT_STATISTICS 332 | if decoded[0] != CURRENT_STATISTICS[0]: 333 | #lock_status = lockUnlockMachine(locking_code, lock_status) 334 | print("Overall products increased by 1") 335 | CURRENT_STATISTICS[0] = decoded[0] 336 | 337 | for i in range(1, len(decoded)): 338 | if decoded[i] != CURRENT_STATISTICS[i]: 339 | print("A " + PRODUCTS[i] + " was made!") 340 | product_made = PRODUCTS[i] 341 | chosen = 1 342 | print("Value changed to " + str(decoded[i]) + " from " + str(CURRENT_STATISTICS[i])) 343 | CURRENT_STATISTICS[i] = decoded[i] 344 | 345 | return chosen, product_made, decoded 346 | 347 | # if no arguments assume that emegeny unlock is 0 348 | if len(sys.argv) == 1: 349 | emergency_unlock = 0 350 | else: 351 | emergency_unlock = int(sys.argv[1]) 352 | if emergency_unlock == 0: 353 | lock_status = "unlocked" 354 | lock_status = lockUnlockMachine(locking_code, lock_status) 355 | print("Machine locked!") 356 | else: 357 | lock_status = "locked" 358 | lock_status = lockUnlockMachine(unlock_code, lock_status) 359 | print("Machine unlocked!") 360 | exit() 361 | 362 | # Hook the SIGINT 363 | signal.signal(signal.SIGINT, end_read) 364 | 365 | # Create an object of the class MFRC522 366 | MIFAREReader = MFRC522.MFRC522() 367 | 368 | # Init buzzer 369 | setupBuzzer(BUZZER) 370 | 371 | while True: 372 | try: 373 | # Init Serial 374 | port = serial.Serial("/dev/serial0", baudrate = 9600, timeout = 1.0) 375 | print("Serial connection initialized") 376 | break 377 | except: 378 | print("Serial connection failed") 379 | 380 | lcd.lcd_display_string(" Machine Locked ", 1) 381 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 382 | lcd.lcd_display_string(" Put tag to unlock ", 3) 383 | lcd.lcd_display_string(" -----> :) <----- ", 4) 384 | time.sleep(1) 385 | beep(0.01) 386 | 387 | port.flushInput() 388 | 389 | buttonPress = False 390 | continue_reading = True 391 | 392 | # Welcome message 393 | print("Welcome to the BlackBetty 2") 394 | print("Press Ctrl-C to stop.") 395 | 396 | lastSeen = "" 397 | counter = 0 398 | disp_init = 1 399 | payment_to_date = 1 400 | client_to_pay = [] 401 | admin_locked = 0 402 | while continue_reading: 403 | # if time elapsed is a multiple of 15 seconds then send keep alive code 404 | if int(time.time() - initial_time) % 5 == 0: 405 | # print time in seconds since it was connected 406 | #print("\nTime elapsed: " + str(int(time.time() - initial_time))) 407 | child.sendline("char-write-req " + heartbeat_handle + " " + keep_alive_code) 408 | #print("Keep alive sent!") 409 | # read the data from product progress handle 410 | # child.sendline("char-read-hnd " + product_progress_handle) 411 | # child.expect(": ") 412 | # data = child.readline() 413 | # print("Encoded data: ", data) 414 | # data2 = [int(x, 16) for x in data.split()] 415 | # data = [x for x in data.split()] 416 | # decoded = BtEncoder.encDecBytes(data2, KEY_DEC) 417 | # print("Decoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 418 | # print("Decoded data as int: " + " ".join([str(d) for d in decoded])) 419 | # read machine status and get alerts: 420 | # child.sendline("char-read-hnd " + machine_status_handle) 421 | # child.expect(": ") 422 | # data = child.readline() 423 | # print(b"Data: " + data) 424 | # data = [int(x, 16) for x in data.split()] 425 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 426 | # print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 427 | # getAlerts(" ".join(["%02x" % d for d in decoded])) 428 | 429 | 430 | if disp_init == 1: 431 | lcd.lcd_clear() 432 | lcd.lcd_display_string(" Put Tag and then ", 1) 433 | lcd.lcd_display_string(" Choose Product ", 2) 434 | lcd.lcd_display_string(" In machine ", 3) 435 | lcd.lcd_display_string(" :) ", 4) 436 | disp_init = 0 437 | time.sleep(0.5) 438 | 439 | # if time.time() - initial_time < 5: 440 | # _, _, CURRENT_STATISTICS = read_statistics() 441 | # print("Current statistics: " + str(CURRENT_STATISTICS)) 442 | # elif client_to_pay != []: 443 | # chosen, product_made, CURRENT_STATISTICS = read_statistics() 444 | 445 | 446 | # if payment_to_date == 0: 447 | # print("Checking previous costumer payment") 448 | 449 | # value_new = 0 450 | # if chosen == 1: 451 | # print("Setting value for uid: " + client_to_pay[0]) 452 | # value_new = value - priceCoffee[product_made] 453 | # if value_new > 0 and chosen == 1: 454 | # print("PAYING") 455 | # # beep(0.05) 456 | # # time.sleep(0.1) 457 | # # beep(0.05) 458 | # set_value(client_to_pay[0], value_new) 459 | # payment_to_date = 1 460 | # preName = get_vorname(client_to_pay[0]) 461 | # #beep(1) 462 | # # set_buylist(uid_str, str("General"), priceCoffee[product_made]) 463 | # msgStr1 = str(" Kaffee abgebucht!") 464 | # msgStr2 = str(" Betty dankt :) ") 465 | # msgStr3 = str(" " + preName + " ") 466 | # value_str = str("Guthaben: " + str('%.2f' % value_new) + " EUR") 467 | # lcd.lcd_display_string(msgStr1, 1) 468 | # lcd.lcd_display_string(msgStr2, 2) 469 | # lcd.lcd_display_string(msgStr3, 3) 470 | # lcd.lcd_display_string(value_str, 4) 471 | # time.sleep(2) 472 | # # sleepTimer(4) 473 | # # lcd.lcd_clear() 474 | # # remove first element from list 475 | # client_to_pay.pop(0) 476 | # counter = 0 477 | # disp_init = 1 478 | # product_made = False 479 | # elif value_new < 0 and chosen == 1: 480 | # lcd.lcd_clear() 481 | # set_value(client_to_pay[0], value_new) 482 | # payment_to_date = 1 483 | # #beep(1) 484 | # #set_buylist(uid_str, str("General"), 0.5) 485 | # msgStr1 = str(" Kaffee abgebucht!") 486 | # msgStr2 = str(" Schulden gemacht!") 487 | # msgStr3 = str("Guthaben aufladen!") 488 | # #value_str = str("Schulden: " + str('%.2f' % (-1*value))) + " EUR") 489 | # lcd.lcd_display_string(msgStr1, 1) 490 | # lcd.lcd_display_string(msgStr2, 2) 491 | # lcd.lcd_display_string(msgStr2, 3) 492 | # lcd.lcd_display_string(value_str, 4) 493 | # time.sleep(2) 494 | # # beep(0.05) 495 | # # time.sleep(0.1) 496 | # # beep(0.05) 497 | # # beep(0.05) 498 | # # time.sleep(4) 499 | # client_to_pay.pop(0) 500 | # counter = 0 501 | # disp_init = 1 502 | # product_made = False 503 | 504 | uid_str = scanCard() 505 | #print(uid_str) 506 | #print(uid_str) 507 | #print("UID: " + uid_str) 508 | if int(time.time() - initial_time) in [20]: 509 | uid_str = "204093081213" 510 | 511 | if uid_str != "0": 512 | print("last seen: ", lastSeen) 513 | product_made = False 514 | if admin_locked == 0: 515 | lcd.lcd_clear() 516 | 517 | if (uid_str == mastercard1) or (uid_str == mastercard2): 518 | if lock_status == "locked": 519 | lock_status = lockUnlockMachine(unlock_code, lock_status) 520 | print("Machine unlocked permanently!") 521 | 522 | lcd.lcd_display_string(" Admin Unlock ", 2) 523 | lcd.lcd_display_string(" -----> :) <----- ", 3) 524 | admin_locked = 1 525 | 526 | 527 | elif lock_status == "unlocked": 528 | lock_status = lockUnlockMachine(locking_code, lock_status) 529 | print("Machine locked!") 530 | lcd.lcd_display_string(" Admin Lock ", 2) 531 | lcd.lcd_display_string(" -----> :( <----- ", 3) 532 | disp_init = 1 533 | admin_locked = 0 534 | time.sleep(2) 535 | 536 | elif admin_locked == 1: 537 | pass 538 | else: 539 | try: 540 | if lastSeen == "": 541 | lastSeen = uid_str 542 | value = get_value(uid_str) 543 | value_str = str("Balance: " + str('%.2f' % value) + " EUR") 544 | lastName = get_name(uid_str) 545 | preName = get_vorname(uid_str) 546 | welStr = str("Hello " + preName) 547 | msgStr3 = str("Hold for 2s please ") 548 | msgStr4 = str("Chip below ") 549 | 550 | lcd.lcd_display_string(welStr, 1) 551 | lcd.lcd_display_string(value_str, 2) 552 | lcd.lcd_display_string(msgStr3, 3) 553 | lcd.lcd_display_string(msgStr4, 4) 554 | time.sleep(1.5) 555 | 556 | elif lastSeen == uid_str and product_made == False: 557 | print("Opening coffe machine for choice") 558 | lcd.lcd_clear() 559 | 560 | lcd.lcd_display_string(" Select a ", 1) 561 | lcd.lcd_display_string(" product ", 2) 562 | lcd.lcd_display_string(" in the machine ", 3) 563 | lcd.lcd_display_string(" -----> :) <----- ", 4) 564 | lock_status = lockUnlockMachine(unlock_code, lock_status) 565 | initial_time = time.time() 566 | chosen = 0 567 | started = 0 568 | over = 0 569 | while over == 0: 570 | #print("Waiting for product to be made") 571 | time_total = int(time.time() - initial_time) 572 | if time_total > 25 and started == 0: 573 | print("No product made in 25 seconds, locking machine") 574 | lock_status = lockUnlockMachine(locking_code, lock_status) 575 | lcd.lcd_display_string(" No Product ", 1) 576 | lcd.lcd_display_string(" selected ", 2) 577 | lcd.lcd_display_string(" Locking Machine ", 3) 578 | lcd.lcd_display_string(" -----> :( <----- ", 4) 579 | time.sleep(1.5) 580 | #lcd.lcd_clear() 581 | lastSeen = "" 582 | disp_init = 1 583 | chosen = 0 584 | break 585 | if int(time.time() - initial_time) % 5 == 0: 586 | child.sendline("char-write-req " + heartbeat_handle + " " + keep_alive_code) 587 | try: 588 | # read the data from product progress handle 589 | child.sendline("char-read-hnd " + product_progress_handle) 590 | child.expect(": ") 591 | data = child.readline() 592 | data2 = [int(x, 16) for x in data.split()] 593 | #print("Encoded: ", data) 594 | data = [x for x in data.split()] 595 | decoded = BtEncoder.encDecBytes(data2, KEY_DEC) 596 | as_hex = ["%02x" % d for d in decoded] 597 | #print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 598 | if data[1] not in [b"e1", b"ac"] and time_total > 5 and started == 0: 599 | #lock_status = lockUnlockMachine(locking_code, lock_status) 600 | beep(0.5) 601 | print("PRODUCT MADE") 602 | client_to_pay.append(uid_str) 603 | lcd.lcd_clear() 604 | preName = get_vorname(uid_str) 605 | product_made = in_machine_products[int(as_hex[2])] 606 | lcd.lcd_display_string(" " + product_made + " detected ", 1) 607 | lcd.lcd_display_string(" Will charge ", 2) 608 | lcd.lcd_display_string(" " + preName + " ", 3) 609 | lcd.lcd_display_string(" -----> :) <----- ", 4) 610 | 611 | print("Product made was: ", product_made) 612 | started = 1 613 | disp_init = 1 614 | chosen = 1 615 | lock_status = lockUnlockMachine(locking_code, lock_status) 616 | print("Checking costumer payment") 617 | value_new = 0 618 | if chosen == 1: 619 | print("Setting value for uid: " + client_to_pay[0]) 620 | value_new = value - priceCoffee[product_made] 621 | if value_new > 0 and chosen == 1: 622 | print("PAYING") 623 | # beep(0.05) 624 | # time.sleep(0.1) 625 | # beep(0.05) 626 | set_value(client_to_pay[0], value_new) 627 | payment_to_date = 1 628 | preName = get_vorname(client_to_pay[0]) 629 | #beep(1) 630 | # set_buylist(uid_str, str("General"), priceCoffee[product_made]) 631 | msgStr1 = str(product_made + " was made!") 632 | msgStr2 = str(" Happy betty :) ") 633 | msgStr3 = str(" " + preName + " ") 634 | value_str = str("Balance: " + str('%.2f' % value_new) + " EUR") 635 | lcd.lcd_display_string(msgStr1, 1) 636 | lcd.lcd_display_string(msgStr2, 2) 637 | lcd.lcd_display_string(msgStr3, 3) 638 | lcd.lcd_display_string(value_str, 4) 639 | time.sleep(2) 640 | # sleepTimer(4) 641 | # lcd.lcd_clear() 642 | # remove first element from list 643 | client_to_pay.pop(0) 644 | counter = 0 645 | disp_init = 1 646 | product_made = False 647 | elif value_new < 0 and chosen == 1: 648 | lcd.lcd_clear() 649 | set_value(client_to_pay[0], value_new) 650 | payment_to_date = 1 651 | #beep(1) 652 | #set_buylist(uid_str, str("General"), 0.5) 653 | msgStr1 = str(product_made + " was made!") 654 | msgStr2 = str(" Thank you!") 655 | msgStr3 = str("Balance < 0!") 656 | #value_str = str("Schulden: " + str('%.2f' % (-1*value))) + " EUR") 657 | lcd.lcd_display_string(msgStr1, 1) 658 | lcd.lcd_display_string(msgStr2, 2) 659 | lcd.lcd_display_string(msgStr2, 3) 660 | lcd.lcd_display_string(value_str, 4) 661 | time.sleep(2) 662 | # beep(0.05) 663 | # time.sleep(0.1) 664 | # beep(0.05) 665 | # beep(0.05) 666 | # time.sleep(4) 667 | client_to_pay.pop(0) 668 | counter = 0 669 | disp_init = 1 670 | product_made = False 671 | lastSeen = "" 672 | elif started == 1 and data[1] in [b"e1", b"ac"]: 673 | over = 1 674 | lcd.lcd_clear() 675 | preName = get_vorname(uid_str) 676 | lcd.lcd_display_string(" Product Ended ", 1) 677 | lcd.lcd_display_string(" Will charge ", 2) 678 | lcd.lcd_display_string(" " + preName + " ", 3) 679 | lcd.lcd_display_string(" -----> :) <----- ", 4) 680 | time.sleep(2) 681 | else: 682 | continue 683 | #print("NO PRODUCT MADE") 684 | #print("Product progress data: " + str(data)) 685 | # # read the statistics data from statistics_data_handle 686 | # child.sendline("char-read-hnd " + statistics_data_handle) 687 | # child.expect(": ") 688 | # data = child.readline() 689 | # #print(b"Statistics data: " + data) 690 | # # decode the statistics data 691 | # data = [int(x, 16) for x in data.split()] 692 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 693 | # # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 694 | # decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 695 | # # for every hex string in decoded list, convert to int 696 | # decoded = [int(x, 16) for x in decoded] 697 | # # change the values that are different from the previous ones when comparing with CURRENT_STATISTICS 698 | # if decoded[0] != CURRENT_STATISTICS[0]: 699 | # #lock_status = lockUnlockMachine(locking_code, lock_status) 700 | # print("Overall products increased by 1") 701 | # CURRENT_STATISTICS[0] = decoded[0] 702 | 703 | # for i in range(1, len(decoded)): 704 | # if decoded[i] != CURRENT_STATISTICS[i]: 705 | # print("A " + PRODUCTS[i] + " was made!") 706 | # product_made = PRODUCTS[i] 707 | # chosen = 1 708 | # print("Value changed: " + str(decoded[i]) + " -> " + str(CURRENT_STATISTICS[i])) 709 | # CURRENT_STATISTICS[i] = decoded[i] 710 | except Exception as e: 711 | print("Error: " + str(e)) 712 | if b'Disconnected' in e: 713 | # run setup 714 | print("Disconnected") 715 | child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, characteristics) 716 | 717 | 718 | 719 | continue 720 | # if chosen == 0: 721 | # product_made = False 722 | # continue 723 | # elif chosen == 1: 724 | # print("Setting value for uid: " + uid_str) 725 | # value_new = value - priceCoffee[product_made] 726 | # if value_new >= 0 and chosen == 1: 727 | # beep(0.05) 728 | # time.sleep(0.1) 729 | # beep(0.05) 730 | # set_value(uid_str, value_new) 731 | # #set_buylist(uid_str, str("General"), priceCoffee[product_made]) 732 | # msgStr1 = str(" Kaffee abgebucht!") 733 | # msgStr2 = str(" Betty dankt :) ") 734 | # value_str = str("Guthaben: " + str('%.2f' % value_new) + " EUR") 735 | # lcd.lcd_display_string(msgStr1, 1) 736 | # lcd.lcd_display_string(msgStr2, 2) 737 | # lcd.lcd_display_string(value_str, 4) 738 | # sleepTimer(4) 739 | # lcd.lcd_clear() 740 | # lastSeen = "" 741 | # counter = 0 742 | # disp_init = 1 743 | # product_made = False 744 | # elif value_new < 0 and chosen == 1: 745 | # lcd.lcd_clear() 746 | # #set_value(uid_str, value_new) 747 | # #set_buylist(uid_str, str("General"), 0.5) 748 | # msgStr1 = str(" Kaffee abgebucht!") 749 | # msgStr2 = str(" Schulden gemacht!") 750 | # msgStr3 = str("Guthaben aufladen!") 751 | # #value_str = str("Schulden: " + str('%.2f' % (-1*value))) + " EUR") 752 | # lcd.lcd_display_string(msgStr1, 1) 753 | # lcd.lcd_display_string(msgStr2, 2) 754 | # lcd.lcd_display_string(msgStr2, 3) 755 | # lcd.lcd_display_string(value_str, 4) 756 | # beep(0.05) 757 | # time.sleep(0.1) 758 | # beep(0.05) 759 | # beep(0.05) 760 | # time.sleep(4) 761 | # lastSeen = "" 762 | # counter = 0 763 | # disp_init = 1 764 | # product_made = False 765 | # else: 766 | # lcd.lcd_clear() 767 | # lastName1 = get_name(lastSeen) 768 | # PreName1 = get_vorname(lastSeen) 769 | # lastName2 = get_name(uid_str) 770 | # PreName2 = get_vorname(uid_str) 771 | # msgStr1 = str("Andere UID! Erwarte:") 772 | # msgStr2 = str(preName1 + " " + lastName1) 773 | # msgStr3 = str("Gerade gelesen: ") 774 | # msgStr4 = str(preName2 + " " + lastName2) 775 | # lcd.lcd_display_string(msgStr1, 1) 776 | # lcd.lcd_display_string(msgStr2, 2) 777 | # lcd.lcd_display_string(msgStr3, 3) 778 | # lcd.lcd_display_string(msgStr4, 4) 779 | # beep(0.5) 780 | # time.sleep(3) 781 | # lastSeen = "" 782 | # counter = 0 783 | # disp_init = 1 784 | 785 | except Exception as e: 786 | print("The error raised is: ", e) 787 | if b'Disconnected' in e: 788 | # run setup 789 | print("Disconnected") 790 | child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, characteristics) 791 | 792 | else: 793 | if lastSeen != "": 794 | counter = counter + 1 795 | #print(counter) 796 | if counter >= 10: 797 | lcd.lcd_clear() 798 | lastSeen = "" 799 | counter = 0 800 | beep(0.2) 801 | time.sleep(0.1) 802 | beep(0.2) 803 | 804 | msgStr1 = str("Chip removed?") 805 | msgStr3 = str("Product not chosen") 806 | msgStr4 = str("bye! :(") 807 | lcd.lcd_display_string(msgStr1, 1) 808 | lcd.lcd_display_string(msgStr3, 3) 809 | lcd.lcd_display_string(msgStr4, 4) 810 | sleepTimer(4) 811 | disp_init = 1 812 | sleepTimer(0.1) 813 | 814 | 815 | #print("Decoded statistics data: " + " ".join(["%02x" % d for d in decoded])) 816 | 817 | # Every 5 seconds read machine_status and decode them to hex using BtEncoder.encDecBytes 818 | # if int(time.time() - initial_time) % 5 == 0: 819 | # key = "machine_status" 820 | # print("\nCurrently reading: " + key) 821 | # child.sendline("char-read-hnd " + characteristics[key][1]) 822 | # child.expect(": ") 823 | # data = child.readline() 824 | # print(b"Data: " + data) 825 | # try: 826 | # data = [int(x, 16) for x in data.split()] 827 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 828 | # #print("Decoded data as INT: " + str(decoded)) 829 | # # if key is machine_status, decode it to alerts 830 | # if key == "machine_status": 831 | # print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 832 | # getAlerts(" ".join(["%02x" % d for d in decoded])) 833 | # except: 834 | # print("Error decoding data due to " + str(data)) 835 | 836 | -------------------------------------------------------------------------------- /src/bt_encoder.py: -------------------------------------------------------------------------------- 1 | class BtEncoder: 2 | def __init__(self): 3 | self.numbers1 = [14, 4, 3, 2, 1, 13, 8, 11, 6, 15, 12, 7, 10, 5, 0, 9] 4 | self.numbers2 = [10, 6, 13, 12, 14, 11, 1, 9, 15, 7, 0, 5, 3, 2, 4, 8] 5 | 6 | def hexStrToInt(self, hexStr): 7 | ''' 8 | get a big hex string like "aa 00 aa 00 aa" and treat as little endian 9 | return list of int 10 | ''' 11 | return [int(hexStr[i:i+2], 16) for i in range(0, len(hexStr), 3)] 12 | 13 | def mod256(self, i): 14 | while i > 255: 15 | i -= 256 16 | while i < 0: 17 | i += 256 18 | return i 19 | 20 | def shuffle(self, dataNibble, nibbleCount, keyLeftNibbel, keyRightNibbel): 21 | i5 = self.mod256(nibbleCount >> 4) 22 | tmp1 = self.numbers1[self.mod256(dataNibble + nibbleCount + keyLeftNibbel) % 16] 23 | tmp2 = self.numbers2[self.mod256(tmp1 + keyRightNibbel + i5 - nibbleCount - keyLeftNibbel) % 16] 24 | tmp3 = self.numbers1[self.mod256(tmp2 + keyLeftNibbel + nibbleCount - keyRightNibbel - i5) % 16] 25 | return self.mod256(tmp3 - nibbleCount - keyLeftNibbel) % 16 26 | 27 | def encDecBytes(self, data, key): 28 | key = int(key, 16) 29 | result = [] 30 | keyLeftNibbel = key >> 4 31 | keyRightNibbel = key & 15 32 | nibbelCount = 0 33 | for d in data: 34 | dataLeftNibbel = d >> 4 35 | dataRightNibbel = d & 15 36 | resultLeftNibbel = self.shuffle(dataLeftNibbel, nibbelCount, keyLeftNibbel, keyRightNibbel) 37 | resultRightNibbel = self.shuffle(dataRightNibbel, nibbelCount + 1, keyLeftNibbel, keyRightNibbel) 38 | result.append((resultLeftNibbel << 4) | resultRightNibbel) 39 | nibbelCount += 2 40 | return result 41 | def bruteforce_key(self, data): 42 | data = [int(d, 16) for d in data.split()] 43 | for key in range(0, 256): 44 | key = hex(key)[2:] 45 | if len(key) == 1: 46 | key = "0" + key 47 | result = self.encDecBytes(data, key) 48 | if result[0] == int(key, 16): 49 | print("key: " + key) 50 | print("result: " + str(result)) 51 | return key 52 | return None 53 | 54 | if __name__ == "__main__": 55 | # test 56 | bt = BtEncoder() 57 | data = ["3b 98 f8 d6 88 80 d3 cb bf 23 70 22 a4 a8 c6 ab 46 f7 a6 24 0d 2c a6 be dc 7e cf 6d 28 41 18 6f 88 31 cd 65 ea 81 ef de 33 24 fa 0c 5d de fc 28 44 21 3e 23 15 2d 9a 14 e7 72 ed 1b 3f 65 13 bf 88 8d d1 6f ea 5a ef 6b 32 36 ae fc ed 94 a1 28 d4 4c 59 de 19 2c b9 f7 6c 33 68 bb 30 6f c8 9e b3 65 d5 6f 55 e5 d3 3a 1a 33 c2 c5 23 74 9c 1b a9 12 ac 51 71 1c 21 00 dc 6e d7 ed ea ef e4 1e 5c e1 dc 6d 88 9d ed de a3 29 fa 93 45 a3 fa e3 47 56 87 23 79 39 a2 99 33 7d 59 51 32 41 b9 6f ec b6 d1 65 c2 81 90 75 33 24 fa f4 a9 de fc ec 72 4c 3e 9e 15 8c de 14 e7 72 ed 1b 3f 65 13 bf 88 8d d1 6f ea 5a ef 6b"] 58 | #key = bt.bruteforce_key(data[0]) 59 | key = "2a" 60 | for data in data: 61 | data = [int(d, 16) for d in data.split()] 62 | # decode data with key 63 | decoded = bt.encDecBytes(data, key) 64 | # print decoded data 65 | print("".join([chr(d) for d in decoded[1:]])) 66 | # print as hex 67 | print("".join(["%02x" % d for d in decoded])) 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from dotenv import load_dotenv 4 | import pymysql 5 | pymysql.install_as_MySQLdb() 6 | import MySQLdb as mdb 7 | import logging 8 | 9 | # create logger in blue.log in current directory 10 | logging.basicConfig( 11 | filename='blue.log', 12 | level=logging.DEBUG, 13 | format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', 14 | datefmt='%Y-%m-%d %H:%M:%S', 15 | ) 16 | 17 | load_dotenv() 18 | 19 | def db_connect(): 20 | # Open database connection 21 | while True: 22 | try: 23 | db = mdb.connect(host = "127.0.0.1", user = "root", passwd = os.getenv("PASSWD"), db = "AnnelieseDB") 24 | return db 25 | except: 26 | logging.info("Database connection failed. Trying again in 5 seconds...") 27 | time.sleep(5) 28 | continue 29 | 30 | # Function to go to Benutzerverwaltung table and retrieve UID from id = 1000 31 | def getUID_stats(db): 32 | value = str() 33 | c = db.cursor() 34 | db.commit() 35 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE id = 1000 ") 36 | value = c.fetchone()[1] 37 | c.close 38 | return value 39 | 40 | # Function to get full name of given UID 41 | def get_name(db, UID): 42 | c = db.cursor() 43 | db.commit() 44 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 45 | for row in c.fetchall(): 46 | name = row[2] 47 | c.close 48 | return name 49 | 50 | # Function to get price of selected product 51 | def get_price(db, product): 52 | price = float() 53 | c = db.cursor() 54 | db.commit() 55 | c.execute("SELECT SQL_NO_CACHE * FROM Produktliste WHERE Produkt = '" + product + "' ") 56 | for row in c.fetchall(): 57 | price = row[2] 58 | c.close 59 | return price 60 | 61 | # Function to get full name of given UID 62 | def get_vorname(db, UID): 63 | c = db.cursor() 64 | db.commit() 65 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 66 | for row in c.fetchall(): 67 | vorname = row[3] 68 | c.close 69 | return vorname 70 | 71 | # Function to get ID/chip number of given UID 72 | def get_chip(db, UID): 73 | c = db.cursor() 74 | db.commit() 75 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 76 | for row in c.fetchall(): 77 | chip = row[0] 78 | c.close 79 | return chip 80 | 81 | # Function to get value of given UID 82 | def get_value(db, UID): 83 | value = float() 84 | c = db.cursor() 85 | db.commit() 86 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 87 | for row in c.fetchall(): 88 | value = row[4] 89 | c.close 90 | return value 91 | 92 | # Function to set new value of given UID 93 | def set_value(db, UID, value): 94 | c = db.cursor() 95 | c.execute("UPDATE Benutzerverwaltung SET Guthaben = " + str(value) + " WHERE UID = " + UID + " ") 96 | db.commit() 97 | # where ID = 1000 98 | current = int(getUID_stats(db)) 99 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(current + 1) + " WHERE id = 1000;") 100 | db.commit() 101 | c.close() 102 | 103 | # Function to set insert new row into Kaufliste 104 | def set_buylist(db, UID, product_name): 105 | chip = get_chip(db, UID) 106 | c = db.cursor() 107 | price = get_price(db, product_name) 108 | c.execute("INSERT INTO Kaufliste (ID, Chip, Produkt, Preis, Timestamp) VALUES (NULL, '" + str(chip) + "', '" + product_name + "', '" + str(price) + "', CURRENT_TIMESTAMP)") 109 | db.commit() 110 | c.close() -------------------------------------------------------------------------------- /src/i2c_lib.py: -------------------------------------------------------------------------------- 1 | import smbus 2 | from time import * 3 | 4 | class i2c_device: 5 | def __init__(self, addr, port=1): 6 | self.addr = addr 7 | self.bus = smbus.SMBus(port) 8 | 9 | # Write a single command 10 | def write_cmd(self, cmd): 11 | self.bus.write_byte(self.addr, cmd) 12 | sleep(0.0001) 13 | 14 | # Write a command and argument 15 | def write_cmd_arg(self, cmd, data): 16 | self.bus.write_byte_data(self.addr, cmd, data) 17 | sleep(0.0001) 18 | 19 | # Write a block of data 20 | def write_block_data(self, cmd, data): 21 | self.bus.write_block_data(self.addr, cmd, data) 22 | sleep(0.0001) 23 | 24 | # Read a single byte 25 | def read(self): 26 | return self.bus.read_byte(self.addr) 27 | 28 | # Read 29 | def read_data(self, cmd): 30 | return self.bus.read_byte_data(self.addr, cmd) 31 | 32 | # Read a block of data 33 | def read_block_data(self, cmd): 34 | return self.bus.read_block_data(self.addr, cmd) 35 | -------------------------------------------------------------------------------- /src/jura_encoder.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import struct 4 | import binascii 5 | from bitarray import bitarray 6 | 7 | class JuraEncoder: 8 | ''' 9 | Class to encode and decode Jura protocol 10 | ''' 11 | def __init__(self): 12 | # base layout for the bytes 13 | self.base_layout = bitarray('01011011') 14 | 15 | def tojura(self, letter, hex = 0): 16 | ''' 17 | Convert a letter to the Jura protocol bytes 18 | If hex is set to 1, the output will be in hex 19 | ''' 20 | if len(letter) != 1: raise ValueError('Needs a single byte') 21 | if hex: hex_code = "" 22 | c = bitarray(8) 23 | c.frombytes(letter) 24 | # make sure we only have 8 bits 25 | c = c[8:] 26 | # flip the nibbles bits 27 | c = c[2:4] + c[0:2] + c[6:8] + c[4:6] 28 | # flip the nibbles positions 29 | c = c[4:] + c[:4] 30 | # create layouts from base_layout 31 | bytes = [self.base_layout.copy() for i in range(4)] 32 | for i in range(4): 33 | # change the third and sixth bit 34 | bytes[i][2] = c[i * 2] 35 | bytes[i][5] = c[i * 2 + 1] 36 | if hex: 37 | # convert each bitarray to hex 38 | hex_code += binascii.hexlify(bytes[i]).decode() + " " 39 | 40 | return hex_code[:-1] if hex else bytes 41 | 42 | def fromjura(self, bytes, hex = 0): 43 | ''' 44 | Convert Jura protocol bytes to a letter 45 | If hex is set to 1, the input will be in hex 46 | ''' 47 | if hex: 48 | hex = bytes 49 | chars_hex_convert = [] 50 | for j in hex.split(): 51 | bit_array = bitarray('{0:b}'.format(ord(binascii.unhexlify(j)))) 52 | if len(bit_array) < 8: 53 | bit_array = bitarray('0' * (8 - len(bit_array))) + bit_array 54 | chars_hex_convert.append(bit_array) 55 | bytes = chars_hex_convert 56 | 57 | if len(bytes) != 4: raise ValueError('Needs an array of size 4') 58 | 59 | # create a bitarray from the bytes 60 | out = bitarray(8) 61 | out[0] = bytes[0][2] 62 | out[1] = bytes[0][5] 63 | out[2] = bytes[1][2] 64 | out[3] = bytes[1][5] 65 | out[4] = bytes[2][2] 66 | out[5] = bytes[2][5] 67 | out[6] = bytes[3][2] 68 | out[7] = bytes[3][5] 69 | 70 | # flip the nibbles positions 71 | out = out[4:] + out[:4] 72 | # flip the nibbles bits 73 | out = out[2:4] + out[0:2] + out[6:8] + out[4:6] 74 | 75 | return out.tobytes() 76 | 77 | 78 | def testencoder(): 79 | ''' 80 | Test the encoder 81 | ''' 82 | jura = JuraEncoder() 83 | teststring = b'TY:\r\n' # enforce ascii 84 | bytes = [] 85 | hex = [] 86 | for c in teststring: 87 | bytes.append(jura.tojura(chr(c).encode())) 88 | hex.append(jura.tojura(chr(c).encode(), 1)) 89 | 90 | print("Bytes:\n", bytes) 91 | print("Hex: ", hex) 92 | chars_bytes = b"" 93 | chars_hex = b"" 94 | for arr_hex in hex: 95 | chars_hex += jura.fromjura(arr_hex, 1) 96 | # 0F FF 11 FF FF 11 00 00 97 | # bytes = [[bitarray('00001111'), bitarray('11111111'), bitarray('00010001'), bitarray('11111111')], 98 | # [bitarray('11110001'), bitarray('00000000'), bitarray('00000000'), bitarray('00000000')]] 99 | for arr in bytes: 100 | chars_bytes += jura.fromjura(arr) 101 | 102 | print("chars_hex: ", chars_hex) 103 | print("chars_bytes: ", chars_bytes) 104 | assert chars_bytes == teststring, "encoding/decoding functions must be symmetrical" 105 | assert chars_hex == teststring, "encoding/decoding functions must be symmetrical" 106 | 107 | 108 | if __name__ == "__main__": 109 | testencoder() 110 | 111 | -------------------------------------------------------------------------------- /src/lcddriver.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append("./lib") 3 | 4 | import i2c_lib 5 | from time import * 6 | 7 | # LCD Address 8 | ADDRESS = 0x3f 9 | #ADDRESS = 0x27 10 | 11 | # LCD width 12 | LCD_WIDTH = 20 13 | 14 | # commands 15 | LCD_CLEARDISPLAY = 0x01 16 | LCD_RETURNHOME = 0x02 17 | LCD_ENTRYMODESET = 0x04 18 | LCD_DISPLAYCONTROL = 0x08 19 | LCD_CURSORSHIFT = 0x10 20 | LCD_FUNCTIONSET = 0x20 21 | LCD_SETCGRAMADDR = 0x40 22 | LCD_SETDDRAMADDR = 0x80 23 | 24 | # flags for display entry mode 25 | LCD_ENTRYRIGHT = 0x00 26 | LCD_ENTRYLEFT = 0x02 27 | LCD_ENTRYSHIFTINCREMENT = 0x01 28 | LCD_ENTRYSHIFTDECREMENT = 0x00 29 | 30 | # flags for display on/off control 31 | LCD_DISPLAYON = 0x04 32 | LCD_DISPLAYOFF = 0x00 33 | LCD_CURSORON = 0x02 34 | LCD_CURSOROFF = 0x00 35 | LCD_BLINKON = 0x01 36 | LCD_BLINKOFF = 0x00 37 | 38 | # flags for display/cursor shift 39 | LCD_DISPLAYMOVE = 0x08 40 | LCD_CURSORMOVE = 0x00 41 | LCD_MOVERIGHT = 0x04 42 | LCD_MOVELEFT = 0x00 43 | 44 | # flags for function set 45 | LCD_8BITMODE = 0x10 46 | LCD_4BITMODE = 0x00 47 | LCD_2LINE = 0x08 48 | LCD_1LINE = 0x00 49 | LCD_5x10DOTS = 0x04 50 | LCD_5x8DOTS = 0x00 51 | 52 | # flags for backlight control 53 | LCD_BACKLIGHT = 0x08 54 | LCD_NOBACKLIGHT = 0x00 55 | 56 | En = 0b00000100 # Enable bit 57 | Rw = 0b00000010 # Read/Write bit 58 | Rs = 0b00000001 # Register select bit 59 | 60 | class lcd: 61 | #initializes objects and lcd 62 | def __init__(self): 63 | self.lcd_device = i2c_lib.i2c_device(ADDRESS) 64 | 65 | self.lcd_write(0x03) 66 | self.lcd_write(0x03) 67 | self.lcd_write(0x03) 68 | self.lcd_write(0x02) 69 | 70 | self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE) 71 | self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON) 72 | self.lcd_write(LCD_CLEARDISPLAY) 73 | self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT) 74 | sleep(0.2) 75 | 76 | # clocks EN to latch command 77 | def lcd_strobe(self, data): 78 | self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT) 79 | sleep(.0005) 80 | self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT)) 81 | sleep(.0001) 82 | 83 | def lcd_write_four_bits(self, data): 84 | self.lcd_device.write_cmd(data | LCD_BACKLIGHT) 85 | self.lcd_strobe(data) 86 | 87 | # write a command to lcd 88 | def lcd_write(self, cmd, mode=0): 89 | self.lcd_write_four_bits(mode | (cmd & 0xF0)) 90 | self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0)) 91 | 92 | #turn on/off the lcd backlight 93 | def lcd_backlight(self, state): 94 | if state in ("on","On","ON"): 95 | self.lcd_device.write_cmd(LCD_BACKLIGHT) 96 | elif state in ("off","Off","OFF"): 97 | self.lcd_device.write_cmd(LCD_NOBACKLIGHT) 98 | else: 99 | print ("Unknown State!") 100 | 101 | # put string function 102 | def lcd_display_string(self, string, line): 103 | if line == 1: 104 | self.lcd_write(0x80) 105 | if line == 2: 106 | self.lcd_write(0xC0) 107 | if line == 3: 108 | self.lcd_write(0x94) 109 | if line == 4: 110 | self.lcd_write(0xD4) 111 | 112 | string = string.center(LCD_WIDTH, " ") 113 | 114 | for char in string: 115 | self.lcd_write(ord(char), Rs) 116 | 117 | # clear lcd and set to home 118 | def lcd_clear(self): 119 | self.lcd_write(LCD_CLEARDISPLAY) 120 | self.lcd_write(LCD_RETURNHOME) 121 | -------------------------------------------------------------------------------- /src/old_blue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import pexpect 4 | import time 5 | from bitarray import bitarray 6 | from bt_encoder import BtEncoder 7 | from jura_encoder import JuraEncoder 8 | from setup import setup 9 | import os 10 | from dotenv import load_dotenv 11 | from connection import * 12 | import pymysql 13 | pymysql.install_as_MySQLdb() 14 | import MySQLdb as mdb 15 | import RPi.GPIO as GPIO 16 | import lcddriver 17 | import MFRC522 18 | import signal 19 | import time 20 | import serial 21 | import sys 22 | import logging 23 | 24 | # create logger in blue.log in current directory 25 | logging.basicConfig( 26 | filename='blue.log', 27 | level=logging.DEBUG, 28 | format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', 29 | datefmt='%Y-%m-%d %H:%M:%S', 30 | ) 31 | # Define pin for buzzer 32 | BuzzerPin = 7 33 | 34 | def setupBuzzer(pin): 35 | global BuzzerPin 36 | BuzzerPin = pin 37 | GPIO.setmode(GPIO.BOARD) # Numbers GPIOs by physical location 38 | GPIO.setup(BuzzerPin, GPIO.OUT) 39 | GPIO.output(BuzzerPin, GPIO.LOW) 40 | 41 | def beep(duration): 42 | # Init buzzer 43 | # setupBuzzer(BuzzerPin) 44 | # GPIO.output(BuzzerPin, GPIO.HIGH) 45 | # time.sleep(duration) 46 | # GPIO.output(BuzzerPin, GPIO.LOW) 47 | print("beep off") 48 | 49 | 50 | 51 | beep(0.5) 52 | 53 | load_dotenv() 54 | 55 | BtEncoder = BtEncoder() 56 | JuraEncoder = JuraEncoder() 57 | 58 | # BlackBetty2 mac address read from .env file 59 | DEVICE = os.getenv("DEVICE") 60 | print(DEVICE) 61 | 62 | mastercard1 = os.getenv("MASTER_CARD_1") 63 | mastercard2 = os.getenv("MASTER_CARD_2") 64 | passwd = os.getenv("PASSWD") 65 | 66 | # Open database connection 67 | while True: 68 | try: 69 | db = mdb.connect(host = "127.0.0.1", user = "root", passwd = os.getenv("PASSWD"), db = "AnnelieseDB") 70 | break 71 | except: 72 | logging.info("Database connection failed. Trying again in 5 seconds...") 73 | time.sleep(5) 74 | continue 75 | 76 | 77 | # Initialize LCD 78 | lcd = lcddriver.lcd() 79 | lcd.lcd_clear() 80 | 81 | # Function to go to Benutzerverwaltung table and retrieve UID from id = 1000 82 | def getUID_stats(): 83 | value = str() 84 | c = db.cursor() 85 | db.commit() 86 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE id = 1000 ") 87 | value = c.fetchone()[1] 88 | c.close 89 | return value 90 | 91 | # Function to get price of selected product 92 | def get_price(product): 93 | price = float() 94 | c = db.cursor() 95 | db.commit() 96 | c.execute("SELECT SQL_NO_CACHE * FROM Produktliste WHERE Produkt = '" + product + "' ") 97 | for row in c.fetchall(): 98 | price = row[2] 99 | c.close 100 | return price 101 | 102 | 103 | priceCoffee = { 104 | "Americano":0.45, 105 | "Espresso":0.45, 106 | "Coffee":0.45, 107 | "Cappuccino":0.6, 108 | "Milkcoffee":0.6, 109 | "Espresso Macchiato":0.55, 110 | "Latte Macchiato":0.75, 111 | "Milk Foam":0.25, 112 | "Flat White":0.7 113 | } 114 | 115 | # update priceCoffee with function get_price 116 | for key in priceCoffee: 117 | priceCoffee[key] = get_price(key) 118 | 119 | print(priceCoffee) 120 | 121 | 122 | PRODUCTS = { 123 | 0:"Overall", 124 | 1:"Americano", 125 | 2:"Espresso", 126 | 4:"Coffee", 127 | 29:"Cappuccino", 128 | 5:"Milkcoffee", 129 | 6:"Espresso Macchiato", 130 | 7:"Latte Macchiato", 131 | 8:"Milk Foam", 132 | 46:"Flat White" 133 | } 134 | 135 | in_machine_products = { 136 | 0:"Overall", 137 | 1:"Americano", 138 | 2:"Espresso", 139 | 3:"Coffee", 140 | 4:"Cappuccino", 141 | 5:"Milkcoffee", 142 | 6:"Espresso Macchiato", 143 | 7:"Latte Macchiato", 144 | 8:"Milk Foam", 145 | 46:"Flat White" 146 | } 147 | 148 | ALERTS = { 149 | 0: "insert tray", 1: "fill water", 2: "empty grounds", 3: "empty tray", 4: "insert coffee bin", 150 | 5: "outlet missing", 6: "rear cover missing", 7: "milk alert", 8: "fill system", 9: "system filling", 151 | 10: "no beans", 11: "welcome", 12: "heating up", 13: "coffee ready", 14: "no milk (milk sensor)", 152 | 15: "error milk (milk sensor)", 16: "no signal (milk sensor)", 17: "please wait", 18: "coffee rinsing", 153 | 19: "ventilation closed", 20: "close powder cover", 21: "fill powder", 22: "system emptying", 154 | 23: "not enough powder", 24: "remove water tank", 25: "press rinse", 26: "goodbye", 27: "periphery alert", 155 | 28: "powder product", 29: "program-mode status", 30: "error status", 31: "enjoy product", 32: "filter alert", 156 | 33: "decalc alert", 34: "cleaning alert", 35: "cappu rinse alert", 36: "energy safe", 37: "active RF filter", 157 | 38: "RemoteScreen", 39: "LockedKeys", 40: "close tab", 41: "cappu clean alert", 42: "Info - cappu clean alert", 158 | 43: "Info - coffee clean alert", 44: "Info - decalc alert", 45: "Info - filter used up alert", 46: "steam ready", 159 | 47: "SwitchOff Delay active", 48: "close front cover", 49: "left bean alert", 50: "right bean alert", 51: "cleaning", 160 | 52: "cleaning finished", 53: "cleaning finished", 54: "cleaning finished", 55: "cleaning finished", 161 | 56: "cleaning finished", 57: "cleaning finished", 58: "cleaning finished", 59: "cleaning finished", 162 | } 163 | 164 | def sleepTimer(secs): 165 | startTime = time.time() 166 | while (time.time() - startTime) < secs: 167 | pass 168 | 169 | # define function that locks or unlocks the machine 170 | def lockUnlockMachine(code, lock_status, unlock_code = "77e1"): 171 | child.sendline("char-write-req " + barista_mode_handle + " " + code) 172 | #print(child.readline()) 173 | #print(child.readline()) 174 | if code == unlock_code: 175 | lock_status = "unlocked" 176 | else: 177 | lock_status = "locked" 178 | return lock_status 179 | 180 | # Function to get full name of given UID 181 | def get_name(UID): 182 | c = db.cursor() 183 | db.commit() 184 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 185 | for row in c.fetchall(): 186 | name = row[2] 187 | c.close 188 | return name 189 | 190 | # Function to get full name of given UID 191 | def get_vorname(UID): 192 | c = db.cursor() 193 | db.commit() 194 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 195 | for row in c.fetchall(): 196 | vorname = row[3] 197 | c.close 198 | return vorname 199 | 200 | # Function to get ID/chip number of given UID 201 | def get_chip(UID): 202 | c = db.cursor() 203 | db.commit() 204 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 205 | for row in c.fetchall(): 206 | chip = row[0] 207 | c.close 208 | return chip 209 | 210 | # Function to get value of given UID 211 | def get_value(UID): 212 | value = float() 213 | c = db.cursor() 214 | db.commit() 215 | c.execute("SELECT SQL_NO_CACHE * FROM Benutzerverwaltung WHERE UID = " + UID + " ") 216 | for row in c.fetchall(): 217 | value = row[4] 218 | c.close 219 | return value 220 | 221 | # Function to set new value of given UID 222 | def set_value(UID, value): 223 | c = db.cursor() 224 | c.execute("UPDATE Benutzerverwaltung SET Guthaben = " + str(value) + " WHERE UID = " + UID + " ") 225 | db.commit() 226 | # where ID = 1000 227 | current = int(getUID_stats()) 228 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(current + 1) + " WHERE id = 1000;") 229 | db.commit() 230 | c.close() 231 | 232 | 233 | # Function to set insert new row into Kaufliste 234 | def set_buylist(UID, product_name): 235 | chip = get_chip(UID) 236 | c = db.cursor() 237 | price = get_price(product_name) 238 | c.execute("INSERT INTO Kaufliste (ID, Chip, Produkt, Preis, Timestamp) VALUES (NULL, '" + str(chip) + "', '" + product_name + "', '" + str(price) + "', CURRENT_TIMESTAMP)") 239 | db.commit() 240 | c.close() 241 | 242 | def readlineCR(port): 243 | rv = "" 244 | while True: 245 | ch = port.read() 246 | rv += ch 247 | if ch == '\r' or ch == '': 248 | return rv 249 | #global RFIDREADER 250 | RFIDREADER = MFRC522.MFRC522() 251 | 252 | def scanCard(): 253 | #global RFIDREADER 254 | # Create an object of the class MFRC522 255 | #del(RFIDREADER) 256 | #RFIDREADER = MFRC522.MFRC522() 257 | RFIDREADER.MFRC522_Init() 258 | # Scan for cards 259 | (status,TagType) = RFIDREADER.MFRC522_Request(RFIDREADER.PICC_REQIDL) 260 | 261 | # If a card is found 262 | if status == RFIDREADER.MI_OK: 263 | print("Card detected") 264 | 265 | # Get the UID of the card 266 | (status,uid) = RFIDREADER.MFRC522_Anticoll() 267 | 268 | # If we have the UID, continue 269 | if status == RFIDREADER.MI_OK: 270 | # Change UID to string 271 | uid_str = str(uid[0]).zfill(3) + str(uid[1]).zfill(3) + str(uid[2]).zfill(3) + str(uid[3]).zfill(3) 272 | return uid_str 273 | else: 274 | return "0" 275 | # define function that locks or unlocks the machine 276 | 277 | 278 | # define function that receives decoded machine status and converts it to the corresponding alerts (if any) 279 | # if corresponing bit is not set, the alert is not active 280 | # remove key from the beggining 281 | def getAlerts(status): 282 | # status comes like this: 2a 00 04 00 00 04 40 00 00 00 00 00 00 00 00 00 00 00 00 06 283 | # remove key from the beggining 284 | status = status[3:] 285 | # bin(int('ff', base=16))[2:] 286 | status = [x for x in status.split()] 287 | # only use the first 13 bytes 288 | status = status[:13] 289 | status = [bin(int(byte,16))[2:]for byte in status] 290 | # divide each item in status into 8 bits 291 | status = [list(byte.zfill(8)) for byte in status] 292 | # print status 293 | # print(status) 294 | # combine into one string 295 | status = ''.join([item for sublist in status for item in sublist]) 296 | 297 | # print("status: ", status) 298 | for i in range(len(status)): 299 | # if bit is set, print corresponding alert 300 | if status[i] == "1": 301 | print("Alert in bit " + str(i) + " with the alert " + ALERTS[i]) 302 | 303 | # Main Characteristic UUID and handle 304 | machine_status = "5a401524-ab2e-2548-c435-08c300000710" 305 | machine_status_handle = "0x000b" 306 | 307 | barista_mode = "5a401530-ab2e-2548-c435-08c300000710" 308 | barista_mode_handle = "0x0017" 309 | 310 | product_progress = "5a401527-ab2e-2548-c435-08c300000710" 311 | product_progress_handle = "0x001a" 312 | 313 | heartbeat_uuid = "5a401529-ab2e-2548-c435-08c300000710" # or p_mode 314 | heartbeat_handle = "0x0011" 315 | 316 | heartbeat_read_uuid = "5a401538-ab2e-2548-c435-08c300000710" # or p_mode_read 317 | heartbeat_read_handle = "0x0032" 318 | 319 | start_product = "5a401525-ab2e-2548-c435-08c300000710" 320 | start_product_handle = "0x000e" 321 | 322 | statistics_command_uuid = "5A401533-ab2e-2548-c435-08c300000710" 323 | statistics_command_handle = "0x0026" 324 | 325 | statistics_data_uuid = "5A401534-ab2e-2548-c435-08c300000710" 326 | statistics_data_handle = "0x0029" 327 | 328 | uart_rx_uuid = "5a401624-ab2e-2548-c435-08c300000710" 329 | uart_rx_hnd = "0x0036" 330 | 331 | uart_tx_uuid = "5a401625-ab2e-2548-c435-08c300000710" 332 | uart_tx_hnd = "0x0039" 333 | 334 | # make dictionary with name: [uuid, handle] 335 | characteristics = { 336 | "machine_status": [machine_status, machine_status_handle], 337 | "barista_mode": [barista_mode, barista_mode_handle], 338 | "product_progress": [product_progress, product_progress_handle], 339 | "heartbeat": [heartbeat_uuid, heartbeat_handle], 340 | "heartbeat_read": [heartbeat_read_uuid, heartbeat_read_handle], 341 | "start_product": [start_product, start_product_handle], 342 | "statistics_command": [statistics_command_uuid, statistics_command_handle], 343 | "statistics_data": [statistics_data_uuid, statistics_data_handle], 344 | "uart_tx": [uart_tx_uuid, uart_tx_hnd], 345 | "uart_rx": [uart_rx_uuid, uart_rx_hnd] 346 | } 347 | 348 | 349 | child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, characteristics) 350 | print(getUID_stats()) 351 | if int(getUID_stats()) < CURRENT_STATISTICS[0]: 352 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 353 | print("updating the value in the table") 354 | c = db.cursor() 355 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 356 | db.commit() 357 | c.close() 358 | 359 | # child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 360 | # # Connect to the device. 361 | # print("Connecting to ") 362 | # print(DEVICE) 363 | # child.sendline("connect") 364 | # print(child.child_fd) 365 | # #try: 366 | # child.expect("Connection successful", timeout=5) 367 | # print("Connected!") 368 | # logging.info("Connected") 369 | 370 | # child.sendline("char-write-cmd " + characteristics["statistics_command"][1] + " " + all_statistics) 371 | # time.sleep(1) 372 | # child.sendline("char-read-hnd " + characteristics["statistics_data"][1]) 373 | # child.expect(": ") 374 | # data = child.readline() 375 | # #print(b"Statistics data: " + data) 376 | # # decode the statistics data 377 | # data = [int(x, 16) for x in data.split()] 378 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 379 | # # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 380 | # decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 381 | # # for every hex string in decoded list, convert to int 382 | # decoded = [int(x, 16) for x in decoded] 383 | # CURRENT_STATISTICS = decoded 384 | # print("Statistics data: ", CURRENT_STATISTICS) 385 | # Capture SIGINT for cleanup when the script is aborted 386 | def end_read(signal,frame): 387 | global continue_reading 388 | beep(2) 389 | print("Ctrl+C captured, ending read.") 390 | lcd.lcd_display_string(" Program over ", 1) 391 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 392 | lcd.lcd_display_string(" Unlocking ", 3) 393 | lcd.lcd_display_string(" -----> :( <----- ", 4) 394 | time.sleep(3) 395 | lcd.lcd_clear() 396 | lcd.lcd_display_string(" Program not ", 1) 397 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 398 | lcd.lcd_display_string(" Running ", 3) 399 | lcd.lcd_display_string(" -----> :( <----- ", 4) 400 | continue_reading = False 401 | GPIO.cleanup() 402 | os.system("cd /home/pi/Jura-Python-BT/src/ && sudo ./backupMySQLdb.sh") 403 | print("Bakcup done!") 404 | logging.info("Backup done") 405 | logging.info("Program ended") 406 | child.sendline("char-write-req " + characteristics["statistics_command"][1] + " " + all_statistics) 407 | time.sleep(1.5) 408 | child.sendline("char-read-hnd " + characteristics["statistics_data"][1]) 409 | child.expect(": ") 410 | data = child.readline() 411 | #print(b"Statistics data: " + data) 412 | # decode the statistics data 413 | data = [int(x, 16) for x in data.split()] 414 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 415 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 416 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 417 | # for every hex string in decoded list, convert to int 418 | decoded = [int(x, 16) for x in decoded] 419 | CURRENT_STATISTICS = decoded 420 | print("Current Statistics: " + str(decoded)) 421 | if int(getUID_stats()) < CURRENT_STATISTICS[0]: 422 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 423 | print("updating the value in the table from", getUID_stats(), "to", CURRENT_STATISTICS[0]) 424 | c = db.cursor() 425 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 426 | db.commit() 427 | c.close() 428 | db.close() 429 | _ = lockUnlockMachine(unlock_code, "locked") 430 | exit(0) 431 | 432 | def read_statistics(): 433 | try: 434 | product_made = False 435 | # write the all statistics command to statistics_command_handle 436 | child.sendline("char-write-cmd " + statistics_command_handle + " " + all_statistics) 437 | #print("All statistics sent!") 438 | time.sleep(1.5) 439 | # read the data from product progress handle 440 | # read the statistics data from statistics_data_handle 441 | child.sendline("char-read-hnd " + statistics_data_handle) 442 | child.expect(": ") 443 | data = child.readline() 444 | #print(b"Statistics data: " + data) 445 | # decode the statistics data 446 | data = [int(x, 16) for x in data.split()] 447 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 448 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 449 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 450 | # for every hex string in decoded list, convert to int 451 | decoded = [int(x, 16) for x in decoded] 452 | #print("Statistics data: ", decoded) 453 | # change the values that are different from the previous ones when comparing with CURRENT_STATISTICS 454 | if decoded[0] != CURRENT_STATISTICS[0]: 455 | #lock_status = lockUnlockMachine(locking_code, lock_status) 456 | print("Overall products increased by 1") 457 | CURRENT_STATISTICS[0] = decoded[0] 458 | 459 | for i in range(1, len(decoded)): 460 | if decoded[i] != CURRENT_STATISTICS[i]: 461 | print("A " + PRODUCTS[i] + " was made!") 462 | product_made = PRODUCTS[i] 463 | print("Value changed to " + str(decoded[i]) + " from " + str(CURRENT_STATISTICS[i])) 464 | CURRENT_STATISTICS[i] = decoded[i] 465 | if int(getUID_stats()) < CURRENT_STATISTICS[0]: 466 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 467 | print("updating the value in the table from", getUID_stats(), "to", CURRENT_STATISTICS[0]) 468 | c = db.cursor() 469 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 470 | db.commit() 471 | c.close() 472 | return product_made 473 | except Exception as e: 474 | print("Error reading statistics!") 475 | logging.error("Error reading statistics " + str(e)) 476 | return False 477 | 478 | # if no arguments assume that emegeny unlock is 0 479 | if len(sys.argv) == 1: 480 | emergency_unlock = 0 481 | else: 482 | emergency_unlock = int(sys.argv[1]) 483 | if emergency_unlock == 0: 484 | lock_status = "unlocked" 485 | lock_status = lockUnlockMachine(locking_code, lock_status) 486 | print("Machine locked!") 487 | else: 488 | lock_status = "locked" 489 | lock_status = lockUnlockMachine(unlock_code, lock_status) 490 | print("Machine unlocked!") 491 | exit() 492 | 493 | # Hook the SIGINT 494 | signal.signal(signal.SIGINT, end_read) 495 | 496 | 497 | # Init buzzer 498 | setupBuzzer(BuzzerPin) 499 | 500 | while True: 501 | try: 502 | # Init Serial 503 | port = serial.Serial("/dev/serial0", baudrate = 9600, timeout = 1.0) 504 | print("Serial connection initialized") 505 | break 506 | except: 507 | print("Serial connection failed, ending program") 508 | logging.error("Serial connection failed, ending program") 509 | end_read(0,0) 510 | 511 | lcd.lcd_display_string(" Machine Locked ", 1) 512 | lcd.lcd_display_string(" ~~~~~~~~ ", 2) 513 | lcd.lcd_display_string(" Put tag to unlock ", 3) 514 | lcd.lcd_display_string(" -----> :) <----- ", 4) 515 | time.sleep(1) 516 | beep(0.01) 517 | 518 | port.flushInput() 519 | 520 | buttonPress = False 521 | continue_reading = True 522 | 523 | # Welcome message 524 | print("Welcome to the BlackBetty 2") 525 | print("Press Ctrl-C to stop.") 526 | 527 | lastSeen = "" 528 | counter = 0 529 | disp_init = 1 530 | payment_to_date = 1 531 | client_to_pay = "" 532 | admin_locked = 0 533 | admin_prod = 0 534 | total_prod = 0 535 | payed_prod = 0 536 | #number = 0 537 | 538 | time.sleep(1) 539 | while continue_reading: 540 | #time.sleep(0.2) 541 | current_time = int(time.time() - initial_time) 542 | #print("Current time: " + str(current_time)) 543 | # get hour of the day 544 | hour = int(time.strftime("%H")) 545 | minute = int(time.strftime("%M")) 546 | # print ("Hour: " + str(hour)) 547 | # if current time is a multiple of 300 seconds then reset the bluetooth connection 548 | 549 | # if current_time % 30 == 0 and counter == 0: 550 | # print("Resetting BT connection") 551 | # beep(0.01) 552 | # child.close() 553 | # print(child.child_fd) 554 | # print("Run gatttool...") 555 | # child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 556 | # print(child.child_fd) 557 | # # Connect to the device. 558 | # print("Connecting to ") 559 | # print(DEVICE) 560 | # child.sendline("connect") 561 | # #try: 562 | # child.expect("Connection successful", timeout=5) 563 | # print("Connected!") 564 | # #child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS = setup(DEVICE, characteristics) 565 | # #lock_status = lockUnlockMachine(locking_code, lock_status) 566 | if (hour == 1 or hour == 5) and minute == 30 : 567 | # reboot pi 568 | child.sendline("char-write-req " + characteristics["statistics_command"][1] + " " + all_statistics) 569 | time.sleep(1.5) 570 | child.sendline("char-read-hnd " + characteristics["statistics_data"][1]) 571 | child.expect(": ") 572 | data = child.readline() 573 | #print(b"Statistics data: " + data) 574 | # decode the statistics data 575 | data = [int(x, 16) for x in data.split()] 576 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 577 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 578 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 579 | # for every hex string in decoded list, convert to int 580 | decoded = [int(x, 16) for x in decoded] 581 | CURRENT_STATISTICS = decoded 582 | print("Current Statistics: " + str(decoded)) 583 | if int(getUID_stats()) < CURRENT_STATISTICS[0]: 584 | # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 585 | print("updating the value in the table") 586 | c = db.cursor() 587 | c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 588 | db.commit() 589 | c.close() 590 | print("Rebooting pi") 591 | lcd.lcd_clear() 592 | os.system("cd /home/pi/Jura-Python-BT/src/ && sudo ./backupMySQLdb.sh") 593 | os.system("sudo reboot") 594 | logging.info("Rebooting pi") 595 | 596 | # if current_time % 20 == 0: 597 | # _ = read_statistics() 598 | # print(CURRENT_STATISTICS) 599 | # if int(getUID_stats()) < CURRENT_STATISTICS[0]: 600 | # # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 601 | # print("updating the value in the table from", getUID_stats(), "to", CURRENT_STATISTICS[0]) 602 | # c = db.cursor() 603 | # c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 604 | # db.commit() 605 | # c.close() 606 | 607 | # if time elapsed is a multiple of 15 seconds then send keep alive code 608 | if current_time % 5 == 0: 609 | #print("Sending keep alive code") 610 | child.sendline("char-write-req " + heartbeat_handle + " " + keep_alive_code) 611 | #print(child.child_fd) 612 | #print(child.pid) 613 | data = child.readline() 614 | data += child.readline() 615 | #print(b"Keep alive: " + data) 616 | # match substring Disconnected in data 617 | # reboot pi 618 | # child.sendline("char-write-cmd " + characteristics["statistics_command"][1] + " " + all_statistics) 619 | # time.sleep(1.2) 620 | # child.sendline("char-read-hnd " + characteristics["statistics_data"][1]) 621 | # child.expect(": ") 622 | # data = child.readline() 623 | # #print(b"Statistics data: " + data) 624 | # # decode the statistics data 625 | # data = [int(x, 16) for x in data.split()] 626 | # decoded = BtEncoder.encDecBytes(data, KEY_DEC) 627 | # # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 628 | # decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 629 | # # for every hex string in decoded list, convert to int 630 | # decoded = [int(x, 16) for x in decoded] 631 | # CURRENT_STATISTICS = decoded 632 | # print("Current Statistics: " + str(decoded)) 633 | # if int(getUID_stats()) < CURRENT_STATISTICS[0]: 634 | # # change the UID in the database Benutzerverwaltung to CURRENT_STATISTICS[0] where id = 1000 635 | # print("updating the value in the table") 636 | # c = db.cursor() 637 | # c.execute("UPDATE Benutzerverwaltung SET UID = " + str(CURRENT_STATISTICS[0]) + " WHERE id = 1000;") 638 | # db.commit() 639 | # c.close() 640 | if "Disconnected" in str(data): 641 | # run setup 642 | print("Disconnected here") 643 | logging.debug("Disconnected here") 644 | child.close() 645 | while True: 646 | try: 647 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 648 | # Connect to the device. 649 | print("Connecting to ") 650 | print(DEVICE) 651 | child.sendline("connect") 652 | #print(child.child_fd) 653 | #try: 654 | child.expect("Connection successful", timeout=5) 655 | print("Connected!") 656 | logging.info("Connected") 657 | lock_status = "unlocked" 658 | lock_status = lockUnlockMachine(locking_code, lock_status) 659 | print("Machine locked!") 660 | break 661 | except: 662 | continue 663 | 664 | if disp_init == 1: 665 | lcd.lcd_clear() 666 | lcd.lcd_display_string(" Put Tag and then ", 1) 667 | lcd.lcd_display_string(" Choose Product ", 2) 668 | lcd.lcd_display_string(" In machine ", 3) 669 | lcd.lcd_display_string(" :) ", 4) 670 | disp_init = 0 671 | time.sleep(0.5) 672 | 673 | uid_str = scanCard() 674 | GPIO.cleanup() 675 | 676 | if admin_locked == 1 and current_time % 10 == 0: 677 | prod = read_statistics() 678 | if prod != False: 679 | admin_prod += 1 680 | total_prod += 1 681 | set_buylist("01", prod) 682 | print("Admin made a product that was not payed for") 683 | 684 | if uid_str != "0": 685 | print("last seen: ", lastSeen) 686 | product_made = False 687 | if admin_locked == 0: 688 | lcd.lcd_clear() 689 | 690 | if (uid_str == mastercard1) or (uid_str == mastercard2): 691 | if lock_status == "locked": 692 | lock_status = lockUnlockMachine(unlock_code, lock_status) 693 | print("Machine unlocked permanently!") 694 | 695 | lcd.lcd_display_string(" Admin Unlock ", 2) 696 | lcd.lcd_display_string(" -----> :) <----- ", 3) 697 | admin_locked = 1 698 | 699 | 700 | 701 | elif lock_status == "unlocked": 702 | lock_status = lockUnlockMachine(locking_code, lock_status) 703 | print("Machine locked!") 704 | lcd.lcd_display_string(" Admin Lock ", 2) 705 | lcd.lcd_display_string(" -----> :( <----- ", 3) 706 | disp_init = 1 707 | admin_locked = 0 708 | #number += 2 709 | #print("Number: ", number) 710 | time.sleep(1) 711 | 712 | elif admin_locked == 1: 713 | pass 714 | else: 715 | try: 716 | if lastSeen == "": 717 | lastSeen = uid_str 718 | value = get_value(uid_str) 719 | if value < 0: 720 | # alert user in lcd that they need to charge balance 721 | lcd.lcd_clear() 722 | lcd.lcd_display_string(" Your balance is ", 1) 723 | lcd.lcd_display_string(" < 0! ", 2) 724 | lcd.lcd_display_string(" Please charge it ", 3) 725 | lcd.lcd_display_string(" Locking Machine ", 4) 726 | beep(0.5) 727 | time.sleep(0.5) 728 | beep(0.5) 729 | time.sleep(0.5) 730 | beep(0.5) 731 | time.sleep(2) 732 | lock_status = lockUnlockMachine(locking_code, lock_status) 733 | print("User balance is < 0") 734 | lastSeen = "" 735 | client_to_pay = "" 736 | disp_init = 1 737 | 738 | 739 | else: 740 | value_str = str("Balance: " + str('%.2f' % value) + " EUR") 741 | lastName = get_name(uid_str) 742 | preName = get_vorname(uid_str) 743 | welStr = str("Hello " + preName) 744 | msgStr3 = str("Hold for 2s please ") 745 | msgStr4 = str("Chip below ") 746 | 747 | lcd.lcd_display_string(welStr, 1) 748 | lcd.lcd_display_string(value_str, 2) 749 | lcd.lcd_display_string(msgStr3, 3) 750 | lcd.lcd_display_string(msgStr4, 4) 751 | time.sleep(1.5) 752 | 753 | elif lastSeen == uid_str and product_made == False: 754 | beep(0.1) 755 | print("Opening coffe machine for choice") 756 | lcd.lcd_clear() 757 | lcd.lcd_display_string(" Select a ", 1) 758 | lcd.lcd_display_string(" product ", 2) 759 | lcd.lcd_display_string(" in the machine ", 3) 760 | lcd.lcd_display_string(" -----> :) <----- ", 4) 761 | lock_status = lockUnlockMachine(unlock_code, lock_status) 762 | intial_time_2 = time.time() 763 | chosen = 0 764 | started = 0 765 | over = 0 766 | while over == 0: 767 | #print("Waiting for product to be made") 768 | time_total = int(time.time() - intial_time_2) 769 | if time_total > 30 and started == 0: 770 | #print("No product made in 25 seconds, locking machine") 771 | lock_status = lockUnlockMachine(locking_code, lock_status) 772 | lcd.lcd_display_string(" No Product ", 1) 773 | lcd.lcd_display_string(" selected ", 2) 774 | lcd.lcd_display_string(" Locking Machine ", 3) 775 | lcd.lcd_display_string(" -----> :( <----- ", 4) 776 | time.sleep(1.5) 777 | #lcd.lcd_clear() 778 | lastSeen = "" 779 | disp_init = 1 780 | chosen = 0 781 | break 782 | if int(time.time() - intial_time_2) % 2 == 0: 783 | child.sendline("char-write-req " + heartbeat_handle + " " + keep_alive_code) 784 | try: 785 | # read the data from product progress handle 786 | child.sendline("char-read-hnd " + product_progress_handle) 787 | try: 788 | child.expect(": ") 789 | except: 790 | pass 791 | data = child.readline() 792 | data2 = [int(x, 16) for x in data.split()] 793 | #print("Encoded: ", data) 794 | data = [x for x in data.split()] 795 | decoded = BtEncoder.encDecBytes(data2, KEY_DEC) 796 | as_hex = ["%02x" % d for d in decoded] 797 | #print("Decoded: ", as_hex) 798 | #print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 799 | if as_hex[1] not in ["3e", "00"] and time_total > 5 and started == 0: 800 | #lock_status = lockUnlockMachine(locking_code, lock_status) 801 | beep(0.5) 802 | print("PRODUCT MADE") 803 | client_to_pay = uid_str 804 | lcd.lcd_clear() 805 | preName = get_vorname(uid_str) 806 | print("as_hex[2]: ", as_hex[2]) 807 | product_made = in_machine_products[int(as_hex[2], 16)] 808 | price_product = priceCoffee[product_made] 809 | lcd.lcd_display_string(" " + product_made + " detected ", 1) 810 | lcd.lcd_display_string(" Will charge " + str(price_product), 2) 811 | lcd.lcd_display_string(" " + preName + " ", 3) 812 | lcd.lcd_display_string(" -----> :) <----- ", 4) 813 | time.sleep(1.5) 814 | print("Product made was: ", product_made) 815 | started = 1 816 | disp_init = 1 817 | chosen = 1 818 | lock_status = lockUnlockMachine(locking_code, lock_status) 819 | lastSeen = "" 820 | elif started == 1 and as_hex[1] in ["3e", "00"]: 821 | over = 1 822 | print("Checking costumer payment") 823 | value_new = 0 824 | if chosen == 1: 825 | print("Setting value for uid: " + client_to_pay + " Name: " + preName) 826 | value_new = value - priceCoffee[product_made] 827 | if value_new > 0 and chosen == 1: 828 | print("PAYING") 829 | # beep(0.05) 830 | # time.sleep(0.1) 831 | # beep(0.05) 832 | set_value(client_to_pay, value_new) 833 | payed_prod += 1 834 | total_prod += 1 835 | payment_to_date = 1 836 | preName = get_vorname(client_to_pay) 837 | #beep(1) 838 | set_buylist(client_to_pay, product_made) 839 | lcd.lcd_clear() 840 | msgStr1 = str(product_made + " was made!") 841 | msgStr2 = str(" Happy betty :) ") 842 | msgStr3 = str(" " + preName + " ") 843 | value_str = str("Balance: " + str('%.2f' % value_new) + " EUR") 844 | lcd.lcd_display_string(msgStr1, 1) 845 | lcd.lcd_display_string(msgStr2, 2) 846 | lcd.lcd_display_string(msgStr3, 3) 847 | lcd.lcd_display_string(value_str, 4) 848 | time.sleep(2) 849 | client_to_pay = "" 850 | counter = 0 851 | disp_init = 1 852 | product_made = False 853 | elif value_new < 0 and chosen == 1: 854 | print("PAYING") 855 | lcd.lcd_clear() 856 | set_value(client_to_pay, value_new) 857 | payed_prod += 1 858 | total_prod += 1 859 | payment_to_date = 1 860 | #beep(1) 861 | set_buylist(client_to_pay, product_made) 862 | msgStr1 = str(product_made + " was made!") 863 | msgStr2 = str(" Thank you!") 864 | msgStr3 = str("Balance < 0!") 865 | value_str = str("Balance: " + str('%.2f' % value_new) + " EUR") 866 | lcd.lcd_display_string(msgStr1, 1) 867 | lcd.lcd_display_string(msgStr2, 2) 868 | lcd.lcd_display_string(msgStr2, 3) 869 | lcd.lcd_display_string(value_str, 4) 870 | time.sleep(2) 871 | client_to_pay = "" 872 | counter = 0 873 | disp_init = 1 874 | product_made = False 875 | lcd.lcd_clear() 876 | preName = get_vorname(uid_str) 877 | lcd.lcd_display_string(" Product Ended ", 1) 878 | lcd.lcd_display_string(" charged ", 2) 879 | lcd.lcd_display_string(" " + preName + " ", 3) 880 | lcd.lcd_display_string(" -----> :) <----- ", 4) 881 | client_to_pay = "" 882 | lastSeen = "" 883 | time.sleep(2) 884 | else: 885 | continue 886 | 887 | except Exception as e: 888 | print("Error: " + str(e)) 889 | logging.error("Error: " + str(e)) 890 | try: 891 | #if str(b'\x1b[0mDisconnected') in str(e): 892 | # run setup 893 | logging.debug("Disconnected") 894 | child.close() 895 | while True: 896 | try: 897 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 898 | # Connect to the device. 899 | print("Connecting to ") 900 | print(DEVICE) 901 | child.sendline("connect") 902 | #print(child.child_fd) 903 | #try: 904 | child.expect("Connection successful", timeout=5) 905 | print("Connected!") 906 | logging.info("Connected") 907 | lock_status = "unlocked" 908 | lock_status = lockUnlockMachine(locking_code, lock_status) 909 | print("Machine locked!") 910 | break 911 | except: 912 | continue 913 | except: 914 | pass 915 | continue 916 | except Exception as e: 917 | print("The error raised is: ", e) 918 | logging.error("The error raised is: " + str(e)) 919 | try: 920 | #if str(b'\x1b[0mDisconnected') in str(e): 921 | logging.debug("Disconnected") 922 | child.close() 923 | while True: 924 | try: 925 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 926 | # Connect to the device. 927 | print("Connecting to ") 928 | print(DEVICE) 929 | child.sendline("connect") 930 | #print(child.child_fd) 931 | #try: 932 | child.expect("Connection successful", timeout=5) 933 | print("Connected!") 934 | logging.info("Connected") 935 | lock_status = "unlocked" 936 | lock_status = lockUnlockMachine(locking_code, lock_status) 937 | print("Machine locked!") 938 | break 939 | except: 940 | continue 941 | except: 942 | pass 943 | continue 944 | else: 945 | if lastSeen != "": 946 | counter = counter + 1 947 | #print(counter) 948 | if counter >= 10: 949 | lcd.lcd_clear() 950 | lastSeen = "" 951 | counter = 0 952 | beep(0.2) 953 | time.sleep(0.1) 954 | beep(0.2) 955 | 956 | msgStr1 = str("Chip removed?") 957 | msgStr3 = str("Product not chosen") 958 | msgStr4 = str("bye! :(") 959 | lcd.lcd_display_string(msgStr1, 1) 960 | lcd.lcd_display_string(msgStr3, 3) 961 | lcd.lcd_display_string(msgStr4, 4) 962 | sleepTimer(4) 963 | disp_init = 1 964 | client_to_pay = "" 965 | lastSeen = "" 966 | lock_status = lockUnlockMachine(locking_code, lock_status) 967 | sleepTimer(0.1) 968 | -------------------------------------------------------------------------------- /src/setup.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | import time 3 | from bt_encoder import BtEncoder 4 | from jura_encoder import JuraEncoder 5 | import logging 6 | import os 7 | logging.basicConfig( 8 | filename='blue.log', 9 | level=logging.DEBUG, 10 | format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', 11 | datefmt='%Y-%m-%d %H:%M:%S', 12 | ) 13 | ## 14 | # @file setup.py 15 | # 16 | # @brief In this file the connection to the Jura coffee machine is established. 17 | # 18 | # 19 | # @section author_blue Author(s) 20 | # - Created by Francisco Fonseca on 30/04/2023. 21 | # 22 | # 23 | 24 | ## The instance of the BtEncoder class 25 | BtEncoder = BtEncoder() 26 | ## The instance of the JuraEncoder class 27 | JuraEncoder = JuraEncoder() 28 | def setup(DEVICE, characteristics): 29 | '''! 30 | @brief This function is used to setup the connection to the Jura coffee machine. 31 | @param DEVICE The MAC address of the Jura coffee machine. 32 | @param characteristics A dictionary containing the characteristics of the Jura coffee machine. 33 | @return child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS 34 | ''' 35 | current_time = time.time() 36 | # send command gatttool -b ED:95:43:60:13:92 -I -t random to system with no output using pexpect 37 | # then send command connect to gatttool 38 | # then send command char-write-cmd 0x0011 0e f7 2a to gatttool 39 | # gatttool -b ED:95:43:60:13:92 -I -t random 40 | # connect 41 | # char-write-req 0x0011 0e f7 2a 42 | while True: 43 | try: 44 | time.sleep(0.1) 45 | if time.time() - current_time > 180: 46 | logging.debug("Exiting and rebooting") 47 | os.system("sudo reboot") 48 | break 49 | print("Run gatttool...") 50 | child = pexpect.spawn("gatttool -b " + DEVICE + " -I -t random") 51 | # Connect to the device. 52 | print("Connecting to ") 53 | print(DEVICE) 54 | child.sendline("connect") 55 | #try: 56 | child.expect("Connection successful", timeout=5) 57 | print("Connected!") 58 | # print the time the connection was made 59 | initial_time = time.time() 60 | print("Initial time: " + str(initial_time)) 61 | #time.sleep(5) 62 | # get current key 63 | child.sendline("char-read-hnd " + characteristics["machine_status"][1]) 64 | child.expect(": ", timeout=5) 65 | data = child.readline().decode() 66 | print(data) 67 | KEY_DEC = BtEncoder.bruteforce_key(data) 68 | print("Key: ", KEY_DEC) 69 | data = [int(x, 16) for x in data.split()] 70 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 71 | print("\nDecoded data as HEX: " + " ".join(["%02x" % d for d in decoded])) 72 | keep_alive_code = KEY_DEC + " 7F 80" 73 | locking_code = KEY_DEC + " 01" 74 | unlock_code = KEY_DEC + " 00" 75 | all_statistics = KEY_DEC + " 00 10 FF FF" 76 | # encode keep alive code 77 | keep_alive_code = BtEncoder.encDecBytes([int(x, 16) for x in keep_alive_code.split()], KEY_DEC) 78 | keep_alive_code = "".join(["%02x" % d for d in keep_alive_code]) 79 | locking_code = BtEncoder.encDecBytes([int(x, 16) for x in locking_code.split()], KEY_DEC) 80 | locking_code = "".join(["%02x" % d for d in locking_code]) 81 | unlock_code = BtEncoder.encDecBytes([int(x, 16) for x in unlock_code.split()], KEY_DEC) 82 | unlock_code = "".join(["%02x" % d for d in unlock_code]) 83 | all_statistics = BtEncoder.encDecBytes([int(x, 16) for x in all_statistics.split()], KEY_DEC) 84 | all_statistics = "".join(["%02x" % d for d in all_statistics]) 85 | print("Keep alive code: " + keep_alive_code) 86 | print("Locking code: " + locking_code) 87 | print("Unlock code: " + unlock_code) 88 | print("All statistics: " + all_statistics) 89 | child.sendline("char-write-req " + characteristics["statistics_command"][1] + " " + all_statistics) 90 | time.sleep(1.5) 91 | child.sendline("char-read-hnd " + characteristics["statistics_data"][1]) 92 | child.expect(": ") 93 | data = child.readline() 94 | #print(b"Statistics data: " + data) 95 | # decode the statistics data 96 | data = [int(x, 16) for x in data.split()] 97 | decoded = BtEncoder.encDecBytes(data, KEY_DEC) 98 | # join decoded data to a list for every three bytes example: [001200, 000000, 000098] 99 | decoded = ["".join(["%02x" % d for d in decoded[i:i+3]]) for i in range(0, len(decoded), 3)] 100 | # for every hex string in decoded list, convert to int 101 | decoded = [int(x, 16) for x in decoded] 102 | CURRENT_STATISTICS = decoded 103 | print("Current Statistics: " + str(decoded)) 104 | return child, keep_alive_code, locking_code, unlock_code, KEY_DEC, all_statistics, initial_time, CURRENT_STATISTICS 105 | except: 106 | print("Failed to connect to device. Retrying...") 107 | logging.debug("Failed to connect to device at " + str(time.time()) + " Retrying...") 108 | continue 109 | --------------------------------------------------------------------------------