mirror of
https://github.com/malaow3/Glucose-Widget.git
synced 2026-08-24 02:24:13 -05:00
Add files
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user