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
commit
5ac1aba64c
@ -0,0 +1,5 @@
|
|||||||
|
build
|
||||||
|
compile_commands.json
|
||||||
|
config.ini
|
||||||
|
Todo.md
|
||||||
|
.cache
|
||||||
@ -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…
Reference in New Issue