ffrouin
Published © GPL3+

Nightscout Lamp [Prototype]

Using an arduino UNO and a 8266ex chip to recover medical data from a nightscout server to finally update a ws2812b lamp.

IntermediateWork in progress8 hours127
Nightscout Lamp [Prototype]

Things used in this project

Story

Read more

Schematics

Circuit Diagram

Functional Diagram

Code

nightscout-gw.ino

C/C++
to push on esp01 (8266ex) device
#include <Arduino.h>
#include <NTPClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <WiFiUdp.h>

const char* build_date="09/04/2024";
const char* release="v0.14";

const char* ssid = "";
const char* password = "";

const char* nightscout_api_entries = "https://<nightscout_hostname>[:port]/api/v1/entries?count=3&token=<YOUR_TOKEN>";
const char* nightscout_api_devstatus = "https://<nightscout_hostname>[:port]/api/v1/devicestatus?count=1&token=<YOUR_TOKEN>";

const long utcOffsetInSeconds = 7200;

bool debug = false;
bool json_sent = false;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

void printLog(const char* msg) {
  if (debug) {
    String l = "#8266ex [";
    l += timeClient.getFormattedTime();
    l += "] ";
    l += msg;
    Serial.println(l);
  }
}

void printfLog(const char* msg, String v) {
  if (debug) {
    String l = "#8266ex [";
    l += timeClient.getFormattedTime();
    l += "] ";  
    l += msg;
    Serial.printf(l.c_str(),v);
  }
}

String acquireSettingsFromSerial(void) {
  String s = "again";
  if(Serial.available()) {
  s = Serial.readString();
  s.replace("\r\n","");
  if (s.equals("debug")) {
    debug = true;
    printLog("debug enabled\n");
  }
  if (s.equals("jsonreceived")) {
    json_sent = true;
    printLog("json ack received\n");
  }
  if (!s.equals("")) {
    printfLog("\"%s\" received on esp8266ex\n",s);
  }
  }
  return s;
}

void setup() {
  Serial.begin(38400);

  String instruction="tryit";
  while(!instruction.equals("READY")) {
    instruction = acquireSettingsFromSerial();
  }
  printLog("\n");

  Serial.printf("#8266ex starting nightscout gw release %s\n", release);
  Serial.printf("#8266ex build on %s\n\n", build_date);

  WiFi.mode(WIFI_STA);

  Serial.printf("#8266ex trying to connect to wifi network %s",ssid);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(200);
  }
  Serial.print(" connected\n\n");

  Serial.print("#8266ex IP Address: ");
  Serial.println(WiFi.localIP());
  Serial.print("#8266ex IP Gateway: ");
  Serial.println(WiFi.gatewayIP());

  Serial.print("#8266ex Syncing time : ");
  timeClient.update();
  Serial.println(timeClient.getFormattedTime());
 
  Serial.println();
  delay(3000);
  Serial.println("#8266ex READY\n");
  delay(2000);
}

void load_nightscout_data(String url, JsonDocument *doc) {
  // wait for WiFi connection
  if ((WiFi.status() == WL_CONNECTED)) {

    std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);

    // Ignore SSL certificate validation
    client->setInsecure();
    
    //create an HTTPClient instance
    HTTPClient https;

    if (https.begin(*client, url)) {  // HTTPS
      https.addHeader("accept", "application/json");
      printLog(url.c_str());
      int httpCode = https.GET();
 
      if (httpCode > 0) {
        //Serial.printf("[HTTPS] GET... code: %d\n", httpCode);

        if (httpCode == HTTP_CODE_OK) {
          
          String payload = https.getString();
          printLog(payload.c_str());
          deserializeJson(*doc, payload);
        }
      } else {
        printfLog("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str());
      }

      https.end();
    } else {
      printLog("[HTTPS] Unable to connect\n");
    }
  } else {
     printLog("[WIFI] not available\n");
  }
  printLog("\n");
}

