From d79821c61254b6978b225435c3cb2184b0cce985 Mon Sep 17 00:00:00 2001 From: Darko Lukic Date: Wed, 14 Mar 2018 00:20:46 +0100 Subject: [PATCH] Add sensor data and WiFi support in REST API --- frontend/README.md | 8 + frontend/app.py | 13 ++ frontend/config.py | 5 + frontend/params.py | 47 +++++ frontend/requirements.txt | 1 + frontend/wifi.py | 166 ++++++++++++++++++ install_files/CosmicPi-UI.service | 4 +- .../CosmicPi-database_cleaner.service | 4 +- install_files/CosmicPi-detector.service | 4 +- install_files/CosmicPi-mqtt.service | 4 +- installparttwo.sh | 0 11 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 frontend/README.md create mode 100644 frontend/app.py create mode 100644 frontend/config.py create mode 100644 frontend/params.py create mode 100644 frontend/requirements.txt create mode 100644 frontend/wifi.py mode change 100644 => 100755 installparttwo.sh diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..b3b5c8a --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,8 @@ +# CosmicPi REST API + +## Development +Make sure that CosmicPi UI service does not run (`sudo systemctl stop CosmicPi-UI`), +navigate to `/frontend` and run: +``` +FLASK_DEBUG=1 FLASK_APP=${PWD}/app.py python -m flask run --host=0.0.0.0 +``` \ No newline at end of file diff --git a/frontend/app.py b/frontend/app.py new file mode 100644 index 0000000..6d7c927 --- /dev/null +++ b/frontend/app.py @@ -0,0 +1,13 @@ +from flask import Flask, request +from flask_restful import Api +from .params import Params +from .wifi import Wifi + +app = Flask(__name__) +api = Api(app) + +api.add_resource(Params, '/params') +api.add_resource(Wifi, '/wifi') + +if __name__ == '__main__': + app.run(debug=True) diff --git a/frontend/config.py b/frontend/config.py new file mode 100644 index 0000000..74c13ae --- /dev/null +++ b/frontend/config.py @@ -0,0 +1,5 @@ +import configparser + + +Config = configparser.ConfigParser() +Config.read('../config/CosmicPi.config') \ No newline at end of file diff --git a/frontend/params.py b/frontend/params.py new file mode 100644 index 0000000..f34d6db --- /dev/null +++ b/frontend/params.py @@ -0,0 +1,47 @@ +from flask_restful import Resource +from .config import Config +import sqlite3 + + +SQLITE_LOCATION = Config.get("Storage", "sqlite_location") + + +class Params(Resource): + def get(self): + params = {'HardwareSerial': Params._get_serial()} + + # Get the latest datapoint + conn = sqlite3.connect(SQLITE_LOCATION, timeout=60.0) + cursor = conn.cursor() + cursor.execute("SELECT * FROM Events ORDER BY UTCUnixTime DESC, SubSeconds DESC;") + latest_datapoint = cursor.fetchone() + + # Get column names + cursor.execute("PRAGMA table_info(Events);") + col_names = cursor.fetchall() + conn.close() + + # Extract data + for i in range(0, len(col_names)): + # Field name + f_name = col_names[i][1] + + # Fill in values + params[f_name] = latest_datapoint[i] + return params + + @staticmethod + def _get_serial(): + """ + Extract serial from cpuinfo file + """ + cpu_serial = "0000000000000000" + try: + f = open('/proc/cpuinfo', 'r') + for line in f: + if line[0:6] == 'Serial': + cpu_serial = line[10:26] + f.close() + except: + cpu_serial = "ERROR000000000" + return cpu_serial diff --git a/frontend/requirements.txt b/frontend/requirements.txt new file mode 100644 index 0000000..aafcfc6 --- /dev/null +++ b/frontend/requirements.txt @@ -0,0 +1 @@ +flask_restful \ No newline at end of file diff --git a/frontend/wifi.py b/frontend/wifi.py new file mode 100644 index 0000000..73d839a --- /dev/null +++ b/frontend/wifi.py @@ -0,0 +1,166 @@ +from flask import request +from flask_restful import Resource +from .config import Config +import subprocess +import re +import time +import thread +import urllib2 + + +DEFAULT_WIFI_NAME = Config.get("Default WiFi", "name") +DEFAULT_WIFI_PASS = Config.get("Default WiFi", "password") +WPA_SUPPLICANT_LOCATION = Config.get("MISC", "wpa_supplicant_location") + + +class Wifi(Resource): + def get(self): + # Get current network + connected_network_name_response = '' + try: + connected_network_name_response = subprocess.check_output(['sudo', 'iwgetid']) + except subprocess.CalledProcessError as e: + err_text = 'ERROR get connected network: %s' % str(e) + connected_network_name_response = err_text + print(err_text) + connected_network_name_str = re.findall('\"(.*?)\"', connected_network_name_response) + + if len(connected_network_name_str) < 1: + connected_network_name_str = '' + else: + connected_network_name_str = connected_network_name_str[0] + + # Available networks + wifi_network_list = [connected_network_name_str] + available_networks_response = '' + try: + available_networks_response = subprocess.check_output(['sudo', 'iw', 'dev', 'wlan0', 'scan']) + except subprocess.CalledProcessError as e: + err_text = 'ERROR get list of networks: %s' % str(e) + print(err_text) + available_networks_response = err_text + available_networks_lines = available_networks_response.split('\n') + for availableNetworksLine in available_networks_lines: + if 'SSID' in availableNetworksLine: + essid = availableNetworksLine.replace('SSID:', '').strip() + wifi_network_list.append(essid) + wifi_network_list = filter(lambda x: x != '', wifi_network_list) + + # Print everything + return { + 'current': connected_network_name_str, + 'available': wifi_network_list, + } + + def put(self): + ssid = request.form['ssid'] + password = request.form['password'] + + thread.start_new_thread(connect_to_wifi, (ssid, password)) + + msg = 'The CosmicPi will now try to connect to the WiFi "{}". ' \ + "If no internet connection is found or the connection was not " \ + "successful the CosmiPi will recreate the WiFi hotspot. Please " \ + "wait at least two minutes.".format(ssid) + return { + 'message': msg + } + + +def fall_back_to_ap(): + # empty the wpa supplicant to it's default + wpa_supplicant_string = "country=GB\n" # Todo: Check, that this string in front is still correct! + wpa_supplicant_string += "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n" + wpa_supplicant_string += "update_config=1\n" + wpa_supplicant_string += "\nnetwork={\n" + net = (DEFAULT_WIFI_NAME, DEFAULT_WIFI_PASS) + wpa_supplicant_string += '\tssid="{}"\n'.format(net[0]) + # check if we need a password + if str(net[1]) == "": + wpa_supplicant_string += '\tpsk="{}"\n'.format(net[1]) + wpa_supplicant_string += "}\n" + with open(WPA_SUPPLICANT_LOCATION, 'w') as file: + file.write(wpa_supplicant_string) + # configure controler to accept the new configuration + try: + import fcntl + time.sleep(2) + subprocess.call("wpa_cli -i wlan0 reconfigure", shell=True) + time.sleep(2) + except ImportError: + print("This OS is not linux enough to handle a hotspot in this way") + + # restart the hotspot + try: + import fcntl + subprocess.call("systemctl start dnsmasq", + shell=True) # well, here we actually care if the hotspot starts, but oh well... + time.sleep(2) + subprocess.call("ifconfig wlan0 down", shell=True) + time.sleep(2) + subprocess.call("ifconfig wlan0 up", shell=True) + except ImportError: + print("This OS is not linux enough to handle a hotspot in this way") + + +def connect_to_wifi(name, pw): + # build the string for the WPA supplicant + wpa_supplicant_string = "country=GB\n" # Todo: Check, that this string in front is still correct! + wpa_supplicant_string += "ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n" + wpa_supplicant_string += "update_config=1\n" + networks = [(name, pw), (DEFAULT_WIFI_NAME, DEFAULT_WIFI_PASS)] + for net in networks: + wpa_supplicant_string += "\nnetwork={\n" + wpa_supplicant_string += '\tssid="{}"\n'.format(net[0]) + # check if we need a password + if not str(net[1]) == "": + wpa_supplicant_string += '\tpsk="{}"\n'.format(net[1]) + wpa_supplicant_string += "}\n" + + # deactivate htospot and turn the WiFi back on + try: + import fcntl + subprocess.call("systemctl stop dnsmasq", shell=True) # we very much don't care if this fails + time.sleep(2) + subprocess.call("ifconfig wlan0 down", shell=True) + time.sleep(2) + subprocess.call("ifconfig wlan0 up", shell=True) + except ImportError: + print("This OS is not linux enough to handle a hotspot in this way") + # write wpa_supplicant string to the file + with open(WPA_SUPPLICANT_LOCATION, 'w') as file: + file.write(wpa_supplicant_string) + # configure controler to accept the new configuration + try: + import fcntl + time.sleep(2) + subprocess.call("wpa_cli -i wlan0 reconfigure", shell=True) + except ImportError: + print("This OS is not linux enough to handle a hotspot in this way") + + # wait for an internet connection (max 2 min) + start_time = time.time() + have_internet = False + while (have_internet == False) and ((start_time + 120) > time.time()): + have_internet = internet_on() + + # if we have no internet, restart the hotspot, otherwise we are done for now + if have_internet: + print("Successfully connected to the internet (yeah)") + # wait a bit before we send the mail + time.sleep(5) + # ToDo: This would be a good point to send a mail or something similar to the user. + # Just to inform them where their cosmicPi is and what's it doing + return + else: + print("No internet connection here, falling back to hotspot!") + fall_back_to_ap() + return + + +def internet_on(): + try: + urllib2.urlopen('http://heise.de', timeout=2) + return True + except urllib2.URLError: + return False diff --git a/install_files/CosmicPi-UI.service b/install_files/CosmicPi-UI.service index 0fe9b1c..ecc2295 100644 --- a/install_files/CosmicPi-UI.service +++ b/install_files/CosmicPi-UI.service @@ -3,9 +3,9 @@ Description=Software for the CosmicPi UI [Service] # Flask Env Vars -Environment=FLASK_APP=PATH_TO_EXECUTABLE/frontend/web_ui.py +Environment=FLASK_APP=/home/pi/cosmicpi-rpi_V1.5/frontend/web_ui.py ExecStart=/usr/bin/python -m flask run --host=0.0.0.0 --port=80 -WorkingDirectory=PATH_TO_EXECUTABLE/frontend/ +WorkingDirectory=/home/pi/cosmicpi-rpi_V1.5/frontend/ Restart=on-failure [Install] diff --git a/install_files/CosmicPi-database_cleaner.service b/install_files/CosmicPi-database_cleaner.service index ef8a436..8a9d29c 100644 --- a/install_files/CosmicPi-database_cleaner.service +++ b/install_files/CosmicPi-database_cleaner.service @@ -2,8 +2,8 @@ Description=Software for cleaning the local database from time to time, to keep it from exploding [Service] -ExecStart=/usr/bin/python PATH_TO_EXECUTABLE/backend/database_cleaning.py -WorkingDirectory=PATH_TO_EXECUTABLE/backend/ +ExecStart=/usr/bin/python /home/pi/cosmicpi-rpi_V1.5/backend/database_cleaning.py +WorkingDirectory=/home/pi/cosmicpi-rpi_V1.5/backend/ Restart=on-failure [Install] diff --git a/install_files/CosmicPi-detector.service b/install_files/CosmicPi-detector.service index a43d9ee..7cb8c64 100644 --- a/install_files/CosmicPi-detector.service +++ b/install_files/CosmicPi-detector.service @@ -2,8 +2,8 @@ Description=Software for connecting to the the CosmicPi detector [Service] -ExecStart=/usr/bin/python PATH_TO_EXECUTABLE/backend/detector_connect.py -WorkingDirectory=PATH_TO_EXECUTABLE/backend/ +ExecStart=/usr/bin/python /home/pi/cosmicpi-rpi_V1.5/backend/detector_connect.py +WorkingDirectory=/home/pi/cosmicpi-rpi_V1.5/backend/ Restart=on-failure [Install] diff --git a/install_files/CosmicPi-mqtt.service b/install_files/CosmicPi-mqtt.service index e35094d..7f21530 100644 --- a/install_files/CosmicPi-mqtt.service +++ b/install_files/CosmicPi-mqtt.service @@ -2,8 +2,8 @@ Description=Software for sending locally stored events from the CosmicPi to an MQTT srver [Service] -ExecStart=/usr/bin/python PATH_TO_EXECUTABLE/backend/mqtt_publisher.py -WorkingDirectory=PATH_TO_EXECUTABLE/backend/ +ExecStart=/usr/bin/python /home/pi/cosmicpi-rpi_V1.5/backend/mqtt_publisher.py +WorkingDirectory=/home/pi/cosmicpi-rpi_V1.5/backend/ Restart=on-failure [Install] diff --git a/installparttwo.sh b/installparttwo.sh old mode 100644 new mode 100755