Time to add this to a repository.

This is an MVP, the goal is that this will be able to print to a simple
Raspberry Pi screen, however, right now this will print the glucose
levels to stdout.
master
Ronald 1 year ago
commit 5ac1aba64c

5
.gitignore vendored

@ -0,0 +1,5 @@
build
compile_commands.json
config.ini
Todo.md
.cache

3
.gitmodules vendored

@ -0,0 +1,3 @@
[submodule "libs/log.c"]
path = libs/log.c
url = https://github.com/rxi/log.c

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.25)
project(GlucoseMonitor C)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Find the pkg-config package
find_package(PkgConfig REQUIRED)
# Use pkg-config to find cJSON
pkg_check_modules(CJSON REQUIRED libcjson)
# Use pkg-config to find curl
pkg_check_modules(LIBCURL REQUIRED libcurl)
# Add the executable
add_executable(glucose_monitor src/main.c libs/log.c/src/log.c)
# Link against the cJSON library
target_link_libraries(glucose_monitor PRIVATE ${LIBCURL_LIBRARIES} ${CJSON_LIBRARIES})
# Include cJSON's include directories
target_include_directories(glucose_monitor PRIVATE include libs/log.c/src ${LIBCURL_LIBRARIES} ${CJSON_INCLUDE_DIRS})

@ -0,0 +1 @@
Subproject commit f9ea34994bd58ed342d2245cd4110bb5c6790153