void loop() { 
  JsonDocument glycemia;
  JsonDocument pump_state;
  JsonDocument json_output;

  load_nightscout_data(nightscout_api_entries, &glycemia);
  
  if (glycemia[0]["sgv"] > 0) {
    for (int i=0;i<3;i++) {
      json_output["glycemia"][i]["value"] = glycemia[i]["sgv"];

      struct tm tm;
      const char* dateString = glycemia[i]["dateString"];
      strptime(dateString,"%Y-%m-%dT%H:%M:%S",&tm);
      json_output["glycemia"][i]["date"]=mktime(&tm);
    }
  }

  if (debug) {
    delay(5000);
  }
  
  load_nightscout_data(nightscout_api_devstatus, &pump_state);
  
  if (pump_state[0]["pump"]["battery"]["percent"] > 0) {
    json_output["pump"]["battery"] = pump_state[0]["pump"]["battery"]["percent"];
    json_output["pump"]["reservoir"] = pump_state[0]["pump"]["reservoir"];
    json_output["pump"]["activeInsuline"] = pump_state[0]["pump"]["iob"]["bolusiob"];
    json_output["uploader"]["battery"] = pump_state[0]["uploaderBattery"];
  }

  if (debug) {
    delay(5000);
  }
  
  timeClient.update();
  json_sent = false;
  for (int i=0;i<600;i++) {
     if (!json_sent) {
       json_output["time"]=timeClient.getEpochTime()-utcOffsetInSeconds;
       serializeJson(json_output, Serial);
       Serial.println();
       while(!Serial.available()) {};
       acquireSettingsFromSerial();
     }
     delay(500);
  }
}

nightscout-lamp.ino

C/C++
to push on arduino device
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
#include <time.h>
#include "Adafruit_NeoPixel.h"
#define PIN  7
#define NUMPIXELS 36
#define BRIGHTNESS_PIN 0
#define COLOR_PIN 1
#define APP_BTN_PIN 2
#define ESP8266_RX 12
#define ESP8266_TX 13

const char* build_date="09/04/2024";
const char* prog_release="v0.27";
const char* build_nb="0232";

int mode = 0;
int btn_state_last=0;
int btn_state=0;

Adafruit_NeoPixel strip(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);
SoftwareSerial esp8266(ESP8266_RX, ESP8266_TX);

long glowTimer = millis();

void stripFadeOut(int l) {
  int ub = getUserBrightness();

  for (int b=ub;b>0;b-=5) {
    strip.setBrightness(b);
    strip.show();
    delay(l/20);
  }
  strip.setBrightness(0);
  strip.show();
}
void userControlLoop(bool debug=false) {
  btn_state=digitalRead(APP_BTN_PIN);
  if (btn_state==0 && btn_state_last==1) {
     if (debug) {
       Serial.println(F("#arduino user btn pressed"));
     }
     btn_state_last = btn_state;
     mode++;
     stripFadeOut(300);
  } else {
    btn_state_last = btn_state;
  }
}

void rainbow(long firstPixelHue, bool rainbowDelay=false) {
  for(int i=0; i<strip.numPixels(); i++) { 
    int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
    strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
  }
  strip.show();
  if (rainbowDelay) {
    delay(floor(analogRead(COLOR_PIN)/100));
  }
}

int glowCorrection(int l, int t) {
  int dt = millis() - glowTimer;
  if (dt>=t) {
    glowTimer=millis();
    dt=0;
  }
  float bd = dt;
  bd /= t;
  float fd = sin(PI*bd); // return a sinusoidal value between -1 and 1, full cycle will take t msec
  fd *= l;   // fd should be between -l and l
  return(floor(fd));  
}

int getUserBrightness() {
  int ub = analogRead(BRIGHTNESS_PIN);
  float b = ub;
  b /= 1020;
  b *= 255;
  
  if (b<5) {
    return(0);
  }

  return(floor(b));
}

class nightscoutLamp {
  private:
    int g0;
    int g1;
    int g2;
    long d0; // date of g0
    long d1; //  date of g1
    long d2; // date of g2
    long t; // time
    unsigned long t_start;
    int pb; // pump battery
    int ub; // uploader battery
    uint32_t c; // lamp color
    uint32_t ubc; // uploader battery color
    uint32_t pbc; // pump battery color
    int rainbowHue;
      
  public:
      bool animation = false;
      
      nightscoutLamp(void);
      long getTime(void);
      void printData(void);
      bool checkJsonData(String s);
      void updateColor(bool debug=false);
      void updateLamp(bool showPoints, bool debug=false);     
};

nightscoutLamp::nightscoutLamp(void) {
  g0=0;
  g1=0;
  g2=0;
  d0=0; 
  d1=0; 
  d2=0; 
  t=0; 
  pb=0; 
  ub=0;
  c = uint32_t(0xffffff);
  pbc = c;
  ubc = c;
  rainbowHue = 0;
}

void nightscoutLamp::printData(void) {
  Serial.print(g0);
  Serial.print(F(" "));
  Serial.print(g1);
  Serial.print(F(" "));
  Serial.print(g2);
  Serial.print(F(" "));
  Serial.print(d0);
  Serial.print(F(" "));
  Serial.print(d1);
  Serial.print(F(" "));
  Serial.print(d2);
  Serial.print(F(" "));
  Serial.print(t);
  Serial.print(F(" "));
  Serial.print(pb);
  Serial.print(F(" "));
  Serial.println(ub);
}

