Add files

This commit is contained in:
malaow3
2020-09-26 13:53:45 -04:00
committed by GitHub
commit 6ed73100bd
5 changed files with 377 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# Glucose-Widget
Continuous Glucose Monitor iOS 14 Widget setup using Python and JS
As per Scriptable docs, "the widget will refresh periodically and the rate at which the widget refreshes is largely determined by the operating system". My experience has been the widget will update within a new glucose reading (~5 minute window), but will not update as frequently with low power mode active.
# Setup
* Install Scriptable on iOS 14 device (https://scriptable.app)
* Copy repository to local machine
* Sign up for Repl.it account (https://repl.it/) for webhosting
* Create a new Repl with python
* Copy contents of main.py and keep_alive.py to repl
* Add .env file containing username and password –– format should be:
```python
username="myusername"
password="mypassword"
```
* Run repl and copy link of webpage that is set up
* _Optional:_ set up account on uptimerobot (http://uptimerobot.com) to ping repl site to ensure it is constantly active
* Copy contents of Bggraph.js and Bggraph2.js to scriptable and modify the appropriate urls
* Add widget to home screen
Note: Repl.it web hosting is optional, this can be hosted anywhere that supports python and Flask —— I chose Repl since it is free
# Widget
<img src="https://i.imgur.com/EhpGp2M.jpeg" width=250>
+36
View File
@@ -0,0 +1,36 @@
/*
file: bggraph.js
fileOverview: Large widget script
*/
// Replace url with your repl link
const url = "https://example.myaccount.repl.co"
let req = new Request(url)
let res = await req.loadJSON()
if (config.runsInWidget) {
let widget = await createWidget(res.bg, "#212121")
Script.setWidget(widget)
Script.complete()
}
async function createWidget(pretitle, color) {
let w = new ListWidget()
w.backgroundColor = new Color(color)
let item = " " + pretitle
let preTxt = w.addText(item)
preTxt.textColor = Color.white()
preTxt.centerAlignText()
preTxt.font = Font.systemFont(42)
w.addSpacer(1)
let imgReq = new Request("https://example.myaccount.repl.co/plot.png")
let img = await imgReq.loadImage()
let wimg = w.addImage(img)
wimg.rightAlignImage()
wimg.imageSize = new Size(450, 255)
w.centerAlignContent
w.setPadding(0, 0, 0, 0)
return w
}
+31
View File
@@ -0,0 +1,31 @@
/*
file: bggraph2.js
fileOverview: Medium widget script
*/
// Replace url with your repl link
const url = "https://example.myaccount.repl.co"
let req = new Request(url)
let res = await req.loadJSON()
if (config.runsInWidget) {
let widget = await createWidget(res.bg, "#212121")
Script.setWidget(widget)
Script.complete()
}
async function createWidget(pretitle, color) {
let w = new ListWidget()
w.backgroundColor = new Color(color)
let item = " " + pretitle
let preTxt = w.addText(item)
preTxt.textColor = Color.white()
preTxt.font = Font.systemFont(32)
let imgReq = new Request("https://example.myaccount.repl.co/plot2.png")
let img = await imgReq.loadImage()
let wimg = w.addImage(img)
wimg.centerAlignImage()
wimg.imageSize = new Size(360, 90)
return w
}
+127
View File
@@ -0,0 +1,127 @@
'''
file: keep_alive.py
fileOverview: Webserver backend to display data
'''
from flask import Flask, render_template
from threading import Thread
from flask import jsonify
import json
import io
from flask import Response
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
app = Flask('')
@app.route('/plot.png')
def plot_png():
fig = create_figure()
fig.patch.set_facecolor('#212121')
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return Response(output.getvalue(), mimetype='image/png')
def create_figure():
text_file = open("datalist.txt", "r")
lines = text_file.read().split(',')
lines = [int(i) for i in lines]
print(lines)
fig = Figure()
axis = fig.add_subplot(1, 1, 1)
xs = range(11)
ys = lines
axis.axis(ymin=40, ymax=400)
axis.spines['bottom'].set_color('#FFFFFF')
axis.spines['top'].set_color('#FFFFFF')
axis.spines['right'].set_color('#FFFFFF')
axis.spines['left'].set_color('#FFFFFF')
axis.tick_params(axis='x', colors='#FFFFFF')
axis.tick_params(axis='y', colors='#FFFFFF')
axis.yaxis.label.set_color('#FFFFFF')
axis.xaxis.label.set_color('#FFFFFF')
axis.set_facecolor("#212121")
axis.tick_params(
axis='x',
which='both',
bottom=False,
top=False,
labelbottom=False)
axis.tick_params(
axis='y',
which='both',
labelsize=16)
axis.scatter(xs, ys, s=100)
return fig
@app.route('/plot2.png')
def plot_png2():
fig = create_figure2()
fig.patch.set_facecolor('#212121')
output = io.BytesIO()
FigureCanvas(fig).print_png(output)
return Response(output.getvalue(), mimetype='image/png')
def create_figure2():
text_file = open("datalist.txt", "r")
lines = text_file.read().split(',')
lines = [int(i) for i in lines]
print(lines)
fig = Figure(figsize=(16, 4))
axis = fig.add_subplot(1, 1, 1)
xs = range(11)
ys = lines
axis.axis(ymin=40, ymax=400)
axis.spines['bottom'].set_color('#FFFFFF')
axis.spines['top'].set_color('#FFFFFF')
axis.spines['right'].set_color('#FFFFFF')
axis.spines['left'].set_color('#FFFFFF')
axis.tick_params(axis='x', colors='#FFFFFF')
axis.tick_params(axis='y', colors='#FFFFFF')
axis.yaxis.label.set_color('#FFFFFF')
axis.xaxis.label.set_color('#FFFFFF')
axis.set_facecolor("#212121")
axis.tick_params(
axis='x',
which='both',
bottom=False,
top=False,
labelbottom=False)
axis.tick_params(
axis='y',
which='both',
labelsize=26)
axis.scatter(xs, ys, s=300)
return fig
@app.route("/img")
def img():
return render_template("img.html")
@app.route('/')
def home():
f = open("output.txt", "r")
data = json.loads(f.read())
return jsonify(data)
def run():
try:
app.run(host='0.0.0.0', port=8080)
except: # noqa: E722
pass
def keep_alive():
t = Thread(target=run)
t.start()
+158
View File
@@ -0,0 +1,158 @@
'''
file: main.py
fileOverview: Scrape Glucose Data and output contents to text file
and create thread for webserver
'''
import urllib.request
import urllib.error
import urllib.parse
import json
import os
import time
from keep_alive import keep_alive
def get_data():
"""
Scrapes Glucose Data and returns results
Returns:
(String, String, List): Tuple containing glucose value, trend value, and list of glucose data
"""
method = "POST"
handler = urllib.request.HTTPHandler()
opener = urllib.request.build_opener(handler)
sessionIdUrl = 'https://share2.dexcom.com/ShareWebServices/Services/General/LoginPublisherAccountByName'
glucoseUrl = 'https://share2.dexcom.com/ShareWebServices/Services/Publisher/ReadPublisherLatestGlucoseValues?sessionID=' # noqa:E501
username = os.getenv("username")
password = os.getenv("password")
glucoseGetParams = '&minutes=1440&maxCount=11'
payload = {"password": password, "applicationId": "d89443d2-327c-4a6f-89e5-496bbb0317db", "accountName": username}
payload = json.dumps(payload).encode('utf8')
seshRequest = urllib.request.Request(sessionIdUrl, payload)
seshRequest.add_header("Content-Type", 'application/json')
seshRequest.add_header("User-Agent", 'Dexcom Share/3.0.2.11 CFNetwork/672.0.2 Darwin/14.0.0')
seshRequest.add_header("Accept", 'application/json')
seshRequest.get_method = lambda: method
sessionID = None
try:
connection = opener.open(seshRequest)
except urllib.error.HTTPError as e:
connection = e
if connection.code == 200:
sessionID = connection.read()
sessionID = sessionID[1:-1]
sessionID = sessionID.decode("utf8")
else:
print((connection.code))
getGlucoseUrl = glucoseUrl + sessionID + glucoseGetParams
glucoseRequest = urllib.request.Request(getGlucoseUrl)
glucoseRequest.get_method = lambda: method
glucoseRequest.add_header("Accept", 'application/json')
glucoseRequest.add_header("Content-Length", '0')
emptyLoad = {"": ""}
try:
connection2 = opener.open(glucoseRequest, json.dumps(emptyLoad).encode("utf8"))
except urllib.error.HTTPError as e:
connection2 = e
glucose = None
trend = None
data = []
if connection2.code == 200:
glucoseReading = connection2.read()
glucoseReading = json.loads(glucoseReading)
glucose = glucoseReading[0]["Value"]
trend = glucoseReading[0]["Trend"]
data = []
for item in glucoseReading:
data.append(int(item["Value"]))
else:
print((connection2.code))
return (glucose, trend, data)
def replace_trend(trend):
"""
Replace trend number with arrows
Args:
trend (int): trend number
Returns:
String: trend represented with arrows
"""
trendtext = None
if trend == 0:
trendtext = ""
if trend == 1:
# trendtext = "rising quickly"
trendtext = "↑↑"
if trend == 2:
# trendtext = "rising"
trendtext = ""
if trend == 3:
# trendtext = "rising slightly"
trendtext = ""
if trend == 4:
# trendtext = "steady"
trendtext = ""
if trend == 5:
# trendtext = "falling slightly"
trendtext = ""
if trend == 6:
# trendtext = "falling"
trendtext = ""
if trend == 7:
trendtext = "↓↓"
if trend == 8:
# trendtext = "unable to determine a trend"
trendtext = " "
if trend == 9:
# trendtext = "trend unavailable"
trendtext = " "
return trendtext
glucose, trend, datalist = get_data()
trend_str = replace_trend(trend)
final_string = f"{glucose}{trend_str}"
# final_string = f"{glucose}"
data = {"bg": final_string}
f = open("output.txt", "w")
json.dump(data, f)
f.close()
with open('datalist.txt', 'w') as f:
for counter in range(len(datalist)):
item = datalist[counter]
if counter != len(datalist)-1:
f.write(f"{item},")
else:
f.write(f"{item}")
keep_alive()
try:
while 1:
glucose, trend, datalist = get_data()
trend_str = replace_trend(trend)
final_string = f"{glucose}{trend_str}"
data = {"bg": final_string}
f = open("output.txt", "w")
json.dump(data, f)
f.close()
with open('datalist.txt', 'w') as f:
for counter in range(len(datalist)):
item = datalist[counter]
if counter != len(datalist)-1:
f.write(f"{item},")
else:
f.write(f"{item}")
time.sleep(30)
except KeyboardInterrupt:
f.close()