Trndi includes an embedded HTTP API server that exposes glucose readings and predictions via REST endpoints. The server runs in a separate thread and does not interfere with the GUI’s responsiveness.
This is especially useful for Dexcom users, as they have no easy API access
Enable the web server in your configuration file (eg ~/.config/Trndi.cfg):
[webserver]
enable=true
port=8080
token=your_optional_auth_token_here
In the Registry, access HKEY_CURRENT_USER\Software\Trndi
Add these keys:
webserver.enable=true
webserver.port=8080
webserver.token=your_optional_auth_token_here
In your app’s config add the values seen under Windows
true to start the web serverIf a token is configured, requests must include it in the Authorization header:
curl -H "Authorization: Bearer your_token_here" http://localhost:8080/glucose
If no token is configured, all requests are allowed.
Returns the current glucose reading with both mg/dL and mmol/L values.
Response Format:
{
"0": {
"mgdl": "163.0",
"mmol": "9.0",
"mgdl_delta": "0.0",
"mmol_delta": "0.0",
"trend": 3,
"timestamp": "2025-11-13 14:30:00"
},
"1": {
"mgdl": "163.0",
"mmol": "9.0",
"mgdl_delta": "-5.0",
"mmol_delta": "-0.3",
"trend": 3,
"timestamp": "2025-11-13 14:25:00"
},
...
}
Fields:
mgdl: Current glucose value in mg/dLmmol: Current glucose value in mmol/L (converted)mgdl_delta: Change since last reading in mg/dLmmol_delta: Change since last reading in mmol/Ltrend: Trend arrow (see Trend Values below)timestamp: Reading timestampStatus Codes:
200 OK: Data available503 Service Unavailable: No data available500 Internal Server Error: Service not configuredExample:
curl -s http://localhost:8080/glucose | jq '.current | {mgdl, mmol, trend}'
Output:
{
"mgdl": "87.0",
"mmol": "4.8",
"trend": 3
}
Returns predicted glucose readings (if predictions are enabled).
Response Format:
{
"predictions": [
{
"mgdl": "157.4",
"mmol": "8.7",
"mgdl_delta": "-5.6",
"mmol_delta": "-0.3",
"trend": 7,
"timestamp": "2025-11-13 14:35:00"
},
{
"mgdl": "152.0",
"mmol": "8.4",
"mgdl_delta": "-5.4",
"mmol_delta": "-0.3",
"trend": 7,
"timestamp": "2025-11-13 14:40:00"
}
]
}
Note: Returns an empty array if predictions are not enabled or unavailable.
Example:
curl -s http://localhost:8080/predict | jq '.predictions[0] | {mgdl, mmol}'
Returns server status and data availability.
Response Format:
{
"status": "ok",
"data_available": true
}
Fields:
status: Always “ok” if server is runningdata_available: Boolean indicating if glucose data callbacks are configuredThe trend field uses the following numeric values:
| Value | Meaning | Arrow |
|---|---|---|
| 0 | None | - |
| 1 | DoubleUp | ⇈ |
| 2 | SingleUp | ↑ |
| 3 | FortyFiveUp | ↗ |
| 4 | Flat | → |
| 5 | FortyFiveDown | ↘ |
| 6 | SingleDown | ↓ |
| 7 | DoubleDown | ⇊ |
| 8 | NotComputable | ? |
| 9 | RateOutOfRange | ⚠ |
The API includes full CORS support with the following headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
All endpoints support OPTIONS preflight requests.
The web server is implemented using:
fpSocket, fpBind, fpListen, fpAccept for direct socket controlThe server uses a simple callback pattern where the main GUI thread maintains cached glucose readings that the web server thread reads. Since the web server only reads cached data and doesn’t make API calls, no mutex or critical section is required.
Callback Functions:
type
TGetCurrentReadingFunc = function: BGReading of object;
TGetPredictionsFunc = function: BGResults of object;
These callbacks are called from the web server thread and must:
Trndi.cfgwebserver.enable=true, the StartWebServer function is calledTTrndiWebServer instance is created with callbacksWhen the application closes:
StopWebServer is called in FormDestroyTerminateWaitFor ensures the thread completesasync function getCurrentGlucose() {
try {
const response = await fetch('http://localhost:8080/glucose');
const data = await response.json();
if (data.current) {
console.log(`Glucose: ${data.current.mmol} mmol/L`);
console.log(`Trend: ${data.current.trend}`);
}
} catch (error) {
console.error('Failed to fetch glucose data:', error);
}
}
setInterval(getCurrentGlucose, 60000); // Update every minute
import requests
import time
def get_glucose():
try:
response = requests.get('http://localhost:8080/glucose')
data = response.json()
if 'current' in data:
current = data['current']
print(f"Glucose: {current['mmol']} mmol/L")
print(f"Delta: {current['mmol_delta']} mmol/L")
print(f"Trend: {current['trend']}")
except Exception as e:
print(f"Error: {e}")
while True:
get_glucose()
time.sleep(60) # Poll every minute
# configuration.yaml
sensor:
- platform: rest
name: "Trndi Glucose"
resource: "http://localhost:8080/glucose"
value_template: "{{ value_json.current.mmol }}"
unit_of_measurement: "mmol/L"
json_attributes:
- current
scan_interval: 60
template:
- sensor:
- name: "Trndi Glucose Trend"
state: >
{% set trend = state_attr('sensor.trndi_glucose', 'current').trend %}
{% set arrows = {
1: '⇈', 2: '↑', 3: '↗', 4: '→',
5: '↘', 6: '↓', 7: '⇊'
} %}
{{ arrows.get(trend, '?') }}
The web server binds to all interfaces (INADDR_ANY). To restrict to localhost only, you would need to modify the bind address in the code.
Store the authentication token securely:
Consider using a firewall to restrict access:
# Allow only from localhost
sudo ufw allow from 127.0.0.1 to any port 8080
# Allow from local network
sudo ufw allow from 192.168.1.0/24 to any port 8080
Problem: Web server doesn’t respond to requests
Solutions:
cat ~/.config/Trndi.cfg | grep webserversudo lsof -i :8080Problem: curl: (7) Failed to connect to localhost port 8080: Connection refused
Solutions:
Problem: Server responds but returns 503 status
Solutions:
Problem: Requests fail with “Unauthorized”
Solutions:
Authorization: Bearer <token> header formatTypical response times on localhost:
/glucose: < 1ms/predict: < 5ms (depending on prediction count)/status: < 1msThe current implementation handles requests sequentially (one at a time). This is sufficient for typical use cases (polling every 30-60 seconds). If you need high concurrency, consider:
To use a port other than 8080:
[webserver]
port=3000
Remember to update firewall rules and client code accordingly.
For trusted local network environments:
[webserver]
enable=true
port=8080
# token= (leave empty or omit)
Example nginx configuration:
upstream trndi {
server localhost:8080;
}
server {
listen 443 ssl;
server_name glucose.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://trndi;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Add authentication at proxy level
auth_basic "Trndi API";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
The web server implementation can be found in:
units/trndi/trndi.webserver.threaded.pp - Main web server classinc/umain_init.inc - Startup/shutdown and callback implementationsunits/forms/umain.pp - Callback method declarationsThe web API is part of Trndi and follows the same GNU GPL v3 license. See LICENSE.md for details.