bool nightscoutLamp::checkJsonData(String s) {
  JsonDocument doc;
  deserializeJson(doc,s);
  
  if ((doc["glycemia"][0]["value"] > 30) &&
      (doc["glycemia"][1]["value"] > 30) &&
      (doc["glycemia"][2]["value"] > 30) &&
      (doc["glycemia"][0]["date"] > 1712200000) &&
      (doc["glycemia"][1]["date"] > 1712200000) &&
      (doc["glycemia"][2]["date"] > 1712200000) &&
      (doc["pump"]["battery"] > 0) &&
      (doc["uploader"]["battery"] > 0) &&
      (doc["time"] > 1712200000)) {

          g0=doc["glycemia"][0]["value"];
          g1=doc["glycemia"][1]["value"];
          g2=doc["glycemia"][2]["value"];
          d0=doc["glycemia"][0]["date"]; 
          d1=doc["glycemia"][1]["date"]; 
          d2=doc["glycemia"][2]["date"]; 
          pb=doc["pump"]["battery"]; 
          ub=doc["uploader"]["battery"];
          t=doc["time"];
          t_start=millis();
          return(true);                      
  }
  return(false);
}

void nightscoutLamp::updateColor(bool debug=false) {
  if (t>0) {
  if (pb<=20) {
     pbc = (uint32_t(0xff0000));
   } else {
     if (pb<=40) {
       pbc = (uint32_t(0xffff00));
    } else {
       pbc = (uint32_t(0x00ff00));
     }
   }
   
   if (ub<=20) {
     ubc = (uint32_t(0xff0000));
   } else {
     if (ub<=40) {
       ubc = (uint32_t(0xffff00));
     } else {
       ubc = (uint32_t(0x00ff00));
     }
  }
  
  if (g0>=250) {
  //  Serial.println("#arduino high glycemia >=250");
    c = uint32_t(0x8800ff); // violet
  } else {
    if (g0>=180) {
  //    Serial.println("#arduino high glycemia >=180 and <250");
      c = uint32_t(0xdd6600); // orange
    } else {
      if (g0>=80) {
  //      Serial.println("#arduino glycemia in target >=80 and <180");
        c = uint32_t(0x00ff00); // green
      } else {
        if (g0<=70) {
     //     Serial.println("#arduino glycemia low <=70");
          c = (uint32_t(0xffff00)); // yellow
        } else {
       //   Serial.println("#arduino glycemia low >=70 and <80");
          c = (uint32_t(0xff0000)); // red
        }
      }
    }
  }
  }
}

long nightscoutLamp::getTime(void) {
  int d = int((millis()-t_start)/1000);
  return(t+d);
}

void nightscoutLamp::updateLamp(bool showPoints, bool debug=false) {
  if (t>0) {
    if (animation) {
      if (debug) {
       // Serial.println(F("fade out start"));        
      }
      stripFadeOut(200);
    }
    if (getTime()-d0>1200) { // data older than 20min
      if (rainbowHue>65536) {
        rainbowHue = 0;
      }
      strip.setBrightness(getUserBrightness());
      rainbow(rainbowHue);
      rainbowHue += 256;   
    } else {   
      strip.fill(strip.Color(0,0,0));
      if (showPoints) {
        float dx = (250-40)/(NUMPIXELS-11);
        int i0 = NUMPIXELS-11-floor((g0-40)/dx);
        int i1 = NUMPIXELS-11-floor((g1-40)/dx);
        int i2 = NUMPIXELS-11-floor((g2-40)/dx);
        strip.setPixelColor(i0, (c >> 16) & 0xff,(c >> 8) & 0xff,c & 0xff);
        strip.setPixelColor(i1, (c >> 16) & 0xff,(c >> 8) & 0xff,c & 0xff);
        strip.setPixelColor(i2, (c >> 16) & 0xff,(c >> 8) & 0xff,c & 0xff);
        if (g0 > 250 || g0 < 50) { // override points if too high or too low
          for (int i=0;i<=NUMPIXELS-11;i++) {
            strip.setPixelColor(i, (c >> 16) & 0xff,(c >> 8) & 0xff,c & 0xff);
          }
        }
      } else {
        for (int i=0;i<=NUMPIXELS-11;i++) {
            strip.setPixelColor(i, (c >> 16) & 0xff,(c >> 8) & 0xff,c & 0xff);
          }
      }
      int lp = floor(pb*0.05);
      for (int i=NUMPIXELS-11+5;i>=(NUMPIXELS-lp-6);i--) {    
        strip.setPixelColor(i, (pbc >> 16) & 0xff,(pbc >> 8) & 0xff,pbc & 0xff);
      }
  
      int lu = floor(ub*0.05);
      for (int i=NUMPIXELS;i>=(NUMPIXELS-lu);i--) {    
        strip.setPixelColor(i, (ubc >> 16) & 0xff,(ubc >> 8) & 0xff,ubc & 0xff);
      }
    }
    if (animation) {
      if (debug) {
       // Serial.println(F("fade in start"));
      }
      // stripFadeIn(2000);
    } else {
      int ub = getUserBrightness();
      int gc = glowCorrection(abs(g0-g1)*3,2000);
      ub += gc;
      ub = max(0,ub);
      ub = min(ub,255);
      strip.setBrightness(ub);
      strip.show();     
    }
  }
}