@ -0,0 +1,436 @@
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <cjson/cJSON.h>
#include <curl/curl.h>
#include "log.h"
#include "main.h"
void handle_sigint(int signal)
{
struct timespec ts = {10, 0}; // 2 seconds, 0 nanoseconds
log_info("Received SIGINT gracefully shutting down...");
nanosleep(&ts, NULL);
log_info("Finished cleaning up");
exit(1);
}
size_t write_chunk(void *data, size_t size, size_t nmemb, void *clientp)
{
size_t real_size;
struct Response *response;
char *ptr;
real_size = size * nmemb;
response = (struct Response *) clientp;
ptr = realloc(response->string, response->size + real_size + 1);
if (ptr == NULL) {
return CURL_WRITEFUNC_ERROR;
}
response->string = ptr;
memcpy(&(response->string[response->size]), data, real_size);
response->size += real_size;
response->string[response->size] = '\0'; // ensure that string is NULL-terminated
return real_size;
}
/*
* Ensure that the response returned is freed
*/
struct Response* post_request(const struct Request *request)
{
CURL *curl;
curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "curl_easy_init failed");
return NULL;
}
struct Response *response = (struct Response *)malloc(sizeof(struct Response));
response->string = malloc(1);
response->size = 0;
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
curl_easy_setopt(curl, CURLOPT_URL, request->endpoint);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "PostmanRuntime/7.28.4");
/*
* Set userdata to response struct defined earlier
* This allows us to store the response of the rqeuest in the response structure
*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
struct curl_slist *headers;
headers = NULL;
headers = curl_slist_append(headers, "version: 4.7");
headers = curl_slist_append(headers, "product: llu.ios");
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Accept: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->body);
CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK) {
log_error("curl_easy_perform failed: %s\n", curl_easy_strerror(result));
return NULL;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return response;
}
/*
* Ensure that the response returned is freed
*/
struct Response* get_request(const struct Request *request)
{
CURL *curl;
curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "curl_easy_init failed");
return NULL;
}
struct Response *response = (struct Response *)malloc(sizeof(struct Response));
response->string = malloc(1);
response->size = 0;
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_URL, request->endpoint);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "PostmanRuntime/7.28.4");
/*
* Set userdata to response struct defined earlier
* This allows us to store the response of the rqeuest in the response structure
*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
struct curl_slist *headers;
headers = NULL;
char authorization_header[BUFFER_SIZE];
sprintf(authorization_header, "Authorization: Bearer %s", request->token);
headers = curl_slist_append(headers, authorization_header);
headers = curl_slist_append(headers, "version: 4.7");
headers = curl_slist_append(headers, "product: llu.ios");
headers = curl_slist_append(headers, "Accept: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK) {
log_error("curl_easy_perform failed: %s\n", curl_easy_strerror(result));
return NULL;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return response;
}
size_t get_auth_token(const char *email, const char *password, char *auth_token)
{
int body_size;
struct Request request;
request.endpoint = "https://api.libreview.io/llu/auth/login";
body_size = sprintf(
request.body,
"{\n \"email\": \"%s\",\n \"password\": \"%s\"\n}",
email,
password
);
if (body_size == 0) {
return 0;
}
Response *response = post_request(&request);
cJSON *json = cJSON_Parse(response->string);
if (json == NULL) {
fprintf(stderr, "error parsing JSON\n");
free(response->string);
free(response);
return 0;
}
cJSON *data = cJSON_GetObjectItem(json, "data");
if (data == NULL) {
log_error("failed to parse JSON object data");
free(response->string);
free(response);
cJSON_Delete(json);
return 0;
}
cJSON *auth_ticket = cJSON_GetObjectItem(data, "authTicket");
if (auth_ticket == NULL) {
printf("No authTicket found\n");
free(response->string);
free(response);
cJSON_Delete(json);
return 0;
}
cJSON *token = cJSON_GetObjectItem(auth_ticket, "token");
if (token == NULL) {
printf("No token found\n");
free(response->string);
free(response);
cJSON_Delete(json);
return 0;
}
size_t token_length = strlen(token->valuestring)+1;
strncpy(auth_token, token->valuestring, token_length);
cJSON_Delete(json);
free(response->string);
free(response);
return strlen(auth_token);
}
size_t get_patient_id(const char *token, char *patient_id)
{
CURL *curl;
curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "curl_easy_init failed");
return 0;
}
struct Request request;
request.endpoint = "https://api.libreview.io/llu/connections";
request.token = token;
request.body = "";
Response *response = get_request(&request);
if (response->string == NULL) {
patient_id = NULL;
log_error("get_request returned null");
return 0;
}
cJSON *json = cJSON_Parse(response->string);
if (json == NULL) {
fprintf(stderr, "error parsing JSON\n");
free(response->string);
return 0;
}
cJSON *data = cJSON_GetObjectItem(json, "data");
if (data == NULL) {
fprintf(stderr, "no data found\n");
free(response->string);
cJSON_Delete(json);
return 0;
}
int number_of_connections = cJSON_GetArraySize(data);
if (number_of_connections < 1) {
fprintf(stderr, "no connections found\n");
free(response->string);
cJSON_Delete(json);
return 0;
} else if (number_of_connections > 1) {
fprintf(stderr, "more than one connection found, sorry program only supports accounts with one connection");
free(response->string);
cJSON_Delete(json);
exit(-1);
}
cJSON *sub_item = cJSON_GetArrayItem(data, 0);
cJSON *patient_id_object = cJSON_GetObjectItem(sub_item, "patientId");
if (patient_id_object == NULL) {
fprintf(stderr, "failed to get patient ID");
free(response->string);
cJSON_Delete(json);
return 0;
}
size_t patient_id_length = strlen(patient_id_object->valuestring);
strncpy(patient_id, patient_id_object->valuestring, patient_id_length);
free(response->string);
cJSON_Delete(json);
return patient_id_length;
}
double get_glucose_units(const char *token, const char *patient_id)
{
CURL *curl;
curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "curl_easy_init failed");
return 0;
}
struct Response response;
response.string = malloc(1);
response.size = 0;
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
char url[BUFFER_SIZE];
/*
* Having the null byte in the string means it ends up in the URL
* this makes libcurl very sad, as the URL is no longer valid.
* We therefore copy everything except the NULL byte to non_null_terinated_patient_id
* Why is this -2? Shouldn't it just be -1 to get rid of the NULL byte?
*/
size_t length_of_patient_id_without_null_byte = strlen(patient_id)-2;
char non_null_terminated_patient_id[length_of_patient_id_without_null_byte];
memcpy(non_null_terminated_patient_id, patient_id, length_of_patient_id_without_null_byte);
sprintf(url, "https://api.libreview.io/llu/connections/%s/graph", non_null_terminated_patient_id);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Postmanuntime/7.28.4");
/*
* Set userdata to response struct defined earlier
* This allows us to store the response of the rqeuest in the response structure
*/
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &response);
struct curl_slist *headers;
headers = NULL;
char authorization_header[BUFFER_SIZE];
sprintf(authorization_header, "Authorization: Bearer %s", token);
headers = curl_slist_append(headers, authorization_header);
headers = curl_slist_append(headers, "version: 4.7");
headers = curl_slist_append(headers, "product: llu.ios");
headers = curl_slist_append(headers, "Accept: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
CURLcode result = curl_easy_perform(curl);
if (result != CURLE_OK) {
fprintf(stderr, "curl_easy_perform failed: %s\n", curl_easy_strerror(result));
return 0;
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
cJSON *json = cJSON_Parse(response.string);
if (json == NULL) {
fprintf(stderr, "error parsing JSON\n");
free(response.string);
return 0;
}
cJSON *data = cJSON_GetObjectItem(json, "data");
if (data == NULL) {
fprintf(stderr, "error parsing JSON for data object\n");
free(response.string);
cJSON_Delete(json);
return 0;
}
cJSON *connection = cJSON_GetObjectItem(data, "connection");
if (connection == NULL) {
fprintf(stderr, "error parsing JSON for connection\n");
free(response.string);
cJSON_Delete(json);
return 0;
}
cJSON *glucose_measurement = cJSON_GetObjectItem(connection, "glucoseMeasurement");
if (glucose_measurement == NULL) {
fprintf(stderr, "error parsing JSON for glucoseMeasurement\n");
free(response.string);
cJSON_Delete(json);
return 0;
}
cJSON *value = cJSON_GetObjectItem(glucose_measurement, "Value");
if (value == NULL) {
fprintf(stderr, "error parsing JSON\n");
free(response.string);
cJSON_Delete(json);
return 0;
}
cJSON_Delete(json);
free(response.string);
return value->valuedouble;
}
int main(void)
{
signal(SIGINT, handle_sigint);
curl_global_init(CURL_GLOBAL_DEFAULT);
char token[BUFFER_SIZE]; ;
size_t token_size;
token_size = get_auth_token(USERNAME, PASSWORD, token);
if (token_size < 1) {
log_fatal("get_auth_token failed to return a token");
exit(-1);
}
log_debug("token: %s\n", token);
char patient_id[BUFFER_SIZE];
size_t patient_id_length;
patient_id_length = get_patient_id(token, patient_id);
if (patient_id_length < 1) {
log_fatal("failed to get patient ID, exiting...");
exit(-1);
}
log_debug("patient ID: %s\n", patient_id);
struct timespec ts = {5, 0}; // 5 seconds, 0 nanoseconds
time_t t;
char time_str[BUFFER_SIZE];
while (true) {
time(&t);
strftime(time_str, BUFFER_SIZE, "%H:%M:%S %A %d %B %Y", localtime(&t));
printf("Glucose units at %s are %lf\n", time_str, get_glucose_units(token, patient_id));
nanosleep(&ts, NULL);
}
curl_global_cleanup();
}

@ -0,0 +1,26 @@
#include <stdlib.h>
#define BUFFER_SIZE 1024
#define USERNAME ""
#define PASSWORD ""
typedef struct Response {
char *string;
size_t size;
} Response;
typedef struct Request {
const char *endpoint;
const char *token;
char *body;
} Request;
void handle_signal(int signal);
size_t write_chunk(void *data, size_t size, size_t nmemb, void *userdata);
struct Response* post_request(const struct Request *request);
struct Response* get_request(const struct Request *request);
size_t get_patient_id(const char *token, char *patient_id);
double get_glucose_units(const char *token, const char *patient_id);
int main(void);
Loading…
Cancel
Save