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
The simplest way to turn the web server on is from the Settings dialog: open Settings → System, and tick Enable Web API under System Features. Restart Trndi for the change to take effect. This toggles the webserver.enable flag described below; the server then listens on port 8080 with no authentication. To use a different port, or to require a bearer token, set those values manually as shown below.
Enable the web server in your configuration file (eg ~/.config/Trndi.cfg):
[webserver]
enable=true
port=8080
token=your_optional_auth_token_hereIn the Registry, access HKEY_CURRENT_USER\Software\Trndi
Add these keys:
webserver.enable=true
webserver.port=8080
webserver.token=your_optional_auth_token_hereIn your app's config add the values seen under Windows
- enable: Set to
trueto start the web server - port: Port number for the HTTP server (default: 8080)
- token: Optional authentication token (leave empty for no authentication)
If a token is configured, requests must include it in the Authorization header:
curl -H "Authorization: Bearer your_token_here" http://localhost:8080/glucoseIf 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",
"timestamp_utc": "2025-11-13T13:30:00Z"
},
"1": {
"mgdl": "163.0",
"mmol": "9.0",
"mgdl_delta": "-5.0",
"mmol_delta": "-0.3",
"trend": 3,
"timestamp": "2025-11-13 14:25:00",
"timestamp_utc": "2025-11-13T13:25:00Z"
},
...
}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 timestamp in local time (YYYY-MM-DD HH:MM:SS, no zone) — kept for backwards compatibilitytimestamp_utc: Reading timestamp in UTC (YYYY-MM-DDTHH:MM:SSZ) — preferred for new consumers
Status Codes:
200 OK: Data available503 Service Unavailable: No data available500 Internal Server Error: Service not configured
Example:
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",
"timestamp_utc": "2025-11-13T13:35:00Z"
},
{
"mgdl": "152.0",
"mmol": "8.4",
"mgdl_delta": "-5.4",
"mmol_delta": "-0.3",
"trend": 7,
"timestamp": "2025-11-13 14:40:00",
"timestamp_utc": "2025-11-13T13:40:00Z"
}
]
}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 configured
This endpoint is kept for compatibility with existing clients.
Returns a richer health payload suitable for uptime/monitoring checks.
Response Format:
{
"status": "ok",
"service": "trndi-webapi",
"timestamp_utc": "2026-03-27T10:21:38Z",
"uptime_seconds": 123,
"port": 8080,
"auth_required": false,
"data_available": true,
"endpoints": [
"/glucose",
"/predict",
"/status",
"/health"
]
}Fields:
status: Alwaysokwhile the web server is runningservice: Fixed identifier for the embedded servertimestamp_utc: Server time in UTC (YYYY-MM-DDTHH:MM:SSZ)uptime_seconds: Seconds since this web server instance startedport: Bound listening portauth_required: Whether bearer token auth is enableddata_available: Whether a glucose callback is configuredendpoints: Current endpoint list exposed by the server
Example:
curl -s http://localhost:8080/health | jqThe 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:
- TThread: Runs in a separate thread to avoid blocking the GUI
- Raw BSD Sockets: Uses
fpSocket,fpBind,fpListen,fpAcceptfor direct socket control - Callback Pattern: Accesses glucose data through function pointers to avoid direct coupling
The 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:
- Return quickly (no blocking operations)
- Access only cached/pre-fetched data
- Handle empty/missing data gracefully
- Web server configuration is read from
Trndi.cfg - If
webserver.enable=true, theStartWebServerfunction is called - A
TTrndiWebServerinstance is created with callbacks - The server thread starts and binds to the configured port
- The thread enters an accept loop, handling one request at a time
When the application closes:
StopWebServeris called inFormDestroy- The thread is terminated with
Terminate WaitForensures the thread completes- The socket is closed and resources are freed
async 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 minuteimport 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:
- Use a strong random token (e.g., 32+ characters)
- Don't commit tokens to version control
- Rotate tokens periodically
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 8080Problem: Web server doesn't respond to requests
Solutions:
- Check configuration file:
cat ~/.config/Trndi.cfg | grep webserver - Verify port is not in use:
sudo lsof -i :8080 - Check logs/terminal output for errors
- Try a different port in configuration
Problem: curl: (7) Failed to connect to localhost port 8080: Connection refused
Solutions:
- Verify Trndi is running
- Confirm web server is enabled in config
- Check firewall settings
- Try connecting from different machine to test network access
Problem: Server responds but returns 503 status
Solutions:
- Wait for first glucose reading to arrive (can take 1-5 minutes)
- Verify API backend is configured correctly
- Check main GUI shows current reading
- Review API credentials in settings
Problem: Requests fail with "Unauthorized"
Solutions:
- Check token in config matches request header
- Verify
Authorization: Bearer <token>header format - Try without token (remove from config) to test
- Memory: ~1-2 MB for web server thread
- CPU: Minimal (only active during requests)
- Network: Each request: ~500 bytes request + ~500-1000 bytes response
Typical response times on localhost:
/glucose: < 1ms/predict: < 5ms (depending on prediction count)/status: < 1ms
The 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:
- Using a reverse proxy (nginx, Apache)
- Implementing connection pooling
- Caching responses with short TTL
To use a port other than 8080:
[webserver]
port=3000Remember 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 declarations
The web API is part of Trndi and follows the same GNU GPL v3 license. See LICENSE.md for details.