nightscoutLamp nsl;
int esp8266ComError = 0;

void esp8266Com(bool debug=false) {
  if (esp8266.available()) {
    String data = esp8266.readString();   
    if (debug) {
      Serial.println(data);
    }
    if (data.startsWith("{") && data.endsWith("}\r\n")) {
      if (nsl.checkJsonData(data)) {
        esp8266.println(F("jsonreceived"));
        nsl.animation = true;
        /*
        if (debug) {
          nsl.printData();
        }
        */
        esp8266ComError = 0;
      } else {
        esp8266.println(F("corrupted json content"));
        esp8266ComError++;
        /*
        if (debug) {
          Serial.print("esp8266ComError content #");
          Serial.println(esp8266ComError);
        }
        */
      }
    } else {
      esp8266.println(F("corrupted json format"));
      esp8266ComError++;
      /*
      if (debug) {
        Serial.print("esp8266ComError format #");
        Serial.println(esp8266ComError);
      }     
      */
    }
  }
  if (Serial.available()) {
    String s = Serial.readString();
    if (!s.equals("")) {
      esp8266.write(s.c_str());
    }
  }
}

void setup() {
  strip.begin();
  strip.fill(uint32_t(0xffffff));
  strip.setBrightness(10);
  strip.show();

  delay(500);
  
  Serial.begin(38400);

  Serial.println();
  Serial.print(F("#arduino starting nightscout lamp release "));
  Serial.println(prog_release);
  Serial.print(F("#arduino build #"));
  Serial.print(build_nb);
  Serial.print(F(" on "));
  Serial.print(build_date);
  Serial.println();
  Serial.println(F("#arduino READY\n"));

  strip.setBrightness(20);
  strip.show();
  delay(500);

  pinMode(ESP8266_RX, INPUT);
  pinMode(ESP8266_TX, OUTPUT);
  esp8266.begin(38400);
  //esp8266.println("debug"); // turn on debug mode for esp8266
  delay(500);
  esp8266.println(F("READY")); 
  strip.setBrightness(30);
  strip.show();
}

void solarLight(void) {
  int uc = analogRead(COLOR_PIN);
  float c = uc;
  c /= 1024;
  c *= 255;
  uint32_t b = 255-floor(c);
  strip.fill(uint32_t(256*256*255+256*255+b));
  strip.setBrightness(getUserBrightness());
  strip.show();
}

void userColor(void) {
  int uc = analogRead(COLOR_PIN);
  float c = uc;
  c /= 1024;
  c *= 65536;
  int pixelHue = int(c);
  for(int i=0; i<strip.numPixels(); i++) { 
    strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
  }
  strip.setBrightness(getUserBrightness());
  strip.show();
}

int rhue = 0;

void loop() {
  esp8266Com(true);

  if (esp8266ComError < 5) {   
    userControlLoop(false);
    switch (mode) {   
      case 0: //nightscout  
        if (esp8266ComError == 0) {
          nsl.updateColor(false); 
        }
        nsl.updateLamp(false,true);
        nsl.animation = false;
        break;
       case 1: //nightscout  
        if (esp8266ComError == 0) {
          nsl.updateColor(false); 
        }
        nsl.updateLamp(true,true);
        nsl.animation = false;
        break;     
      case 2: // solar light
        solarLight();
        break;
      case 3: // user color
        userColor();
        break;
      case 4:
        if (rhue>65536) {
          rhue = 0;
        }
        rainbow(rhue,true);
        rhue += 256;
        strip.setBrightness(getUserBrightness());
        strip.show();
        break;    
      case 5:
        strip.clear();
        strip.show();
        break;
      default:
        esp8266.println(F("READY")); 
        mode = 0;
        break;    
    }
  }
}

Credits

ffrouin

ffrouin

3 projects • 3 followers
Open Source and Standard greatly help integration and maintenance costs. IT, 3D Printing and Electronic R&D, Repair : The Domestic Industry.

Comments