Add sensor data and WiFi support in REST API
This commit is contained in:
parent
4269c1461f
commit
d79821c612
11 changed files with 248 additions and 8 deletions
8
frontend/README.md
Normal file
8
frontend/README.md
Normal file
|
|
@ -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
|
||||
```
|
||||
13
frontend/app.py
Normal file
13
frontend/app.py
Normal file
|
|
@ -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)
|
||||
5
frontend/config.py
Normal file
5
frontend/config.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import configparser
|
||||
|
||||
|
||||
Config = configparser.ConfigParser()
|
||||
Config.read('../config/CosmicPi.config')
|
||||
47
frontend/params.py
Normal file
47
frontend/params.py
Normal file
|
|
@ -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
|
||||
1
frontend/requirements.txt
Normal file
1
frontend/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
flask_restful
|
||||
166
frontend/wifi.py
Normal file
166
frontend/wifi.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
0
installparttwo.sh
Normal file → Executable file
0
installparttwo.sh
Normal file → Executable file
Loading…
Reference in a new issue